From 18c9c2e62384d8c9454aaabfff169ce5ad37778a Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Wed, 3 Aug 2022 14:36:56 +0400 Subject: [PATCH] feat(base_layer/core): add domain hashing wrapper for consensus encoding --- base_layer/core/src/blocks/block_header.rs | 6 +- .../core/src/consensus/consensus_encoding.rs | 4 +- .../{hash_writer.rs => hashing.rs} | 92 ++++++++++--------- base_layer/core/src/consensus/mod.rs | 3 +- base_layer/core/src/transactions/mod.rs | 4 + .../transaction_components/mod.rs | 4 +- .../side_chain/checkpoint_challenge.rs | 4 +- .../side_chain/contract_definition.rs | 4 +- .../transaction_input.rs | 21 +++-- .../transaction_kernel.rs | 4 +- .../transaction_output.rs | 27 +++--- .../transaction_protocol/sender.rs | 11 +-- .../transaction_initializer.rs | 4 +- .../dan_validators/checkpoint_validator.rs | 4 +- 14 files changed, 103 insertions(+), 89 deletions(-) rename base_layer/core/src/consensus/consensus_encoding/{hash_writer.rs => hashing.rs} (53%) diff --git a/base_layer/core/src/blocks/block_header.rs b/base_layer/core/src/blocks/block_header.rs index 979908211d..403c962c62 100644 --- a/base_layer/core/src/blocks/block_header.rs +++ b/base_layer/core/src/blocks/block_header.rs @@ -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}, }; @@ -235,7 +235,7 @@ impl BlockHeader { .finalize() .to_vec() } else { - ConsensusHashWriter::default() + ConsensusHasher::default() .chain(&self.version) .chain(&self.height) .chain(&self.prev_hash) @@ -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 since a hash is always 32-bytes. Clean this // up if we decide to migrate to a fixed 32-byte type .chain(©_into_fixed_array::<_, 32>(&self.merged_mining_hash()).unwrap()) diff --git a/base_layer/core/src/consensus/consensus_encoding.rs b/base_layer/core/src/consensus/consensus_encoding.rs index 7c278745be..98b053f747 100644 --- a/base_layer/core/src/consensus/consensus_encoding.rs +++ b/base_layer/core/src/consensus/consensus_encoding.rs @@ -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; diff --git a/base_layer/core/src/consensus/consensus_encoding/hash_writer.rs b/base_layer/core/src/consensus/consensus_encoding/hashing.rs similarity index 53% rename from base_layer/core/src/consensus/consensus_encoding/hash_writer.rs rename to base_layer/core/src/consensus/consensus_encoding/hashing.rs index af20b9d964..a930ec6399 100644 --- a/base_layer/core/src/consensus/consensus_encoding/hash_writer.rs +++ b/base_layer/core/src/consensus/consensus_encoding/hashing.rs @@ -20,34 +20,47 @@ // 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(PhantomData); + +impl DomainSeparatedConsensusHasher { + #[allow(clippy::new_ret_no_self)] + pub fn new(label: &'static str) -> ConsensusHasher> { + ConsensusHasher::new(DomainSeparatedHasher::new_with_label(label)) + } +} + #[derive(Clone)] -pub struct ConsensusHashWriter { - digest: H, +pub struct ConsensusHasher { + writer: WriteHashWrapper, } -impl ConsensusHashWriter { - pub fn new(digest: H) -> Self { - Self { digest } +impl ConsensusHasher { + pub fn new(digest: D) -> Self { + Self { + writer: WriteHashWrapper(digest), + } } } -impl ConsensusHashWriter -where H: FixedOutput + Update +impl ConsensusHasher +where D: Digest { pub fn finalize(self) -> [u8; 32] { - self.digest.finalize_fixed().into() + self.writer.0.finalize().into() } - pub fn update_consensus_encode(&mut self, data: &T) { + pub fn update_consensus_encode(&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."); } @@ -55,52 +68,45 @@ where H: FixedOutput + Update self.update_consensus_encode(data); self } +} - pub fn into_digest(self) -> H { - self.digest +impl Default for ConsensusHasher { + fn default() -> Self { + ConsensusHasher::new(HashDigest::new()) } } -impl Write for ConsensusHashWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - 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); + +impl Write for WriteHashWrapper { + fn write(&mut self, buf: &[u8]) -> io::Result { + 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 { - 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::::new_with_label("foo") + .chain(b"\xff\x01") + .finalize(); + let hash = DomainSeparatedConsensusHasher::::new("foo") + .chain(&255u64) + .finalize(); + + assert_eq!(hash, expected_hash.as_ref()); } } diff --git a/base_layer/core/src/consensus/mod.rs b/base_layer/core/src/consensus/mod.rs index 7c6f783f18..806ac13211 100644 --- a/base_layer/core/src/consensus/mod.rs +++ b/base_layer/core/src/consensus/mod.rs @@ -36,7 +36,8 @@ pub use consensus_encoding::{ ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, - ConsensusHashWriter, + ConsensusHasher, + DomainSeparatedConsensusHasher, MaxSizeBytes, MaxSizeVec, ToConsensusBytes, diff --git a/base_layer/core/src/transactions/mod.rs b/base_layer/core/src/transactions/mod.rs index 770608f33c..16d31537a1 100644 --- a/base_layer/core/src/transactions/mod.rs +++ b/base_layer/core/src/transactions/mod.rs @@ -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}; @@ -24,3 +26,5 @@ pub mod weight; #[macro_use] pub mod test_helpers; + +hash_domain!(TransactionHashDomain, "com.tari.base_layer.core.transactions", 0); diff --git a/base_layer/core/src/transactions/transaction_components/mod.rs b/base_layer/core/src/transactions/transaction_components/mod.rs index 0c35b48612..a6d4f7c41d 100644 --- a/base_layer/core/src/transactions/transaction_components/mod.rs +++ b/base_layer/core/src/transactions/transaction_components/mod.rs @@ -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. @@ -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) diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/checkpoint_challenge.rs b/base_layer/core/src/transactions/transaction_components/side_chain/checkpoint_challenge.rs index 29b0415b80..b6f1a9c663 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/checkpoint_challenge.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/checkpoint_challenge.rs @@ -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); @@ -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) diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/contract_definition.rs b/base_layer/core/src/transactions/transaction_components/side_chain/contract_definition.rs index 426b12cb55..2b46d75aae 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/contract_definition.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/contract_definition.rs @@ -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, }; @@ -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() } } diff --git a/base_layer/core/src/transactions/transaction_components/transaction_input.rs b/base_layer/core/src/transactions/transaction_components/transaction_input.rs index b2a93cfa3e..5135a1dd6b 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_input.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_input.rs @@ -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, @@ -52,6 +52,7 @@ use crate::{ TransactionError, UnblindedOutput, }, + TransactionHashDomain, }, }; @@ -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::::new("script_challenge") + .chain(nonce_commitment) + .chain(script) + .chain(input_data) + .chain(script_public_key) + .chain(commitment) + .finalize() + }, } } @@ -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) diff --git a/base_layer/core/src/transactions/transaction_components/transaction_kernel.rs b/base_layer/core/src/transactions/transaction_components/transaction_kernel.rs index efcebd6ed6..294c5972e9 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_kernel.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_kernel.rs @@ -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}, @@ -147,7 +147,7 @@ impl TransactionKernel { impl Hashable for TransactionKernel { /// Produce a canonical hash for a transaction kernel. fn hash(&self) -> Vec { - ConsensusHashWriter::default().chain(self).finalize().to_vec() + ConsensusHasher::default().chain(self).finalize().to_vec() } } diff --git a/base_layer/core/src/transactions/transaction_components/transaction_output.rs b/base_layer/core/src/transactions/transaction_components/transaction_output.rs index 8866f863c5..2355a7ac4d 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -30,13 +30,11 @@ use std::{ io::{Read, Write}, }; -use digest::FixedOutput; use log::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use tari_common_types::types::{ BlindingFactor, - Challenge, ComSignature, Commitment, CommitmentFactory, @@ -57,12 +55,19 @@ use tari_script::TariScript; use super::TransactionOutputVersion; use crate::{ - consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusHashWriter}, + consensus::{ + ConsensusDecoding, + ConsensusEncoding, + ConsensusEncodingSized, + ConsensusHasher, + DomainSeparatedConsensusHasher, + }, covenants::Covenant, transactions::{ tari_amount::MicroTari, transaction_components, transaction_components::{EncryptedValue, OutputFeatures, OutputType, TransactionError, TransactionInput}, + TransactionHashDomain, }, }; @@ -193,7 +198,7 @@ impl TransactionOutput { ); if !self.metadata_signature.verify_challenge( &(&self.commitment + &self.sender_offset_public_key), - &challenge.finalize_fixed(), + &challenge, &CommitmentFactory::default(), ) { return Err(TransactionError::InvalidSignatureError( @@ -240,7 +245,7 @@ impl TransactionOutput { } /// Convenience function that returns the challenge for the metadata commitment signature - pub fn get_metadata_signature_challenge(&self, partial_commitment_nonce: Option<&PublicKey>) -> Challenge { + pub fn get_metadata_signature_challenge(&self, partial_commitment_nonce: Option<&PublicKey>) -> [u8; 32] { let nonce_commitment = match partial_commitment_nonce { None => self.metadata_signature.public_nonce().clone(), Some(partial_nonce) => self.metadata_signature.public_nonce() + partial_nonce, @@ -269,8 +274,8 @@ impl TransactionOutput { covenant: &Covenant, encrypted_value: &EncryptedValue, minimum_value_promise: MicroTari, - ) -> Challenge { - let common = ConsensusHashWriter::default() + ) -> [u8; 32] { + let common = DomainSeparatedConsensusHasher::::new("metadata_signature_challenge") .chain(public_commitment_nonce) .chain(script) .chain(features) @@ -280,8 +285,8 @@ impl TransactionOutput { .chain(encrypted_value); match version { - TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => common.into_digest(), - TransactionOutputVersion::V2 => common.chain(&minimum_value_promise).into_digest(), + TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => common.finalize(), + TransactionOutputVersion::V2 => common.chain(&minimum_value_promise).finalize(), } } @@ -329,7 +334,7 @@ impl TransactionOutput { &secret_x, &nonce_a, &nonce_b, - &e.finalize_fixed(), + &e, &CommitmentFactory::default(), )?) } @@ -391,7 +396,7 @@ impl TransactionOutput { } pub fn witness_hash(&self) -> Vec { - ConsensusHashWriter::default() + ConsensusHasher::default() .chain(&self.proof) .chain(&self.metadata_signature) .finalize() diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index 221de28b05..9c46f2789e 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -23,7 +23,7 @@ use std::fmt; use derivative::Derivative; -use digest::{Digest, FixedOutput}; +use digest::Digest; use serde::{Deserialize, Serialize}; use tari_common_types::{ transaction::TxId, @@ -493,9 +493,7 @@ impl SenderTransactionProtocol { ) -> Result { // Create sender signature let public_commitment_nonce = PublicKey::from_secret_key(private_commitment_nonce); - let e = output - .get_metadata_signature_challenge(Some(&public_commitment_nonce)) - .finalize_fixed(); + let e = output.get_metadata_signature_challenge(Some(&public_commitment_nonce)); let sender_signature = Signature::sign(sender_offset_private_key.clone(), private_commitment_nonce.clone(), &e)?; let sender_signature = sender_signature.get_signature(); @@ -805,7 +803,6 @@ impl fmt::Display for SenderState { #[cfg(test)] mod test { - use digest::Digest; use rand::rngs::OsRng; use tari_common_types::types::{CommitmentFactory, PrivateKey, PublicKey, RangeProof}; use tari_crypto::{ @@ -964,9 +961,7 @@ mod test { assert!(output.verify_metadata_signature().is_err()); assert!(partial_metadata_signature.verify_challenge( &commitment, - &output - .get_metadata_signature_challenge(Some(&sender_public_commitment_nonce)) - .finalize(), + &output.get_metadata_signature_challenge(Some(&sender_public_commitment_nonce)), &commitment_factory )); diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index c4d7a4b278..ea311baecb 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -25,7 +25,7 @@ use std::{ fmt::{Debug, Error, Formatter}, }; -use digest::{Digest, FixedOutput}; +use digest::Digest; use log::*; use rand::rngs::OsRng; use tari_common_types::{ @@ -233,7 +233,7 @@ impl SenderTransactionInitializer { ); if !output.metadata_signature.verify_challenge( &(&commitment + &output.sender_offset_public_key), - &e.finalize_fixed(), + &e, &commitment_factory, ) { return self.clone().build_err(&*format!( diff --git a/base_layer/core/src/validation/dan_validators/checkpoint_validator.rs b/base_layer/core/src/validation/dan_validators/checkpoint_validator.rs index a2545d3b90..3d6840d07e 100644 --- a/base_layer/core/src/validation/dan_validators/checkpoint_validator.rs +++ b/base_layer/core/src/validation/dan_validators/checkpoint_validator.rs @@ -135,7 +135,7 @@ mod test { use tari_common_types::types::FixedHash; use crate::{ - consensus::ConsensusHashWriter, + consensus::ConsensusHasher, validation::dan_validators::{ helpers::create_checkpoint_challenge, test_helpers::{ @@ -290,7 +290,7 @@ mod test { // To create an invalid signature, let's use a challenge from a different checkpoint let mut checkpoint = create_contract_checkpoint(0); - let challenge: FixedHash = ConsensusHashWriter::default() + let challenge: FixedHash = ConsensusHasher::default() .chain(&"invalid data".as_bytes()) .finalize() .into();