diff --git a/node-data/src/ledger.rs b/node-data/src/ledger.rs index b314a245a3..3a08d69856 100644 --- a/node-data/src/ledger.rs +++ b/node-data/src/ledger.rs @@ -24,7 +24,7 @@ pub use attestation::{ use crate::bls::PublicKeyBytes; use crate::Serializable; -use rusk_abi::hash::Hasher; +use rusk_abi::hash::Hasher64; use sha3::Digest; use std::io::{self, Read, Write}; diff --git a/node-data/src/ledger/faults.rs b/node-data/src/ledger/faults.rs index 8409967d83..dc4f74185c 100644 --- a/node-data/src/ledger/faults.rs +++ b/node-data/src/ledger/faults.rs @@ -62,7 +62,7 @@ impl Fault { pub fn hash(&self) -> [u8; 32] { let mut b = vec![]; self.write(&mut b).expect("Write to a vec shall not fail"); - Hasher::digest(b).to_bytes() + Hasher64::digest(b).to_bytes() } pub fn same(&self, other: &Fault) -> bool { @@ -80,20 +80,20 @@ impl Fault { match self { Fault::DoubleCandidate(a, b) => { let seed = Candidate::SIGN_SEED; - let a = Hasher::digest(a.get_signed_data(seed)).to_bytes(); - let b = Hasher::digest(b.get_signed_data(seed)).to_bytes(); + let a = Hasher64::digest(a.get_signed_data(seed)).to_bytes(); + let b = Hasher64::digest(b.get_signed_data(seed)).to_bytes(); (a, b) } Fault::DoubleRatificationVote(a, b) => { let seed = Ratification::SIGN_SEED; - let a = Hasher::digest(a.get_signed_data(seed)).to_bytes(); - let b = Hasher::digest(b.get_signed_data(seed)).to_bytes(); + let a = Hasher64::digest(a.get_signed_data(seed)).to_bytes(); + let b = Hasher64::digest(b.get_signed_data(seed)).to_bytes(); (a, b) } Fault::DoubleValidationVote(a, b) => { let seed = Validation::SIGN_SEED; - let a = Hasher::digest(a.get_signed_data(seed)).to_bytes(); - let b = Hasher::digest(b.get_signed_data(seed)).to_bytes(); + let a = Hasher64::digest(a.get_signed_data(seed)).to_bytes(); + let b = Hasher64::digest(b.get_signed_data(seed)).to_bytes(); (a, b) } } diff --git a/node-data/src/ledger/transaction.rs b/node-data/src/ledger/transaction.rs index e291b243e1..e1fec73e0c 100644 --- a/node-data/src/ledger/transaction.rs +++ b/node-data/src/ledger/transaction.rs @@ -42,7 +42,7 @@ impl Transaction { /// ### Returns /// An array of 32 bytes representing the hash of the transaction. pub fn hash(&self) -> [u8; 32] { - Hasher::digest(self.inner.to_var_bytes()).to_bytes() + Hasher64::digest(self.inner.to_var_bytes()).to_bytes() } /// Computes the transaction ID. @@ -56,7 +56,7 @@ impl Transaction { /// ### Returns /// An array of 32 bytes representing the transaction ID. pub fn id(&self) -> [u8; 32] { - Hasher::digest(self.inner.to_hash_input_bytes()).to_bytes() + Hasher64::digest(self.inner.to_hash_input_bytes()).to_bytes() } pub fn gas_price(&self) -> u64 { diff --git a/rusk-abi/CHANGELOG.md b/rusk-abi/CHANGELOG.md index 2fd12cb248..84f5656850 100644 --- a/rusk-abi/CHANGELOG.md +++ b/rusk-abi/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `gen_contract_id` and 32-byte hasher [#1884] - Memoize the `verify_bls` function - Memoize the `verify_proof` function [#1228] - New ABIs: `owner_raw`, `self_owner_raw` [#1710] @@ -200,6 +201,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add LICENSE - Add README.md +[#1884]: https://github.com/dusk-network/rusk/issues/1884 [#1710]: https://github.com/dusk-network/rusk/issues/1710 [#1630]: https://github.com/dusk-network/rusk/issues/1630 [#1609]: https://github.com/dusk-network/rusk/issues/1609 diff --git a/rusk-abi/Cargo.toml b/rusk-abi/Cargo.toml index 726af67b48..3b36a1634b 100644 --- a/rusk-abi/Cargo.toml +++ b/rusk-abi/Cargo.toml @@ -31,6 +31,8 @@ lru = { version = "0.12", optional = true } rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } once_cell = "1.15" ff = { version = "0.13", default-features = false } +rand = "0.8" +hex = "0.4" [features] # By default, we include the contract writing features. diff --git a/rusk-abi/src/hash.rs b/rusk-abi/src/hash.rs index 6bb95e7e83..523baf6e9b 100644 --- a/rusk-abi/src/hash.rs +++ b/rusk-abi/src/hash.rs @@ -5,11 +5,9 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use blake2b_simd::{Params, State}; -use execution_core::BlsScalar; -/// Hashes scalars and arbitrary slices of bytes using Blake2b, returning a -/// valid [`BlsScalar`]. Using the `Hasher` yields the same result as when using -/// `BlsScalar::hash_to_scalar`. +/// Hashes scalars and arbitrary slices of bytes using Blake2b, returning an +/// array of 32 bytes. /// /// This hash cannot be proven inside a circuit, if that is desired, use /// `poseidon_hash` instead. @@ -36,44 +34,11 @@ impl Hasher { self.state.update(data.as_ref()); } - /// Process input data in a chained manner. - pub fn chain_update(self, data: impl AsRef<[u8]>) -> Self { - let mut hasher = self; - hasher.state.update(data.as_ref()); - hasher - } - /// Retrieve result and consume hasher instance. - pub fn finalize(self) -> BlsScalar { - BlsScalar::from_bytes_wide(self.state.finalize().as_array()) - } - - /// Compute hash of arbitrary data into a valid [`BlsScalar`]. This - /// equivalent to using `BlsScalar::hash_to_scalar`. - pub fn digest(data: impl AsRef<[u8]>) -> BlsScalar { - let mut hasher = Hasher::new(); - hasher.update(data.as_ref()); - hasher.finalize() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use rand_core::{OsRng, RngCore}; - - #[test] - fn test_hash() { - let mut input = [0u8; 100000]; - OsRng.fill_bytes(&mut input[..]); - - let mut hasher = Hasher::new(); - for input_chunk in input.chunks(100) { - hasher.update(input_chunk); - } - let hash = hasher.finalize(); - - assert_eq!(hash, BlsScalar::hash_to_scalar(&input)); - assert_eq!(hash, Hasher::digest(&input)); + pub fn finalize(self) -> [u8; 32] { + let hash = self.state.finalize(); + let mut a = [0u8; 32]; + a.clone_from_slice(&hash.as_array()[..32]); + a } } diff --git a/rusk-abi/src/hash64.rs b/rusk-abi/src/hash64.rs new file mode 100644 index 0000000000..c2c317f9fb --- /dev/null +++ b/rusk-abi/src/hash64.rs @@ -0,0 +1,79 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use blake2b_simd::{Params, State}; +use execution_core::BlsScalar; + +/// Hashes scalars and arbitrary slices of bytes using Blake2b, returning a +/// valid [`BlsScalar`]. Using the `Hasher` yields the same result as when using +/// `BlsScalar::hash_to_scalar`. +/// +/// This hash cannot be proven inside a circuit, if that is desired, use +/// `poseidon_hash` instead. +pub struct Hasher64 { + state: State, +} + +impl Default for Hasher64 { + fn default() -> Self { + Hasher64 { + state: Params::new().hash_length(64).to_state(), + } + } +} + +impl Hasher64 { + /// Create new hasher instance. + pub fn new() -> Self { + Self::default() + } + + /// Process data, updating the internal state. + pub fn update(&mut self, data: impl AsRef<[u8]>) { + self.state.update(data.as_ref()); + } + + /// Process input data in a chained manner. + pub fn chain_update(self, data: impl AsRef<[u8]>) -> Self { + let mut hasher = self; + hasher.state.update(data.as_ref()); + hasher + } + + /// Retrieve result and consume hasher instance. + pub fn finalize(self) -> BlsScalar { + BlsScalar::from_bytes_wide(self.state.finalize().as_array()) + } + + /// Compute hash of arbitrary data into a valid [`BlsScalar`]. This + /// equivalent to using `BlsScalar::hash_to_scalar`. + pub fn digest(data: impl AsRef<[u8]>) -> BlsScalar { + let mut hasher = Hasher64::new(); + hasher.update(data.as_ref()); + hasher.finalize() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand_core::{OsRng, RngCore}; + + #[test] + fn test_hash() { + let mut input = [0u8; 100000]; + OsRng.fill_bytes(&mut input[..]); + + let mut hasher = Hasher64::new(); + for input_chunk in input.chunks(100) { + hasher.update(input_chunk); + } + let hash = hasher.finalize(); + + assert_eq!(hash, BlsScalar::hash_to_scalar(&input)); + assert_eq!(hash, Hasher64::digest(&input)); + } +} diff --git a/rusk-abi/src/host.rs b/rusk-abi/src/host.rs index 5b3c651330..8194aa4a1a 100644 --- a/rusk-abi/src/host.rs +++ b/rusk-abi/src/host.rs @@ -21,7 +21,7 @@ mod cache; pub use piecrust::*; -use crate::hash::Hasher; +use crate::hash64::Hasher64; use crate::{Metadata, PublicInput, Query}; /// Create a new session based on the given `vm`. The vm *must* have been @@ -132,7 +132,7 @@ fn host_verify_bls(arg_buf: &mut [u8], arg_len: u32) -> u32 { /// scalar. The output of the hasher is truncated (last nibble) to fit onto a /// scalar. pub fn hash(bytes: Vec) -> BlsScalar { - Hasher::digest(bytes) + Hasher64::digest(bytes) } /// Compute the poseidon hash of the given scalars diff --git a/rusk-abi/src/lib.rs b/rusk-abi/src/lib.rs index 986c186e6c..1c83d7290e 100644 --- a/rusk-abi/src/lib.rs +++ b/rusk-abi/src/lib.rs @@ -36,6 +36,8 @@ pub use host::*; pub mod dusk; #[doc(hidden)] pub mod hash; +#[doc(hidden)] +pub mod hash64; use hash::Hasher; @@ -63,10 +65,22 @@ const fn reserved(b: u8) -> ContractId { ContractId::from_bytes(bytes) } -/// Generate a [`ContractId`] address from the given slice of bytes, that is -/// also a valid [`BlsScalar`] -pub fn gen_contract_id(bytes: &[u8]) -> ContractId { - ContractId::from_bytes(Hasher::digest(bytes).into()) +/// Generate a [`ContractId`] address from: +/// - slice of bytes, +/// - nonce +/// - metadata +/// that is also a valid [`BlsScalar`] +pub fn gen_contract_id( + bytes: impl AsRef<[u8]>, + nonce: u64, + metadata: impl AsRef<[u8]>, +) -> ContractId { + let mut hasher = Hasher::new(); + hasher.update(bytes.as_ref()); + hasher.update(nonce.to_le_bytes()); + hasher.update(metadata.as_ref()); + let hash_bytes = hasher.finalize(); + ContractId::from_bytes(hash_bytes) } /// Converts a `ContractId` to a `BlsScalar` @@ -77,3 +91,31 @@ pub fn contract_to_scalar(module_id: &ContractId) -> BlsScalar { BlsScalar::from_slice(module_id.as_bytes()) .expect("Something went REALLY wrong if a contract id is not a scalar") } + +#[cfg(test)] +mod tests { + use super::*; + use rand::rngs::StdRng; + use rand::{RngCore, SeedableRng}; + + #[test] + fn test_gen_contract_id() { + let mut rng = StdRng::seed_from_u64(42); + + let mut bytes = vec![0; 1000]; + rng.fill_bytes(&mut bytes); + + let nonce = rng.next_u64(); + + let mut version = vec![0, 100]; + rng.fill_bytes(&mut version); + + let contract_id = + gen_contract_id(bytes.as_slice(), nonce, version.as_slice()); + + assert_eq!( + hex::encode(contract_id.as_bytes()), + "a138d3b9c87235dac6f62d1d30b75cffbb94601d9cbe5bd540b3e1e5842c8a7d" + ); + } +} diff --git a/rusk-abi/tests/lib.rs b/rusk-abi/tests/lib.rs index 1b738c51bf..a2443aa7e5 100644 --- a/rusk-abi/tests/lib.rs +++ b/rusk-abi/tests/lib.rs @@ -18,7 +18,7 @@ use execution_core::{ PublicKey, SecretKey, }; use ff::Field; -use rusk_abi::hash::Hasher; +use rusk_abi::hash64::Hasher64; use rusk_abi::PublicInput; use rusk_abi::{ContractData, ContractId, Session, VM}; @@ -44,7 +44,7 @@ fn hash_host() { assert_eq!( "0x58c751eca2d6a41227e0c52ef579f4688d698b3447a8bcc27fb2831e11d3239e", - format!("{:?}", Hasher::digest(input)) + format!("{:?}", Hasher64::digest(input)) ); }