Skip to content

Commit

Permalink
feat(base_layer/core): add domain hashing wrapper for consensus encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
sdbondi committed Aug 3, 2022
1 parent 32184b5 commit 18c9c2e
Show file tree
Hide file tree
Showing 14 changed files with 103 additions and 89 deletions.
6 changes: 3 additions & 3 deletions base_layer/core/src/blocks/block_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ use thiserror::Error;
#[cfg(feature = "base_node")]
use crate::blocks::{BlockBuilder, NewBlockHeaderTemplate};
use crate::{
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusHashWriter},
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusHasher},
proof_of_work::{PowAlgorithm, PowError, ProofOfWork},
};

Expand Down Expand Up @@ -235,7 +235,7 @@ impl BlockHeader {
.finalize()
.to_vec()
} else {
ConsensusHashWriter::default()
ConsensusHasher::default()
.chain(&self.version)
.chain(&self.height)
.chain(&self.prev_hash)
Expand Down Expand Up @@ -302,7 +302,7 @@ impl Hashable for BlockHeader {
.finalize()
.to_vec()
} else {
ConsensusHashWriter::default()
ConsensusHasher::default()
// TODO: this excludes extraneous length varint used for Vec<u8> since a hash is always 32-bytes. Clean this
// up if we decide to migrate to a fixed 32-byte type
.chain(&copy_into_fixed_array::<_, 32>(&self.merged_mining_hash()).unwrap())
Expand Down
4 changes: 2 additions & 2 deletions base_layer/core/src/consensus/consensus_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ mod crypto;
mod epoch_time;
mod fixed_hash;
mod generic;
mod hash_writer;
mod hashing;
mod integers;
mod micro_tari;
mod script;
mod vec;
use std::io;

pub use hash_writer::ConsensusHashWriter;
pub use hashing::{ConsensusHasher, DomainSeparatedConsensusHasher};
pub use vec::MaxSizeVec;

pub use self::bytes::MaxSizeBytes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,87 +20,93 @@
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use std::io::Write;
use std::{io, io::Write, marker::PhantomData};

use digest::{consts::U32, Digest, FixedOutput, Update};
use digest::{consts::U32, Digest};
use tari_common_types::types::HashDigest;
use tari_crypto::hashing::{DomainSeparatedHasher, DomainSeparation};

use crate::consensus::ConsensusEncoding;

/// Domain separated consensus encoding hasher.
pub struct DomainSeparatedConsensusHasher<M: DomainSeparation>(PhantomData<M>);

impl<M: DomainSeparation> DomainSeparatedConsensusHasher<M> {
#[allow(clippy::new_ret_no_self)]
pub fn new(label: &'static str) -> ConsensusHasher<DomainSeparatedHasher<HashDigest, M>> {
ConsensusHasher::new(DomainSeparatedHasher::new_with_label(label))
}
}

#[derive(Clone)]
pub struct ConsensusHashWriter<H> {
digest: H,
pub struct ConsensusHasher<D> {
writer: WriteHashWrapper<D>,
}

impl<H: Digest> ConsensusHashWriter<H> {
pub fn new(digest: H) -> Self {
Self { digest }
impl<D: Digest> ConsensusHasher<D> {
pub fn new(digest: D) -> Self {
Self {
writer: WriteHashWrapper(digest),
}
}
}

impl<H> ConsensusHashWriter<H>
where H: FixedOutput<OutputSize = U32> + Update
impl<D> ConsensusHasher<D>
where D: Digest<OutputSize = U32>
{
pub fn finalize(self) -> [u8; 32] {
self.digest.finalize_fixed().into()
self.writer.0.finalize().into()
}

pub fn update_consensus_encode<T: ConsensusEncoding>(&mut self, data: &T) {
pub fn update_consensus_encode<T: ConsensusEncoding + ?Sized>(&mut self, data: &T) {
// UNWRAP: ConsensusEncode MUST only error if the writer errors, HashWriter::write is infallible
data.consensus_encode(self)
data.consensus_encode(&mut self.writer)
.expect("Incorrect implementation of ConsensusEncoding encountered. Implementations MUST be infallible.");
}

pub fn chain<T: ConsensusEncoding>(mut self, data: &T) -> Self {
self.update_consensus_encode(data);
self
}
}

pub fn into_digest(self) -> H {
self.digest
impl Default for ConsensusHasher<HashDigest> {
fn default() -> Self {
ConsensusHasher::new(HashDigest::new())
}
}

impl<H: Update> Write for ConsensusHashWriter<H> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.digest.update(buf);
/// This private struct wraps a Digest and implements the Write trait to satisfy the consensus encoding trait..
#[derive(Clone)]
struct WriteHashWrapper<D>(D);

impl<D: Digest> Write for WriteHashWrapper<D> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.update(buf);
Ok(buf.len())
}

fn flush(&mut self) -> std::io::Result<()> {
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

impl Default for ConsensusHashWriter<HashDigest> {
fn default() -> Self {
ConsensusHashWriter::new(HashDigest::new())
}
}

#[cfg(test)]
mod test {
use rand::{rngs::OsRng, RngCore};
use tari_common_types::types::HashDigest;
mod tests {
use tari_crypto::{hash::blake2::Blake256, hash_domain};

use super::*;

#[test]
fn it_updates_the_digest_state() {
let mut writer = ConsensusHashWriter::default();
let mut data = [0u8; 1024];
OsRng.fill_bytes(&mut data);

// Even if data is streamed in chunks, the preimage and therefore the resulting hash are the same
writer.write_all(&data[0..256]).unwrap();
writer.write_all(&data[256..500]).unwrap();
writer.write_all(&data[500..1024]).unwrap();
let hash = writer.finalize();
let empty: [u8; 32] = Update::chain(HashDigest::new(), [0u8; 1024]).finalize_fixed().into();
assert_ne!(hash, empty);

let mut writer = ConsensusHashWriter::default();
writer.write_all(&data).unwrap();
assert_eq!(writer.finalize(), hash);
fn it_hashes_using_the_domain_hasher() {
hash_domain!(TestHashDomain, "tari.test", 0);
let expected_hash = DomainSeparatedHasher::<Blake256, TestHashDomain>::new_with_label("foo")
.chain(b"\xff\x01")
.finalize();
let hash = DomainSeparatedConsensusHasher::<TestHashDomain>::new("foo")
.chain(&255u64)
.finalize();

assert_eq!(hash, expected_hash.as_ref());
}
}
3 changes: 2 additions & 1 deletion base_layer/core/src/consensus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ pub use consensus_encoding::{
ConsensusDecoding,
ConsensusEncoding,
ConsensusEncodingSized,
ConsensusHashWriter,
ConsensusHasher,
DomainSeparatedConsensusHasher,
MaxSizeBytes,
MaxSizeVec,
ToConsensusBytes,
Expand Down
4 changes: 4 additions & 0 deletions base_layer/core/src/transactions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
pub mod aggregated_body;

mod crypto_factories;

pub use crypto_factories::CryptoFactories;
use tari_crypto::hash_domain;

mod coinbase_builder;
pub use coinbase_builder::{CoinbaseBuildError, CoinbaseBuilder};
Expand All @@ -24,3 +26,5 @@ pub mod weight;

#[macro_use]
pub mod test_helpers;

hash_domain!(TransactionHashDomain, "com.tari.base_layer.core.transactions", 0);
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub const MAX_TRANSACTION_RECIPIENTS: usize = 15;
//---------------------------------------- Crate functions ----------------------------------------------------//

use super::tari_amount::MicroTari;
use crate::{consensus::ConsensusHashWriter, covenants::Covenant};
use crate::{consensus::ConsensusHasher, covenants::Covenant};

/// Implement the canonical hashing function for TransactionOutput and UnblindedOutput for use in
/// ordering as well as for the output hash calculation for TransactionInput.
Expand All @@ -103,7 +103,7 @@ pub(super) fn hash_output(
encrypted_value: &EncryptedValue,
minimum_value_promise: MicroTari,
) -> [u8; 32] {
let common_hash = ConsensusHashWriter::default()
let common_hash = ConsensusHasher::default()
.chain(&version)
.chain(features)
.chain(commitment)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

use tari_common_types::types::{Commitment, FixedHash};

use crate::consensus::ConsensusHashWriter;
use crate::consensus::ConsensusHasher;

#[derive(Debug, Clone, Copy)]
pub struct CheckpointChallenge(FixedHash);
Expand All @@ -35,7 +35,7 @@ impl CheckpointChallenge {
checkpoint_number: u64,
) -> Self {
// TODO: Use new tari_crypto domain-separated hashing
let hash = ConsensusHashWriter::default()
let hash = ConsensusHasher::default()
.chain(contract_id)
.chain(checkpoint_commitment)
.chain(merkle_root)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize};
use tari_common_types::types::{FixedHash, PublicKey};

use crate::{
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusHashWriter, MaxSizeVec},
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusHasher, MaxSizeVec},
transactions::transaction_components::FixedString,
};

Expand All @@ -50,7 +50,7 @@ impl ContractDefinition {
}

pub fn calculate_contract_id(&self) -> FixedHash {
ConsensusHashWriter::default().chain(self).finalize().into()
ConsensusHasher::default().chain(self).finalize().into()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use tari_script::{ExecutionStack, ScriptContext, StackItem, TariScript};

use super::{TransactionInputVersion, TransactionOutputVersion};
use crate::{
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusHashWriter, MaxSizeBytes},
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusHasher, DomainSeparatedConsensusHasher, MaxSizeBytes},
covenants::Covenant,
transactions::{
tari_amount::MicroTari,
Expand All @@ -52,6 +52,7 @@ use crate::{
TransactionError,
UnblindedOutput,
},
TransactionHashDomain,
},
};

Expand Down Expand Up @@ -170,13 +171,15 @@ impl TransactionInput {
commitment: &Commitment,
) -> [u8; 32] {
match version {
TransactionInputVersion::V0 | TransactionInputVersion::V1 => ConsensusHashWriter::default()
.chain(nonce_commitment)
.chain(script)
.chain(input_data)
.chain(script_public_key)
.chain(commitment)
.finalize(),
TransactionInputVersion::V0 | TransactionInputVersion::V1 => {
DomainSeparatedConsensusHasher::<TransactionHashDomain>::new("script_challenge")
.chain(nonce_commitment)
.chain(script)
.chain(input_data)
.chain(script_public_key)
.chain(commitment)
.finalize()
},
}
}

Expand Down Expand Up @@ -378,7 +381,7 @@ impl TransactionInput {
ref minimum_value_promise,
} => {
// TODO: Change this hash to what is in RFC-0121/Consensus Encoding #testnet-reset
let writer = ConsensusHashWriter::default()
let writer = ConsensusHasher::default()
.chain(version)
.chain(features)
.chain(commitment)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use tari_utilities::{hex::Hex, message_format::MessageFormat, Hashable};

use super::TransactionKernelVersion;
use crate::{
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusHashWriter},
consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusHasher},
transactions::{
tari_amount::MicroTari,
transaction_components::{KernelFeatures, TransactionError},
Expand Down Expand Up @@ -147,7 +147,7 @@ impl TransactionKernel {
impl Hashable for TransactionKernel {
/// Produce a canonical hash for a transaction kernel.
fn hash(&self) -> Vec<u8> {
ConsensusHashWriter::default().chain(self).finalize().to_vec()
ConsensusHasher::default().chain(self).finalize().to_vec()
}
}

Expand Down
Loading

0 comments on commit 18c9c2e

Please sign in to comment.