From a1f78dca7f3dd2e5699019536865c6ea7471bda4 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Thu, 16 Jan 2025 12:29:24 +0100 Subject: [PATCH 01/21] [Zcash]: Add `google::protobuf::Any chain_specific` parameters to BitcoinV2 --- .../src/modules/planner/psbt_planner.rs | 1 + .../src/modules/protobuf_builder/mod.rs | 1 + rust/chains/tw_greenfield/src/public_key.rs | 4 +- .../src/transaction/message/transfer_out.rs | 4 +- .../src/ethermint_public_key.rs | 2 +- .../src/injective_public_key.rs | 2 +- rust/tw_cosmos_sdk/build.rs | 1 - .../modules/serializer/protobuf_serializer.rs | 68 +++++++++++-------- rust/tw_cosmos_sdk/src/modules/tx_builder.rs | 5 +- rust/tw_cosmos_sdk/src/public_key/mod.rs | 2 +- .../tw_cosmos_sdk/src/public_key/secp256k1.rs | 4 +- .../message/cosmos_auth_message.rs | 12 ++-- .../message/cosmos_bank_message.rs | 4 +- .../transaction/message/cosmos_gov_message.rs | 2 +- .../message/cosmos_staking_message.rs | 22 +++--- .../src/transaction/message/ibc_message.rs | 8 +-- .../src/transaction/message/mod.rs | 2 +- .../src/transaction/message/stride_message.rs | 14 ++-- .../transaction/message/terra_wasm_message.rs | 6 +- .../transaction/message/thorchain_message.rs | 16 ++--- .../src/transaction/message/wasm_message.rs | 6 +- rust/tw_cosmos_sdk/tests/sign.rs | 18 +++-- rust/tw_proto/build.rs | 21 ++++-- .../src/common/google/protobuf/any.proto | 2 +- .../src/common/google/protobuf/any.rs | 25 +++---- .../common/google/protobuf/timestamp.proto | 2 +- .../src/common/google/protobuf/timestamp.rs | 2 +- rust/tw_proto/src/lib.rs | 14 ++-- .../tests/chains/common/bitcoin/mod.rs | 1 + src/proto/BitcoinV2.proto | 8 +++ src/proto/Zcash.proto | 13 ++++ 31 files changed, 172 insertions(+), 120 deletions(-) create mode 100644 src/proto/Zcash.proto diff --git a/rust/chains/tw_bitcoin/src/modules/planner/psbt_planner.rs b/rust/chains/tw_bitcoin/src/modules/planner/psbt_planner.rs index 545862d1691..5818b199df9 100644 --- a/rust/chains/tw_bitcoin/src/modules/planner/psbt_planner.rs +++ b/rust/chains/tw_bitcoin/src/modules/planner/psbt_planner.rs @@ -68,6 +68,7 @@ impl PsbtPlanner { let out_point = Proto::OutPoint { hash: txin.previous_output.hash.to_vec().into(), vout: txin.previous_output.index, + ..Proto::OutPoint::default() }; let sequence = Proto::mod_Input::Sequence { sequence: txin.sequence, diff --git a/rust/chains/tw_bitcoin/src/modules/protobuf_builder/mod.rs b/rust/chains/tw_bitcoin/src/modules/protobuf_builder/mod.rs index dc25bc975e5..79f0a668df3 100644 --- a/rust/chains/tw_bitcoin/src/modules/protobuf_builder/mod.rs +++ b/rust/chains/tw_bitcoin/src/modules/protobuf_builder/mod.rs @@ -32,6 +32,7 @@ impl ProtobufBuilder { out_point: Some(Proto::OutPoint { hash: Cow::from(input.previous_output.hash.to_vec()), vout: input.previous_output.index, + ..Proto::OutPoint::default() }), sequence: input.sequence, script_sig: Self::script_data(&input.script_sig), diff --git a/rust/chains/tw_greenfield/src/public_key.rs b/rust/chains/tw_greenfield/src/public_key.rs index 41f4a3cced6..d2f7a1a78fd 100644 --- a/rust/chains/tw_greenfield/src/public_key.rs +++ b/rust/chains/tw_greenfield/src/public_key.rs @@ -20,9 +20,9 @@ impl JsonPublicKey for GreenfieldPublicKey { } impl ProtobufPublicKey for GreenfieldPublicKey { - fn to_proto(&self) -> google::protobuf::Any { + fn to_proto(&self) -> google::protobuf::Any<'static> { let proto = tw_cosmos_sdk::proto::cosmos::crypto::eth::ethsecp256k1::PubKey { - key: self.0.compressed().to_vec(), + key: self.0.compressed().to_vec().into(), }; to_any(&proto) } diff --git a/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs b/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs index 00a01116db5..e22b58598a9 100644 --- a/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs +++ b/rust/chains/tw_greenfield/src/transaction/message/transfer_out.rs @@ -65,8 +65,8 @@ pub struct GreenfieldTransferOut { impl CosmosMessage for GreenfieldTransferOut { fn to_proto(&self) -> SigningResult { let msg = GreenfieldProto::bridge::MsgTransferOut { - from: self.from.to_string(), - to: self.to.to_string(), + from: self.from.to_string().into(), + to: self.to.to_string().into(), amount: Some(build_coin(&self.amount)), }; Ok(to_any(&msg)) diff --git a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs index c2bf2d8f0ad..2a480be2701 100644 --- a/rust/chains/tw_native_evmos/src/ethermint_public_key.rs +++ b/rust/chains/tw_native_evmos/src/ethermint_public_key.rs @@ -53,7 +53,7 @@ impl JsonPublicKey for EthermintEthSecp256PublicKey { } impl ProtobufPublicKey for EthermintEthSecp256PublicKey { - fn to_proto(&self) -> google::protobuf::Any { + fn to_proto(&self) -> google::protobuf::Any<'static> { self.0.to_proto() } } diff --git a/rust/chains/tw_native_injective/src/injective_public_key.rs b/rust/chains/tw_native_injective/src/injective_public_key.rs index c405867f6e8..e65a1ddc20c 100644 --- a/rust/chains/tw_native_injective/src/injective_public_key.rs +++ b/rust/chains/tw_native_injective/src/injective_public_key.rs @@ -53,7 +53,7 @@ impl JsonPublicKey for InjectiveEthSecp256PublicKey { } impl ProtobufPublicKey for InjectiveEthSecp256PublicKey { - fn to_proto(&self) -> google::protobuf::Any { + fn to_proto(&self) -> google::protobuf::Any<'static> { self.0.to_proto() } } diff --git a/rust/tw_cosmos_sdk/build.rs b/rust/tw_cosmos_sdk/build.rs index 830293ac58a..4a5670aefbf 100644 --- a/rust/tw_cosmos_sdk/build.rs +++ b/rust/tw_cosmos_sdk/build.rs @@ -58,7 +58,6 @@ fn main() { ) .expect("Error configuring pb-rs builder") .gen_info(true) - .dont_use_cow(true) .build(); FileDescriptor::run(&out_protos).expect("Error generating proto files"); } diff --git a/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs b/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs index 91141c2e1f1..623132eb085 100644 --- a/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs +++ b/rust/tw_cosmos_sdk/src/modules/serializer/protobuf_serializer.rs @@ -10,15 +10,16 @@ use crate::public_key::ProtobufPublicKey; use crate::transaction::{ Coin, Fee, SignMode, SignedTransaction, SignerInfo, TxBody, UnsignedTransaction, }; +use std::borrow::Cow; use std::marker::PhantomData; use tw_coin_entry::error::prelude::*; use tw_memory::Data; use tw_proto::serialize; -pub fn build_coin(coin: &Coin) -> base_proto::Coin { +pub fn build_coin(coin: &Coin) -> base_proto::Coin<'static> { base_proto::Coin { - amount: coin.amount.to_string(), - denom: coin.denom.clone(), + amount: coin.amount.to_string().into(), + denom: coin.denom.clone().into(), } } @@ -37,26 +38,34 @@ pub struct SignDirectArgs { impl ProtobufSerializer { /// Serializes a signed transaction into the Cosmos [`tx_proto::TxRaw`] message. /// [`tx_proto::TxRaw`] can be broadcasted to the network. - pub fn build_signed_tx(signed: &SignedTransaction) -> SigningResult { + pub fn build_signed_tx( + signed: &SignedTransaction, + ) -> SigningResult> { let tx_body = Self::build_tx_body(&signed.tx_body)?; - let body_bytes = serialize(&tx_body).expect("Unexpected error on tx_body serialization"); + let body_bytes = serialize(&tx_body) + .expect("Unexpected error on tx_body serialization") + .into(); let auth_info = Self::build_auth_info(&signed.signer, &signed.fee); - let auth_info_bytes = - serialize(&auth_info).expect("Unexpected error on auth_info serialization"); + let auth_info_bytes = serialize(&auth_info) + .expect("Unexpected error on auth_info serialization") + .into(); Ok(tx_proto::TxRaw { body_bytes, auth_info_bytes, - signatures: vec![signed.signature.clone()], + signatures: vec![signed.signature.clone().into()], }) } - pub fn build_direct_signed_tx(args: &SignDirectArgs, signature: Data) -> tx_proto::TxRaw { + pub fn build_direct_signed_tx( + args: &SignDirectArgs, + signature: Data, + ) -> tx_proto::TxRaw<'static> { tx_proto::TxRaw { - body_bytes: args.tx_body.clone(), - auth_info_bytes: args.auth_info.clone(), - signatures: vec![signature], + body_bytes: args.tx_body.clone().into(), + auth_info_bytes: args.auth_info.clone().into(), + signatures: vec![signature.into()], } } @@ -64,27 +73,30 @@ impl ProtobufSerializer { /// [`tx_proto::SignDoc`] is used to generate a transaction prehash and sign it. pub fn build_sign_doc( unsigned: &UnsignedTransaction, - ) -> SigningResult { + ) -> SigningResult> { let tx_body = Self::build_tx_body(&unsigned.tx_body)?; - let body_bytes = serialize(&tx_body).expect("Unexpected error on tx_body serialization"); + let body_bytes = serialize(&tx_body) + .expect("Unexpected error on tx_body serialization") + .into(); let auth_info = Self::build_auth_info(&unsigned.signer, &unsigned.fee); - let auth_info_bytes = - serialize(&auth_info).expect("Unexpected error on auth_info serialization"); + let auth_info_bytes = serialize(&auth_info) + .expect("Unexpected error on auth_info serialization") + .into(); Ok(tx_proto::SignDoc { body_bytes, auth_info_bytes, - chain_id: unsigned.chain_id.clone(), + chain_id: unsigned.chain_id.clone().into(), account_number: unsigned.account_number, }) } - pub fn build_direct_sign_doc(args: &SignDirectArgs) -> tx_proto::SignDoc { + pub fn build_direct_sign_doc(args: &SignDirectArgs) -> tx_proto::SignDoc<'static> { tx_proto::SignDoc { - body_bytes: args.tx_body.clone(), - auth_info_bytes: args.auth_info.clone(), - chain_id: args.chain_id.clone(), + body_bytes: args.tx_body.clone().into(), + auth_info_bytes: args.auth_info.clone().into(), + chain_id: args.chain_id.clone().into(), account_number: args.account_number, } } @@ -92,7 +104,7 @@ impl ProtobufSerializer { pub fn build_auth_info( signer: &SignerInfo, fee: &Fee, - ) -> tx_proto::AuthInfo { + ) -> tx_proto::AuthInfo<'static> { tx_proto::AuthInfo { signer_infos: vec![Self::build_signer_info(signer)], fee: Some(Self::build_fee(fee)), @@ -101,7 +113,7 @@ impl ProtobufSerializer { } } - pub fn build_tx_body(tx_body: &TxBody) -> SigningResult { + pub fn build_tx_body(tx_body: &TxBody) -> SigningResult> { let messages: Vec<_> = tx_body .messages .iter() @@ -110,14 +122,14 @@ impl ProtobufSerializer { Ok(tx_proto::TxBody { messages, - memo: tx_body.memo.clone(), + memo: tx_body.memo.clone().into(), timeout_height: tx_body.timeout_height, extension_options: Vec::default(), non_critical_extension_options: Vec::default(), }) } - pub fn build_signer_info(signer: &SignerInfo) -> tx_proto::SignerInfo { + pub fn build_signer_info(signer: &SignerInfo) -> tx_proto::SignerInfo<'static> { use tx_proto::mod_ModeInfo::{self as mode_info, OneOfsum as SumEnum}; // Single is the mode info for a single signer. It is structured as a message @@ -135,13 +147,13 @@ impl ProtobufSerializer { } } - fn build_fee(fee: &Fee) -> tx_proto::Fee { + fn build_fee(fee: &Fee) -> tx_proto::Fee<'static> { tx_proto::Fee { amount: fee.amounts.iter().map(build_coin).collect(), gas_limit: fee.gas_limit, // Ignore `payer` and `granter` even if they set. - payer: String::default(), - granter: String::default(), + payer: Cow::default(), + granter: Cow::default(), } } diff --git a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs index 4f190f32272..5cd9f6740d4 100644 --- a/rust/tw_cosmos_sdk/src/modules/tx_builder.rs +++ b/rust/tw_cosmos_sdk/src/modules/tx_builder.rs @@ -632,10 +632,11 @@ where let grant_msg = match auth.grant_type { ProtoGrantType::grant_stake(ref stake) => google::protobuf::Any { - type_url: STAKE_AUTHORIZATION_MSG_TYPE.to_string(), + type_url: STAKE_AUTHORIZATION_MSG_TYPE.to_string().into(), value: serialize(stake) .tw_err(|_| SigningErrorType::Error_invalid_params) - .context("Error serializing Grant Stake Protobuf message")?, + .context("Error serializing Grant Stake Protobuf message")? + .into(), }, ProtoGrantType::None => { return SigningError::err(SigningErrorType::Error_invalid_params) diff --git a/rust/tw_cosmos_sdk/src/public_key/mod.rs b/rust/tw_cosmos_sdk/src/public_key/mod.rs index 746de80b584..578f202e94e 100644 --- a/rust/tw_cosmos_sdk/src/public_key/mod.rs +++ b/rust/tw_cosmos_sdk/src/public_key/mod.rs @@ -39,7 +39,7 @@ pub trait CosmosPublicKey: JsonPublicKey + ProtobufPublicKey + Sized { } pub trait ProtobufPublicKey { - fn to_proto(&self) -> google::protobuf::Any; + fn to_proto(&self) -> google::protobuf::Any<'static>; } pub trait JsonPublicKey { diff --git a/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs index 6b147ed4238..1bff6136c4e 100644 --- a/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs +++ b/rust/tw_cosmos_sdk/src/public_key/secp256k1.rs @@ -62,9 +62,9 @@ impl CosmosPublicKey for Secp256PublicKey { } impl ProtobufPublicKey for Secp256PublicKey { - fn to_proto(&self) -> google::protobuf::Any { + fn to_proto(&self) -> google::protobuf::Any<'static> { let proto = cosmos::crypto::secp256k1::PubKey { - key: self.public_key.clone(), + key: self.public_key.clone().into(), }; to_any_with_type_url(&proto, self.protobuf_type_url.clone()) } diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs index 32918948a7f..073f3499af9 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_auth_message.rs @@ -12,7 +12,7 @@ use tw_proto::{google, to_any}; pub struct AuthGrantMessage { pub granter: Address, pub grantee: Address, - pub grant_msg: google::protobuf::Any, + pub grant_msg: ProtobufMessage, pub expiration_secs: i64, } @@ -28,8 +28,8 @@ impl CosmosMessage for AuthGrantMessage
{ }; let proto_msg = cosmos::authz::v1beta1::MsgGrant { - granter: self.granter.to_string(), - grantee: self.grantee.to_string(), + granter: self.granter.to_string().into(), + grantee: self.grantee.to_string().into(), grant: Some(grant), }; Ok(to_any(&proto_msg)) @@ -46,9 +46,9 @@ pub struct AuthRevokeMessage { impl CosmosMessage for AuthRevokeMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = cosmos::authz::v1beta1::MsgRevoke { - granter: self.granter.to_string(), - grantee: self.grantee.to_string(), - msg_type_url: self.msg_type_url.clone(), + granter: self.granter.to_string().into(), + grantee: self.grantee.to_string().into(), + msg_type_url: self.msg_type_url.clone().into(), }; Ok(to_any(&proto_msg)) } diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs index 7b09ec52056..e6bac168271 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_bank_message.rs @@ -26,8 +26,8 @@ pub struct SendMessage { impl CosmosMessage for SendMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = cosmos::bank::v1beta1::MsgSend { - from_address: self.from_address.to_string(), - to_address: self.to_address.to_string(), + from_address: self.from_address.to_string().into(), + to_address: self.to_address.to_string().into(), amount: self.amount.iter().map(build_coin).collect(), }; Ok(to_any(&proto_msg)) diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs index 6c990c14fff..7e074f3c19f 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_gov_message.rs @@ -36,7 +36,7 @@ impl CosmosMessage for VoteMessage
{ let proto_msg = cosmos::gov::v1beta1::MsgVote { proposal_id: self.proposal_id, - voter: self.voter.to_string(), + voter: self.voter.to_string().into(), option, }; Ok(to_any(&proto_msg)) diff --git a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs index dc3c6238b9c..aab7e5b3c4f 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/cosmos_staking_message.rs @@ -31,8 +31,8 @@ impl CosmosMessage for DelegateMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = cosmos::staking::v1beta1::MsgDelegate { amount: Some(build_coin(&self.amount)), - delegator_address: self.delegator_address.to_string(), - validator_address: self.validator_address.to_string(), + delegator_address: self.delegator_address.to_string().into(), + validator_address: self.validator_address.to_string().into(), }; Ok(to_any(&proto_msg)) } @@ -60,8 +60,8 @@ impl CosmosMessage for UndelegateMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = cosmos::staking::v1beta1::MsgUndelegate { amount: Some(build_coin(&self.amount)), - delegator_address: self.delegator_address.to_string(), - validator_address: self.validator_address.to_string(), + delegator_address: self.delegator_address.to_string().into(), + validator_address: self.validator_address.to_string().into(), }; Ok(to_any(&proto_msg)) } @@ -90,9 +90,9 @@ impl CosmosMessage for BeginRedelegateMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = cosmos::staking::v1beta1::MsgBeginRedelegate { amount: Some(build_coin(&self.amount)), - delegator_address: self.delegator_address.to_string(), - validator_src_address: self.validator_src_address.to_string(), - validator_dst_address: self.validator_dst_address.to_string(), + delegator_address: self.delegator_address.to_string().into(), + validator_src_address: self.validator_src_address.to_string().into(), + validator_dst_address: self.validator_dst_address.to_string().into(), }; Ok(to_any(&proto_msg)) } @@ -118,8 +118,8 @@ pub struct WithdrawDelegationRewardMessage { impl CosmosMessage for WithdrawDelegationRewardMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward { - delegator_address: self.delegator_address.to_string(), - validator_address: self.validator_address.to_string(), + delegator_address: self.delegator_address.to_string().into(), + validator_address: self.validator_address.to_string().into(), }; Ok(to_any(&proto_msg)) } @@ -145,8 +145,8 @@ pub struct SetWithdrawAddressMessage { impl CosmosMessage for SetWithdrawAddressMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = cosmos::distribution::v1beta1::MsgSetWithdrawAddress { - delegator_address: self.delegator_address.to_string(), - withdraw_address: self.withdraw_address.to_string(), + delegator_address: self.delegator_address.to_string().into(), + withdraw_address: self.withdraw_address.to_string().into(), }; Ok(to_any(&proto_msg)) } diff --git a/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs index e3cb7ae29eb..54756cbe698 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/ibc_message.rs @@ -38,11 +38,11 @@ impl CosmosMessage for TransferTokensMessage
{ }; let proto_msg = ibc::applications::transfer::v1::MsgTransfer { - source_port: self.source_port.clone(), - source_channel: self.source_channel.clone(), + source_port: self.source_port.clone().into(), + source_channel: self.source_channel.clone().into(), token: Some(build_coin(&self.token)), - sender: self.sender.to_string(), - receiver: self.receiver.to_string(), + sender: self.sender.to_string().into(), + receiver: self.receiver.to_string().into(), timeout_height: Some(height), timeout_timestamp: self.timeout_timestamp, }; diff --git a/rust/tw_cosmos_sdk/src/transaction/message/mod.rs b/rust/tw_cosmos_sdk/src/transaction/message/mod.rs index 0ecc466e1fc..e5785909372 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/mod.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/mod.rs @@ -19,7 +19,7 @@ pub mod terra_wasm_message; pub mod thorchain_message; pub mod wasm_message; -pub type ProtobufMessage = google::protobuf::Any; +pub type ProtobufMessage = google::protobuf::Any<'static>; pub type CosmosMessageBox = Box; pub type JsonMessage = AnyMsg; diff --git a/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs index 1ef8e86eaec..b2556a43798 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/stride_message.rs @@ -18,9 +18,9 @@ pub struct StrideLiquidStakeMessage { impl CosmosMessage for StrideLiquidStakeMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = stride::stakeibc::MsgLiquidStake { - creator: self.creator.to_string(), - amount: self.amount.to_string(), - host_denom: self.host_denom.clone(), + creator: self.creator.to_string().into(), + amount: self.amount.to_string().into(), + host_denom: self.host_denom.clone().into(), }; Ok(to_any(&proto_msg)) } @@ -36,10 +36,10 @@ pub struct StrideLiquidRedeemMessage { impl CosmosMessage for StrideLiquidRedeemMessage { fn to_proto(&self) -> SigningResult { let proto_msg = stride::stakeibc::MsgRedeemStake { - creator: self.creator.clone(), - amount: self.amount.to_string(), - receiver: self.receiver.clone(), - host_zone: self.host_zone.clone(), + creator: self.creator.clone().into(), + amount: self.amount.to_string().into(), + receiver: self.receiver.clone().into(), + host_zone: self.host_zone.clone().into(), }; Ok(to_any(&proto_msg)) } diff --git a/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs index b74e6a01bd5..acce6442063 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/terra_wasm_message.rs @@ -28,9 +28,9 @@ pub struct TerraExecuteContractMessage { impl CosmosMessage for TerraExecuteContractMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = terra::wasm::v1beta1::MsgExecuteContract { - sender: self.sender.to_string(), - contract: self.contract.to_string(), - execute_msg: self.execute_msg.to_bytes(), + sender: self.sender.to_string().into(), + contract: self.contract.to_string().into(), + execute_msg: self.execute_msg.to_bytes().into(), coins: self.coins.iter().map(build_coin).collect(), }; Ok(to_any(&proto_msg)) diff --git a/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs index 6158c4f5815..18765c17587 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/thorchain_message.rs @@ -21,9 +21,9 @@ pub struct ThorchainAsset { impl ThorchainAsset { pub fn to_proto(&self) -> types::Asset { types::Asset { - chain: self.chain.clone(), - symbol: self.symbol.clone(), - ticker: self.ticker.clone(), + chain: self.chain.clone().into(), + symbol: self.symbol.clone().into(), + ticker: self.ticker.clone().into(), synth: self.synth, } } @@ -39,7 +39,7 @@ impl ThorchainCoin { pub fn to_proto(&self) -> types::Coin { types::Coin { asset: Some(self.asset.to_proto()), - amount: self.amount.to_string(), + amount: self.amount.to_string().into(), decimals: self.decimals, } } @@ -54,8 +54,8 @@ pub struct ThorchainSendMessage { impl CosmosMessage for ThorchainSendMessage { fn to_proto(&self) -> SigningResult { let proto_msg = types::MsgSend { - from_address: self.from_address.clone(), - to_address: self.to_address.clone(), + from_address: self.from_address.clone().into(), + to_address: self.to_address.clone().into(), amount: self.amount.iter().map(build_coin).collect(), }; Ok(to_any(&proto_msg)) @@ -72,8 +72,8 @@ impl CosmosMessage for ThorchainDepositMessage { fn to_proto(&self) -> SigningResult { let proto_msg = types::MsgDeposit { coins: self.coins.iter().map(ThorchainCoin::to_proto).collect(), - memo: self.memo.clone(), - signer: self.signer.clone(), + memo: self.memo.clone().into(), + signer: self.signer.clone().into(), }; Ok(to_any(&proto_msg)) } diff --git a/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs index 21163f691a3..11c80a2c8e2 100644 --- a/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs +++ b/rust/tw_cosmos_sdk/src/transaction/message/wasm_message.rs @@ -63,9 +63,9 @@ pub struct WasmExecuteContractMessage { impl CosmosMessage for WasmExecuteContractMessage
{ fn to_proto(&self) -> SigningResult { let proto_msg = cosmwasm::wasm::v1::MsgExecuteContract { - sender: self.sender.to_string(), - contract: self.contract.to_string(), - msg: self.msg.to_bytes(), + sender: self.sender.to_string().into(), + contract: self.contract.to_string().into(), + msg: self.msg.to_bytes().into(), funds: self.coins.iter().map(build_coin).collect(), }; Ok(to_any(&proto_msg)) diff --git a/rust/tw_cosmos_sdk/tests/sign.rs b/rust/tw_cosmos_sdk/tests/sign.rs index ae3c5595f14..4a23db87668 100644 --- a/rust/tw_cosmos_sdk/tests/sign.rs +++ b/rust/tw_cosmos_sdk/tests/sign.rs @@ -289,8 +289,6 @@ fn test_sign_direct() { /// and `AuthInfo` will be generated from `SigningInput` parameters. #[test] fn test_sign_direct_with_body_bytes() { - use tw_cosmos_sdk::proto::cosmos::tx::v1beta1 as tx_proto; - let coin = TestCoinContext::default() .with_public_key_type(PublicKeyType::Secp256k1) .with_hrp("cosmos"); @@ -302,7 +300,7 @@ fn test_sign_direct_with_body_bytes() { // Do not specify the `AuthInfo` bytes. auth_info_bytes: Cow::default(), }; - let mut input = Proto::SigningInput { + let input = Proto::SigningInput { account_number: 1037, chain_id: "gaia-13003".into(), fee: Some(make_fee(200000, make_amount("muon", "200"))), @@ -317,7 +315,7 @@ fn test_sign_direct_with_body_bytes() { // also similar TX: BCDAC36B605576C8182C2829C808B30A69CAD4959D5ED1E6FF9984ABF280D603 test_sign_protobuf::(TestInput { coin: &coin, - input: input.clone(), + input, tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CowBCokBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmkKLWNvc21vczFoc2s2anJ5eXFqZmhwNWRoYzU1dGM5anRja3lneDBlcGg2ZGQwMhItY29zbW9zMXp0NTBhenVwYW5xbGZhbTVhZmh2M2hleHd5dXRudWtlaDRjNTczGgkKBG11b24SATESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3FEgQKAggBGAgSEQoLCgRtdW9uEgMyMDAQwJoMGkD54fQAFlekIAnE62hZYl0uQelh/HLv0oQpCciY5Dn8H1SZFuTsrGdu41PH1Uxa4woptCELi/8Ov9yzdeEFAC9H"}"#, signature: "f9e1f4001657a42009c4eb6859625d2e41e961fc72efd2842909c898e439fc1f549916e4ecac676ee353c7d54c5ae30a29b4210b8bff0ebfdcb375e105002f47", signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"+eH0ABZXpCAJxOtoWWJdLkHpYfxy79KEKQnImOQ5/B9UmRbk7KxnbuNTx9VMWuMKKbQhC4v/Dr/cs3XhBQAvRw=="}]"#, @@ -389,12 +387,12 @@ fn test_sign_vote() { // Successfully broadcasted https://www.mintscan.io/cosmos/txs/2EFA054B842B1641B131137B13360F95164C6C1D51BB4A4AC6DE8F75F504AA4C test_sign_protobuf::(TestInput { - coin: &coin, - input: input.clone(), - tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClQKUgobL2Nvc21vcy5nb3YudjFiZXRhMS5Nc2dWb3RlEjMITRItY29zbW9zMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwN3Bzd3U0GAESZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhMKDQoFdWF0b20SBDI0MTgQkfsFGkA+Nb3NULc38quGC1x+8ZXry4w9mMX3IA7wUjFboTv7kVOwPlleIc8UqIsjVvKTUFnUuW8dlGQzNR1KkvbvZ1NA"}"#, - signature: "3e35bdcd50b737f2ab860b5c7ef195ebcb8c3d98c5f7200ef052315ba13bfb9153b03e595e21cf14a88b2356f2935059d4b96f1d946433351d4a92f6ef675340", - signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Asv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarl"},"signature":"PjW9zVC3N/KrhgtcfvGV68uMPZjF9yAO8FIxW6E7+5FTsD5ZXiHPFKiLI1byk1BZ1LlvHZRkMzUdSpL272dTQA=="}]"#, - }); + coin: &coin, + input: input.clone(), + tx: r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"ClQKUgobL2Nvc21vcy5nb3YudjFiZXRhMS5Nc2dWb3RlEjMITRItY29zbW9zMW1yeTQ3cGtnYTV0ZHN3dGx1eTBtOHRlc2xwYWxrZHEwN3Bzd3U0GAESZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAsv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarlEgQKAggBEhMKDQoFdWF0b20SBDI0MTgQkfsFGkA+Nb3NULc38quGC1x+8ZXry4w9mMX3IA7wUjFboTv7kVOwPlleIc8UqIsjVvKTUFnUuW8dlGQzNR1KkvbvZ1NA"}"#, + signature: "3e35bdcd50b737f2ab860b5c7ef195ebcb8c3d98c5f7200ef052315ba13bfb9153b03e595e21cf14a88b2356f2935059d4b96f1d946433351d4a92f6ef675340", + signature_json: r#"[{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Asv9teRyiTMiKU5gzwiD1D30MeEInSnstEep5tVQRarl"},"signature":"PjW9zVC3N/KrhgtcfvGV68uMPZjF9yAO8FIxW6E7+5FTsD5ZXiHPFKiLI1byk1BZ1LlvHZRkMzUdSpL272dTQA=="}]"#, + }); // `MsgVote` doesn't support JSON serialization and signing. test_sign_json_error::(TestErrorInput { diff --git a/rust/tw_proto/build.rs b/rust/tw_proto/build.rs index 83b73253681..db64fb9f778 100644 --- a/rust/tw_proto/build.rs +++ b/rust/tw_proto/build.rs @@ -13,8 +13,9 @@ fn main() { let proto_ext = Some(Path::new("proto").as_os_str()); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto"); + let cargo_manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let proto_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + let proto_dir = cargo_manifest_dir .join("..") .join("..") .join("src") @@ -46,9 +47,21 @@ fn main() { .create(&out_dir) .expect("Error creating out directory"); - let out_protos = ConfigBuilder::new(&protos, None, Some(&out_dir), &[proto_dir]) - .expect("Error configuring pb-rs builder") - .build(); + // `tw_proto/common_proto` contains google.protobuf proto files that are used in Cosmos protocol. + let common_proto_dir = cargo_manifest_dir + .join("src") + .join("common") + .canonicalize() + .expect("Cannot find common proto directory"); + + let out_protos = ConfigBuilder::new( + &protos, + None, + Some(&out_dir), + &[common_proto_dir, proto_dir], + ) + .expect("Error configuring pb-rs builder") + .build(); FileDescriptor::run(&out_protos).expect("Error generating proto files"); #[cfg(feature = "fuzz")] diff --git a/rust/tw_proto/src/common/google/protobuf/any.proto b/rust/tw_proto/src/common/google/protobuf/any.proto index c7aa5a0baa1..9771c5c2a26 100644 --- a/rust/tw_proto/src/common/google/protobuf/any.proto +++ b/rust/tw_proto/src/common/google/protobuf/any.proto @@ -32,7 +32,7 @@ // To recompile the file use the following command inside `wallet-core` directory: // ``` // cargo install pb-rs -// pb-rs --dont_use_cow --single-mod --output_directory rust/tw_proto/common_proto/google/protobuf/ rust/tw_proto/common_proto/google/protobuf/any.proto +// pb-rs --single-mod --output_directory rust/tw_proto/src/common/google/protobuf/ rust/tw_proto/src/common/google/protobuf/any.proto // ``` syntax = "proto3"; diff --git a/rust/tw_proto/src/common/google/protobuf/any.rs b/rust/tw_proto/src/common/google/protobuf/any.rs index 743b9b93b94..fbd3f90464b 100644 --- a/rust/tw_proto/src/common/google/protobuf/any.rs +++ b/rust/tw_proto/src/common/google/protobuf/any.rs @@ -9,24 +9,25 @@ #![cfg_attr(rustfmt, rustfmt_skip)] +use std::borrow::Cow; use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; use quick_protobuf::sizeofs::*; use super::*; #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct Any { - pub type_url: String, - pub value: Vec, +#[derive(Debug, arbitrary::Arbitrary, Default, PartialEq, Clone)] +pub struct Any<'a> { + pub type_url: Cow<'a, str>, + pub value: Cow<'a, [u8]>, } -impl<'a> MessageRead<'a> for Any { +impl<'a> MessageRead<'a> for Any<'a> { fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { let mut msg = Self::default(); while !r.is_eof() { match r.next_tag(bytes) { - Ok(10) => msg.type_url = r.read_string(bytes)?.to_owned(), - Ok(18) => msg.value = r.read_bytes(bytes)?.to_owned(), + Ok(10) => msg.type_url = r.read_string(bytes).map(Cow::Borrowed)?, + Ok(18) => msg.value = r.read_bytes(bytes).map(Cow::Borrowed)?, Ok(t) => { r.read_unknown(bytes, t)?; } Err(e) => return Err(e), } @@ -35,16 +36,16 @@ impl<'a> MessageRead<'a> for Any { } } -impl MessageWrite for Any { +impl<'a> MessageWrite for Any<'a> { fn get_size(&self) -> usize { 0 - + if self.type_url == String::default() { 0 } else { 1 + sizeof_len((&self.type_url).len()) } - + if self.value.is_empty() { 0 } else { 1 + sizeof_len((&self.value).len()) } + + if self.type_url == "" { 0 } else { 1 + sizeof_len((&self.type_url).len()) } + + if self.value == Cow::Borrowed(b"") { 0 } else { 1 + sizeof_len((&self.value).len()) } } fn write_message(&self, w: &mut Writer) -> Result<()> { - if self.type_url != String::default() { w.write_with_tag(10, |w| w.write_string(&**&self.type_url))?; } - if !self.value.is_empty() { w.write_with_tag(18, |w| w.write_bytes(&**&self.value))?; } + if self.type_url != "" { w.write_with_tag(10, |w| w.write_string(&**&self.type_url))?; } + if self.value != Cow::Borrowed(b"") { w.write_with_tag(18, |w| w.write_bytes(&**&self.value))?; } Ok(()) } } diff --git a/rust/tw_proto/src/common/google/protobuf/timestamp.proto b/rust/tw_proto/src/common/google/protobuf/timestamp.proto index 353b1632497..8b33782ea8b 100644 --- a/rust/tw_proto/src/common/google/protobuf/timestamp.proto +++ b/rust/tw_proto/src/common/google/protobuf/timestamp.proto @@ -32,7 +32,7 @@ // To recompile the file use the following command inside `wallet-core` directory: // ``` // cargo install pb-rs -// pb-rs --dont_use_cow --single-mod --output_directory rust/tw_proto/common_proto/google/protobuf/ rust/tw_proto/common_proto/google/protobuf/any.proto +// pb-rs --single-mod --output_directory rust/tw_proto/src/common/google/protobuf/ rust/tw_proto/src/common/google/protobuf/timestamp.proto // ``` syntax = "proto3"; diff --git a/rust/tw_proto/src/common/google/protobuf/timestamp.rs b/rust/tw_proto/src/common/google/protobuf/timestamp.rs index 2e073dbe982..a0e81e5aab3 100644 --- a/rust/tw_proto/src/common/google/protobuf/timestamp.rs +++ b/rust/tw_proto/src/common/google/protobuf/timestamp.rs @@ -14,7 +14,7 @@ use quick_protobuf::sizeofs::*; use super::*; #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] +#[derive(Debug, arbitrary::Arbitrary, Default, PartialEq, Clone)] pub struct Timestamp { pub seconds: i64, pub nanos: i32, diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs index aa0620c22bc..86a9b6a31f5 100644 --- a/rust/tw_proto/src/lib.rs +++ b/rust/tw_proto/src/lib.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. use quick_protobuf::{BytesReader, MessageInfo, Writer}; +use std::borrow::Cow; #[allow(non_snake_case)] #[rustfmt::skip] @@ -14,6 +15,8 @@ mod impls; #[allow(unused_variables)] #[rustfmt::skip] mod generated { + use crate::google; + include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); } @@ -42,20 +45,21 @@ pub fn deserialize<'a, T: MessageRead<'a>>(data: &'a [u8]) -> ProtoResult { T::from_reader(&mut reader, data) } -pub fn to_any(message: &T) -> google::protobuf::Any +pub fn to_any(message: &T) -> google::protobuf::Any<'static> where T: MessageInfo + MessageWrite, { - let value = serialize(message).expect("Protobuf serialization should never fail"); - let type_url = type_url::(); + let value = Cow::from(serialize(message).expect("Protobuf serialization should never fail")); + let type_url = Cow::from(type_url::()); google::protobuf::Any { type_url, value } } -pub fn to_any_with_type_url(message: &T, type_url: String) -> google::protobuf::Any +pub fn to_any_with_type_url(message: &T, type_url: String) -> google::protobuf::Any<'static> where T: MessageInfo + MessageWrite, { - let value = serialize(message).expect("Protobuf serialization should never fail"); + let type_url = Cow::from(type_url); + let value = Cow::from(serialize(message).expect("Protobuf serialization should never fail")); google::protobuf::Any { type_url, value } } diff --git a/rust/tw_tests/tests/chains/common/bitcoin/mod.rs b/rust/tw_tests/tests/chains/common/bitcoin/mod.rs index 8a3e5af260f..7c33bef9628 100644 --- a/rust/tw_tests/tests/chains/common/bitcoin/mod.rs +++ b/rust/tw_tests/tests/chains/common/bitcoin/mod.rs @@ -75,6 +75,7 @@ pub mod input { Some(Proto::OutPoint { hash: reverse_txid(txid).into(), vout, + ..Proto::OutPoint::default() }) } diff --git a/src/proto/BitcoinV2.proto b/src/proto/BitcoinV2.proto index 8f74dbb53c4..37b58c1c1fa 100644 --- a/src/proto/BitcoinV2.proto +++ b/src/proto/BitcoinV2.proto @@ -5,6 +5,7 @@ option java_package = "wallet.core.jni.proto"; import "BabylonStaking.proto"; import "Common.proto"; +import "google/protobuf/any.proto"; enum InputSelector { // Automatically select enough inputs in an ascending order to cover the outputs of the transaction. @@ -43,6 +44,10 @@ message OutPoint { // The position in the previous transactions output that this input references. uint32 vout = 2; + + // Chain specific arguments, like for Decred. + // Should be empty for Bitcoin chain. + google.protobuf.Any chain_specific = 3; } message Input { @@ -253,6 +258,9 @@ message TransactionBuilder { // If set, `SigningInput.outputs` and `SigningInput.change` will be ignored. // The `Output.value` will be overwritten, leave default. Output max_amount_output = 8; + // Chain specific arguments, like for ZCash. + // Should be empty for Bitcoin chain. + google.protobuf.Any chain_specific = 12; // One of the "Dust" amount policies. // Later, we plan to add support for `DynamicDust` policy with a `min_relay_fee` amount. oneof dust_policy { diff --git a/src/proto/Zcash.proto b/src/proto/Zcash.proto new file mode 100644 index 00000000000..c6fd076cf59 --- /dev/null +++ b/src/proto/Zcash.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package TW.Zcash.Proto; +option java_package = "wallet.core.jni.proto"; + +import "BitcoinV2.proto"; +import "Common.proto"; + +message TransactionBuilderExtraData { + // Currently, `branch_id` is the only configurable Zcash specific parameter. + // There can also be `version_group_id` configured in the future. + bytes branch_id = 1; +} From 87a6954bd4c3316fcedaaea21d6d80922e098611 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Fri, 17 Jan 2025 11:56:53 +0100 Subject: [PATCH 02/21] [BitcoinV2]: Add `TransactionBuilder::fee_policy` * Add `TransactionBuilder::zcash_extra_data` --- rust/chains/tw_bitcoin/src/context.rs | 4 ++ .../src/modules/signing_request/mod.rs | 20 ++++++-- rust/chains/tw_bitcoincash/src/context.rs | 4 ++ rust/frameworks/tw_utxo/src/context.rs | 4 ++ .../tw_utxo/src/fee/fee_estimator.rs | 46 +++++++++++++++++++ rust/frameworks/tw_utxo/src/fee/mod.rs | 14 ++++++ rust/frameworks/tw_utxo/src/lib.rs | 1 + .../tw_utxo/src/modules/fee_estimator.rs | 23 ---------- rust/frameworks/tw_utxo/src/modules/mod.rs | 1 - .../tw_utxo/src/modules/tx_planner.rs | 20 ++++---- .../modules/utxo_selector/exact_selector.rs | 7 ++- .../src/modules/utxo_selector/max_selector.rs | 7 ++- rust/frameworks/tw_utxo/tests/build_tx.rs | 7 ++- rust/tw_proto/src/lib.rs | 1 + src/proto/BitcoinV2.proto | 23 +++++----- src/proto/Zcash.proto | 7 +-- 16 files changed, 128 insertions(+), 61 deletions(-) create mode 100644 rust/frameworks/tw_utxo/src/fee/fee_estimator.rs create mode 100644 rust/frameworks/tw_utxo/src/fee/mod.rs delete mode 100644 rust/frameworks/tw_utxo/src/modules/fee_estimator.rs diff --git a/rust/chains/tw_bitcoin/src/context.rs b/rust/chains/tw_bitcoin/src/context.rs index 8ecdd4f8e55..43b50152d00 100644 --- a/rust/chains/tw_bitcoin/src/context.rs +++ b/rust/chains/tw_bitcoin/src/context.rs @@ -5,13 +5,17 @@ use tw_coin_entry::error::prelude::SigningResult; use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress; use tw_utxo::context::{AddressPrefixes, UtxoContext}; +use tw_utxo::fee::fee_estimator::StandardFeeEstimator; use tw_utxo::script::Script; +use tw_utxo::transaction::standard_transaction::Transaction; #[derive(Default)] pub struct StandardBitcoinContext; impl UtxoContext for StandardBitcoinContext { type Address = StandardBitcoinAddress; + type Transaction = Transaction; + type FeeEstimator = StandardFeeEstimator; fn addr_to_script_pubkey( addr: &Self::Address, diff --git a/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs index 8e62b8b6d2b..0677c7e2e2e 100644 --- a/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs +++ b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs @@ -2,6 +2,7 @@ // // Copyright © 2017 Trust Wallet. +use crate::context::StandardBitcoinContext; use crate::modules::tx_builder::output_protobuf::OutputProtobuf; use crate::modules::tx_builder::public_keys::PublicKeys; use crate::modules::tx_builder::utxo_protobuf::UtxoProtobuf; @@ -13,15 +14,18 @@ use tw_misc::traits::OptionalEmpty; use tw_proto::BitcoinV2::Proto; use tw_utxo::context::UtxoContext; use tw_utxo::dust::DustPolicy; +use tw_utxo::fee::fee_estimator::StandardFeeEstimator; +use tw_utxo::fee::FeePolicy; use tw_utxo::modules::tx_planner::{PlanRequest, RequestType}; use tw_utxo::modules::utxo_selector::InputSelector; use tw_utxo::transaction::standard_transaction::builder::TransactionBuilder; use tw_utxo::transaction::standard_transaction::Transaction; use Proto::mod_TransactionBuilder::OneOfdust_policy as ProtoDustPolicy; +use Proto::mod_TransactionBuilder::OneOffee_policy as ProtoFeePolicy; const DEFAULT_TX_VERSION: u32 = 1; -pub type StandardSigningRequest = PlanRequest; +pub type StandardSigningRequest = PlanRequest; pub struct SigningRequestBuilder { _phantom: PhantomData, @@ -35,7 +39,7 @@ impl SigningRequestBuilder { ) -> SigningResult { let chain_info = Self::chain_info(coin, &input.chain_info)?; let dust_policy = Self::dust_policy(&transaction_builder.dust_policy)?; - let fee_per_vbyte = transaction_builder.fee_per_vb; + let fee_estimator = Self::fee_estimator(&transaction_builder.fee_policy)?; let version = Self::transaction_version(&transaction_builder.version); let public_keys = Self::get_public_keys(input)?; @@ -68,7 +72,7 @@ impl SigningRequestBuilder { return Ok(StandardSigningRequest { ty: RequestType::SendMax { unsigned_tx }, dust_policy, - fee_per_vbyte, + fee_estimator, }); } @@ -101,7 +105,7 @@ impl SigningRequestBuilder { input_selector, }, dust_policy, - fee_per_vbyte, + fee_estimator, }) } @@ -138,6 +142,14 @@ impl SigningRequestBuilder { } } + fn fee_estimator(proto: &ProtoFeePolicy) -> SigningResult> { + let fee_policy = match proto { + ProtoFeePolicy::fee_per_vb(fee_per_vb) => FeePolicy::FeePerVb(*fee_per_vb), + ProtoFeePolicy::None => FeePolicy::FeePerVb(0), + }; + Ok(StandardFeeEstimator::new(fee_policy)) + } + fn transaction_version(proto: &Proto::TransactionVersion) -> u32 { match proto { Proto::TransactionVersion::UseDefault => DEFAULT_TX_VERSION, diff --git a/rust/chains/tw_bitcoincash/src/context.rs b/rust/chains/tw_bitcoincash/src/context.rs index 9190ff463e6..3ac37543482 100644 --- a/rust/chains/tw_bitcoincash/src/context.rs +++ b/rust/chains/tw_bitcoincash/src/context.rs @@ -5,13 +5,17 @@ use crate::address::Address; use tw_coin_entry::error::prelude::*; use tw_utxo::context::{AddressPrefixes, UtxoContext}; +use tw_utxo::fee::fee_estimator::StandardFeeEstimator; use tw_utxo::script::Script; +use tw_utxo::transaction::standard_transaction::Transaction; #[derive(Default)] pub struct BitcoinCashContext; impl UtxoContext for BitcoinCashContext { type Address = Address; + type Transaction = Transaction; + type FeeEstimator = StandardFeeEstimator; fn addr_to_script_pubkey( addr: &Self::Address, diff --git a/rust/frameworks/tw_utxo/src/context.rs b/rust/frameworks/tw_utxo/src/context.rs index 3b9cb66fb63..afb372c5011 100644 --- a/rust/frameworks/tw_utxo/src/context.rs +++ b/rust/frameworks/tw_utxo/src/context.rs @@ -2,7 +2,9 @@ // // Copyright © 2017 Trust Wallet. +use crate::fee::fee_estimator::FeeEstimator; use crate::script::Script; +use crate::transaction::transaction_interface::TransactionInterface; use std::str::FromStr; use tw_coin_entry::error::prelude::*; @@ -13,6 +15,8 @@ pub struct AddressPrefixes { pub trait UtxoContext { type Address: FromStr; + type Transaction: TransactionInterface; + type FeeEstimator: FeeEstimator; fn addr_to_script_pubkey( addr: &Self::Address, diff --git a/rust/frameworks/tw_utxo/src/fee/fee_estimator.rs b/rust/frameworks/tw_utxo/src/fee/fee_estimator.rs new file mode 100644 index 00000000000..3fc1191b25a --- /dev/null +++ b/rust/frameworks/tw_utxo/src/fee/fee_estimator.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::fee::FeePolicy; +use crate::transaction::transaction_interface::TransactionInterface; +use crate::transaction::transaction_parts::Amount; +use std::marker::PhantomData; +use tw_coin_entry::error::prelude::*; + +pub trait FeeEstimator { + fn estimate_fee(&self, tx: &Transaction) -> SigningResult; +} + +pub struct StandardFeeEstimator { + policy: FeePolicy, + _phantom: PhantomData, +} + +impl StandardFeeEstimator { + pub fn new(policy: FeePolicy) -> Self { + StandardFeeEstimator { + policy, + _phantom: PhantomData, + } + } +} + +impl FeeEstimator + for StandardFeeEstimator +{ + fn estimate_fee(&self, tx: &Transaction) -> SigningResult { + let fee_per_vbyte = match self.policy { + FeePolicy::FeePerVb(fee_per_vbyte) => fee_per_vbyte, + }; + + let vsize = tx.vsize(); + Amount::try_from(vsize) + .ok() + .and_then(|vsize| vsize.checked_mul(fee_per_vbyte)) + .or_tw_err(SigningErrorType::Error_wrong_fee) + .with_context(|| { + format!("feePerVByte is too large: '{vsize} * {fee_per_vbyte}' overflow") + }) + } +} diff --git a/rust/frameworks/tw_utxo/src/fee/mod.rs b/rust/frameworks/tw_utxo/src/fee/mod.rs new file mode 100644 index 00000000000..1512f6a79f4 --- /dev/null +++ b/rust/frameworks/tw_utxo/src/fee/mod.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::transaction_parts::Amount; + +pub mod fee_estimator; + +/// Standard fee policy. +pub enum FeePolicy { + // The amount of satoshis per vbyte ("satVb"), used for fee calculation. + // Can be satoshis per byte ("satB") **ONLY** when transaction does not contain segwit UTXOs. + FeePerVb(Amount), +} diff --git a/rust/frameworks/tw_utxo/src/lib.rs b/rust/frameworks/tw_utxo/src/lib.rs index 3e44914ac42..6dfa17d90e4 100644 --- a/rust/frameworks/tw_utxo/src/lib.rs +++ b/rust/frameworks/tw_utxo/src/lib.rs @@ -7,6 +7,7 @@ pub mod constants; pub mod context; pub mod dust; pub mod encode; +pub mod fee; pub mod modules; pub mod script; pub mod sighash; diff --git a/rust/frameworks/tw_utxo/src/modules/fee_estimator.rs b/rust/frameworks/tw_utxo/src/modules/fee_estimator.rs deleted file mode 100644 index f6fc0242ffb..00000000000 --- a/rust/frameworks/tw_utxo/src/modules/fee_estimator.rs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use crate::transaction::transaction_interface::TransactionInterface; -use crate::transaction::transaction_parts::Amount; -use std::marker::PhantomData; -use tw_coin_entry::error::prelude::*; - -pub struct FeeEstimator { - _phantom: PhantomData, -} - -impl FeeEstimator { - pub fn estimate_fee(tx: &Transaction, fee_rate: Amount) -> SigningResult { - let vsize = tx.vsize(); - Amount::try_from(vsize) - .ok() - .and_then(|vsize| vsize.checked_mul(fee_rate)) - .or_tw_err(SigningErrorType::Error_wrong_fee) - .with_context(|| format!("feePerVByte is too large: '{vsize} * {fee_rate}' overflow")) - } -} diff --git a/rust/frameworks/tw_utxo/src/modules/mod.rs b/rust/frameworks/tw_utxo/src/modules/mod.rs index 9d59f9124d1..f3c2690152f 100644 --- a/rust/frameworks/tw_utxo/src/modules/mod.rs +++ b/rust/frameworks/tw_utxo/src/modules/mod.rs @@ -2,7 +2,6 @@ // // Copyright © 2017 Trust Wallet. -pub mod fee_estimator; pub mod keys_manager; pub mod sighash_computer; pub mod sighash_verifier; diff --git a/rust/frameworks/tw_utxo/src/modules/tx_planner.rs b/rust/frameworks/tw_utxo/src/modules/tx_planner.rs index 25caa59f7a7..78c24d1acfd 100644 --- a/rust/frameworks/tw_utxo/src/modules/tx_planner.rs +++ b/rust/frameworks/tw_utxo/src/modules/tx_planner.rs @@ -2,13 +2,13 @@ // // Copyright © 2017 Trust Wallet. +use crate::context::UtxoContext; use crate::dust::dust_filter::DustFilter; use crate::dust::DustPolicy; use crate::modules::utxo_selector::exact_selector::ExactInputSelector; use crate::modules::utxo_selector::max_selector::MaxInputSelector; use crate::modules::utxo_selector::{InputSelector, SelectResult}; use crate::transaction::transaction_interface::TransactionInterface; -use crate::transaction::transaction_parts::Amount; use crate::transaction::unsigned_transaction::UnsignedTransaction; use std::marker::PhantomData; use tw_coin_entry::error::prelude::*; @@ -21,10 +21,10 @@ pub const MAX_TRANSACTION_SIZE: usize = 100 * 1024; /// # Important /// /// If needed to add chain specific parameters, consider adding a different Request struct. -pub struct PlanRequest { - pub ty: RequestType, +pub struct PlanRequest { + pub ty: RequestType, pub dust_policy: DustPolicy, - pub fee_per_vbyte: Amount, + pub fee_estimator: Context::FeeEstimator, } pub enum RequestType { @@ -47,14 +47,16 @@ pub struct TxPlanner { _phantom: PhantomData, } -impl TxPlanner +impl TxPlanner where - Transaction: TransactionInterface, + Context: UtxoContext, { /// * Filters dust UTXOs /// * Checks if all outputs are not dust /// * Select UTXOs as specified in the request - pub fn plan(request: PlanRequest) -> SigningResult> { + pub fn plan( + request: PlanRequest, + ) -> SigningResult> { let dust_filter = DustFilter::new(request.dust_policy); let select_result = match request.ty { @@ -64,7 +66,7 @@ where .context("Error filtering dust UTXOs")?; MaxInputSelector::new(unsigned_tx) - .select_max(request.fee_per_vbyte, request.dust_policy) + .select_max(request.dust_policy, &request.fee_estimator) }, RequestType::SendExact { unsigned_tx, @@ -77,7 +79,7 @@ where ExactInputSelector::new(unsigned_tx) .maybe_change_output(change_output) - .select_inputs(request.dust_policy, input_selector, request.fee_per_vbyte) + .select_inputs(request.dust_policy, input_selector, &request.fee_estimator) }, } .context("Error selecting UTXOs")?; diff --git a/rust/frameworks/tw_utxo/src/modules/utxo_selector/exact_selector.rs b/rust/frameworks/tw_utxo/src/modules/utxo_selector/exact_selector.rs index 9212b846bdd..cba28969949 100644 --- a/rust/frameworks/tw_utxo/src/modules/utxo_selector/exact_selector.rs +++ b/rust/frameworks/tw_utxo/src/modules/utxo_selector/exact_selector.rs @@ -4,13 +4,12 @@ use crate::constants::MAX_TRANSACTION_WEIGHT; use crate::dust::DustPolicy; -use crate::modules::fee_estimator::FeeEstimator; +use crate::fee::fee_estimator::FeeEstimator; use crate::modules::utxo_selector::{InputSelector, SelectPlan, SelectResult}; use crate::script::{Script, Witness}; use crate::transaction::transaction_interface::{ TransactionInterface, TxInputInterface, TxOutputInterface, }; -use crate::transaction::transaction_parts::Amount; use crate::transaction::unsigned_transaction::UnsignedTransaction; use crate::transaction::UtxoToSign; use tw_coin_entry::error::prelude::*; @@ -41,7 +40,7 @@ where mut self, dust_policy: DustPolicy, selector: InputSelector, - fee_rate: Amount, + fee_estimator: &dyn FeeEstimator, ) -> SigningResult> { let mut estimated_tx = self.unsigned_tx.estimate_transaction(); @@ -104,7 +103,7 @@ where } // Estimate the transaction fee. - tx_fee = FeeEstimator::estimate_fee(&estimated_tx, fee_rate)?; + tx_fee = fee_estimator.estimate_fee(&estimated_tx)?; // Check if the total input amount covers the total output amount // and the fee. diff --git a/rust/frameworks/tw_utxo/src/modules/utxo_selector/max_selector.rs b/rust/frameworks/tw_utxo/src/modules/utxo_selector/max_selector.rs index b1cea1e4a50..394eebb325b 100644 --- a/rust/frameworks/tw_utxo/src/modules/utxo_selector/max_selector.rs +++ b/rust/frameworks/tw_utxo/src/modules/utxo_selector/max_selector.rs @@ -4,13 +4,12 @@ use crate::constants::MAX_TRANSACTION_WEIGHT; use crate::dust::DustPolicy; -use crate::modules::fee_estimator::FeeEstimator; +use crate::fee::fee_estimator::FeeEstimator; use crate::modules::utxo_selector::{SelectPlan, SelectResult}; use crate::script::{Script, Witness}; use crate::transaction::transaction_interface::{ TransactionInterface, TxInputInterface, TxOutputInterface, }; -use crate::transaction::transaction_parts::Amount; use crate::transaction::unsigned_transaction::UnsignedTransaction; use crate::transaction::UtxoToSign; use itertools::Itertools; @@ -30,8 +29,8 @@ where pub fn select_max( mut self, - fee_rate: Amount, dust_policy: DustPolicy, + fee_estimator: &dyn FeeEstimator, ) -> SigningResult> { if self.unsigned_tx.transaction().outputs().len() != 1 { return SigningError::err(SigningErrorType::Error_invalid_params) @@ -88,7 +87,7 @@ where self.unsigned_tx .set_inputs(selected_utxos, selected_utxo_args)?; - let tx_fee = FeeEstimator::estimate_fee(&estimated_tx, fee_rate)?; + let tx_fee = fee_estimator.estimate_fee(&estimated_tx)?; let dust_threshold = dust_policy.dust_threshold(); // Check if the total input amount covers the fee, and the remaining amount is not dust. diff --git a/rust/frameworks/tw_utxo/tests/build_tx.rs b/rust/frameworks/tw_utxo/tests/build_tx.rs index 9e3f73272b9..a3b0a7682fd 100644 --- a/rust/frameworks/tw_utxo/tests/build_tx.rs +++ b/rust/frameworks/tw_utxo/tests/build_tx.rs @@ -4,7 +4,8 @@ use tw_keypair::ecdsa::secp256k1::PrivateKey; use tw_keypair::traits::SigningKeyTrait; use tw_keypair::{ecdsa, schnorr}; use tw_misc::traits::ToBytesVec; -use tw_utxo::modules::fee_estimator::FeeEstimator; +use tw_utxo::fee::fee_estimator::{FeeEstimator, StandardFeeEstimator}; +use tw_utxo::fee::FeePolicy; use tw_utxo::modules::sighash_computer::SighashComputer; use tw_utxo::modules::tx_compiler::TxCompiler; use tw_utxo::sighash::SighashType; @@ -23,7 +24,9 @@ fn verify_fee( fee_per_vbyte: i64, expected: i64, ) { - let actual = FeeEstimator::estimate_fee(tx, fee_per_vbyte).unwrap(); + let actual = StandardFeeEstimator::new(FeePolicy::FeePerVb(fee_per_vbyte)) + .estimate_fee(tx) + .unwrap(); assert_eq!(actual, expected); } diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs index 86a9b6a31f5..b35d8c9138d 100644 --- a/rust/tw_proto/src/lib.rs +++ b/rust/tw_proto/src/lib.rs @@ -11,6 +11,7 @@ mod common; mod impls; #[allow(non_snake_case)] +#[allow(unused_imports)] #[allow(unused_mut)] #[allow(unused_variables)] #[rustfmt::skip] diff --git a/src/proto/BitcoinV2.proto b/src/proto/BitcoinV2.proto index 37b58c1c1fa..64d2147211c 100644 --- a/src/proto/BitcoinV2.proto +++ b/src/proto/BitcoinV2.proto @@ -5,7 +5,7 @@ option java_package = "wallet.core.jni.proto"; import "BabylonStaking.proto"; import "Common.proto"; -import "google/protobuf/any.proto"; +import "Zcash.proto"; enum InputSelector { // Automatically select enough inputs in an ascending order to cover the outputs of the transaction. @@ -45,9 +45,9 @@ message OutPoint { // The position in the previous transactions output that this input references. uint32 vout = 2; - // Chain specific arguments, like for Decred. - // Should be empty for Bitcoin chain. - google.protobuf.Any chain_specific = 3; + // Serialized chain specific Protobuf message, e.g. `Verge.OutPointExtraData`. + // Should be empty for standard Bitcoin chains. + bytes chain_specific = 3; } message Input { @@ -246,9 +246,6 @@ message TransactionBuilder { repeated Output outputs = 4; // How the inputs should be selected. InputSelector input_selector = 5; - // The amount of satoshis per vbyte ("satVb"), used for fee calculation. - // Can be satoshis per byte ("satB") **ONLY** when transaction does not contain segwit UTXOs. - int64 fee_per_vb = 6; // (optional) The change output to be added (return to sender) at the end of the outputs list. // The `Output.value` will be overwritten, leave default. // Note there can be no change output if the change amount is less than dust threshold. @@ -258,15 +255,19 @@ message TransactionBuilder { // If set, `SigningInput.outputs` and `SigningInput.change` will be ignored. // The `Output.value` will be overwritten, leave default. Output max_amount_output = 8; - // Chain specific arguments, like for ZCash. - // Should be empty for Bitcoin chain. - google.protobuf.Any chain_specific = 12; // One of the "Dust" amount policies. // Later, we plan to add support for `DynamicDust` policy with a `min_relay_fee` amount. oneof dust_policy { // Use a constant "Dust" threshold. - int64 fixed_dust_threshold = 14; + int64 fixed_dust_threshold = 10; } + oneof fee_policy { + // The amount of satoshis per vbyte ("satVb"), used for fee calculation. + // Can be satoshis per byte ("satB") **ONLY** when transaction does not contain segwit UTXOs. + int64 fee_per_vb = 14; + } + // ZCash specific transaction data. + Zcash.Proto.TransactionBuilderExtraData zcash_extra_data = 20; } // Partially Signed Bitcoin Transaction. diff --git a/src/proto/Zcash.proto b/src/proto/Zcash.proto index c6fd076cf59..f196ee3037e 100644 --- a/src/proto/Zcash.proto +++ b/src/proto/Zcash.proto @@ -3,11 +3,12 @@ syntax = "proto3"; package TW.Zcash.Proto; option java_package = "wallet.core.jni.proto"; -import "BitcoinV2.proto"; -import "Common.proto"; - message TransactionBuilderExtraData { // Currently, `branch_id` is the only configurable Zcash specific parameter. // There can also be `version_group_id` configured in the future. bytes branch_id = 1; + + // Whether to calculate the fee according to ZIP-0317 for the given transaction + // https://zips.z.cash/zip-0317#fee-calculation + bool zip_0317 = 2; } From e40718ed5abb5c0854b44a9a1f1fb44d5033d182 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Fri, 17 Jan 2025 11:59:52 +0100 Subject: [PATCH 03/21] [BitcoinV2]: Fix compile errors --- rust/frameworks/tw_utxo/src/fee/fee_estimator.rs | 4 +--- rust/tw_proto/src/common/google/protobuf/any.rs | 3 ++- rust/tw_proto/src/common/google/protobuf/timestamp.rs | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/frameworks/tw_utxo/src/fee/fee_estimator.rs b/rust/frameworks/tw_utxo/src/fee/fee_estimator.rs index 3fc1191b25a..2f449b30a6e 100644 --- a/rust/frameworks/tw_utxo/src/fee/fee_estimator.rs +++ b/rust/frameworks/tw_utxo/src/fee/fee_estimator.rs @@ -30,9 +30,7 @@ impl FeeEstimator for StandardFeeEstimator { fn estimate_fee(&self, tx: &Transaction) -> SigningResult { - let fee_per_vbyte = match self.policy { - FeePolicy::FeePerVb(fee_per_vbyte) => fee_per_vbyte, - }; + let FeePolicy::FeePerVb(fee_per_vbyte) = self.policy; let vsize = tx.vsize(); Amount::try_from(vsize) diff --git a/rust/tw_proto/src/common/google/protobuf/any.rs b/rust/tw_proto/src/common/google/protobuf/any.rs index fbd3f90464b..42b9434ff13 100644 --- a/rust/tw_proto/src/common/google/protobuf/any.rs +++ b/rust/tw_proto/src/common/google/protobuf/any.rs @@ -15,7 +15,8 @@ use quick_protobuf::sizeofs::*; use super::*; #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, arbitrary::Arbitrary, Default, PartialEq, Clone)] +#[derive(Debug, Default, PartialEq, Clone)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Any<'a> { pub type_url: Cow<'a, str>, pub value: Cow<'a, [u8]>, diff --git a/rust/tw_proto/src/common/google/protobuf/timestamp.rs b/rust/tw_proto/src/common/google/protobuf/timestamp.rs index a0e81e5aab3..1dc1ff5ae0a 100644 --- a/rust/tw_proto/src/common/google/protobuf/timestamp.rs +++ b/rust/tw_proto/src/common/google/protobuf/timestamp.rs @@ -14,7 +14,8 @@ use quick_protobuf::sizeofs::*; use super::*; #[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, arbitrary::Arbitrary, Default, PartialEq, Clone)] +#[derive(Debug, Default, PartialEq, Clone)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Timestamp { pub seconds: i64, pub nanos: i32, From ac6bab6089dd0980f6187737752a89d08cd36b12 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Mon, 20 Jan 2025 11:53:10 +0100 Subject: [PATCH 04/21] [BitcoinV2]: Add `tw_zcash` blockchain skeleton * Add `ZcashTransaction`, `ZcashSighash` --- rust/Cargo.lock | 16 +- rust/Cargo.toml | 1 + rust/chains/tw_zcash/Cargo.toml | 13 ++ rust/chains/tw_zcash/src/compiler.rs | 50 +++++ rust/chains/tw_zcash/src/entry.rs | 90 +++++++++ rust/chains/tw_zcash/src/lib.rs | 9 + rust/chains/tw_zcash/src/modules/mod.rs | 6 + .../tw_zcash/src/modules/signing_request.rs | 5 + .../tw_zcash/src/modules/zcash_sighash.rs | 70 +++++++ rust/chains/tw_zcash/src/signer.rs | 27 +++ rust/chains/tw_zcash/src/transaction/mod.rs | 174 ++++++++++++++++++ .../transaction/standard_transaction/mod.rs | 8 +- rust/tw_coin_registry/Cargo.toml | 1 + rust/tw_coin_registry/src/blockchain_type.rs | 1 + rust/tw_coin_registry/src/dispatcher.rs | 3 + rust/tw_tests/tests/chains/mod.rs | 1 + rust/tw_tests/tests/chains/zcash/mod.rs | 7 + .../tests/chains/zcash/zcash_address.rs | 34 ++++ .../tests/chains/zcash/zcash_compile.rs | 8 + .../tw_tests/tests/chains/zcash/zcash_sign.rs | 8 + .../tests/coin_address_derivation_test.rs | 1 + 21 files changed, 528 insertions(+), 5 deletions(-) create mode 100644 rust/chains/tw_zcash/Cargo.toml create mode 100644 rust/chains/tw_zcash/src/compiler.rs create mode 100644 rust/chains/tw_zcash/src/entry.rs create mode 100644 rust/chains/tw_zcash/src/lib.rs create mode 100644 rust/chains/tw_zcash/src/modules/mod.rs create mode 100644 rust/chains/tw_zcash/src/modules/signing_request.rs create mode 100644 rust/chains/tw_zcash/src/modules/zcash_sighash.rs create mode 100644 rust/chains/tw_zcash/src/signer.rs create mode 100644 rust/chains/tw_zcash/src/transaction/mod.rs create mode 100644 rust/tw_tests/tests/chains/zcash/mod.rs create mode 100644 rust/tw_tests/tests/chains/zcash/zcash_address.rs create mode 100644 rust/tw_tests/tests/chains/zcash/zcash_compile.rs create mode 100644 rust/tw_tests/tests/chains/zcash/zcash_sign.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 3b6ce25bf99..c4c3a8f9bf0 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aead" @@ -1869,6 +1869,7 @@ dependencies = [ "tw_thorchain", "tw_ton", "tw_utxo", + "tw_zcash", ] [[package]] @@ -2245,6 +2246,19 @@ dependencies = [ "tw_proto", ] +[[package]] +name = "tw_zcash" +version = "0.1.0" +dependencies = [ + "tw_bitcoin", + "tw_coin_entry", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_proto", + "tw_utxo", +] + [[package]] name = "typenum" version = "1.16.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 692beec2692..dde4c906dab 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -16,6 +16,7 @@ members = [ "chains/tw_sui", "chains/tw_thorchain", "chains/tw_ton", + "chains/tw_zcash", "frameworks/tw_ton_sdk", "frameworks/tw_utxo", "tw_any_coin", diff --git a/rust/chains/tw_zcash/Cargo.toml b/rust/chains/tw_zcash/Cargo.toml new file mode 100644 index 00000000000..828484dbfa9 --- /dev/null +++ b/rust/chains/tw_zcash/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tw_zcash" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_bitcoin = { path = "../../chains/tw_bitcoin" } +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } +tw_utxo = { path = "../../frameworks/tw_utxo" } diff --git a/rust/chains/tw_zcash/src/compiler.rs b/rust/chains/tw_zcash/src/compiler.rs new file mode 100644 index 00000000000..36e6d7e06a4 --- /dev/null +++ b/rust/chains/tw_zcash/src/compiler.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::BitcoinV2::Proto as BitcoinV2Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct ZcashCompiler; + +impl ZcashCompiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: BitcoinV2Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + _coin: &dyn CoinContext, + _input: BitcoinV2Proto::SigningInput<'_>, + ) -> SigningResult> { + todo!() + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: BitcoinV2Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> BitcoinV2Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(BitcoinV2Proto::SigningOutput, e)) + } + + fn compile_impl( + _coin: &dyn CoinContext, + _input: BitcoinV2Proto::SigningInput<'_>, + _signatures: Vec, + _public_keys: Vec, + ) -> SigningResult> { + todo!() + } +} diff --git a/rust/chains/tw_zcash/src/entry.rs b/rust/chains/tw_zcash/src/entry.rs new file mode 100644 index 00000000000..410516b3aa9 --- /dev/null +++ b/rust/chains/tw_zcash/src/entry.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::compiler::ZcashCompiler; +use crate::signer::ZcashSigner; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::modules::transaction_decoder::NoTransactionDecoder; +use tw_coin_entry::modules::transaction_util::NoTransactionUtil; +use tw_coin_entry::modules::wallet_connector::NoWalletConnector; +use tw_keypair::tw::PublicKey; +use tw_proto::BitcoinV2::Proto as BitcoinV2Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_utxo::address::standard_bitcoin::{StandardBitcoinAddress, StandardBitcoinPrefix}; + +pub struct ZcashEntry; + +impl CoinEntry for ZcashEntry { + type AddressPrefix = StandardBitcoinPrefix; + type Address = StandardBitcoinAddress; + type SigningInput<'a> = BitcoinV2Proto::SigningInput<'a>; + type SigningOutput = BitcoinV2Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + type WalletConnector = NoWalletConnector; + type TransactionDecoder = NoTransactionDecoder; + type TransactionUtil = NoTransactionUtil; + + #[inline] + fn parse_address( + &self, + coin: &dyn CoinContext, + address: &str, + prefix: Option, + ) -> AddressResult { + StandardBitcoinAddress::from_str_with_coin_and_prefix(coin, address, prefix) + } + + #[inline] + fn parse_address_unchecked(&self, address: &str) -> AddressResult { + StandardBitcoinAddress::from_str(address) + } + + #[inline] + fn derive_address( + &self, + coin: &dyn CoinContext, + public_key: PublicKey, + derivation: Derivation, + prefix: Option, + ) -> AddressResult { + StandardBitcoinAddress::derive_as_tw(coin, &public_key, derivation, prefix) + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + ZcashSigner::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + ZcashCompiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + ZcashCompiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/rust/chains/tw_zcash/src/lib.rs b/rust/chains/tw_zcash/src/lib.rs new file mode 100644 index 00000000000..5fa6983c13d --- /dev/null +++ b/rust/chains/tw_zcash/src/lib.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod compiler; +pub mod entry; +pub mod modules; +pub mod signer; +mod transaction; diff --git a/rust/chains/tw_zcash/src/modules/mod.rs b/rust/chains/tw_zcash/src/modules/mod.rs new file mode 100644 index 00000000000..861285a338f --- /dev/null +++ b/rust/chains/tw_zcash/src/modules/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod signing_request; +pub mod zcash_sighash; diff --git a/rust/chains/tw_zcash/src/modules/signing_request.rs b/rust/chains/tw_zcash/src/modules/signing_request.rs new file mode 100644 index 00000000000..40b3f95f392 --- /dev/null +++ b/rust/chains/tw_zcash/src/modules/signing_request.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + + diff --git a/rust/chains/tw_zcash/src/modules/zcash_sighash.rs b/rust/chains/tw_zcash/src/modules/zcash_sighash.rs new file mode 100644 index 00000000000..774e34bfd8d --- /dev/null +++ b/rust/chains/tw_zcash/src/modules/zcash_sighash.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::transaction::ZcashTransaction; +use tw_coin_entry::error::prelude::*; +use tw_hash::H256; +use tw_utxo::encode::stream::Stream; +use tw_utxo::transaction::transaction_interface::TransactionInterface; +use tw_utxo::transaction::transaction_sighash::legacy_sighash::LegacySighash; +use tw_utxo::transaction::UtxoPreimageArgs; + +const DEFAULT_HASH: H256 = H256::new(); + +type ZcashLegacySighash = LegacySighash; + +/// Zcash sighash includes `JoinSplit`, `ShieldedSpends`, `ShieldedOutputs` hashes +/// that aren't included in the encoded transaction. +/// That's why we can't rely on [`LegacySighash`] fully, but implement a custom sighash computer. +pub struct ZcashSighash; + +impl ZcashSighash { + pub fn sighash_tx(tx: &ZcashTransaction, args: &UtxoPreimageArgs) -> SigningResult { + let utxo_to_hash = ZcashLegacySighash::inputs_for_preimage(tx, args)?; + let outputs_to_hash = ZcashLegacySighash::outputs_for_preimage(tx, args); + + let join_splits_hash = DEFAULT_HASH.as_slice(); + let shielded_spends = DEFAULT_HASH.as_slice(); + let shielded_outputs = DEFAULT_HASH.as_slice(); + + let mut stream = Stream::default(); + + stream.append(&tx.version); + stream.append(&tx.version_group_id); + + stream + .append_list(&utxo_to_hash) + .append_list(&outputs_to_hash) + .append_raw_slice(join_splits_hash) + .append_raw_slice(shielded_spends) + .append_raw_slice(shielded_outputs); + + stream + .append(&tx.locktime) + .append(&tx.expiry_height) + .append(&tx.sapling_value_balance) + // Append the sighash type. + .append(&args.sighash_ty.raw_sighash()); + + // The input being signed (replacing the scriptSig with scriptCode + amount) + // The prevout may already be contained in hashPrevout, and the nSequence + // may already be contain in hashSequence. + let utxo = tx + .transparent_inputs + .get(args.input_index) + .or_tw_err(|| SigningErrorType::Error_internal) + .context("Zcash sighash error: input_index is out of bounds")?; + + stream + .append(&utxo.previous_output) + .append(&args.script_pubkey) + .append(&args.amount) + .append(&utxo.sequence); + + let hash = args.tx_hasher.hash(&stream.out()); + H256::try_from(hash.as_slice()) + .tw_err(|_| SigningErrorType::Error_internal) + .context("Bitcoin sighash must be H256") + } +} diff --git a/rust/chains/tw_zcash/src/signer.rs b/rust/chains/tw_zcash/src/signer.rs new file mode 100644 index 00000000000..b7dc662333b --- /dev/null +++ b/rust/chains/tw_zcash/src/signer.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::BitcoinV2::Proto as BitcoinV2Proto; + +pub struct ZcashSigner; + +impl ZcashSigner { + pub fn sign( + coin: &dyn CoinContext, + input: BitcoinV2Proto::SigningInput<'_>, + ) -> BitcoinV2Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(BitcoinV2Proto::SigningOutput, e)) + } + + fn sign_impl( + _coin: &dyn CoinContext, + _input: BitcoinV2Proto::SigningInput<'_>, + ) -> SigningResult> { + todo!() + } +} diff --git a/rust/chains/tw_zcash/src/transaction/mod.rs b/rust/chains/tw_zcash/src/transaction/mod.rs new file mode 100644 index 00000000000..b31fb72b341 --- /dev/null +++ b/rust/chains/tw_zcash/src/transaction/mod.rs @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::zcash_sighash::ZcashSighash; +use tw_coin_entry::error::prelude::{ResultContext, SigningError, SigningErrorType, SigningResult}; +use tw_hash::H256; +use tw_utxo::encode::compact_integer::CompactInteger; +use tw_utxo::encode::stream::Stream; +use tw_utxo::encode::Encodable; +use tw_utxo::signing_mode::SigningMethod; +use tw_utxo::transaction::standard_transaction::{ + TransactionInput, TransactionOutput, SEGWIT_SCALE_FACTOR, +}; +use tw_utxo::transaction::transaction_interface::{TransactionInterface, TxInputInterface}; +use tw_utxo::transaction::transaction_parts::Amount; +use tw_utxo::transaction::transaction_sighash::fork_id_sighash::ForkIdSighash; +use tw_utxo::transaction::transaction_sighash::legacy_sighash::LegacySighash; +use tw_utxo::transaction::transaction_sighash::taproot1_sighash::Taproot1Sighash; +use tw_utxo::transaction::transaction_sighash::witness0_sighash::Witness0Sighash; +use tw_utxo::transaction::{TransactionPreimage, UtxoPreimageArgs, UtxoTaprootPreimageArgs}; + +const SEGWIT_NOT_SUPPORTED: bool = false; +const SAPLING_SPENDING_LEN: usize = 0; +const SAPLING_OUTPUTS_LEN: usize = 0; +const JOIN_SPLITS_LEN: usize = 0; + +/// Transparent ZCash transaction (transparent). +/// https://github.com/zcash/zips/blob/998a97f2a1e5686e0d5c57f399a08b4daf100f8e/zips/zip-0243.rst +/// https://github.com/zcash/zcash/blob/a3435336b0c561799ac6805a27993eca3f9656df/src/primitives/transaction.h#L454 +pub struct ZcashTransaction { + /// Transaction version. + /// Currently, version 4 (0x80000004) is supported only. + pub version: i32, + // If transaction version is 4 (0x80000004), version group ID is 0x892F2085. + pub version_group_id: u32, + /// Unsigned transaction inputs. + pub transparent_inputs: Vec, + /// Transaction outputs. + pub transparent_outputs: Vec, + /// The block number or timestamp at which this transaction is unlocked. + /// + /// | Value | Description + /// |----------------|------------ + /// | 0 | Not locked + /// | < 500000000 | Block number at which this transaction is unlocked + /// | >= 500000000 | UNIX timestamp at which this transaction is unlocked + /// + /// If all inputs have final (`0xffffffff`) sequence numbers then `lockTime` is irrelevant. + /// Otherwise, the transaction may not be added to a block until after `lockTime`. + pub locktime: u32, + // Expiry height. + pub expiry_height: u32, + /// Sapling value balance for the transaction. + /// Always 0 for a transparent transaction. + pub sapling_value_balance: Amount, +} + +impl ZcashTransaction { + fn total_size(&self) -> usize { + let ins = &self.transparent_inputs; + let outs = &self.transparent_outputs; + + let mut s = self.version.encoded_size(); + s += self.version_group_id.encoded_size(); + + s += CompactInteger::from(ins.len()).encoded_size(); + s += ins.iter().map(|i| i.base_size()).sum::(); + + s += CompactInteger::from(outs.len()).encoded_size(); + s += outs.iter().map(|o| o.encoded_size()).sum::(); + + s += self.locktime.encoded_size(); + s += self.expiry_height.encoded_size(); + s += self.sapling_value_balance.encoded_size(); + + s += CompactInteger::from(SAPLING_SPENDING_LEN).encoded_size(); + s += CompactInteger::from(SAPLING_OUTPUTS_LEN).encoded_size(); + s + CompactInteger::from(JOIN_SPLITS_LEN).encoded_size() + } +} + +impl TransactionInterface for ZcashTransaction { + type Input = TransactionInput; + type Output = TransactionOutput; + + fn version(&self) -> i32 { + self.version + } + + fn inputs(&self) -> &[Self::Input] { + &self.transparent_inputs + } + + fn inputs_mut(&mut self) -> &mut [Self::Input] { + &mut self.transparent_inputs + } + + fn replace_inputs(&mut self, inputs: Vec) { + self.transparent_inputs = inputs; + } + + fn outputs(&self) -> &[Self::Output] { + &self.transparent_outputs + } + + fn outputs_mut(&mut self) -> &mut [Self::Output] { + &mut self.transparent_outputs + } + + fn replace_outputs(&mut self, outputs: Vec) { + self.transparent_outputs = outputs; + } + + fn push_output(&mut self, output: Self::Output) { + self.transparent_outputs.push(output); + } + + fn has_witness(&self) -> bool { + SEGWIT_NOT_SUPPORTED + } + + fn locktime(&self) -> u32 { + self.locktime + } + + fn vsize(&self) -> usize { + self.total_size() + } + + fn weight(&self) -> usize { + self.total_size() * SEGWIT_SCALE_FACTOR + } +} + +impl Encodable for ZcashTransaction { + fn encode(&self, stream: &mut Stream) { + stream.append(&self.version); + stream.append(&self.version_group_id); + + stream + .append_list(&self.transparent_inputs) + .append_list(&self.transparent_outputs); + + stream.append(&self.locktime); + stream.append(&self.expiry_height); + stream.append(&self.sapling_value_balance); + + CompactInteger::from(SAPLING_SPENDING_LEN).encode(stream); + CompactInteger::from(SAPLING_OUTPUTS_LEN).encode(stream); + CompactInteger::from(JOIN_SPLITS_LEN).encode(stream); + } + + fn encoded_size(&self) -> usize { + self.total_size() + } +} + +impl TransactionPreimage for ZcashTransaction { + fn preimage_tx(&self, args: &UtxoPreimageArgs) -> SigningResult { + match args.signing_method { + SigningMethod::Legacy => ZcashSighash::sighash_tx(self, args), + SigningMethod::Segwit | SigningMethod::Taproot => { + SigningError::err(SigningErrorType::Error_internal) + .context("ZCash transaction supports Legacy signing method only") + }, + } + } + + fn preimage_taproot_tx(&self, _tr: &UtxoTaprootPreimageArgs) -> SigningResult { + SigningError::err(SigningErrorType::Error_internal) + .context("ZCash transaction doesn't support 'TransactionPreimage::preimage_taproot_tx'") + } +} diff --git a/rust/frameworks/tw_utxo/src/transaction/standard_transaction/mod.rs b/rust/frameworks/tw_utxo/src/transaction/standard_transaction/mod.rs index 8151d6e0a25..ff52cd74b29 100644 --- a/rust/frameworks/tw_utxo/src/transaction/standard_transaction/mod.rs +++ b/rust/frameworks/tw_utxo/src/transaction/standard_transaction/mod.rs @@ -25,15 +25,15 @@ use super::UtxoTaprootPreimageArgs; pub mod builder; /// Must be zero. -const WITNESS_MARKER: u8 = 0; +pub const WITNESS_MARKER: u8 = 0; /// Must be nonzero. -const WITNESS_FLAG: u8 = 1; +pub const WITNESS_FLAG: u8 = 1; // Sizes of various transaction fields. -const WITNESS_FLAG_MARKER: usize = 2; +pub const WITNESS_FLAG_MARKER: usize = 2; // The Segwit scale factor (witnesses are deducted). -const SEGWIT_SCALE_FACTOR: usize = 4; +pub const SEGWIT_SCALE_FACTOR: usize = 4; /// A standard Bitcoin transaction. /// diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index 7bff39d1ea0..2c00ce6bcba 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -32,6 +32,7 @@ tw_sui = { path = "../chains/tw_sui" } tw_thorchain = { path = "../chains/tw_thorchain" } tw_ton = { path = "../chains/tw_ton" } tw_utxo = { path = "../frameworks/tw_utxo" } +tw_zcash = { path = "../chains/tw_zcash" } [build-dependencies] itertools = "0.10.5" diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index cde37c58cde..271f226e8ce 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -25,6 +25,7 @@ pub enum BlockchainType { Sui, TheOpenNetwork, Thorchain, + Zcash, // end_of_blockchain_type - USED TO GENERATE CODE #[serde(other)] Unsupported, diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index d08784ccd56..07e4f950394 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -25,6 +25,7 @@ use tw_solana::entry::SolanaEntry; use tw_sui::entry::SuiEntry; use tw_thorchain::entry::ThorchainEntry; use tw_ton::entry::TheOpenNetworkEntry; +use tw_zcash::entry::ZcashEntry; pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; @@ -46,6 +47,7 @@ const SOLANA: SolanaEntry = SolanaEntry; const SUI: SuiEntry = SuiEntry; const THE_OPEN_NETWORK: TheOpenNetworkEntry = TheOpenNetworkEntry; const THORCHAIN: ThorchainEntry = ThorchainEntry; +const ZCASH: ZcashEntry = ZcashEntry; // end_of_blockchain_entries - USED TO GENERATE CODE pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { @@ -67,6 +69,7 @@ pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult Ok(&SUI), BlockchainType::TheOpenNetwork => Ok(&THE_OPEN_NETWORK), BlockchainType::Thorchain => Ok(&THORCHAIN), + BlockchainType::Zcash => Ok(&ZCASH), // end_of_blockchain_dispatcher - USED TO GENERATE CODE BlockchainType::Unsupported => Err(RegistryError::Unsupported), } diff --git a/rust/tw_tests/tests/chains/mod.rs b/rust/tw_tests/tests/chains/mod.rs index d048ec74a00..9b0d41bd702 100644 --- a/rust/tw_tests/tests/chains/mod.rs +++ b/rust/tw_tests/tests/chains/mod.rs @@ -21,4 +21,5 @@ mod sui; mod tbinance; mod thorchain; mod ton; +mod zcash; mod zetachain; diff --git a/rust/tw_tests/tests/chains/zcash/mod.rs b/rust/tw_tests/tests/chains/zcash/mod.rs new file mode 100644 index 00000000000..2bc4101d462 --- /dev/null +++ b/rust/tw_tests/tests/chains/zcash/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +mod zcash_address; +mod zcash_compile; +mod zcash_sign; diff --git a/rust/tw_tests/tests/chains/zcash/zcash_address.rs b/rust/tw_tests/tests/chains/zcash/zcash_address.rs new file mode 100644 index 00000000000..30bf0307321 --- /dev/null +++ b/rust/tw_tests/tests/chains/zcash/zcash_address.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_any_coin::test_utils::address_utils::{ + test_address_derive, test_address_get_data, test_address_invalid, test_address_normalization, + test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_zcash_address_derive() { + test_address_derive(CoinType::Zcash, "PRIVATE_KEY", "EXPECTED ADDRESS"); +} + +#[test] +fn test_zcash_address_normalization() { + test_address_normalization(CoinType::Zcash, "DENORMALIZED", "EXPECTED"); +} + +#[test] +fn test_zcash_address_is_valid() { + test_address_valid(CoinType::Zcash, "VALID ADDRESS"); +} + +#[test] +fn test_zcash_address_invalid() { + test_address_invalid(CoinType::Zcash, "INVALID ADDRESS"); +} + +#[test] +fn test_zcash_address_get_data() { + test_address_get_data(CoinType::Zcash, "ADDRESS", "HEX(DATA)"); +} diff --git a/rust/tw_tests/tests/chains/zcash/zcash_compile.rs b/rust/tw_tests/tests/chains/zcash/zcash_compile.rs new file mode 100644 index 00000000000..0ec75c3380f --- /dev/null +++ b/rust/tw_tests/tests/chains/zcash/zcash_compile.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#[test] +fn test_zcash_compile() { + todo!() +} diff --git a/rust/tw_tests/tests/chains/zcash/zcash_sign.rs b/rust/tw_tests/tests/chains/zcash/zcash_sign.rs new file mode 100644 index 00000000000..715a4c3296f --- /dev/null +++ b/rust/tw_tests/tests/chains/zcash/zcash_sign.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#[test] +fn test_zcash_sign() { + todo!() +} diff --git a/rust/tw_tests/tests/coin_address_derivation_test.rs b/rust/tw_tests/tests/coin_address_derivation_test.rs index 4f7e27b9375..3b4fc703503 100644 --- a/rust/tw_tests/tests/coin_address_derivation_test.rs +++ b/rust/tw_tests/tests/coin_address_derivation_test.rs @@ -154,6 +154,7 @@ fn test_coin_address_derivation() { CoinType::Sui => "0x01a5c6c1b74cec4fbd12b3e17252b83448136065afcdf24954dc3a9c26df4905", CoinType::TON => "UQCj3jAU_Ec2kXdAqweKt4rYjiwTNwiCfaUnIDHGh7wTwx_G", CoinType::Pactus => "pc1rk2qaaeu9pj3zwtvm49d3d4yqxzpp4te87cx0am", + CoinType::Zcash => todo!(), // end_of_coin_address_derivation_tests_marker_do_not_modify _ => panic!("{:?} must be covered", coin), }; From 729db11f37a76a0beb84ef078022efd2fd276fa4 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Mon, 20 Jan 2025 18:01:51 +0100 Subject: [PATCH 05/21] [BitcoinV2]: Add `ZcashSigningRequestBuilder` --- .../src/modules/signing_request/mod.rs | 10 +- rust/chains/tw_zcash/src/context.rs | 26 +++ rust/chains/tw_zcash/src/lib.rs | 3 +- rust/chains/tw_zcash/src/modules/mod.rs | 2 + .../tw_zcash/src/modules/signing_request.rs | 154 ++++++++++++++++++ .../src/modules/transaction_builder.rs | 71 ++++++++ .../src/modules/zcash_fee_estimator.rs | 32 ++++ .../tw_zcash/src/modules/zcash_sighash.rs | 22 ++- rust/chains/tw_zcash/src/transaction/mod.rs | 19 ++- rust/tw_hash/src/hash_array.rs | 5 + 10 files changed, 329 insertions(+), 15 deletions(-) create mode 100644 rust/chains/tw_zcash/src/context.rs create mode 100644 rust/chains/tw_zcash/src/modules/transaction_builder.rs create mode 100644 rust/chains/tw_zcash/src/modules/zcash_fee_estimator.rs diff --git a/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs index 0677c7e2e2e..b63ed216cb0 100644 --- a/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs +++ b/rust/chains/tw_bitcoin/src/modules/signing_request/mod.rs @@ -125,7 +125,7 @@ impl SigningRequestBuilder { Ok(public_keys) } - fn input_selector(selector: &Proto::InputSelector) -> InputSelector { + pub fn input_selector(selector: &Proto::InputSelector) -> InputSelector { match selector { Proto::InputSelector::SelectAscending => InputSelector::Ascending, Proto::InputSelector::SelectInOrder => InputSelector::InOrder, @@ -134,7 +134,7 @@ impl SigningRequestBuilder { } } - fn dust_policy(proto: &ProtoDustPolicy) -> SigningResult { + pub fn dust_policy(proto: &ProtoDustPolicy) -> SigningResult { match proto { ProtoDustPolicy::fixed_dust_threshold(fixed) => Ok(DustPolicy::FixedAmount(*fixed)), ProtoDustPolicy::None => SigningError::err(SigningErrorType::Error_invalid_params) @@ -142,7 +142,9 @@ impl SigningRequestBuilder { } } - fn fee_estimator(proto: &ProtoFeePolicy) -> SigningResult> { + pub fn fee_estimator( + proto: &ProtoFeePolicy, + ) -> SigningResult> { let fee_policy = match proto { ProtoFeePolicy::fee_per_vb(fee_per_vb) => FeePolicy::FeePerVb(*fee_per_vb), ProtoFeePolicy::None => FeePolicy::FeePerVb(0), @@ -150,7 +152,7 @@ impl SigningRequestBuilder { Ok(StandardFeeEstimator::new(fee_policy)) } - fn transaction_version(proto: &Proto::TransactionVersion) -> u32 { + pub fn transaction_version(proto: &Proto::TransactionVersion) -> u32 { match proto { Proto::TransactionVersion::UseDefault => DEFAULT_TX_VERSION, Proto::TransactionVersion::V1 => 1, diff --git a/rust/chains/tw_zcash/src/context.rs b/rust/chains/tw_zcash/src/context.rs new file mode 100644 index 00000000000..f7f4416c623 --- /dev/null +++ b/rust/chains/tw_zcash/src/context.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::modules::zcash_fee_estimator::ZcashFeeEstimator; +use crate::transaction::ZcashTransaction; +use tw_bitcoin::context::StandardBitcoinContext; +use tw_coin_entry::error::prelude::SigningResult; +use tw_utxo::address::standard_bitcoin::StandardBitcoinAddress; +use tw_utxo::context::{AddressPrefixes, UtxoContext}; +use tw_utxo::script::Script; + +pub struct ZcashContext; + +impl UtxoContext for ZcashContext { + type Address = StandardBitcoinAddress; + type Transaction = ZcashTransaction; + type FeeEstimator = ZcashFeeEstimator; + + fn addr_to_script_pubkey( + addr: &Self::Address, + prefixes: AddressPrefixes, + ) -> SigningResult