diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 4ae8f7601e..58413d9077 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -68,12 +68,11 @@ fn test_withdraw_to_p2sh_address_should_fail() { let p2sh_address = AddressBuilder::new( UtxoAddressFormat::Standard, - coin.as_ref().derivation_method.unwrap_single_addr().hash().clone(), *coin.as_ref().derivation_method.unwrap_single_addr().checksum_type(), coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_sh() + .as_sh(coin.as_ref().derivation_method.unwrap_single_addr().hash().clone()) .build() .expect("valid address props"); diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index a7b3c63aa2..28bd84c4bd 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -1925,6 +1925,8 @@ pub fn output_script(address: &Address) -> Result { } } +pub fn output_script_p2pk(pubkey: &Public) -> Script { Builder::build_p2pk(pubkey) } + pub fn address_by_conf_and_pubkey_str( coin: &str, conf: &Json, @@ -1949,16 +1951,15 @@ pub fn address_by_conf_and_pubkey_str( let conf_builder = UtxoConfBuilder::new(conf, ¶ms, coin); let utxo_conf = try_s!(conf_builder.build()); let pubkey_bytes = try_s!(hex::decode(pubkey)); - let hash = dhash160(&pubkey_bytes); + let pubkey = try_s!(Public::from_slice(&pubkey_bytes)); let address = AddressBuilder::new( addr_format, - hash.into(), utxo_conf.checksum_type, utxo_conf.address_prefixes, utxo_conf.bech32_hrp, ) - .as_pkh() + .as_pkh_from_pk(&pubkey) .build()?; address.display_address() } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index dc9e6fda0b..41d30b5087 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -149,12 +149,11 @@ pub trait QtumBasedCoin: UtxoCommonOps + MarketCoinOps { let utxo = self.as_ref(); AddressBuilder::new( self.addr_format().clone(), - AddressHashEnum::AddressHash(address.0.into()), utxo.conf.checksum_type, utxo.conf.address_prefixes.clone(), utxo.conf.bech32_hrp.clone(), ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash(address.0.into())) .build() .expect("valid address props") } @@ -164,20 +163,6 @@ pub trait QtumBasedCoin: UtxoCommonOps + MarketCoinOps { contract_addr_from_utxo_addr(my_address).mm_err(Qrc20AddressError::from) } - fn utxo_address_from_contract_addr(&self, address: H160) -> Address { - let utxo = self.as_ref(); - AddressBuilder::new( - self.addr_format().clone(), - AddressHashEnum::AddressHash(address.0.into()), - utxo.conf.checksum_type, - utxo.conf.address_prefixes.clone(), - utxo.conf.bech32_hrp.clone(), - ) - .as_pkh() - .build() - .expect("valid address props") - } - fn contract_address_from_raw_pubkey(&self, pubkey: &[u8]) -> Result { let utxo = self.as_ref(); let qtum_address = try_s!(utxo_common::address_from_raw_pubkey( diff --git a/mm2src/coins/utxo/qtum_delegation.rs b/mm2src/coins/utxo/qtum_delegation.rs index f146042112..f0b82822b8 100644 --- a/mm2src/coins/utxo/qtum_delegation.rs +++ b/mm2src/coins/utxo/qtum_delegation.rs @@ -186,7 +186,7 @@ impl QtumCoin { .map(|padded_staker_address_hex| padded_staker_address_hex.trim_start_matches('0')) }) { let hash = H160::from_str(raw).map_to_mm(|e| StakingInfosError::Internal(e.to_string()))?; - let address = self.utxo_address_from_contract_addr(hash); + let address = self.utxo_addr_from_contract_addr(hash); Ok(Some(address.to_string())) } else { Ok(None) diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index 855714d85d..ba37274cb2 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -2,8 +2,8 @@ #![cfg_attr(target_arch = "wasm32", allow(dead_code))] use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; -use crate::utxo::{output_script, sat_from_big_decimal, GetBlockHeaderError, GetConfirmedTxError, GetTxError, - GetTxHeightError, ScripthashNotification}; +use crate::utxo::{output_script, output_script_p2pk, sat_from_big_decimal, GetBlockHeaderError, GetConfirmedTxError, + GetTxError, GetTxHeightError, ScripthashNotification}; use crate::{big_decimal_from_sat_unsigned, NumConversError, RpcTransportEventHandler, RpcTransportEventHandlerShared}; use async_trait::async_trait; use chain::{BlockHeader, BlockHeaderBits, BlockHeaderNonce, OutPoint, Transaction as UtxoTx}; @@ -2329,12 +2329,30 @@ impl UtxoRpcClientOps for ElectrumClient { rpc_req!(self, "blockchain.scripthash.get_balance").into(), JsonRpcErrorType::Internal(err.to_string()) ))); - let hash = electrum_script_hash(&output_script); - let hash_str = hex::encode(hash); - Box::new( - self.scripthash_get_balance(&hash_str) - .map(move |electrum_balance| electrum_balance.to_big_decimal(decimals)), - ) + let hash = hex::encode(electrum_script_hash(&output_script)); + + // If the plain pubkey is available, fetch the balance found in P2PK output as well (if any). + if let Some(pubkey) = address.pubkey() { + let p2pk_hash = hex::encode(electrum_script_hash(&output_script_p2pk(pubkey))); + + let this = self.clone(); + let fut = async move { + Ok(this + .scripthash_get_balances(vec![hash, p2pk_hash]) + .compat() + .await? + .into_iter() + .fold(BigDecimal::from(0), |sum, electrum_balance| { + sum + electrum_balance.to_big_decimal(decimals) + })) + }; + Box::new(fut.boxed().compat()) + } else { + Box::new( + self.scripthash_get_balance(&hash) + .map(move |electrum_balance| electrum_balance.to_big_decimal(decimals)), + ) + } } fn display_balances(&self, addresses: Vec
, decimals: u8) -> UtxoRpcFut> { diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index 6f10e9d791..762d2be812 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -235,12 +235,11 @@ where let addr_format = builder.address_format()?; let my_address = AddressBuilder::new( addr_format, - AddressHashEnum::AddressHash(key_pair.public().address_hash()), conf.checksum_type, conf.address_prefixes.clone(), conf.bech32_hrp.clone(), ) - .as_pkh() + .as_pkh_from_pk(key_pair.public()) .build() .map_to_mm(UtxoCoinBuildError::Internal)?; diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index ddec247769..66a1c5b990 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -632,19 +632,18 @@ pub fn addresses_from_script(coin: &T, script: &Script) -> Res let (addr_format, build_option) = match dst.kind { AddressScriptType::P2PKH => ( coin.addr_format_for_standard_scripts(), - AddressBuilderOption::BuildAsPubkeyHash, + AddressBuilderOption::PubkeyHash(dst.hash), ), AddressScriptType::P2SH => ( coin.addr_format_for_standard_scripts(), - AddressBuilderOption::BuildAsScriptHash, + AddressBuilderOption::ScriptHash(dst.hash), ), - AddressScriptType::P2WPKH => (UtxoAddressFormat::Segwit, AddressBuilderOption::BuildAsPubkeyHash), - AddressScriptType::P2WSH => (UtxoAddressFormat::Segwit, AddressBuilderOption::BuildAsScriptHash), + AddressScriptType::P2WPKH => (UtxoAddressFormat::Segwit, AddressBuilderOption::PubkeyHash(dst.hash)), + AddressScriptType::P2WSH => (UtxoAddressFormat::Segwit, AddressBuilderOption::ScriptHash(dst.hash)), }; AddressBuilder::new( addr_format, - dst.hash, conf.checksum_type, conf.address_prefixes.clone(), conf.bech32_hrp.clone(), @@ -1534,12 +1533,11 @@ pub async fn sign_and_send_taker_funding_spend( ); let payment_address = AddressBuilder::new( UtxoAddressFormat::Standard, - AddressHashEnum::AddressHash(dhash160(&payment_redeem_script)), coin.as_ref().conf.checksum_type, coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_sh() + .as_sh(dhash160(&payment_redeem_script).into()) .build() .map_err(TransactionErr::Plain)?; let payment_address_str = payment_address.to_string(); @@ -2732,12 +2730,11 @@ pub fn check_if_my_payment_sent( UtxoRpcClientEnum::Native(client) => { let target_addr = AddressBuilder::new( coin.addr_format_for_standard_scripts(), - hash.into(), coin.as_ref().conf.checksum_type, coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_sh() + .as_sh(hash.into()) .build()?; let target_addr = target_addr.to_string(); let is_imported = try_s!(client.is_address_imported(&target_addr).await); @@ -4580,15 +4577,9 @@ pub fn address_from_raw_pubkey( hrp: Option, addr_format: UtxoAddressFormat, ) -> Result { - AddressBuilder::new( - addr_format, - try_s!(Public::from_slice(pub_key)).address_hash().into(), - checksum_type, - prefixes, - hrp, - ) - .as_pkh() - .build() + AddressBuilder::new(addr_format, checksum_type, prefixes, hrp) + .as_pkh_from_pk(&try_s!(Public::from_slice(pub_key))) + .build() } pub fn address_from_pubkey( @@ -4598,8 +4589,8 @@ pub fn address_from_pubkey( hrp: Option, addr_format: UtxoAddressFormat, ) -> Address { - AddressBuilder::new(addr_format, pub_key.address_hash().into(), checksum_type, prefixes, hrp) - .as_pkh() + AddressBuilder::new(addr_format, checksum_type, prefixes, hrp) + .as_pkh_from_pk(pub_key) .build() .expect("valid address props") } @@ -4816,12 +4807,11 @@ where let payment_address = AddressBuilder::new( UtxoAddressFormat::Standard, - redeem_script_hash.into(), coin.as_ref().conf.checksum_type, coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_sh() + .as_sh(redeem_script_hash.into()) .build()?; let result = SwapPaymentOutputsResult { payment_address, diff --git a/mm2src/coins/utxo/utxo_common_tests.rs b/mm2src/coins/utxo/utxo_common_tests.rs index 4a716182a7..5b30d7c444 100644 --- a/mm2src/coins/utxo/utxo_common_tests.rs +++ b/mm2src/coins/utxo/utxo_common_tests.rs @@ -81,16 +81,10 @@ pub(super) fn utxo_coin_fields_for_test( } else { UtxoAddressFormat::Standard }; - let my_address = AddressBuilder::new( - addr_format, - key_pair.public().address_hash().into(), - checksum_type, - prefixes, - hrp, - ) - .as_pkh() - .build() - .expect("valid address props"); + let my_address = AddressBuilder::new(addr_format, checksum_type, prefixes, hrp) + .as_pkh_from_pk(key_pair.public()) + .build() + .expect("valid address props"); let my_script_pubkey = Builder::build_p2pkh(my_address.hash()).to_bytes(); let priv_key_policy = PrivKeyPolicy::Iguana(key_pair); diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 3cd2090f1a..a7b5f74b49 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -3088,12 +3088,11 @@ fn test_withdraw_to_p2pkh() { // Create a p2pkh address for the test coin let p2pkh_address = AddressBuilder::new( UtxoAddressFormat::Standard, - coin.as_ref().derivation_method.unwrap_single_addr().hash().clone(), *coin.as_ref().derivation_method.unwrap_single_addr().checksum_type(), coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_pkh() + .as_pkh(coin.as_ref().derivation_method.unwrap_single_addr().hash().clone()) .build() .expect("valid address props"); @@ -3138,12 +3137,11 @@ fn test_withdraw_to_p2sh() { // Create a p2sh address for the test coin let p2sh_address = AddressBuilder::new( UtxoAddressFormat::Standard, - coin.as_ref().derivation_method.unwrap_single_addr().hash().clone(), *coin.as_ref().derivation_method.unwrap_single_addr().checksum_type(), coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_sh() + .as_sh(coin.as_ref().derivation_method.unwrap_single_addr().hash().clone()) .build() .expect("valid address props"); @@ -3188,12 +3186,11 @@ fn test_withdraw_to_p2wpkh() { // Create a p2wpkh address for the test coin let p2wpkh_address = AddressBuilder::new( UtxoAddressFormat::Segwit, - coin.as_ref().derivation_method.unwrap_single_addr().hash().clone(), *coin.as_ref().derivation_method.unwrap_single_addr().checksum_type(), NetworkAddressPrefixes::default(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_pkh() + .as_pkh(coin.as_ref().derivation_method.unwrap_single_addr().hash().clone()) .build() .expect("valid address props"); diff --git a/mm2src/coins/utxo/utxo_withdraw.rs b/mm2src/coins/utxo/utxo_withdraw.rs index 795da12006..a48feb69e6 100644 --- a/mm2src/coins/utxo/utxo_withdraw.rs +++ b/mm2src/coins/utxo/utxo_withdraw.rs @@ -13,7 +13,7 @@ use crypto::hw_rpc_task::HwRpcTaskAwaitingStatus; use crypto::trezor::trezor_rpc_task::{TrezorRequestStatuses, TrezorRpcTaskProcessor}; use crypto::trezor::{TrezorError, TrezorProcessingError}; use crypto::{from_hw_error, CryptoCtx, CryptoCtxError, DerivationPath, HwError, HwProcessingError, HwRpcError}; -use keys::{AddressFormat, AddressHashEnum, KeyPair, Private, Public as PublicKey}; +use keys::{AddressFormat, KeyPair, Private, Public as PublicKey}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use rpc::v1::types::ToTxHash; @@ -469,12 +469,11 @@ where .clone(); let my_address = AddressBuilder::new( addr_format, - AddressHashEnum::AddressHash(key_pair.public().address_hash()), coin.as_ref().conf.checksum_type, coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_pkh() + .as_pkh_from_pk(key_pair.public()) .build() .map_to_mm(WithdrawError::InternalError)?; (key_pair, my_address) diff --git a/mm2src/coins/z_coin/z_htlc.rs b/mm2src/coins/z_coin/z_htlc.rs index 6690977bba..2a8105c873 100644 --- a/mm2src/coins/z_coin/z_htlc.rs +++ b/mm2src/coins/z_coin/z_htlc.rs @@ -48,12 +48,11 @@ pub async fn z_send_htlc( let script_hash = dhash160(&payment_script); let htlc_address = AddressBuilder::new( UtxoAddressFormat::Standard, - script_hash.into(), coin.utxo_arc.conf.checksum_type, coin.utxo_arc.conf.address_prefixes.clone(), None, ) - .as_sh() + .as_sh(script_hash.into()) .build() .map_to_mm(SendOutputsErr::InternalError)?; diff --git a/mm2src/mm2_bitcoin/keys/src/address.rs b/mm2src/mm2_bitcoin/keys/src/address.rs index ed30b78aef..61d262245d 100644 --- a/mm2src/mm2_bitcoin/keys/src/address.rs +++ b/mm2src/mm2_bitcoin/keys/src/address.rs @@ -5,11 +5,13 @@ //! //! https://en.bitcoin.it/wiki/Address +use crate::Public; use crypto::{dgroestl512, dhash256, keccak256, ChecksumType}; use derive_more::Display; use serde::{Deserialize, Serialize}; -use std::fmt; use std::str::FromStr; +use std::{fmt, hash::Hash}; + use {AddressHashEnum, AddressPrefix, CashAddrType, CashAddress, Error, LegacyAddress, NetworkAddressPrefixes, SegwitAddress}; @@ -97,13 +99,15 @@ pub fn detect_checksum(data: &[u8], checksum: &[u8]) -> Result, - /// Public key hash. + /// The public key of the address. + pubkey: Option, + /// Public key/Script hash. hash: AddressHashEnum, /// Checksum type checksum_type: ChecksumType, @@ -113,9 +117,36 @@ pub struct Address { script_type: AddressScriptType, } +impl PartialEq for Address { + /// A `PartialEq` implementation that doesn't take `pubkey` into consideration. That's because + /// we want to rely on address `hash` only for knowing whether two addresses are equal since `pubkey` + /// might not always be available. + fn eq(&self, other: &Self) -> bool { + self.prefix == other.prefix + && self.hrp == other.hrp + && self.hash == other.hash + && self.checksum_type == other.checksum_type + && self.addr_format == other.addr_format + && self.script_type == other.script_type + } +} + +impl Hash for Address { + /// Like the `PartialEq` implementation, this `Hash` implementation doesn't take `pubkey` into consideration. + fn hash(&self, state: &mut H) { + self.prefix.hash(state); + self.hrp.hash(state); + self.hash.hash(state); + self.checksum_type.hash(state); + self.addr_format.hash(state); + self.script_type.hash(state); + } +} + impl Address { pub fn prefix(&self) -> &AddressPrefix { &self.prefix } pub fn hrp(&self) -> &Option { &self.hrp } + pub fn pubkey(&self) -> &Option { &self.pubkey } pub fn hash(&self) -> &AddressHashEnum { &self.hash } pub fn checksum_type(&self) -> &ChecksumType { &self.checksum_type } pub fn addr_format(&self) -> &AddressFormat { &self.addr_format } @@ -173,6 +204,7 @@ impl Address { hash, checksum_type: address.checksum_type, hrp: None, + pubkey: None, addr_format: AddressFormat::Standard, script_type, }) @@ -202,6 +234,7 @@ impl Address { hash, checksum_type, hrp: None, + pubkey: None, addr_format: AddressFormat::CashAddress { network: address.prefix.to_string(), pub_addr_prefix: net_addr_prefixes.p2pkh.get_size_1_prefix(), @@ -248,6 +281,7 @@ impl Address { hash, checksum_type, hrp, + pubkey: None, addr_format: AddressFormat::Segwit, script_type, }) @@ -295,12 +329,13 @@ mod tests { fn test_address_to_string() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("3f4aa1fedf1f54eeb03b759deadb36676b184911".into()), ChecksumType::DSHA256, (*BTC_PREFIXES).clone(), None, ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash( + "3f4aa1fedf1f54eeb03b759deadb36676b184911".into(), + )) .build() .expect("valid address props"); @@ -311,12 +346,13 @@ mod tests { fn test_komodo_address_to_string() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("05aab5342166f8594baf17a7d9bef5d567443327".into()), ChecksumType::DSHA256, (*KMD_PREFIXES).clone(), None, ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash( + "05aab5342166f8594baf17a7d9bef5d567443327".into(), + )) .build() .expect("valid address props"); @@ -327,12 +363,13 @@ mod tests { fn test_zec_t_address_to_string() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("05aab5342166f8594baf17a7d9bef5d567443327".into()), ChecksumType::DSHA256, (*T_ZCASH_PREFIXES).clone(), None, ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash( + "05aab5342166f8594baf17a7d9bef5d567443327".into(), + )) .build() .expect("valid address props"); @@ -343,12 +380,13 @@ mod tests { fn test_komodo_p2sh_address_to_string() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("ca0c3786c96ff7dacd40fdb0f7c196528df35f85".into()), ChecksumType::DSHA256, (*KMD_PREFIXES).clone(), None, ) - .as_sh() + .as_sh(AddressHashEnum::AddressHash( + "ca0c3786c96ff7dacd40fdb0f7c196528df35f85".into(), + )) .build() .expect("valid address props"); // TODO: check with P2PKH @@ -359,12 +397,13 @@ mod tests { fn test_address_from_str() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("3f4aa1fedf1f54eeb03b759deadb36676b184911".into()), ChecksumType::DSHA256, (*BTC_PREFIXES).clone(), None, ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash( + "3f4aa1fedf1f54eeb03b759deadb36676b184911".into(), + )) .build() .expect("valid address props"); @@ -379,12 +418,13 @@ mod tests { fn test_komodo_address_from_str() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("05aab5342166f8594baf17a7d9bef5d567443327".into()), ChecksumType::DSHA256, (*KMD_PREFIXES).clone(), None, ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash( + "05aab5342166f8594baf17a7d9bef5d567443327".into(), + )) .build() .expect("valid address props"); @@ -399,12 +439,13 @@ mod tests { fn test_zec_address_from_str() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("05aab5342166f8594baf17a7d9bef5d567443327".into()), ChecksumType::DSHA256, (*T_ZCASH_PREFIXES).clone(), None, ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash( + "05aab5342166f8594baf17a7d9bef5d567443327".into(), + )) .build() .expect("valid address props"); @@ -419,12 +460,13 @@ mod tests { fn test_komodo_p2sh_address_from_str() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("ca0c3786c96ff7dacd40fdb0f7c196528df35f85".into()), ChecksumType::DSHA256, (*KMD_PREFIXES).clone(), None, ) - .as_sh() + .as_sh(AddressHashEnum::AddressHash( + "ca0c3786c96ff7dacd40fdb0f7c196528df35f85".into(), + )) .build() .expect("valid address props"); @@ -439,12 +481,13 @@ mod tests { fn test_grs_addr_from_str() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("c3f710deb7320b0efa6edb14e3ebeeb9155fa90d".into()), ChecksumType::DGROESTL512, (*GRS_PREFIXES).clone(), None, ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash( + "c3f710deb7320b0efa6edb14e3ebeeb9155fa90d".into(), + )) .build() .expect("valid address props"); @@ -459,12 +502,13 @@ mod tests { fn test_smart_addr_from_str() { let address = AddressBuilder::new( AddressFormat::Standard, - AddressHashEnum::AddressHash("56bb05aa20f5a80cf84e90e5dab05be331333e27".into()), ChecksumType::KECCAK256, (*SYS_PREFIXES).clone(), None, ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash( + "56bb05aa20f5a80cf84e90e5dab05be331333e27".into(), + )) .build() .expect("valid address props"); @@ -528,17 +572,16 @@ mod tests { pub_addr_prefix: 0, p2sh_addr_prefix: 5, }, - AddressHashEnum::AddressHash( - [ - 140, 0, 44, 191, 189, 83, 144, 173, 47, 216, 127, 59, 80, 232, 159, 100, 156, 132, 78, 192, - ] - .into(), - ), ChecksumType::DSHA256, unknown_prefixes, None, ) - .as_sh() + .as_sh(AddressHashEnum::AddressHash( + [ + 140, 0, 44, 191, 189, 83, 144, 173, 47, 216, 127, 59, 80, 232, 159, 100, 156, 132, 78, 192, + ] + .into(), + )) .build() .expect("valid address props"); // actually prefix == 2 is unknown and is neither P2PKH nor P2SH diff --git a/mm2src/mm2_bitcoin/keys/src/address/address_builder.rs b/mm2src/mm2_bitcoin/keys/src/address/address_builder.rs index c292f72510..3d0f8a38b6 100644 --- a/mm2src/mm2_bitcoin/keys/src/address/address_builder.rs +++ b/mm2src/mm2_bitcoin/keys/src/address/address_builder.rs @@ -1,13 +1,17 @@ +use crate::Public; use crypto::ChecksumType; + use {Address, AddressFormat, AddressHashEnum, AddressPrefix, AddressScriptType, NetworkAddressPrefixes}; /// Params for AddressBuilder to select output script type #[derive(PartialEq)] pub enum AddressBuilderOption { /// build for pay to pubkey hash output (witness or legacy) - BuildAsPubkeyHash, + PubkeyHash(AddressHashEnum), /// build for pay to script hash output (witness or legacy) - BuildAsScriptHash, + ScriptHash(AddressHashEnum), + /// build for pay to pubkey hash but using a public key as an input (not pubkey hash) + FromPubKey(Public), } /// Builds Address struct depending on addr_format, validates params to build Address @@ -16,8 +20,6 @@ pub struct AddressBuilder { prefixes: NetworkAddressPrefixes, /// Segwit addr human readable part hrp: Option, - /// Public key hash - hash: AddressHashEnum, /// Checksum type checksum_type: ChecksumType, /// Address Format @@ -29,14 +31,12 @@ pub struct AddressBuilder { impl AddressBuilder { pub fn new( addr_format: AddressFormat, - hash: AddressHashEnum, checksum_type: ChecksumType, prefixes: NetworkAddressPrefixes, hrp: Option, ) -> Self { Self { addr_format, - hash, checksum_type, prefixes, hrp, @@ -50,15 +50,21 @@ impl AddressBuilder { self } + /// Sets Address tx output script type as p2pkh or p2wpkh, but also keep the public key stored. + pub fn as_pkh_from_pk(mut self, public: &Public) -> Self { + self.build_option = Some(AddressBuilderOption::FromPubKey(*public)); + self + } + /// Sets Address tx output script type as p2pkh or p2wpkh - pub fn as_pkh(mut self) -> Self { - self.build_option = Some(AddressBuilderOption::BuildAsPubkeyHash); + pub fn as_pkh(mut self, hash: AddressHashEnum) -> Self { + self.build_option = Some(AddressBuilderOption::PubkeyHash(hash)); self } /// Sets Address tx output script type as p2sh or p2wsh - pub fn as_sh(mut self) -> Self { - self.build_option = Some(AddressBuilderOption::BuildAsScriptHash); + pub fn as_sh(mut self, hash: AddressHashEnum) -> Self { + self.build_option = Some(AddressBuilderOption::ScriptHash(hash)); self } @@ -68,7 +74,8 @@ impl AddressBuilder { AddressFormat::Standard => Ok(Address { prefix: self.get_address_prefix(build_option)?, hrp: None, - hash: self.hash.clone(), + hash: self.get_hash(build_option), + pubkey: self.get_pubkey(build_option), checksum_type: self.checksum_type, addr_format: self.addr_format.clone(), script_type: self.get_legacy_script_type(build_option), @@ -79,7 +86,8 @@ impl AddressBuilder { Ok(Address { prefix: AddressPrefix::default(), hrp: self.hrp.clone(), - hash: self.hash.clone(), + hash: self.get_hash(build_option), + pubkey: self.get_pubkey(build_option), checksum_type: self.checksum_type, addr_format: self.addr_format.clone(), script_type: self.get_segwit_script_type(build_option), @@ -88,7 +96,8 @@ impl AddressBuilder { AddressFormat::CashAddress { .. } => Ok(Address { prefix: self.get_address_prefix(build_option)?, hrp: None, - hash: self.hash.clone(), + hash: self.get_hash(build_option), + pubkey: self.get_pubkey(build_option), checksum_type: self.checksum_type, addr_format: self.addr_format.clone(), script_type: self.get_legacy_script_type(build_option), @@ -98,8 +107,8 @@ impl AddressBuilder { fn get_address_prefix(&self, build_option: &AddressBuilderOption) -> Result { let prefix = match build_option { - AddressBuilderOption::BuildAsPubkeyHash => &self.prefixes.p2pkh, - AddressBuilderOption::BuildAsScriptHash => &self.prefixes.p2sh, + AddressBuilderOption::PubkeyHash(_) | AddressBuilderOption::FromPubKey(_) => &self.prefixes.p2pkh, + AddressBuilderOption::ScriptHash(_) => &self.prefixes.p2sh, }; if prefix.is_empty() { return Err("no prefix for address set".to_owned()); @@ -109,15 +118,30 @@ impl AddressBuilder { fn get_legacy_script_type(&self, build_option: &AddressBuilderOption) -> AddressScriptType { match build_option { - AddressBuilderOption::BuildAsPubkeyHash => AddressScriptType::P2PKH, - AddressBuilderOption::BuildAsScriptHash => AddressScriptType::P2SH, + AddressBuilderOption::PubkeyHash(_) | AddressBuilderOption::FromPubKey(_) => AddressScriptType::P2PKH, + AddressBuilderOption::ScriptHash(_) => AddressScriptType::P2SH, } } fn get_segwit_script_type(&self, build_option: &AddressBuilderOption) -> AddressScriptType { match build_option { - AddressBuilderOption::BuildAsPubkeyHash => AddressScriptType::P2WPKH, - AddressBuilderOption::BuildAsScriptHash => AddressScriptType::P2WSH, + AddressBuilderOption::PubkeyHash(_) | AddressBuilderOption::FromPubKey(_) => AddressScriptType::P2WPKH, + AddressBuilderOption::ScriptHash(_) => AddressScriptType::P2WSH, + } + } + + fn get_hash(&self, build_option: &AddressBuilderOption) -> AddressHashEnum { + match build_option { + AddressBuilderOption::PubkeyHash(hash) => hash.clone(), + AddressBuilderOption::ScriptHash(hash) => hash.clone(), + AddressBuilderOption::FromPubKey(pubkey) => AddressHashEnum::AddressHash(pubkey.address_hash()), + } + } + + fn get_pubkey(&self, build_option: &AddressBuilderOption) -> Option { + match build_option { + AddressBuilderOption::FromPubKey(pubkey) => Some(*pubkey), + _ => None, } } @@ -130,8 +154,9 @@ impl AddressBuilder { fn check_segwit_hash(&self, build_option: &AddressBuilderOption) -> Result<(), String> { let is_hash_valid = match build_option { - AddressBuilderOption::BuildAsPubkeyHash => self.hash.is_address_hash(), - AddressBuilderOption::BuildAsScriptHash => self.hash.is_witness_script_hash(), + AddressBuilderOption::PubkeyHash(hash) => hash.is_address_hash(), + AddressBuilderOption::ScriptHash(hash) => hash.is_witness_script_hash(), + AddressBuilderOption::FromPubKey(_) => true, }; if !is_hash_valid { return Err("invalid hash for segwit address".to_owned()); diff --git a/mm2src/mm2_bitcoin/keys/src/lib.rs b/mm2src/mm2_bitcoin/keys/src/lib.rs index c7d28687b9..d3a01d854d 100644 --- a/mm2src/mm2_bitcoin/keys/src/lib.rs +++ b/mm2src/mm2_bitcoin/keys/src/lib.rs @@ -52,7 +52,7 @@ pub type Message = H256; #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum AddressHashEnum { - /// 20 bytes long hash derived from public `ripemd160(sha256(public))` used in P2PKH, P2SH, P2WPKH + /// 20 bytes long hash derived from public `ripemd160(sha256(public/script))` used in P2PKH, P2SH, P2WPKH AddressHash(H160), /// 32 bytes long hash derived from script `sha256(script)` used in P2WSH WitnessScriptHash(H256), diff --git a/mm2src/mm2_bitcoin/keys/src/public.rs b/mm2src/mm2_bitcoin/keys/src/public.rs index b57f376ee9..cb801585d8 100644 --- a/mm2src/mm2_bitcoin/keys/src/public.rs +++ b/mm2src/mm2_bitcoin/keys/src/public.rs @@ -8,7 +8,7 @@ use std::{fmt, ops}; use {CompactSignature, Error, Message, Signature}; /// Secret public key -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq)] pub enum Public { /// Normal version of public key Normal(H520), diff --git a/mm2src/mm2_bitcoin/script/src/script.rs b/mm2src/mm2_bitcoin/script/src/script.rs index e605dcc8b1..a860caaa79 100644 --- a/mm2src/mm2_bitcoin/script/src/script.rs +++ b/mm2src/mm2_bitcoin/script/src/script.rs @@ -420,6 +420,7 @@ impl Script { _ => unreachable!(), // because we are relying on script_type() checks here }) .map(|public| { + // DISCUSS: Should we really reduce this to pkh? what is it used for? vec![ScriptAddress::new_p2pkh(AddressHashEnum::AddressHash( public.address_hash(), ))] diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index 5012377296..1f3b21261e 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -251,15 +251,13 @@ impl BchDockerOps { for _ in 0..18 { let key_pair = KeyPair::random_compressed(); - let address_hash = key_pair.public().address_hash(); let address = AddressBuilder::new( Default::default(), - address_hash.into(), Default::default(), self.coin.as_ref().conf.address_prefixes.clone(), None, ) - .as_pkh() + .as_pkh_from_pk(key_pair.public()) .build() .expect("valid address props"); @@ -268,7 +266,7 @@ impl BchDockerOps { .wait() .unwrap(); - let script_pubkey = Builder::build_p2pkh(&address_hash.into()); + let script_pubkey = Builder::build_p2pkh(&key_pair.public().address_hash().into()); bch_outputs.push(TransactionOutput { value: 1000_00000000, @@ -997,7 +995,6 @@ pub fn get_balance(mm: &MarketMakerIt, coin: &str) -> BalanceResponse { pub fn utxo_burn_address() -> Address { AddressBuilder::new( UtxoAddressFormat::Standard, - AddressHashEnum::default_address_hash(), ChecksumType::DSHA256, NetworkAddressPrefixes { p2pkh: [60].into(), @@ -1005,7 +1002,7 @@ pub fn utxo_burn_address() -> Address { }, None, ) - .as_pkh() + .as_pkh(AddressHashEnum::default_address_hash()) .build() .expect("valid address props") } diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 1b60232955..826b9e22a6 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -256,6 +256,80 @@ fn test_my_balance() { assert_eq!(my_address, "RRnMcSeKiLrNdbp91qNVQwwXx5azD4S4CD"); } +#[test] +fn test_p2pk_my_balance() { + // PK of the P2PK balance: 03f8f8fa2062590ba9a0a7a86f937de22f540c015864aad35a2a9f6766de906265 + let seed = "salmon angle cushion sauce accuse earth volume until zone youth emerge favorite"; + + let coins = json! ([ + { + "coin": "tBTC", + "name": "tbitcoin", + "fname": "tBitcoin", + "rpcport": 18332, + "pubtype": 111, + "p2shtype": 196, + "wiftype": 239, + "txfee": 0, + "estimate_fee_mode": "ECONOMICAL", + "mm2": 1, + "required_confirmations": 0, + "protocol": { + "type": "UTXO" + }, + } + ]); + + let mm = MarketMakerIt::start( + json! ({ + "gui": "nogui", + "netid": 9998, + "myipaddr": env::var("BOB_TRADE_IP").ok(), + "rpcip": env::var("BOB_TRADE_IP").ok(), + "passphrase": seed.to_string(), + "coins": coins, + "i_am_seed": true, + "rpc_password": "pass", + }), + "pass".into(), + None, + ) + .unwrap(); + let (_dump_log, _dump_dashboard) = mm.mm_dump(); + log!("log path: {}", mm.log_path.display()); + + let electrum = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "method": "electrum", + "coin": "tBTC", + "servers": [{"url":"electrum1.cipig.net:10068"},{"url":"electrum2.cipig.net:10068"},{"url":"electrum3.cipig.net:10068"}], + "mm2": 1, + }))).unwrap(); + assert_eq!( + electrum.0, + StatusCode::OK, + "RPC «electrum» failed with {} {}", + electrum.0, + electrum.1 + ); + + let my_balance = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "my_balance", + "coin": "tBTC", + }))) + .unwrap(); + + let json: Json = json::from_str(&my_balance.1).unwrap(); + let my_balance: &str = json["balance"].as_str().unwrap(); + assert_eq!(my_balance, "0.00076"); + let my_unspendable_balance = json["unspendable_balance"].as_str().unwrap(); + assert_eq!(my_unspendable_balance, "0"); + let my_address = json["address"].as_str().unwrap(); + // Even though the address is a P2PK, it's formatted as P2PKH like most explorers do. + assert_eq!(my_address, "mgrM9w49Q7vqtroLKGekLTqCVFye5u6G3v"); +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_p2wpkh_my_balance() {