Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
32b676a
remove signature_version() method
mariocynicys Sep 18, 2025
6926c98
taproot starter
mariocynicys Sep 20, 2025
5bc21f1
add version field for taproot preparation
mariocynicys Sep 23, 2025
9e67f62
add taproot signing support
mariocynicys Sep 24, 2025
474abc7
add variant AddressHashEnum::TweakedXOnlyPubkey and fix a couple of f…
mariocynicys Sep 24, 2025
0874221
rename as_pkh_from_pk to using_pk
mariocynicys Sep 24, 2025
fc54a5a
add a check to not use bad build option with segwit version
mariocynicys Sep 24, 2025
e61a549
remove some fixme and refine another
mariocynicys Sep 24, 2025
53a2961
side-quest: ease fork_id mambo jambo
mariocynicys Sep 25, 2025
2f77a29
side-quest: ease fork_id mambo jambo cont.
mariocynicys Sep 25, 2025
295f422
disallow parsing witness_v0 signature_version
mariocynicys Sep 25, 2025
9bfa5d8
remove leftover SignatureVersion from Sighash
mariocynicys Sep 25, 2025
66efeff
don't use witnesscripthash for taproot signing
mariocynicys Sep 30, 2025
44f0189
resolve the fixme related to tweaking user-provided pubkeys
mariocynicys Sep 30, 2025
87f633e
update trezor protos to add taproot inputs and outputs
mariocynicys Sep 30, 2025
a8fd301
support full taproot spending (incl. trezor)
mariocynicys Sep 30, 2025
58a2f60
resolve a fixme by not assuming that the segwit address version is co…
mariocynicys Sep 30, 2025
1b8a167
bulk rename AddressHashEnum to LockingDestination
mariocynicys Sep 30, 2025
e6923fb
fix: don't use witness script hash for taproot
mariocynicys Sep 30, 2025
ad28e1b
fix last bits to get taproot enabling working
mariocynicys Oct 1, 2025
0a08dbd
adapt the taproot test to allow withdrawing to taproot address
mariocynicys Oct 1, 2025
35fb5c5
add taproot address parsing tests
mariocynicys Oct 1, 2025
fd70c2c
add a fixme
mariocynicys Oct 3, 2025
d54b0dd
fix mis-written test
mariocynicys Oct 13, 2025
a70aeb1
Merge remote-tracking branch 'origin/dev' into taproot-support
mariocynicys Oct 13, 2025
6689d5f
review(onur): clearer matching and error messages
mariocynicys Oct 13, 2025
648f5d2
Merge remote-tracking branch 'origin/dev' into taproot-support
mariocynicys Dec 10, 2025
ad9cb6a
fix post-merge compilation error
mariocynicys Dec 10, 2025
a3e1da9
resolve cashaddress/legacy fixme
mariocynicys Dec 10, 2025
dc643d7
resolve a fixme regaring runtime panic
mariocynicys Dec 10, 2025
c5dd1d8
add a swap test for taproot coin
mariocynicys Dec 15, 2025
873530d
merge with origin/dev
mariocynicys Jan 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions mm2src/coins/lightning/ln_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ async fn sign_funding_transaction(
let signed = sign_tx(
unsigned,
key_pair,
SignatureVersion::WitnessV0,
SignatureVersion::Witness,
coin.as_ref().conf.fork_id,
)
.map_err(|e| SignFundingTransactionError::TxSignFailed(e.to_string()))?;
Expand Down Expand Up @@ -520,7 +520,7 @@ impl LightningEventHandler {
return;
},
};
let change_destination_script = match Builder::build_p2wpkh(my_address.hash()) {
let change_destination_script = match Builder::build_p2wpkh(my_address.locking_destination()) {
Ok(script) => script.to_bytes().take().into(),
Err(err) => {
error!(
Expand Down
2 changes: 1 addition & 1 deletion mm2src/coins/qrc20/qrc20_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fn test_withdraw_to_p2sh_address_should_fail() {
)
.as_sh(
block_on(coin.as_ref().derivation_method.unwrap_single_addr())
.hash()
.locking_destination()
.clone(),
)
.build()
Expand Down
15 changes: 13 additions & 2 deletions mm2src/coins/rpc_command/get_new_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,13 @@ impl RpcTask for InitGetNewAddressTask {
AddressFormat::Standard | AddressFormat::CashAddress { .. } => {
Some(TrezorInputScriptType::SpendAddress)
},
AddressFormat::Segwit => Some(TrezorInputScriptType::SpendWitness),
AddressFormat::Segwit { version: 0 } => Some(TrezorInputScriptType::SpendWitness),
AddressFormat::Segwit { version: 1 } => Some(TrezorInputScriptType::SpendTaproot),
AddressFormat::Segwit { version: v } => {
return Err(GetNewAddressRpcError::ErrorDerivingAddress(format!(
"Segwit v{v} addresses are not supported for UTXO"
)))?
},
};
Ok(GetNewAddressResponseEnum::Map(
get_new_address_helper(
Expand All @@ -368,7 +374,12 @@ impl RpcTask for InitGetNewAddressTask {
AddressFormat::Standard | AddressFormat::CashAddress { .. } => {
Some(TrezorInputScriptType::SpendAddress)
},
AddressFormat::Segwit => Some(TrezorInputScriptType::SpendWitness),
AddressFormat::Segwit { version: 0 } => Some(TrezorInputScriptType::SpendWitness),
AddressFormat::Segwit { version: v } => {
return Err(GetNewAddressRpcError::ErrorDerivingAddress(format!(
"Segwit v{v} addresses are not supported for Qtum"
)))?
},
};
Ok(GetNewAddressResponseEnum::Map(
get_new_address_helper(
Expand Down
4 changes: 2 additions & 2 deletions mm2src/coins/rpc_command/lightning/open_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use common::{async_blocking, new_uuid, HttpStatusCode};
use db_common::sqlite::rusqlite::Error as SqlError;
use derive_more::Display;
use http::StatusCode;
use keys::AddressHashEnum;
use keys::LockingDestination;
use lightning::util::config::UserConfig;
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::*;
Expand Down Expand Up @@ -185,7 +185,7 @@ pub async fn open_channel(ctx: MmArc, req: OpenChannelRequest) -> OpenChannelRes

// The actual script_pubkey will replace this before signing the transaction after receiving the required
// output script from the other node when the channel is accepted
let script_pubkey = match Builder::build_p2wsh(&AddressHashEnum::WitnessScriptHash(Default::default())) {
let script_pubkey = match Builder::build_p2wsh(&LockingDestination::WitnessScriptHash(Default::default())) {
Ok(script) => script.to_bytes(),
Err(err) => return MmError::err(OpenChannelError::InternalError(err.to_string())),
};
Expand Down
4 changes: 2 additions & 2 deletions mm2src/coins/rpc_command/offline_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ async fn offline_hd_keys_export_internal(

let address =
AddressBuilder::new(address_format, ChecksumType::DSHA256, address_prefixes, bech32_hrp)
.as_pkh_from_pk(*key_pair.public())
.using_pk(*key_pair.public())
.build()
.map_err(OfflineKeysError::Internal)?;

Expand Down Expand Up @@ -554,7 +554,7 @@ async fn offline_iguana_keys_export_internal(

let address =
AddressBuilder::new(AddressFormat::Standard, ChecksumType::DSHA256, address_prefixes, None)
.as_pkh_from_pk(*key_pair.public())
.using_pk(*key_pair.public())
.build()
.map_err(OfflineKeysError::Internal)?;

Expand Down
23 changes: 9 additions & 14 deletions mm2src/coins/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ use keys::bytes::Bytes;
use keys::NetworkAddressPrefixes;
use keys::Signature;
pub use keys::{
Address, AddressBuilder, AddressFormat as UtxoAddressFormat, AddressHashEnum, AddressPrefix, AddressScriptType,
KeyPair, LegacyAddress, Private, Public, Secret,
Address, AddressBuilder, AddressFormat as UtxoAddressFormat, AddressPrefix, AddressScriptType, KeyPair,
LegacyAddress, LockingDestination, Private, Public, Secret,
};
#[cfg(not(target_arch = "wasm32"))]
use lightning_invoice::Currency as LightningCurrency;
Expand Down Expand Up @@ -1876,7 +1876,6 @@ async fn generate_tx<T>(
where
T: AsRef<UtxoCoinFields> + UtxoTxGenerationOps + UtxoTxBroadcastOps,
{
let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err().await);
let key_pair = try_tx_s!(coin.as_ref().priv_key_policy.activated_key_or_err());
let mut builder = UtxoTxBuilder::new(coin)
.await
Expand All @@ -1899,15 +1898,10 @@ where
})
.collect();

let signature_version = match my_address.addr_format() {
UtxoAddressFormat::Segwit => SignatureVersion::WitnessV0,
_ => coin.as_ref().conf.signature_version,
};

let signed = try_tx_s!(sign_tx(
unsigned,
key_pair,
signature_version,
coin.as_ref().conf.signature_version,
coin.as_ref().conf.fork_id
));

Expand All @@ -1917,10 +1911,11 @@ where
/// Builds transaction output script for an Address struct
pub fn output_script(address: &Address) -> Result<Script, keys::Error> {
match address.script_type() {
AddressScriptType::P2PKH => Ok(Builder::build_p2pkh(address.hash())),
AddressScriptType::P2SH => Ok(Builder::build_p2sh(address.hash())),
AddressScriptType::P2WPKH => Builder::build_p2wpkh(address.hash()),
AddressScriptType::P2WSH => Builder::build_p2wsh(address.hash()),
AddressScriptType::P2PKH => Ok(Builder::build_p2pkh(address.locking_destination())),
AddressScriptType::P2SH => Ok(Builder::build_p2sh(address.locking_destination())),
AddressScriptType::P2WPKH => Builder::build_p2wpkh(address.locking_destination()),
AddressScriptType::P2WSH => Builder::build_p2wsh(address.locking_destination()),
AddressScriptType::P2TR => Builder::build_p2tr(address.locking_destination()),
}
}

Expand Down Expand Up @@ -1961,7 +1956,7 @@ pub fn address_by_conf_and_pubkey_str(
utxo_conf.address_prefixes,
utxo_conf.bech32_hrp,
)
.as_pkh_from_pk(pubkey)
.using_pk(pubkey)
.build()?;
address.display_address()
}
Expand Down
15 changes: 9 additions & 6 deletions mm2src/coins/utxo/qtum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use bitcrypto::sign_message_hash;
use common::executor::{AbortableSystem, AbortedError};
use ethereum_types::H160;
use futures::{FutureExt, TryFutureExt};
use keys::AddressHashEnum;
use keys::LockingDestination;
use mm2_metrics::MetricsArc;
use mm2_number::MmNumber;
use rpc::v1::types::H264 as H264Json;
Expand Down Expand Up @@ -106,7 +106,7 @@ pub trait QtumDelegationOps {
fn remove_delegation(&self) -> DelegationFut;

#[allow(clippy::result_large_err)]
fn generate_pod(&self, addr_hash: AddressHashEnum) -> Result<keys::Signature, MmError<DelegationError>>;
fn generate_pod(&self, addr_hash: LockingDestination) -> Result<keys::Signature, MmError<DelegationError>>;
}

#[async_trait]
Expand Down Expand Up @@ -163,7 +163,7 @@ pub trait QtumBasedCoin: UtxoCommonOps + MarketCoinOps {
utxo.conf.address_prefixes.clone(),
utxo.conf.bech32_hrp.clone(),
)
.as_pkh(AddressHashEnum::AddressHash(address.0.into()))
.as_pkh(LockingDestination::AddressHash(address.0.into()))
.build()
.expect("valid address props")
}
Expand Down Expand Up @@ -1361,11 +1361,14 @@ pub fn contract_addr_from_str(addr: &str) -> Result<H160, String> {
}

pub fn contract_addr_from_utxo_addr(address: Address) -> MmResult<H160, ScriptHashTypeNotSupported> {
match address.hash() {
AddressHashEnum::AddressHash(h) => Ok(h.take().into()),
AddressHashEnum::WitnessScriptHash(_) => MmError::err(ScriptHashTypeNotSupported {
match address.locking_destination() {
LockingDestination::AddressHash(h) => Ok(h.take().into()),
LockingDestination::WitnessScriptHash(_) => MmError::err(ScriptHashTypeNotSupported {
script_hash_type: "Witness".to_owned(),
}),
LockingDestination::TweakedXOnlyPubkey(_) => MmError::err(ScriptHashTypeNotSupported {
script_hash_type: "Taproot Script".to_owned(),
}),
}
}

Expand Down
14 changes: 7 additions & 7 deletions mm2src/coins/utxo/qtum_delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use ethabi::{Contract, Token};
use ethereum_types::H160;
use futures::compat::Future01CompatExt;
use futures::{FutureExt, TryFutureExt};
use keys::{AddressHashEnum, Signature};
use keys::{LockingDestination, Signature};
use mm2_err_handle::prelude::*;
use mm2_number::bigdecimal::{BigDecimal, Zero};
use rpc::v1::types::ToTxHash;
Expand Down Expand Up @@ -115,7 +115,7 @@ impl QtumDelegationOps for QtumCoin {
Box::new(fut.boxed().compat())
}

fn generate_pod(&self, addr_hash: AddressHashEnum) -> Result<Signature, MmError<DelegationError>> {
fn generate_pod(&self, addr_hash: LockingDestination) -> Result<Signature, MmError<DelegationError>> {
let mut buffer = b"\x15Qtum Signed Message:\n\x28".to_vec();
buffer.append(&mut addr_hash.to_string().into_bytes());
let hashed = dhash256(&buffer);
Expand All @@ -131,7 +131,7 @@ impl QtumDelegationOps for QtumCoin {

impl QtumCoin {
async fn remove_delegation_impl(&self) -> DelegationResult {
if self.addr_format().is_segwit() {
if self.addr_format().is_segwit_v0() {
return MmError::err(DelegationError::DelegationOpsNotSupported {
reason: "Qtum doesn't support delegation for segwit".to_string(),
});
Expand Down Expand Up @@ -236,15 +236,15 @@ impl QtumCoin {
amount,
staker,
am_i_staking,
is_staking_supported: !my_address.addr_format().is_segwit(),
is_staking_supported: !my_address.addr_format().is_segwit_v0(),
}
.into(),
};
Ok(infos)
}

async fn add_delegation_impl(&self, request: QtumDelegationRequest) -> DelegationResult {
if self.addr_format().is_segwit() {
if self.addr_format().is_segwit_v0() {
return MmError::err(DelegationError::DelegationOpsNotSupported {
reason: "Qtum doesn't support delegation for segwit".to_string(),
});
Expand All @@ -260,7 +260,7 @@ impl QtumCoin {
let staker_address_hex = qtum::contract_addr_from_utxo_addr(to_addr.clone()).map_mm_err()?;
let delegation_output = self.add_delegation_output(
staker_address_hex,
to_addr.hash().clone(),
to_addr.locking_destination().clone(),
fee,
QRC20_GAS_LIMIT_DELEGATION,
QRC20_GAS_PRICE_DEFAULT,
Expand Down Expand Up @@ -377,7 +377,7 @@ impl QtumCoin {
fn add_delegation_output(
&self,
to_addr: H160,
addr_hash: AddressHashEnum,
addr_hash: LockingDestination,
fee: u64,
gas_limit: u64,
gas_price: u64,
Expand Down
4 changes: 2 additions & 2 deletions mm2src/coins/utxo/rpc_clients/electrum_rpc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,7 @@ impl UtxoRpcClientOps for ElectrumClient {
if let Some(pubkey) = address.pubkey() {
// We don't want to show P2PK outputs along with segwit ones (P2WPKH).
// Allow listing the P2PK outputs only if the address is not segwit (i.e. show P2PK outputs along with P2PKH).
if !address.addr_format().is_segwit() {
if !address.addr_format().is_segwit_v0() {
let p2pk_output_script = output_script_p2pk(pubkey);
output_scripts.push(p2pk_output_script);
}
Expand Down Expand Up @@ -984,7 +984,7 @@ impl UtxoRpcClientOps for ElectrumClient {
// If the plain pubkey is available, fetch the balance found in P2PK output as well (if any).
if let Some(pubkey) = address.pubkey() {
// Show the balance in P2PK outputs only for the non-segwit legacy addresses (P2PKH).
if !address.addr_format().is_segwit() {
if !address.addr_format().is_segwit_v0() {
let p2pk_output_script = output_script_p2pk(pubkey);
hashes.push(hex::encode(electrum_script_hash(&p2pk_output_script)));
}
Expand Down
4 changes: 2 additions & 2 deletions mm2src/coins/utxo/slp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use futures01::Future;
use hex::FromHexError;
use keys::hash::H160;
use keys::{
AddressHashEnum, CashAddrType, CashAddress, CompactSignature, KeyPair, NetworkPrefix as CashAddrPrefix, Public,
CashAddrType, CashAddress, CompactSignature, KeyPair, LockingDestination, NetworkPrefix as CashAddrPrefix, Public,
};
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::*;
Expand Down Expand Up @@ -1629,7 +1629,7 @@ impl MmCoin for SlpToken {
// TODO clarify with community whether we should support withdrawal to SLP P2SH addresses
let script_pubkey = match address.address_type {
CashAddrType::P2PKH => {
ScriptBuilder::build_p2pkh(&AddressHashEnum::AddressHash(address_hash)).to_bytes()
ScriptBuilder::build_p2pkh(&LockingDestination::AddressHash(address_hash)).to_bytes()
},
CashAddrType::P2SH => {
return MmError::err(WithdrawError::InvalidAddress(
Expand Down
12 changes: 8 additions & 4 deletions mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ where
conf.address_prefixes.clone(),
conf.bech32_hrp.clone(),
)
.as_pkh_from_pk(*key_pair.public())
.using_pk(*key_pair.public())
.build()
.map_to_mm(UtxoCoinBuildError::Internal)?;

Expand Down Expand Up @@ -330,7 +330,7 @@ where
conf.address_prefixes.clone(),
conf.bech32_hrp.clone(),
)
.as_pkh_from_pk(Public::Compressed(pubkey.serialize().into()))
.using_pk(Public::Compressed(pubkey.serialize().into()))
.build()
.map_to_mm(UtxoCoinBuildError::Internal)?;
let derivation_method = DerivationMethod::SingleAddress(my_address.clone());
Expand Down Expand Up @@ -369,15 +369,17 @@ where
}
};
let addr_format = builder.address_format()?;
println!("Address format: {:?}", addr_format);
let my_address = AddressBuilder::new(
addr_format,
conf.checksum_type,
conf.address_prefixes.clone(),
conf.bech32_hrp.clone(),
)
.as_pkh_from_pk(pubkey)
.using_pk(pubkey)
.build()
.map_to_mm(UtxoCoinBuildError::Internal)?;
println!("My address: {:?}", my_address);

let my_script_pubkey = output_script(&my_address).map(|script| script.to_bytes())?;

Expand Down Expand Up @@ -529,7 +531,9 @@ pub trait UtxoCoinBuilderCommonOps {

let mut address_format = match format_from_req {
Some(from_req) => {
if from_req.is_segwit() != format_from_conf.is_segwit() {
// FIXME: Some test fails cuz we get one format as Standard and the other as CashAddress.
// Keep this as is and adapt that test and announce breaking change.
if from_req != format_from_conf {
let error = format!(
"Both conf {format_from_conf:?} and request {from_req:?} must be either Segwit or Standard/CashAddress"
);
Expand Down
Loading
Loading