From a05312169aafb29af8c22704887cf83e126e9032 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 16 Jun 2026 14:30:26 +0000 Subject: [PATCH 1/4] blind: encapsulate RangeProofMessage and improve error types In #279 we fixed some slicing bugs in a fairly ugly way so that we could easily backport the bugfixes without API breakage. However, the better solution is to close the RangeProofMessage type, add a dedicated error for parsing from an array, and update all callers. This changes the CT validation logic to allow rangeproofs to contain embedded asset IDs alongside explicit assets. This differs from Elements which only allows unblinding an output if both its value and asset commitment are confidential. However, if the confidential asset is explicit, it seems like the "right thing to do" to embed the asset ID and a zero blinding factor. This matches what we do in this library in TxIn::blind_issuances_with_bfs for example. --- clippy.toml | 1 + src/blind.rs | 223 ++++++++++++++++++++++++++++++++++-------------- src/pset/mod.rs | 5 +- 3 files changed, 163 insertions(+), 66 deletions(-) diff --git a/clippy.toml b/clippy.toml index 5f304987..c4931941 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,2 @@ avoid-breaking-exported-api = true +large-error-threshold = 192 # default 128 complains about two public keys in RangeProofMessageError diff --git a/src/blind.rs b/src/blind.rs index 7954ddcc..d8e26e6a 100644 --- a/src/blind.rs +++ b/src/blind.rs @@ -15,7 +15,6 @@ //! # Transactions Blinding //! -use internals::array::ArrayExt as _; use internals::slice::SliceExt; use std::{self, collections::BTreeMap, fmt}; @@ -201,26 +200,144 @@ impl From for ConfidentialTxOutError { ConfidentialTxOutError::Upstream(from) } } -/// The Rangeproof message -#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct RangeProofMessage { - /// The asset id - pub asset: AssetId, - /// The asset blinding factor - pub bf: AssetBlindingFactor, -} -impl RangeProofMessage { - /// Converts the message to bytes - pub fn to_bytes(&self) -> [u8; 64] { - let mut message = [0u8; 64]; +mod range_proof_message { + use core::fmt; + use internals::array::ArrayExt; + use secp256k1_zkp::{Generator, Signing, Secp256k1}; + use super::{Asset, AssetId, AssetBlindingFactor}; + + /// The Rangeproof message + #[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] + pub struct RangeProofMessage { + /// The asset id + asset_id: AssetId, + /// The asset blinding factor + asset_bf: AssetBlindingFactor, + } + + impl RangeProofMessage { + /// Constructs a [`RangeProofMessage`] from an asset ID and blinding factor. + pub fn new(asset_id: AssetId, asset_bf: AssetBlindingFactor) -> Self { + Self { asset_id, asset_bf } + } + + /// The asset ID embedded in the rangeproof message. + pub fn asset_id(&self) -> &AssetId { + &self.asset_id + } + + /// The asset blinding factor embedded in the rangeproof message. + pub fn blinding_factor(&self) -> &AssetBlindingFactor { + &self.asset_bf + } - message[..32].copy_from_slice(self.asset.into_tag().as_ref()); - message[32..].copy_from_slice(self.bf.into_inner().as_ref()); + /// Computes the commmitment of the rangeproof message. + pub fn commitment(&self, secp: &Secp256k1) -> Generator { + Generator::new_blinded(secp, self.asset_id.into_tag(), self.asset_bf.into_inner()) + } - message + /// Parses a message from bytes + pub fn from_byte_array( + secp: &Secp256k1, + inner: [u8; 64], + expected_asset: &Asset, + ) -> Result { + let (asset_id, asset_bf) = inner.split_array::<32, 32>(); + let ret = Self { + asset_id: AssetId::from_byte_array(*asset_id), + asset_bf: AssetBlindingFactor::from_byte_array(*asset_bf) + .map_err(RangeProofMessageError::BlindingFactorOutOfRange)?, + }; + + match expected_asset { + Asset::Null => return Err(RangeProofMessageError::NullExpectedAsset), + Asset::Explicit(asset_id) => { + if ret.asset_id != *asset_id { + return Err(RangeProofMessageError::ExplicitAssetMismatch { + in_txout: *asset_id, + in_message: ret.asset_id, + }) + } + if ret.asset_bf != AssetBlindingFactor::zero() { + return Err(RangeProofMessageError::ExplicitAssetNonzeroBf { + blinding_factor: ret.asset_bf, + }); + } + } + Asset::Confidential(commitment) => { + let ret_commitment = ret.commitment(secp); + if ret_commitment != *commitment { + return Err(RangeProofMessageError::ConfidentialAssetMismatch { + in_txout: *commitment, + in_message: ret_commitment, + }) + } + } + } + + Ok(ret) + } + + /// Converts the message to bytes + pub fn to_byte_array(&self) -> [u8; 64] { + let mut message = [0u8; 64]; + message[..32].copy_from_slice(self.asset_id.into_tag().as_ref()); + message[32..].copy_from_slice(self.asset_bf.into_inner().as_ref()); + message + } + } + + #[non_exhaustive] + #[derive(PartialEq, Eq, Clone, Debug)] + pub enum RangeProofMessageError { + NullExpectedAsset, + ExplicitAssetMismatch { + in_txout: AssetId, + in_message: AssetId, + }, + ExplicitAssetNonzeroBf { + blinding_factor: AssetBlindingFactor, + }, + ConfidentialAssetMismatch { + in_txout: Generator, + in_message: Generator, + }, + BlindingFactorOutOfRange(secp256k1_zkp::Error), + } + + impl fmt::Display for RangeProofMessageError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::NullExpectedAsset => f.write_str("rangeproof associated with null asset"), + Self::ExplicitAssetMismatch { in_txout, in_message } => { + write!(f, "txout had explicit asset ID {in_txout}, but rangeproof encoded asset ID {in_message}") + } + Self::ExplicitAssetNonzeroBf { blinding_factor } => { + write!(f, "txout had explicit asset ID, but rangeproof encoded a nonzero asset blinding factor {blinding_factor}") + } + Self::ConfidentialAssetMismatch { in_txout, in_message } => { + write!(f, "txout had asset commitment {in_txout}, but rangeproof encoded asset commitment {in_message}") + } + Self::BlindingFactorOutOfRange(ref e) => e.fmt(f), + } + } + } + + impl std::error::Error for RangeProofMessageError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::NullExpectedAsset => None, + Self::ExplicitAssetMismatch { .. } => None, + Self::ExplicitAssetNonzeroBf { .. } => None, + Self::ConfidentialAssetMismatch { .. } => None, + Self::BlindingFactorOutOfRange(ref e) => Some(e), + } + + } } } +pub use self::range_proof_message::{RangeProofMessage, RangeProofMessageError}; /// Information about Transaction Input Asset #[cfg_attr( @@ -432,8 +549,7 @@ impl Value { let value = self .explicit() .ok_or(ConfidentialTxOutError::ExpectedExplicitValue)?; - let out_asset_commitment = - Generator::new_blinded(secp, msg.asset.into_tag(), msg.bf.into_inner()); + let out_asset_commitment = msg.commitment(secp); let value_commitment = Value::new_confidential(secp, value, out_asset_commitment, vbf); let rangeproof = RangeProof::new( @@ -442,7 +558,7 @@ impl Value { value_commitment.commitment().expect("confidential value"), value, vbf.into_inner(), - &msg.to_bytes(), + &msg.to_byte_array(), spk.as_bytes(), shared_secret, TxOut::RANGEPROOF_EXP_SHIFT, @@ -536,10 +652,10 @@ impl TxOut { let (out_asset, surjection_proof) = exp_asset.blind(rng, secp, out_secrets.asset_bf, spent_utxo_secrets)?; - let msg = RangeProofMessage { - asset: out_secrets.asset, - bf: out_secrets.asset_bf, - }; + let msg = RangeProofMessage::new( + out_secrets.asset, + out_secrets.asset_bf, + ); let exp_value = Value::Explicit(out_secrets.value); let (out_value, nonce, range_proof) = exp_value.blind( secp, @@ -735,7 +851,7 @@ impl TxOut { /// Unblinds a transaction output, if it is confidential. /// /// It returns the secret elements of the value and asset Pedersen commitments. - pub fn unblind( + pub fn unblind( &self, secp: &Secp256k1, blinding_key: SecretKey, @@ -760,34 +876,22 @@ impl TxOut { shared_secret, self.script_pubkey.as_bytes(), additional_generator, - )?; + ).map_err(UnblindError::Rewind)?; - // Use `MissingRangeproof` error because it's available so does not require - // API breaks. In a later PR we should extend that enum and add #[non_exhaustive] - // to it. The maybe-better `MalformedAssetId` error requires we start with a - // std `FromSliceError` which we don't have. + let value = opening.value; + let value_bf = ValueBlindingFactor(opening.blinding_factor); let asset_and_bf = SliceExt::split_first_chunk::<64>(opening.message.as_ref()) .ok_or(UnblindError::MissingRangeproof)? .0; - let (asset_id, asset_bf) = asset_and_bf.split_array(); - - let asset_id = AssetId::from_byte_array(*asset_id); - let asset_bf = AssetBlindingFactor::from_byte_array(*asset_bf)?; - if let Asset::Confidential(own_asset) = self.asset { - let secp = Secp256k1::signing_only(); // needed to avoid API break - let asset = Generator::new_blinded(&secp, asset_id.into_tag(), asset_bf.into_inner()); - if asset != own_asset { - // See above about use of MissingRangeproof. - return Err(UnblindError::MissingRangeproof); - } - } - - let value = opening.value; - let value_bf = ValueBlindingFactor(opening.blinding_factor); + let message = RangeProofMessage::from_byte_array( + secp, + *asset_and_bf, + &self.asset, + ).map_err(UnblindError::RangeProofMessage)?; Ok(TxOutSecrets { - asset: asset_id, - asset_bf, + asset: *message.asset_id(), + asset_bf: *message.blinding_factor(), value, value_bf, }) @@ -796,6 +900,7 @@ impl TxOut { /// Errors encountered when unblinding `TxOut`s. #[derive(Debug)] +#[non_exhaustive] pub enum UnblindError { /// The `TxOut` is not fully confidential. NotConfidential, @@ -803,18 +908,18 @@ pub enum UnblindError { MissingNonce, /// Transaction output does not have a rangeproof. MissingRangeproof, - /// Malformed asset ID. - MalformedAssetId(core::array::TryFromSliceError), + /// Malformed rangeproof message. + RangeProofMessage(RangeProofMessageError), /// Error originated in `secp256k1_zkp`. - Upstream(secp256k1_zkp::Error), + Rewind(secp256k1_zkp::Error), } impl fmt::Display for UnblindError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { UnblindError::MissingNonce => write!(f, "missing nonce in txout"), - UnblindError::MalformedAssetId(_) => write!(f, "malformed asset id"), - UnblindError::Upstream(e) => write!(f, "{}", e), + UnblindError::RangeProofMessage(_) => f.write_str("failed to parse message embedded in rangeproof"), + UnblindError::Rewind(_) => f.write_str("failed to rewind rangeproof"), UnblindError::NotConfidential => write!(f, "cannot unblind non-confidential txout"), UnblindError::MissingRangeproof => write!(f, "missing rangeproof in txout"), } @@ -825,20 +930,14 @@ impl std::error::Error for UnblindError { fn cause(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { UnblindError::MissingNonce => None, - UnblindError::MalformedAssetId(e) => Some(e), - UnblindError::Upstream(e) => Some(e), + UnblindError::RangeProofMessage(e) => Some(e), + UnblindError::Rewind(e) => Some(e), UnblindError::NotConfidential => None, UnblindError::MissingRangeproof => None, } } } -impl From for UnblindError { - fn from(from: secp256k1_zkp::Error) -> Self { - UnblindError::Upstream(from) - } -} - impl TxIn { /// Blind issuances for this [`TxIn`]. Asset amount and token amount must be /// set in [`AssetIssuance`](crate::AssetIssuance) field for this input @@ -871,10 +970,10 @@ impl TxIn { Value::Explicit(v) => Value::Explicit(v), }; let spk = Script::new(); - let msg = RangeProofMessage { + let msg = RangeProofMessage::new( asset, - bf: AssetBlindingFactor::zero(), - }; + AssetBlindingFactor::zero(), + ); let (comm, prf) = v.blind_with_shared_secret(secp, bf, blind_sk, &spk, &msg)?; if i == 0 { self.asset_issuance.amount = comm; diff --git a/src/pset/mod.rs b/src/pset/mod.rs index b0f976cf..aecd77b6 100644 --- a/src/pset/mod.rs +++ b/src/pset/mod.rs @@ -654,10 +654,7 @@ impl PartiallySignedTransaction { .ok_or(PsetBlindError::MustHaveExplicitTxOut(last_out_index))?; let ephemeral_sk = SecretKey::new(rng); let spk = &self.outputs[last_out_index].script_pubkey; - let msg = RangeProofMessage { - asset: asset_id, - bf: out_abf, - }; + let msg = RangeProofMessage::new(asset_id, out_abf); let blind_res = exp_value.blind( secp, final_vbf, From 55bca007858c3741a20a976afe990acc30f6fdc4 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 1 Jun 2026 17:49:43 +0000 Subject: [PATCH 2/4] add 'hashes 1.0' dependency --- Cargo-recent.lock | 28 +++++++++++++++++++++++++--- Cargo.toml | 2 ++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 5a99fb08..f83c2120 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -27,7 +27,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ "bitcoin-internals 0.3.0", - "bitcoin_hashes", + "bitcoin_hashes 0.14.0", ] [[package]] @@ -69,13 +69,22 @@ dependencies = [ "bitcoin-internals 0.3.0", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes", + "bitcoin_hashes 0.14.0", "hex-conservative 0.2.1", "hex_lit", "secp256k1", "serde", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40" +dependencies = [ + "bitcoin-internals 0.5.0", +] + [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -124,6 +133,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f70c29ac06e7effa19682e91318deae86bdb46c4fd1bbd0f12fd196ff427ab0" +dependencies = [ + "bitcoin-consensus-encoding", + "bitcoin-internals 0.5.0", + "hex-conservative 1.1.0", + "serde", +] + [[package]] name = "bitcoincore-rpc" version = "0.19.0" @@ -210,6 +231,7 @@ dependencies = [ "bincode", "bitcoin", "bitcoin-internals 0.5.0", + "bitcoin_hashes 1.0.0", "getrandom 0.2.16", "hex-conservative 1.1.0", "rand", @@ -490,7 +512,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.14.0", "rand", "secp256k1-sys", "serde", diff --git a/Cargo.toml b/Cargo.toml index a1d5f4af..53c380b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ json-contract = ["serde_json"] "dep:serde", "bitcoin/serde", "bitcoin/serde", + "hashes/serde", "secp256k1-zkp/serde", ] base64 = ["bitcoin/base64"] @@ -29,6 +30,7 @@ base64 = ["bitcoin/base64"] [dependencies] bech32 = "0.11.0" bitcoin = "0.32.2" +hashes = { package = "bitcoin_hashes", version = "1.0", features = [ "hex" ] } internals = { package = "bitcoin-internals", version = "0.5" } secp256k1-zkp = { version = "0.11.0", features = ["global-context", "hashes"] } From 1ad3bd99c80fe2754ddbc3cb29bfe6c534d2bf84 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 19 Jun 2026 11:43:43 +0000 Subject: [PATCH 3/4] genesis: eliminate a bunch of allocations and panic paths Oops :) apparently I did not review this code as well as I should have. --- src/genesis.rs | 23 ++++++++++++++--------- src/issuance.rs | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/genesis.rs b/src/genesis.rs index 74c7d4d9..1dc7d71a 100644 --- a/src/genesis.rs +++ b/src/genesis.rs @@ -19,7 +19,6 @@ use secp256k1_zkp::Tweak; use crate::hashes::{sha256, sha256d, Hash, HashEngine}; use crate::opcodes::all::OP_RETURN; use crate::opcodes::OP_TRUE; -use crate::pset::serialize::Serialize; use crate::{confidential, script, AssetId, Block, BlockExtData, BlockHash, BlockHeader, LockTime, Script, Sequence, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness}; use crate::{AssetIssuance, ContractHash, OutPoint, Txid}; use crate::confidential::Nonce; @@ -89,12 +88,18 @@ impl NetworkParams { } /// Hash commitment of network parameters for a given Network -pub fn commit_to_custom_network_parameters(params: &NetworkParams) -> Vec { +pub fn commit_to_custom_network_parameters(params: &NetworkParams) -> sha256::Hash { + use hex::{BytesToHexIter, Case}; + let mut eng = sha256::Hash::engine(); eng.input(params.network_id.clone().as_bytes()); - eng.input(format!("{:x}", params.fedpeg_script).as_bytes()); - eng.input(format!("{:x}", params.sign_block_script).as_bytes()); - sha256::Hash::from_engine(eng).serialize() + for ch in BytesToHexIter::new(params.fedpeg_script[..].iter(), Case::Lower).flatten() { + eng.input(&[ch.into()]); + } + for ch in BytesToHexIter::new(params.sign_block_script[..].iter(), Case::Lower).flatten() { + eng.input(&[ch.into()]); + } + sha256::Hash::from_engine(eng) } /// Produce the genesis transaction for a given elements Network @@ -105,7 +110,7 @@ fn liquid_genesis_tx(network_params: &NetworkParams) -> Transaction { previous_output: OutPoint::default(), is_pegin: false, script_sig: script::Builder::new() - .push_slice(commit.as_slice()) + .push_slice(commit.as_byte_array()) .into_script(), sequence: Sequence::default(), asset_issuance: AssetIssuance::default(), @@ -135,7 +140,7 @@ fn liquid_genesis_asset_tx(network_params: &NetworkParams) -> Option Block { let merkle_root: sha256d::Hash = if let Some(asset_tx) = liquid_genesis_asset_tx(params) { txdata.push(asset_tx.clone()); - let tx_hashes = vec![tx.txid().to_raw_hash(), asset_tx.txid().to_raw_hash()]; - bitcoin::merkle_tree::calculate_root(tx_hashes.into_iter()) + let tx_hashes = [tx.txid().to_raw_hash(), asset_tx.txid().to_raw_hash()]; + bitcoin::merkle_tree::calculate_root(tx_hashes.iter().copied()) .expect("merkle root") } else { tx.txid().to_raw_hash() diff --git a/src/issuance.rs b/src/issuance.rs index f1f789ee..27c7b30d 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -167,8 +167,8 @@ impl AssetId { /// a Regtest parent network fn pegged_asset_id_for_params_and_parent_chain_hash(params: &NetworkParams, parent_chainhash: bitcoin::blockdata::constants::ChainHash) -> AssetId { let commit = commit_to_custom_network_parameters(params); - let asset_outpoint = OutPoint::new(Txid::from_slice(commit.as_slice()).expect("txid"), 0); - let asset_entropy = AssetId::generate_asset_entropy(asset_outpoint, ContractHash::from_slice(parent_chainhash.to_bytes().as_slice()).unwrap()); + let asset_outpoint = OutPoint::new(Txid::from_byte_array(commit.to_byte_array()), 0); + let asset_entropy = AssetId::generate_asset_entropy(asset_outpoint, ContractHash::from_byte_array(*parent_chainhash.as_ref())); AssetId::from_entropy(asset_entropy) } } From 6b97df86985c11118397844b5d451bfc25178862 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 1 Jun 2026 17:52:36 +0000 Subject: [PATCH 4/4] use bitcoin_hashes 1.0 throughout the codebase This is a fairly big commit but it's mostly mechanical and it didn't seem super helpful to pull it out into multiple commits. The bulk of the prepatory work was done in #278. --- Cargo.toml | 1 + elementsd-tests/Cargo.toml | 1 + elementsd-tests/src/pset.rs | 1 - elementsd-tests/src/taproot.rs | 5 +- fuzz/Cargo.toml | 1 + src/address.rs | 26 +++++----- src/block.rs | 6 +-- src/confidential.rs | 2 +- src/dynafed.rs | 10 ++-- src/encode.rs | 8 ++- src/fast_merkle_root.rs | 24 +++++---- src/genesis.rs | 27 ++++++---- src/hash_types.rs | 39 ++++++++++---- src/internal_macros.rs | 2 +- src/issuance.rs | 30 ++++++----- src/lib.rs | 4 +- src/pset/elip100.rs | 1 - src/pset/map/input.rs | 25 +++++---- src/script.rs | 17 +++--- src/sighash.rs | 39 +++++++------- src/taproot.rs | 94 +++++++++++++++++----------------- src/transaction.rs | 17 +++--- 22 files changed, 208 insertions(+), 172 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 53c380b2..9208dbdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -191,6 +191,7 @@ zero_sized_map_values = "warn" [package.metadata.rbmt.lint] allowed_duplicates = [ + "bitcoin_hashes", "bitcoin-internals", "hex-conservative", ] diff --git a/elementsd-tests/Cargo.toml b/elementsd-tests/Cargo.toml index 5912f723..d08aa099 100644 --- a/elementsd-tests/Cargo.toml +++ b/elementsd-tests/Cargo.toml @@ -16,6 +16,7 @@ rand = "0.8" [package.metadata.rbmt.lint] # FIXME the bulk of these are because elementsd/bitcoind is much older than rust-bitcoin. allowed_duplicates = [ + "bitcoin_hashes", "bitcoin-internals", "hex-conservative", "base64", diff --git a/elementsd-tests/src/pset.rs b/elementsd-tests/src/pset.rs index 7ce1d95b..7b907278 100644 --- a/elementsd-tests/src/pset.rs +++ b/elementsd-tests/src/pset.rs @@ -8,7 +8,6 @@ use crate::{setup, Call}; use bitcoin::{self, Address, Amount}; use elements::encode::serialize; -use elements::hashes::Hash; use elements::hex::DisplayHex as _; use elements::pset::PartiallySignedTransaction; use elements::{AssetId, ContractHash}; diff --git a/elementsd-tests/src/taproot.rs b/elementsd-tests/src/taproot.rs index 8dcc6b13..0bfdc0c9 100644 --- a/elementsd-tests/src/taproot.rs +++ b/elementsd-tests/src/taproot.rs @@ -10,7 +10,6 @@ use bitcoin::Amount; use elements::hex; use elements::confidential::{AssetBlindingFactor, ValueBlindingFactor}; use elements::encode::{deserialize, serialize_hex}; -use elements::hashes::Hash; use elements::script::Builder; use elements::secp256k1_zkp; use elements::sighash::{self, SighashCache}; @@ -215,7 +214,7 @@ fn taproot_spend_test( ); let tweak = secp256k1_zkp::Scalar::from_be_bytes(tweak.to_byte_array()).expect("hash value greater than curve order"); let sig = secp.sign_schnorr( - &secp256k1_zkp::Message::from_digest_slice(&sighash_msg[..]).unwrap(), + &secp256k1_zkp::Message::from_digest(sighash_msg.to_byte_array()), &output_keypair.add_xonly_tweak(secp, &tweak).unwrap(), ); @@ -239,7 +238,7 @@ fn taproot_spend_test( .unwrap(); let sig = secp.sign_schnorr( - &secp256k1_zkp::Message::from_digest_slice(&sighash_msg[..]).unwrap(), + &secp256k1_zkp::Message::from_digest(sighash_msg.to_byte_array()), &test_data.leaf1_keypair, ); diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 899e9a24..0c3e6f46 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -22,6 +22,7 @@ use_self = "warn" [package.metadata.rbmt.lint] allowed_duplicates = [ + "bitcoin_hashes", "bitcoin-internals", "hex-conservative", "getrandom", diff --git a/src/address.rs b/src/address.rs index 7480ccf9..55ea613c 100644 --- a/src/address.rs +++ b/src/address.rs @@ -15,7 +15,7 @@ //! # Addresses //! -use std::convert::TryFrom as _; +use std::convert::{TryFrom as _, TryInto as _}; use std::error; use std::fmt; use std::fmt::Write as _; @@ -23,8 +23,8 @@ use std::str::FromStr; use bech32::{Bech32, Bech32m, ByteIterExt, Fe32, Fe32IterExt, Hrp}; use crate::blech32::{Blech32, Blech32m}; -use crate::hashes::Hash; use bitcoin::base58; +use bitcoin::hashes::Hash as _; use bitcoin::PublicKey; use internals::array::ArrayExt as _; use internals::slice::SliceExt; @@ -329,12 +329,12 @@ impl Address { ) -> Address { let ws = script::Builder::new() .push_int(0) - .push_slice(&WScriptHash::hash(&script[..])[..]) + .push_slice(WScriptHash::hash_script(script).as_ref()) .into_script(); Address { params, - payload: Payload::ScriptHash(ScriptHash::hash(&ws[..])), + payload: Payload::ScriptHash(ScriptHash::hash_script(&ws)), blinding_pubkey: blinder, } } @@ -386,9 +386,9 @@ impl Address { ) -> Option
{ Some(Address { payload: if script.is_p2pkh() { - Payload::PubkeyHash(Hash::from_slice(&script.as_bytes()[3..23]).unwrap()) + Payload::PubkeyHash(PubkeyHash::from_byte_array(script.as_bytes()[3..23].try_into().unwrap())) } else if script.is_p2sh() { - Payload::ScriptHash(Hash::from_slice(&script.as_bytes()[2..22]).unwrap()) + Payload::ScriptHash(ScriptHash::from_byte_array(script.as_bytes()[2..22].try_into().unwrap())) } else if script.is_v0_p2wpkh() { Payload::WitnessProgram { version: Fe32::Q, @@ -418,12 +418,12 @@ impl Address { Payload::PubkeyHash(ref hash) => script::Builder::new() .push_opcode(opcodes::all::OP_DUP) .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&hash[..]) + .push_slice(hash.as_ref()) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG), Payload::ScriptHash(ref hash) => script::Builder::new() .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&hash[..]) + .push_slice(hash.as_byte_array()) .push_opcode(opcodes::all::OP_EQUAL), Payload::WitnessProgram { version: witver, @@ -566,12 +566,12 @@ impl fmt::Display for Address { prefixed[0] = self.params.blinded_prefix; prefixed[1] = self.params.p2pkh_prefix; prefixed[2..35].copy_from_slice(&blinder.serialize()); - prefixed[35..].copy_from_slice(&hash[..]); + prefixed[35..].copy_from_slice(hash.as_ref()); base58::encode_check_to_fmt(fmt, &prefixed[..]) } else { let mut prefixed = [0; 21]; prefixed[0] = self.params.p2pkh_prefix; - prefixed[1..].copy_from_slice(&hash[..]); + prefixed[1..].copy_from_slice(hash.as_ref()); base58::encode_check_to_fmt(fmt, &prefixed[..]) } } @@ -581,12 +581,12 @@ impl fmt::Display for Address { prefixed[0] = self.params.blinded_prefix; prefixed[1] = self.params.p2sh_prefix; prefixed[2..35].copy_from_slice(&blinder.serialize()); - prefixed[35..].copy_from_slice(&hash[..]); + prefixed[35..].copy_from_slice(hash.as_byte_array()); base58::encode_check_to_fmt(fmt, &prefixed[..]) } else { let mut prefixed = [0; 21]; prefixed[0] = self.params.p2sh_prefix; - prefixed[1..].copy_from_slice(&hash[..]); + prefixed[1..].copy_from_slice(hash.as_byte_array()); base58::encode_check_to_fmt(fmt, &prefixed[..]) } } @@ -937,7 +937,7 @@ mod test { "93c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51", ) .unwrap(); - let tap_node_hash = TapNodeHash::all_zeros(); + let tap_node_hash = TapNodeHash::from_byte_array([0; 32]); let mut expected = IntoIterator::into_iter([ "2dszRCFv8Ub4ytKo1Q1vXXGgSx7mekNDwSJ", diff --git a/src/block.rs b/src/block.rs index 0f9b1ba5..f022f1ae 100644 --- a/src/block.rs +++ b/src/block.rs @@ -21,7 +21,7 @@ use std::io; #[cfg(feature = "serde")] use std::fmt; use crate::dynafed; -use crate::hashes::Hash; +use crate::hashes::{HashEngine as _, sha256d}; use crate::Transaction; use crate::encode::{self, serialize, Decodable, Encodable, VarInt}; use crate::{BlockHash, Script, TxMerkleNode}; @@ -235,7 +235,7 @@ impl BlockHeader { }; // Everything except the signblock witness goes into the hash - let mut enc = BlockHash::engine(); + let mut enc = sha256d::Hash::engine(); version.consensus_encode(&mut enc).unwrap(); self.prev_blockhash.consensus_encode(&mut enc).unwrap(); self.merkle_root.consensus_encode(&mut enc).unwrap(); @@ -250,7 +250,7 @@ impl BlockHeader { proposed.consensus_encode(&mut enc).unwrap(); }, } - BlockHash::from_engine(enc) + BlockHash(enc.finalize()) } /// Returns true if this is a block with dynamic federations enabled. diff --git a/src/confidential.rs b/src/confidential.rs index ad3066b0..2e0ed39a 100644 --- a/src/confidential.rs +++ b/src/confidential.rs @@ -17,7 +17,7 @@ //! Structures representing Pedersen commitments of various types //! -use crate::hashes::{sha256d, Hash}; +use crate::hashes::sha256d; use secp256k1_zkp::{self, CommitmentSecrets, Generator, PedersenCommitment, PublicKey, Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, compute_adaptive_blinding_factor, diff --git a/src/dynafed.rs b/src/dynafed.rs index e5e29d14..c2f0227c 100644 --- a/src/dynafed.rs +++ b/src/dynafed.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::ser::{SerializeSeq, SerializeStruct}; use crate::encode::{self, Encodable, Decodable}; -use crate::hashes::{Hash, sha256d}; +use crate::hashes::sha256d; use crate::Script; impl_sha256_midstate_wrapper! { @@ -149,7 +149,7 @@ impl FullParams { let compact_root = crate::fast_merkle_root::fast_merkle_root(&leaves[..]); let leaves = [ - compact_root.to_byte_array(), + compact_root.to_parts().0, self.extra_root().to_byte_array(), ]; ParamsRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) @@ -364,7 +364,7 @@ impl Params { serialize_hash(self.signblockscript().unwrap()).to_byte_array(), serialize_hash(&self.signblock_witness_limit().unwrap()).to_byte_array(), ]; - let compact_root = crate::fast_merkle_root::fast_merkle_root(&leaves[..]); + let compact_root = ElidedRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])); let leaves = [ compact_root.to_byte_array(), @@ -718,8 +718,8 @@ mod tests { signblock_witness: vec![], }, version: Default::default(), - prev_blockhash: BlockHash::all_zeros(), - merkle_root: TxMerkleNode::all_zeros(), + prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, + merkle_root: TxMerkleNode::from_byte_array([0; 32]), time: Default::default(), height: Default::default(), }; diff --git a/src/encode.rs b/src/encode.rs index a0bbc0af..068a3c94 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -298,16 +298,14 @@ impl Decodable for bitcoin::ScriptBuf { } } -impl Encodable for bitcoin::hashes::sha256d::Hash { +impl Encodable for hashes::sha256d::Hash { fn consensus_encode(&self, mut w: W) -> Result { self.as_byte_array().consensus_encode(&mut w) } } -impl Decodable for bitcoin::hashes::sha256d::Hash { +impl Decodable for hashes::sha256d::Hash { fn consensus_decode(d: D) -> Result { - Ok(Self::from_byte_array( - <::Bytes>::consensus_decode(d)?, - )) + Ok(Self::from_byte_array(<[u8; 32]>::consensus_decode(d)?)) } } diff --git a/src/fast_merkle_root.rs b/src/fast_merkle_root.rs index 4d88ea81..7d7c5abe 100644 --- a/src/fast_merkle_root.rs +++ b/src/fast_merkle_root.rs @@ -12,7 +12,7 @@ // If not, see . // -use crate::hashes::{sha256, Hash, HashEngine}; +use crate::hashes::{sha256, HashEngine}; /// Calculate a single sha256 midstate hash of the given left and right leaves. #[inline] @@ -20,7 +20,7 @@ fn sha256midstate(left: &[u8], right: &[u8]) -> sha256::Midstate { let mut engine = sha256::Hash::engine(); engine.input(left); engine.input(right); - engine.midstate() + engine.midstate().expect("hashing exactly 64 bytes") } /// Compute the Merkle root of the give hashes using mid-state only. @@ -45,14 +45,14 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { let mut inner: [sha256::Midstate; 32] = Default::default(); let mut count: u32 = 0; while (count as usize) < leaves.len() { - let mut temp_hash = sha256::Midstate::from_byte_array(leaves[count as usize]); + let mut temp_hash = sha256::Midstate::new(leaves[count as usize], 64); count += 1; // For each of the lower bits in count that are 0, do 1 step. Each // corresponds to an inner value that existed before processing the // current leaf, and each needs a hash to combine it. let mut level = 0; while count & (1u32 << level) == 0 { - temp_hash = sha256midstate(&inner[level][..], &temp_hash[..]); + temp_hash = sha256midstate(inner[level].as_parts().0, temp_hash.as_parts().0); level += 1; } // Store the resulting hash at inner position level. @@ -81,7 +81,7 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { count += 1 << level; level += 1; while count & (1u32 << level) == 0 { - result_hash = sha256midstate(&inner[level][..], &result_hash[..]); + result_hash = sha256midstate(inner[level].as_parts().0, result_hash.as_parts().0); level += 1; } } @@ -92,11 +92,15 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { #[cfg(test)] mod tests { use super::fast_merkle_root; - use crate::hashes::sha256; - use std::str::FromStr; #[test] fn test_fast_merkle_root() { + fn decode_hex(hex: &str) -> [u8; 32] { + let mut ret = hex::decode_to_array(hex).unwrap(); + ret.reverse(); + ret + } + // unit test vectors from Elements Core let test_leaves = [ "b66b041650db0f297b53f8d93c0e8706925bf3323f8c59c14a6fac37bfdcd06f", @@ -116,9 +120,9 @@ mod tests { let mut leaves = vec![]; for i in 0..4 { let root = fast_merkle_root(&leaves); - assert_eq!(root, FromStr::from_str(test_roots[i]).unwrap(), "root #{}", i); - leaves.push(sha256::Midstate::from_str(test_leaves[i]).unwrap().to_byte_array()); + assert_eq!(root.to_parts().0, decode_hex(test_roots[i]), "root #{i}"); + leaves.push(decode_hex(test_leaves[i])); } - assert_eq!(fast_merkle_root(&leaves), FromStr::from_str(test_roots[4]).unwrap()); + assert_eq!(fast_merkle_root(&leaves).to_parts().0, decode_hex(test_roots[4])); } } diff --git a/src/genesis.rs b/src/genesis.rs index 1dc7d71a..4aed10ae 100644 --- a/src/genesis.rs +++ b/src/genesis.rs @@ -16,7 +16,7 @@ use bitcoin::secp256k1::impl_array_newtype; use secp256k1_zkp::Tweak; -use crate::hashes::{sha256, sha256d, Hash, HashEngine}; +use crate::hashes::{sha256, HashEngine}; use crate::opcodes::all::OP_RETURN; use crate::opcodes::OP_TRUE; use crate::{confidential, script, AssetId, Block, BlockExtData, BlockHash, BlockHeader, LockTime, Script, Sequence, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness}; @@ -185,21 +185,31 @@ pub fn genesis_block(params: &NetworkParams) -> Block { let tx = liquid_genesis_tx(params); let mut txdata = vec![tx.clone()]; - let merkle_root: sha256d::Hash = + let merkle_root: crate::TxMerkleNode = if let Some(asset_tx) = liquid_genesis_asset_tx(params) { + // To use `bitcoin::merkle_tree::calculate_root` from bitcoin 0.32, we need a hash + // with fairly specific trait bounds, even though it's just used as a byte array. + // sha256d::Hash from the rust-bitcoin version of bitcoin_hashes works. + use bitcoin::hashes::sha256d::Hash as MerkleHash; + use bitcoin::hashes::Hash as _; + txdata.push(asset_tx.clone()); - let tx_hashes = [tx.txid().to_raw_hash(), asset_tx.txid().to_raw_hash()]; - bitcoin::merkle_tree::calculate_root(tx_hashes.iter().copied()) - .expect("merkle root") + let tx_hashes = [ + MerkleHash::from_byte_array(tx.txid().to_byte_array()), + MerkleHash::from_byte_array(asset_tx.txid().to_byte_array()), + ]; + let root = bitcoin::merkle_tree::calculate_root(tx_hashes.iter().copied()) + .expect("merkle root"); + crate::TxMerkleNode::from_byte_array(root.to_byte_array()) } else { - tx.txid().to_raw_hash() + crate::TxMerkleNode::from_byte_array(tx.txid().to_byte_array()) }; Block { header: BlockHeader { version: 1, - prev_blockhash: BlockHash::all_zeros(), - merkle_root: merkle_root.into(), + prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, + merkle_root, time: 1_296_688_602, height: 0, ext: BlockExtData::Proof { @@ -239,7 +249,6 @@ impl ChainHash { #[cfg(test)] mod test { use crate::genesis::{genesis_block, ChainHash, NetworkParams}; - use crate::hashes::Hash; #[test] fn genesis_block_hash() { diff --git a/src/hash_types.rs b/src/hash_types.rs index b4ce1861..cf0b157f 100644 --- a/src/hash_types.rs +++ b/src/hash_types.rs @@ -18,7 +18,7 @@ //! to avoid mixing data of the same hash format (like `SHA256d`) but of different meaning //! (transaction id, block hash etc). -use crate::hashes::{hash160, hash_newtype, sha256, sha256d, Hash}; +use crate::hashes::{hash160, sha256, sha256d}; // Re-export bitcoin's pubkeyhash types. We already re-export bitcoin's `PublicKey` type. pub use bitcoin::{PubkeyHash, WPubkeyHash}; @@ -30,7 +30,7 @@ macro_rules! impl_hashencode { &self, w: W, ) -> Result { - self.0.consensus_encode(w) + self.as_byte_array().consensus_encode(w) } } @@ -44,32 +44,53 @@ macro_rules! impl_hashencode { }; } -hash_newtype! { +hashes::hash_newtype! { /// An elements transaction ID - pub struct Txid(sha256d::Hash); + pub struct Txid(pub(crate) sha256d::Hash); /// An elements witness transaction ID - pub struct Wtxid(sha256d::Hash); + pub struct Wtxid(pub(crate) sha256d::Hash); /// An elements blockhash - pub struct BlockHash(sha256d::Hash); + pub struct BlockHash(pub(crate) sha256d::Hash); /// "Hash of the transaction according to the signature algorithm" - pub struct Sighash(sha256d::Hash); + pub struct Sighash(pub(crate) sha256d::Hash); /// A hash of Bitcoin Script bytecode. - pub struct ScriptHash(hash160::Hash); + pub struct ScriptHash(pub(crate) hash160::Hash); /// SegWit version of a Bitcoin Script bytecode hash. - pub struct WScriptHash(sha256::Hash); + pub struct WScriptHash(pub(crate) sha256::Hash); /// A hash of the Merkle tree branch or root for transactions pub struct TxMerkleNode(sha256d::Hash); } + +hashes::impl_hex_for_newtype!(Txid, Wtxid, BlockHash, ScriptHash, WScriptHash, TxMerkleNode); +#[cfg(feature = "serde")] +hashes::impl_serde_for_newtype!(Txid, Wtxid, BlockHash, ScriptHash, WScriptHash, TxMerkleNode); +// We do not implement serde or display/fromstr for 'Sighash'. In general it's dangerous +// to deserialize sighashes; they must be the output of a cryptographic hash function. +hashes::impl_debug_only_for_newtype!(Sighash); + impl_hashencode!(Txid); impl_hashencode!(Wtxid); impl_hashencode!(Sighash); impl_hashencode!(BlockHash); impl_hashencode!(TxMerkleNode); +impl BlockHash { + /// Dummy hash used as the previous blockhash of the genesis block. + pub const GENESIS_PREVIOUS_BLOCK_HASH: Self = Self::from_byte_array([0; 32]); +} + +impl Txid { + /// The `Txid` used in a coinbase prevout. + /// + /// This is used as the "txid" of the dummy input of a coinbase transaction. This is not a real + /// TXID and should not be used in any other contexts. + pub const COINBASE_PREVOUT: Self = Self::from_byte_array([0; 32]); +} + impl ScriptHash { /// Computes the `ScriptHash` of a script. pub fn hash_script(s: &crate::Script) -> Self { diff --git a/src/internal_macros.rs b/src/internal_macros.rs index 7dfbcf38..3e2b7431 100644 --- a/src/internal_macros.rs +++ b/src/internal_macros.rs @@ -422,7 +422,7 @@ macro_rules! impl_sha256_midstate_wrapper { /// (Private) convert a sha256 midstate to an object. fn from_midstate(value: crate::hashes::sha256::Midstate) -> Self { - Self(value.to_byte_array()) + Self(value.to_parts().0) } } diff --git a/src/issuance.rs b/src/issuance.rs index 27c7b30d..82e31e26 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -17,7 +17,7 @@ use std::io; use crate::encode::{self, Encodable, Decodable}; -use crate::hashes::{hash_newtype, sha256, sha256d, Hash}; +use crate::hashes::{hash_newtype, sha256, sha256d}; use crate::fast_merkle_root::fast_merkle_root; use secp256k1_zkp::Tag; use crate::genesis::{commit_to_custom_network_parameters, NetworkParams}; @@ -42,7 +42,9 @@ hash_newtype!( #[hash_newtype(backward)] pub struct ContractHash(sha256::Hash); ); - +hashes::impl_hex_for_newtype!(ContractHash); +#[cfg(feature = "serde")] +hashes::impl_serde_for_newtype!(ContractHash); impl_sha256_midstate_wrapper! { /// A hash of some data used as "asset entropy" to seed the ID of a new asset. @@ -62,15 +64,17 @@ impl ContractHash { /// the hash. #[cfg(feature = "json-contract")] pub fn from_json_contract(json: &str) -> Result { + use crate::hashes::HashEngine as _; + // Parsing the JSON into a BTreeMap will recursively order object keys // lexicographically. This order is respected when we later serialize // it again. let ordered: ::std::collections::BTreeMap = ::serde_json::from_str(json)?; - let mut engine = ContractHash::engine(); + let mut engine = sha256::Hash::engine(); ::serde_json::to_writer(&mut engine, &ordered).expect("engines don't error"); - Ok(ContractHash::from_engine(engine)) + Ok(ContractHash(engine.finalize())) } } @@ -263,28 +267,28 @@ mod test { let tether = ContractHash::from_str("3c7f0a53c2ff5b99590620d7f6604a7a3a7bfbaaa6aa61f7bfc7833ca03cde82").unwrap(); let correct = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"#; - let expected = ContractHash::hash(correct.as_bytes()); - assert_eq!(tether, expected); - assert_eq!(expected, ContractHash::from_json_contract(correct).unwrap()); + let expected = sha256::Hash::hash(correct.as_bytes()).to_byte_array(); + assert_eq!(tether.to_byte_array(), expected); + assert_eq!(expected, ContractHash::from_json_contract(correct).unwrap().to_byte_array()); let invalid_json = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey:"#; assert!(ContractHash::from_json_contract(invalid_json).is_err()); let unordered = r#"{"precision":8,"ticker":"USDt","entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","version":0}"#; - assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap()); + assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap().to_byte_array()); let unordered = r#"{"precision":8,"name":"Tether USD","ticker":"USDt","entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","version":0}"#; - assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap()); + assert_eq!(expected, ContractHash::from_json_contract(unordered).unwrap().to_byte_array()); let spaces = r#"{"precision":8, "name" : "Tether USD", "ticker":"USDt", "entity":{"domain":"tether.to" }, "issuer_pubkey" :"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","version":0} "#; - assert_eq!(expected, ContractHash::from_json_contract(spaces).unwrap()); + assert_eq!(expected, ContractHash::from_json_contract(spaces).unwrap().to_byte_array()); let nested_correct = r#"{"entity":{"author":"Tether Inc","copyright":2020,"domain":"tether.to","hq":"Mars"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"#; - let nested_expected = ContractHash::hash(nested_correct.as_bytes()); - assert_eq!(nested_expected, ContractHash::from_json_contract(nested_correct).unwrap()); + let nested_expected = sha256::Hash::hash(nested_correct.as_bytes()).to_byte_array(); + assert_eq!(nested_expected, ContractHash::from_json_contract(nested_correct).unwrap().to_byte_array()); let nested_unordered = r#"{"ticker":"USDt","entity":{"domain":"tether.to","hq":"Mars","author":"Tether Inc","copyright":2020},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"version":0}"#; - assert_eq!(nested_expected, ContractHash::from_json_contract(nested_unordered).unwrap()); + assert_eq!(nested_expected, ContractHash::from_json_contract(nested_unordered).unwrap().to_byte_array()); } #[test] diff --git a/src/lib.rs b/src/lib.rs index 64b74ebe..dfc51d3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ /// Re-export of bitcoin crate pub extern crate bitcoin; +/// Re-export of `bitcoin_hashes` crate +pub extern crate hashes; /// Re-export of hex crate pub extern crate hex; /// Re-export of secp256k1-zkp crate @@ -74,8 +76,6 @@ mod transaction; mod endian; pub mod genesis; -// re-export bitcoin deps which we re-use -pub use bitcoin::hashes; // export everything at the top level so it can be used as `elements::Transaction` etc. pub use crate::address::{Address, AddressError, AddressParams}; pub use crate::blind::{ diff --git a/src/pset/elip100.rs b/src/pset/elip100.rs index 56c1db00..d95e9166 100644 --- a/src/pset/elip100.rs +++ b/src/pset/elip100.rs @@ -221,7 +221,6 @@ mod test { use crate::encode::serialize; use crate::{OutPoint, Txid}; - use bitcoin::hashes::Hash; use hex::DisplayHex as _; use crate::{ diff --git a/src/pset/map/input.rs b/src/pset/map/input.rs index ca54d56a..c5216bac 100644 --- a/src/pset/map/input.rs +++ b/src/pset/map/input.rs @@ -332,7 +332,7 @@ impl Default for Input { sha256_preimages: BTreeMap::new(), hash160_preimages: BTreeMap::new(), hash256_preimages: BTreeMap::new(), - previous_txid: Txid::all_zeros(), + previous_txid: Txid::COINBASE_PREVOUT, previous_output_index: 0, sequence: None, required_time_locktime: None, @@ -656,7 +656,7 @@ impl Map for Input { } } PSET_IN_RIPEMD160 => { - pset_insert_hash_pair( + pset_insert_hash_pair::( &mut self.ripemd160_preimages, raw_key, &raw_value, @@ -664,7 +664,7 @@ impl Map for Input { )?; } PSET_IN_SHA256 => { - pset_insert_hash_pair( + pset_insert_hash_pair::( &mut self.sha256_preimages, raw_key, &raw_value, @@ -672,7 +672,7 @@ impl Map for Input { )?; } PSET_IN_HASH160 => { - pset_insert_hash_pair( + pset_insert_hash_pair::( &mut self.hash160_preimages, raw_key, &raw_value, @@ -680,7 +680,7 @@ impl Map for Input { )?; } PSET_IN_HASH256 => { - pset_insert_hash_pair( + pset_insert_hash_pair::( &mut self.hash256_preimages, raw_key, &raw_value, @@ -1171,26 +1171,29 @@ impl Decodable for Input { } } -fn pset_insert_hash_pair( - map: &mut BTreeMap>, +fn pset_insert_hash_pair( + map: &mut BTreeMap>, raw_key: raw::Key, raw_value: &[u8], hash_type: error::PsetHash, ) -> Result<(), encode::Error> where - H: hashes::Hash + serialize::Deserialize, + Eng: hashes::HashEngine + Default, + Eng::Hash: serialize::Deserialize, { if raw_key.key.is_empty() { return Err(pset::Error::InvalidKey(raw_key).into()); } - let key_val: H = serialize::Deserialize::deserialize(&raw_key.key)?; + let key_val: Eng::Hash = serialize::Deserialize::deserialize(&raw_key.key)?; match map.entry(key_val) { Entry::Vacant(empty_key) => { let val: Vec = serialize::Deserialize::deserialize(raw_value)?; - if ::hash(&val) != key_val { + let mut eng = Eng::default(); + eng.input(&val); + if eng.finalize() != key_val { return Err(pset::Error::InvalidPreimageHashPair { preimage: val, - hash: Vec::from(key_val.borrow()), + hash: key_val.as_byte_array().as_ref().to_vec(), hash_type, } .into()); diff --git a/src/script.rs b/src/script.rs index 0b0a4507..5388463f 100644 --- a/src/script.rs +++ b/src/script.rs @@ -31,7 +31,6 @@ use secp256k1_zkp::{Verification, Secp256k1}; #[cfg(feature = "serde")] use serde; use crate::encode::{self, Decodable, Encodable}; -use crate::hashes::Hash; use crate::{opcodes, ScriptHash, WScriptHash, PubkeyHash, WPubkeyHash}; use bitcoin::PublicKey; @@ -241,7 +240,7 @@ impl Script { Builder::new() .push_opcode(opcodes::all::OP_DUP) .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&pubkey_hash[..]) + .push_slice(pubkey_hash.as_ref()) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG) .into_script() @@ -251,19 +250,19 @@ impl Script { pub fn new_p2sh(script_hash: &ScriptHash) -> Script { Builder::new() .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&script_hash[..]) + .push_slice(script_hash.as_byte_array()) .push_opcode(opcodes::all::OP_EQUAL) .into_script() } /// Generates P2WPKH-type of scriptPubkey pub fn new_v0_wpkh(pubkey_hash: &WPubkeyHash) -> Script { - Script::new_witness_program(bech32::Fe32::Q, &pubkey_hash.to_raw_hash().to_byte_array()) + Script::new_witness_program(bech32::Fe32::Q, pubkey_hash.as_ref()) } /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script pub fn new_v0_wsh(script_hash: &WScriptHash) -> Script { - Script::new_witness_program(bech32::Fe32::Q, &script_hash.to_raw_hash().to_byte_array()) + Script::new_witness_program(bech32::Fe32::Q, script_hash.as_byte_array()) } /// Generates P2TR for script spending path using an internal public key and some optional @@ -302,12 +301,12 @@ impl Script { /// Returns 160-bit hash of the script pub fn script_hash(&self) -> ScriptHash { - ScriptHash::hash(self.as_bytes()) + ScriptHash(hashes::hash160::hash(self.as_bytes())) } /// Returns 256-bit hash of the script for P2WSH outputs pub fn wscript_hash(&self) -> WScriptHash { - WScriptHash::hash(self.as_bytes()) + WScriptHash(hashes::sha256::hash(self.as_bytes())) } /// The length in bytes of the script @@ -329,7 +328,7 @@ impl Script { #[must_use] pub fn to_p2sh(&self) -> Script { Builder::new().push_opcode(opcodes::all::OP_HASH160) - .push_slice(&ScriptHash::hash(&self.0)[..]) + .push_slice(self.script_hash().as_byte_array()) .push_opcode(opcodes::all::OP_EQUAL) .into_script() } @@ -339,7 +338,7 @@ impl Script { #[must_use] pub fn to_v0_p2wsh(&self) -> Script { Builder::new().push_int(0) - .push_slice(&WScriptHash::hash(&self.0)[..]) + .push_slice(self.wscript_hash().as_byte_array()) .into_script() } diff --git a/src/sighash.rs b/src/sighash.rs index 21f6f840..78b408c7 100644 --- a/src/sighash.rs +++ b/src/sighash.rs @@ -22,7 +22,7 @@ use std::borrow::Borrow; use crate::encode::{self, Encodable}; use crate::hash_types::Sighash; -use crate::hashes::{sha256d, Hash, sha256}; +use crate::hashes::{sha256d, sha256t, sha256, HashEngine as _}; use crate::script::Script; use std::ops::{Deref, DerefMut}; use std::io; @@ -427,7 +427,7 @@ impl> SighashCache { sighash_type: SchnorrSighashType, genesis_hash: BlockHash, ) -> Result { - let mut enc = TapSighashHash::engine(); + let mut enc = sha256t::Hash::engine(); self.taproot_encode_signing_data_to( &mut enc, input_index, @@ -437,7 +437,7 @@ impl> SighashCache { sighash_type, genesis_hash, )?; - Ok(TapSighashHash::from_engine(enc)) + Ok(TapSighashHash(enc.finalize())) } /// Compute the BIP341 sighash for a key spend @@ -448,7 +448,7 @@ impl> SighashCache { sighash_type: SchnorrSighashType, genesis_hash: BlockHash, ) -> Result { - let mut enc = TapSighashHash::engine(); + let mut enc = sha256t::Hash::engine(); self.taproot_encode_signing_data_to( &mut enc, input_index, @@ -458,7 +458,7 @@ impl> SighashCache { sighash_type, genesis_hash, )?; - Ok(TapSighashHash::from_engine(enc)) + Ok(TapSighashHash(enc.finalize())) } /// Compute the BIP341 sighash for a script spend @@ -473,7 +473,7 @@ impl> SighashCache { sighash_type: SchnorrSighashType, genesis_hash: BlockHash, ) -> Result { - let mut enc = TapSighashHash::engine(); + let mut enc = sha256t::Hash::engine(); self.taproot_encode_signing_data_to( &mut enc, input_index, @@ -483,7 +483,7 @@ impl> SighashCache { sighash_type, genesis_hash )?; - Ok(TapSighashHash::from_engine(enc)) + Ok(TapSighashHash(enc.finalize())) } /// Encode the BIP143 signing data for any flag type into a given object implementing a @@ -505,7 +505,7 @@ impl> SighashCache { value: confidential::Value, sighash_type: EcdsaSighashType, ) -> Result<(), encode::Error> { - let zero_hash = sha256d::Hash::all_zeros(); + let zero_hash = [0u8; 32]; let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag(); @@ -548,9 +548,9 @@ impl> SighashCache { if sighash != EcdsaSighashType::Single && sighash != EcdsaSighashType::None { self.segwit_cache().outputs.consensus_encode(&mut writer)?; } else if sighash == EcdsaSighashType::Single && input_index < self.tx.output.len() { - let mut single_enc = Sighash::engine(); + let mut single_enc = sha256d::Hash::engine(); self.tx.output[input_index].consensus_encode(&mut single_enc)?; - Sighash::from_engine(single_enc).consensus_encode(&mut writer)?; + Sighash(single_enc.finalize()).consensus_encode(&mut writer)?; } else { zero_hash.consensus_encode(&mut writer)?; } @@ -576,10 +576,10 @@ impl> SighashCache { value: confidential::Value, sighash_type: EcdsaSighashType ) -> Sighash { - let mut enc = Sighash::engine(); + let mut enc = sha256d::Hash::engine(); self.encode_segwitv0_signing_data_to(&mut enc, input_index, script_code, value, sighash_type) .expect("engines don't error"); - Sighash::from_engine(enc) + Sighash(enc.finalize()) } /// Encodes the signing data from which a signature hash for a given input index with a given @@ -689,10 +689,10 @@ impl> SighashCache { script_pubkey: &Script, sighash_type: EcdsaSighashType, ) -> Sighash { - let mut engine = Sighash::engine(); + let mut engine = sha256d::Hash::engine(); self.encode_legacy_signing_data_to(&mut engine, input_index, script_pubkey, sighash_type) .expect("engines don't error"); - Sighash::from_engine(engine) + Sighash(engine.finalize()) } #[inline] @@ -965,14 +965,12 @@ impl std::str::FromStr for SchnorrSighashType { mod tests{ use super::*; use crate::encode::deserialize; - use std::str::FromStr; fn test_segwit_sighash(tx: &str, script: &str, input_index: usize, value: &str, hash_type: EcdsaSighashType, expected_result: &str) { let tx: Transaction = deserialize(&hex::decode_to_vec(tx).unwrap()).unwrap(); let script = Script::from(hex::decode_to_vec(script).unwrap()); - // A hack to parse sha256d strings are sha256 so that we don't reverse them... - let raw_expected = crate::hashes::sha256::Hash::from_str(expected_result).unwrap(); - let expected_result = Sighash::from_slice(&raw_expected[..]).unwrap(); + let raw_expected = hex::decode_to_array(expected_result).unwrap(); + let expected_result = Sighash::from_byte_array(raw_expected); let mut cache = SighashCache::new(&tx); let value : confidential::Value = deserialize(&hex::decode_to_vec(value).unwrap()).unwrap(); @@ -1004,9 +1002,8 @@ mod tests{ fn test_legacy_sighash(tx: &str, script: &str, input_index: usize, hash_type: EcdsaSighashType, expected_result: &str) { let tx: Transaction = deserialize(&hex::decode_to_vec(tx).unwrap()).unwrap(); let script = Script::from(hex::decode_to_vec(script).unwrap()); - // A hack to parse sha256d strings are sha256 so that we don't reverse them... - let raw_expected = crate::hashes::sha256::Hash::from_str(expected_result).unwrap(); - let expected_result = Sighash::from_slice(&raw_expected[..]).unwrap(); + let raw_expected = hex::decode_to_array(expected_result).unwrap(); + let expected_result = Sighash::from_byte_array(raw_expected); let sighash_cache = SighashCache::new(&tx); let actual_result = sighash_cache.legacy_sighash(input_index, &script, hash_type); assert_eq!(actual_result, expected_result); diff --git a/src/taproot.rs b/src/taproot.rs index 1f906c29..e6496cca 100644 --- a/src/taproot.rs +++ b/src/taproot.rs @@ -16,36 +16,49 @@ use std::cmp::Reverse; use std::{error, io, fmt}; -use crate::hashes::{sha256t_hash_newtype, Hash, HashEngine}; +use crate::hashes::HashEngine as _; use crate::schnorr::{UntweakedPublicKey, TweakedPublicKey, TapTweak}; use crate::Script; use std::collections::{BTreeMap, BTreeSet, BinaryHeap}; +use hashes::sha256t; use secp256k1_zkp::{self, Secp256k1, Scalar}; use crate::encode::Encodable; -// Taproot test vectors from BIP-341 state the hashes without any reversing -sha256t_hash_newtype! { +hashes::sha256t_tag! { pub struct TapLeafTag = hash_str("TapLeaf/elements"); +} +hashes::sha256t_tag! { + pub struct TapBranchTag = hash_str("TapBranch/elements"); +} +hashes::sha256t_tag! { + pub struct TapTweakTag = hash_str("TapTweak/elements"); +} +hashes::sha256t_tag! { + pub struct TapSighashTag = hash_str("TapSighash/elements"); +} + +// Taproot test vectors from BIP-341 state the hashes without any reversing +hashes::hash_newtype! { /// Taproot-tagged hash for elements tapscript Merkle tree leafs. - #[hash_newtype(forward)] - pub struct TapLeafHash(_); + pub struct TapLeafHash(sha256t::Hash::); - pub struct TapBranchTag = hash_str("TapBranch/elements"); /// Tagged hash used in taproot trees; see BIP-340 for tagging rules. - #[hash_newtype(forward)] - pub struct TapNodeHash(_); + pub struct TapNodeHash(sha256t::Hash::); - pub struct TapTweakTag = hash_str("TapTweak/elements"); /// Taproot-tagged hash for elements public key tweaks. - #[hash_newtype(forward)] - pub struct TapTweakHash(_); + pub struct TapTweakHash(sha256t::Hash::); - pub struct TapSighashTag = hash_str("TapSighash/elements"); /// Taproot-tagged hash for the elements taproot signature hash. - #[hash_newtype(forward)] - pub struct TapSighashHash(_); + pub struct TapSighashHash(pub(crate) sha256t::Hash::); } +hashes::impl_hex_for_newtype!(TapLeafHash, TapNodeHash, TapTweakHash); +#[cfg(feature = "serde")] +hashes::impl_serde_for_newtype!(TapLeafHash, TapNodeHash, TapTweakHash); +// We do not implement serde or display/fromstr for 'Sighash'. In general it's dangerous +// to deserialize sighashes; they must be the output of a cryptographic hash function. +hashes::impl_debug_only_for_newtype!(TapSighashHash); + impl TapTweakHash { /// Create a new BIP341 [`TapTweakHash`] from key and tweak @@ -54,7 +67,7 @@ impl TapTweakHash { internal_key: UntweakedPublicKey, merkle_root: Option, ) -> TapTweakHash { - let mut eng = TapTweakHash::engine(); + let mut eng = sha256t::Hash::engine(); // always hash the key eng.input(&internal_key.serialize()); if let Some(h) = merkle_root { @@ -62,7 +75,7 @@ impl TapTweakHash { } else { // nothing to hash } - TapTweakHash::from_engine(eng) + TapTweakHash(eng.finalize()) } /// Converts a `TapTweakHash` into a `Scalar` ready for use with key tweaking API. @@ -75,14 +88,14 @@ impl TapTweakHash { impl TapLeafHash { /// function to compute leaf hash from components pub fn from_script(script: &Script, ver: LeafVersion) -> TapLeafHash { - let mut eng = TapLeafHash::engine(); + let mut eng = sha256t::Hash::engine(); ver.as_u8() .consensus_encode(&mut eng) .expect("engines don't error"); script .consensus_encode(&mut eng) .expect("engines don't error"); - TapLeafHash::from_engine(eng) + TapLeafHash(eng.finalize()) } } @@ -481,7 +494,7 @@ impl NodeInfo { b_leaf.merkle_branch.push(a.hash)?; // add hashing partner all_leaves.push(b_leaf); } - let mut eng = TapNodeHash::engine(); + let mut eng = sha256t::Hash::engine(); if a.hash < b.hash { eng.input(a.hash.as_ref()); eng.input(b.hash.as_ref()); @@ -490,7 +503,7 @@ impl NodeInfo { eng.input(a.hash.as_ref()); }; Ok(Self { - hash: TapNodeHash::from_engine(eng), + hash: TapNodeHash(eng.finalize()), leaves: all_leaves, }) } @@ -548,11 +561,12 @@ impl TaprootMerkleBranch { )) } else { let inner = sl - // TODO: Use chunks_exact after MSRV changes to 1.31 - .chunks(TAPROOT_CONTROL_NODE_SIZE) + // TODO: Use array_chunks after this stabilizes + .chunks_exact(TAPROOT_CONTROL_NODE_SIZE) .map(|chunk| { - TapNodeHash::from_slice(chunk) - .expect("chunk exact always returns the correct size") + use core::convert::TryFrom as _; + let chunk = <&[u8; 32]>::try_from(chunk).expect("need array_chunks in stdlib"); + TapNodeHash::from_byte_array(*chunk) }) .collect(); Ok(TaprootMerkleBranch(inner)) @@ -687,7 +701,7 @@ impl ControlBlock { let mut curr_hash = TapNodeHash::from_byte_array(leaf_hash.to_byte_array()); // Verify the proof for elem in self.merkle_branch.as_inner() { - let mut eng = TapNodeHash::engine(); + let mut eng = sha256t::Hash::engine(); if curr_hash.as_byte_array() < elem.as_byte_array() { eng.input(curr_hash.as_ref()); eng.input(elem.as_ref()); @@ -696,7 +710,7 @@ impl ControlBlock { eng.input(curr_hash.as_ref()); } // Recalculate the curr hash as parent hash - curr_hash = TapNodeHash::from_engine(eng); + curr_hash = TapNodeHash(eng.finalize()); } // compute the taptweak let tweak = TapTweakHash::from_key_and_tweak(self.internal_key, Some(curr_hash)); @@ -858,36 +872,22 @@ impl error::Error for TaprootError {} mod tests{ use super::*; use crate::hashes::{sha256, HashEngine}; - use crate::hashes::sha256t::Tag; use std::str::FromStr; fn tag_engine(tag_name: &str) -> sha256::HashEngine { let mut engine = sha256::Hash::engine(); let tag_hash = sha256::Hash::hash(tag_name.as_bytes()); - engine.input(&tag_hash[..]); - engine.input(&tag_hash[..]); + engine.input(tag_hash.as_byte_array()); + engine.input(tag_hash.as_byte_array()); engine } #[test] - fn test_midstates() { - // check that hash creation is the same as building into the same engine - fn empty_hash(tag_name: &str) -> [u8; 32] { - let mut e = tag_engine(tag_name); - e.input(&[]); - sha256::Hash::from_engine(e).to_byte_array() - } - - // test that engine creation roundtrips - assert_eq!(tag_engine("TapLeaf/elements").midstate(), TapLeafTag::engine().midstate()); - assert_eq!(tag_engine("TapBranch/elements").midstate(), TapBranchTag::engine().midstate()); - assert_eq!(tag_engine("TapTweak/elements").midstate(), TapTweakTag::engine().midstate()); - assert_eq!(tag_engine("TapSighash/elements").midstate(), TapSighashTag::engine().midstate()); - - assert_eq!(empty_hash("TapLeaf/elements"), TapLeafHash::hash(&[]).to_byte_array()); - assert_eq!(empty_hash("TapBranch/elements"), TapNodeHash::hash(&[]).to_byte_array()); - assert_eq!(empty_hash("TapTweak/elements"), TapTweakHash::hash(&[]).to_byte_array()); - assert_eq!(empty_hash("TapSighash/elements"), TapSighashHash::hash(&[]).to_byte_array()); + fn test_engine_initialization() { + assert_eq!(tag_engine("TapLeaf/elements").finalize().to_byte_array(), sha256t::Hash::::engine().finalize().to_byte_array()); + assert_eq!(tag_engine("TapBranch/elements").finalize().to_byte_array(), sha256t::Hash::::engine().finalize().to_byte_array()); + assert_eq!(tag_engine("TapTweak/elements").finalize().to_byte_array(), sha256t::Hash::::engine().finalize().to_byte_array()); + assert_eq!(tag_engine("TapSighash/elements").finalize().to_byte_array(), sha256t::Hash::::engine().finalize().to_byte_array()); } #[test] diff --git a/src/transaction.rs b/src/transaction.rs index 367174b7..1ba454b7 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -20,8 +20,9 @@ use std::collections::HashMap; use std::convert::TryFrom; use bitcoin::{self, VarInt}; +use bitcoin::hashes::Hash as _; use internals::slice::SliceExt; -use crate::hashes::Hash; +use crate::hashes::{sha256d, HashEngine as _}; use crate::{confidential, ContractHash}; use crate::encode::{self, Encodable, Decodable}; @@ -99,7 +100,7 @@ impl OutPoint { #[inline] pub fn null() -> OutPoint { OutPoint { - txid: Txid::all_zeros(), + txid: Txid::COINBASE_PREVOUT, vout: u32::MAX, } } @@ -151,7 +152,7 @@ impl ::std::str::FromStr for OutPoint { } let bitcoin_outpoint = bitcoin::OutPoint::from_str(s)?; Ok(OutPoint { - txid: Txid::from(bitcoin_outpoint.txid.to_raw_hash()), + txid: Txid::from_byte_array(bitcoin_outpoint.txid.to_byte_array()), vout: bitcoin_outpoint.vout, }) } @@ -792,7 +793,7 @@ impl TxOut { // Parse destination chain's genesis block let genesis_hash = bitcoin::BlockHash::from_raw_hash( - crate::hashes::Hash::from_slice(iter.next()?.ok()?.push_bytes()?).ok()? + bitcoin::hashes::Hash::from_slice(iter.next()?.ok()?.push_bytes()?).ok()? ); // Parse destination scriptpubkey @@ -1027,20 +1028,20 @@ impl Transaction { /// The txid of the transaction. pub fn txid(&self) -> Txid { - let mut enc = Txid::engine(); + let mut enc = sha256d::Hash::engine(); self.version.consensus_encode(&mut enc).unwrap(); 0u8.consensus_encode(&mut enc).unwrap(); self.input.consensus_encode(&mut enc).unwrap(); self.output.consensus_encode(&mut enc).unwrap(); self.lock_time.consensus_encode(&mut enc).unwrap(); - Txid::from_engine(enc) + Txid(enc.finalize()) } /// Get the witness txid of the transaction. pub fn wtxid(&self) -> Wtxid { - let mut enc = Txid::engine(); + let mut enc = sha256d::Hash::engine(); self.consensus_encode(&mut enc).unwrap(); - Wtxid::from_engine(enc) + Wtxid(enc.finalize()) } /// Get the total transaction fee in the given asset.