> {
+ let PsbtRequest {
+ mut psbt,
+ unsigned_tx,
+ } = PsbtRequest::build(input)?;
+
+ let fee = unsigned_tx
+ .total_input()?
+ .checked_sub(unsigned_tx.total_output()?)
+ .or_tw_err(SigningErrorType::Error_not_enough_utxos)
+ .context("PSBT sum(input) < sum(output)")?;
+
+ let keys_manager = Self::keys_manager_for_tx(
+ &input.private_keys,
+ &unsigned_tx,
+ input.dangerous_use_fixed_schnorr_rng,
+ )?;
+
+ let signed_tx =
+ TxSigner::sign_tx(unsigned_tx, &keys_manager).context("Error signing transaction")?;
+
+ update_psbt_signed(&mut psbt, &signed_tx);
+
+ Ok(Proto::PsbtSigningOutput {
+ transaction: Some(ProtobufBuilder::tx_to_proto(&signed_tx)),
+ encoded: Cow::from(signed_tx.encode_out()),
+ txid: Cow::from(signed_tx.txid()),
+ // `vsize` could have been changed after the transaction being signed.
+ vsize: signed_tx.vsize() as u64,
+ fee,
+ weight: signed_tx.weight() as u64,
+ psbt: Cow::from(psbt.serialize()),
+ ..Proto::PsbtSigningOutput::default()
+ })
+ }
+
+ fn keys_manager_for_tx(
+ private_keys: &[P],
+ unsigned_tx: &UnsignedTransaction,
+ dangerous_use_fixed_schnorr_rng: bool,
+ ) -> SigningResult
+ where
+ P: AsRef<[u8]>,
+ {
let has_taproot = unsigned_tx
.input_args()
.iter()
@@ -43,7 +124,7 @@ impl BitcoinSigner {
let mut keys_manager = KeysManager::default();
// Parse private keys and put them to the keys manager.
- for private in input.private_keys.iter() {
+ for private in private_keys.iter() {
let ecdsa_private = ecdsa::secp256k1::PrivateKey::try_from(private.as_ref())
.into_tw()
.context("Invalid ecdsa secp256k1 private key")?;
@@ -54,7 +135,7 @@ impl BitcoinSigner {
.into_tw()
.context("Invalid schnorr private key")?;
- if input.dangerous_use_fixed_schnorr_rng {
+ if dangerous_use_fixed_schnorr_rng {
keys_manager.add_schnorr_private(schnorr_private.no_aux_rand());
} else {
keys_manager.add_schnorr_private(schnorr_private);
@@ -62,19 +143,6 @@ impl BitcoinSigner {
}
}
- let signed_tx =
- TxSigner::sign_tx(unsigned_tx, &keys_manager).context("Error signing transaction")?;
-
- Ok(Proto::SigningOutput {
- transaction: Some(ProtobufBuilder::tx_to_proto(&signed_tx)),
- encoded: Cow::from(signed_tx.encode_out()),
- txid: Cow::from(signed_tx.txid()),
- // `vsize` could have been changed after the transaction being signed.
- vsize: signed_tx.vsize() as u64,
- // `fee` should haven't been changed since it's a difference between `sum(inputs)` and `sum(outputs)`.
- fee: plan.fee_estimate,
- weight: signed_tx.weight() as u64,
- ..Proto::SigningOutput::default()
- })
+ Ok(keys_manager)
}
}
diff --git a/rust/tw_bitcoin/src/modules/signing_request/mod.rs b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs
similarity index 95%
rename from rust/tw_bitcoin/src/modules/signing_request/mod.rs
rename to rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs
index 8795d56fefd..81548a754fb 100644
--- a/rust/tw_bitcoin/src/modules/signing_request/mod.rs
+++ b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs
@@ -8,6 +8,7 @@ use crate::modules::tx_builder::utxo_protobuf::UtxoProtobuf;
use crate::modules::tx_builder::BitcoinChainInfo;
use tw_coin_entry::coin_context::CoinContext;
use tw_coin_entry::error::prelude::*;
+use tw_misc::traits::OptionalEmpty;
use tw_proto::BitcoinV2::Proto;
use tw_utxo::dust::DustPolicy;
use tw_utxo::modules::tx_planner::{PlanRequest, RequestType};
@@ -138,7 +139,7 @@ impl SigningRequestBuilder {
}
}
- fn chain_info(
+ pub fn chain_info(
coin: &dyn CoinContext,
chain_info: &Option,
) -> SigningResult {
@@ -150,17 +151,22 @@ impl SigningRequestBuilder {
}
if let Some(info) = chain_info {
+ let hrp = info.hrp.to_string().empty_or_some();
return Ok(BitcoinChainInfo {
p2pkh_prefix: prefix_to_u8(info.p2pkh_prefix, "p2pkh")?,
p2sh_prefix: prefix_to_u8(info.p2sh_prefix, "p2sh")?,
+ hrp,
});
}
// Try to get the chain info from the context.
+ // Note that not all Bitcoin forks support HRP (segwit addresses).
+ let hrp = coin.hrp();
match (coin.p2pkh_prefix(), coin.p2sh_prefix()) {
(Some(p2pkh_prefix), Some(p2sh_prefix)) => Ok(BitcoinChainInfo {
p2pkh_prefix,
p2sh_prefix,
+ hrp,
}),
_ => SigningError::err(SigningErrorType::Error_invalid_params)
.context("Neither 'SigningInput.chain_info' nor p2pkh/p2sh prefixes specified in the registry.json")
diff --git a/rust/tw_bitcoin/src/modules/transaction_util.rs b/rust/chains/tw_bitcoin/src/modules/transaction_util.rs
similarity index 100%
rename from rust/tw_bitcoin/src/modules/transaction_util.rs
rename to rust/chains/tw_bitcoin/src/modules/transaction_util.rs
diff --git a/rust/tw_bitcoin/src/modules/tx_builder/mod.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/mod.rs
similarity index 65%
rename from rust/tw_bitcoin/src/modules/tx_builder/mod.rs
rename to rust/chains/tw_bitcoin/src/modules/tx_builder/mod.rs
index c816d0f01cf..37c99c33e53 100644
--- a/rust/tw_bitcoin/src/modules/tx_builder/mod.rs
+++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/mod.rs
@@ -4,9 +4,12 @@
pub mod output_protobuf;
pub mod public_keys;
+pub mod script_parser;
pub mod utxo_protobuf;
pub struct BitcoinChainInfo {
pub p2pkh_prefix: u8,
pub p2sh_prefix: u8,
+ /// Note that not all Bitcoin forks support HRP (segwit addresses).
+ pub hrp: Option,
}
diff --git a/rust/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs
similarity index 100%
rename from rust/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs
rename to rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs
diff --git a/rust/tw_bitcoin/src/modules/tx_builder/public_keys.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/public_keys.rs
similarity index 83%
rename from rust/tw_bitcoin/src/modules/tx_builder/public_keys.rs
rename to rust/chains/tw_bitcoin/src/modules/tx_builder/public_keys.rs
index 2ff811bd679..6c822c686b6 100644
--- a/rust/tw_bitcoin/src/modules/tx_builder/public_keys.rs
+++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/public_keys.rs
@@ -41,4 +41,14 @@ impl PublicKeys {
.or_tw_err(SigningErrorType::Error_missing_private_key)
.with_context(|| format!("Missing either a private or public key corresponding to the pubkey hash: {pubkey_hash}"))
}
+
+ pub fn get_ecdsa_public_key(
+ &self,
+ pubkey_hash: &H160,
+ ) -> SigningResult {
+ let pubkey_data = self.get_public_key(pubkey_hash)?;
+ ecdsa::secp256k1::PublicKey::try_from(pubkey_data)
+ .into_tw()
+ .context("Expected a valid ecdsa secp256k1 public key")
+ }
}
diff --git a/rust/chains/tw_bitcoin/src/modules/tx_builder/script_parser.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/script_parser.rs
new file mode 100644
index 00000000000..26d8a70e009
--- /dev/null
+++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/script_parser.rs
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: Apache-2.0
+//
+// Copyright © 2017 Trust Wallet.
+
+use crate::modules::tx_builder::BitcoinChainInfo;
+use tw_coin_entry::error::prelude::*;
+use tw_hash::{H160, H256};
+use tw_keypair::{ecdsa, schnorr};
+use tw_memory::Data;
+use tw_utxo::address::legacy::LegacyAddress;
+use tw_utxo::address::segwit::SegwitAddress;
+use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress;
+use tw_utxo::address::taproot::TaprootAddress;
+use tw_utxo::script::standard_script::conditions;
+use tw_utxo::script::Script;
+
+pub enum StandardScript {
+ /// Compressed or uncompressed public key bytes.
+ P2PK(ecdsa::secp256k1::PublicKey),
+ /// Public key hash.
+ P2PKH(H160),
+ /// Script hash.
+ P2SH(H160),
+ /// Public key hash.
+ P2WPKH(H160),
+ /// Script hash.
+ P2WSH(H256),
+ /// Tweaked public key.
+ /// The public key can be tweaked as either key-path or script-path,
+ P2TR(schnorr::XOnlyPublicKey),
+ /// OP_RETURN payload.
+ OpReturn(Data),
+}
+
+impl StandardScript {
+ pub fn try_to_address(
+ &self,
+ chain_info: &BitcoinChainInfo,
+ ) -> AddressResult