From cd6869bd2b65304f763f6965d40be3ade8027a44 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:41:22 +0700 Subject: [PATCH] [BCH]: Migrate BitcoinCash to BitcoinV2 (Rust) (#4049) * feat(bch): Add `BitcoinCash` blockchain type * feat(bch): Add support for `CashAddress` * TODO implement `Entry::parse_address_unchecked` * feat(bch): Finalize BitcoinCash `Address` * Add `UncheckedCashAddress` * TODO test `Address::from_str_unchecked` * feat(bch): Add `UtxoContext` to allow custom Address types * feat(bch): Add signing test with from/to Cash address * feat(bch): Add Compiling test * feat(bch): Fix `CoinEntry::normalize_address` to validate address network * feat(bch): Add Base58 address prefix to BitcoinCash * feat(bch): Add `ECash` Blockchain type * feat(bch): Add Android signing test * [CI] Trigger CI * feat(bch): Fix codegen-v2 and ECash CoinType test --- .../bitcoincash/TestBitcoinCashSigning.kt | 81 ++++++ .../rust/templates/blockchain_crate/entry.rs | 6 +- include/TrustWalletCore/TWBlockchain.h | 1 + registry.json | 4 +- rust/Cargo.lock | 16 ++ rust/Cargo.toml | 1 + rust/chains/tw_aptos/src/entry.rs | 6 +- rust/chains/tw_binance/src/entry.rs | 6 +- rust/chains/tw_bitcoin/src/context.rs | 28 ++ rust/chains/tw_bitcoin/src/entry.rs | 17 +- rust/chains/tw_bitcoin/src/lib.rs | 1 + .../chains/tw_bitcoin/src/modules/compiler.rs | 18 +- .../tw_bitcoin/src/modules/planner/mod.rs | 15 +- .../src/modules/planner/psbt_planner.rs | 12 +- .../src/modules/psbt_request/mod.rs | 15 +- rust/chains/tw_bitcoin/src/modules/signer.rs | 13 +- .../src/modules/signing_request/mod.rs | 16 +- .../tw_bitcoin/src/modules/tx_builder/mod.rs | 11 + .../src/modules/tx_builder/output_protobuf.rs | 79 ++---- .../src/modules/tx_builder/utxo_protobuf.rs | 95 ++----- rust/chains/tw_bitcoincash/Cargo.toml | 15 ++ rust/chains/tw_bitcoincash/src/address.rs | 89 +++++++ .../src/cash_address/cash_base32.rs | 78 ++++++ .../src/cash_address/checksum.rs | 66 +++++ .../tw_bitcoincash/src/cash_address/mod.rs | 249 ++++++++++++++++++ .../src/cash_address/unchecked.rs | 147 +++++++++++ rust/chains/tw_bitcoincash/src/context.rs | 30 +++ rust/chains/tw_bitcoincash/src/entry.rs | 95 +++++++ rust/chains/tw_bitcoincash/src/lib.rs | 8 + rust/chains/tw_cosmos/src/entry.rs | 6 +- rust/chains/tw_ethereum/src/entry.rs | 6 +- rust/chains/tw_greenfield/src/entry.rs | 6 +- rust/chains/tw_internet_computer/src/entry.rs | 6 +- rust/chains/tw_native_evmos/src/entry.rs | 6 +- rust/chains/tw_native_injective/src/entry.rs | 6 +- rust/chains/tw_ronin/src/entry.rs | 6 +- rust/chains/tw_solana/src/entry.rs | 6 +- rust/chains/tw_sui/src/entry.rs | 6 +- rust/chains/tw_thorchain/src/entry.rs | 6 +- rust/chains/tw_ton/src/entry.rs | 6 +- rust/frameworks/tw_utxo/src/address/legacy.rs | 18 +- rust/frameworks/tw_utxo/src/address/segwit.rs | 25 +- .../tw_utxo/src/address/standard_bitcoin.rs | 2 +- .../frameworks/tw_utxo/src/address/taproot.rs | 13 +- .../tw_utxo/src/address/witness_program.rs | 2 +- rust/frameworks/tw_utxo/src/context.rs | 21 ++ rust/frameworks/tw_utxo/src/lib.rs | 1 + rust/frameworks/tw_utxo/src/script/mod.rs | 6 + rust/tw_any_coin/src/any_address.rs | 12 +- rust/tw_coin_entry/src/coin_entry.rs | 6 +- rust/tw_coin_entry/src/coin_entry_ext.rs | 33 ++- rust/tw_coin_entry/src/prefix.rs | 11 + 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_encoding/src/bech32.rs | 2 +- .../chains/bitcoin/bitcoin_sign/p2pkh.rs | 78 ------ .../chains/bitcoincash/bitcoincash_address.rs | 153 +++++++++++ .../chains/bitcoincash/bitcoincash_compile.rs | 49 ++++ .../chains/bitcoincash/bitcoincash_sign.rs | 37 +++ rust/tw_tests/tests/chains/bitcoincash/mod.rs | 10 + .../tests/chains/bitcoincash/test_cases.rs | 65 +++++ .../tests/chains/ecash/ecash_address.rs | 64 +++++ .../tw_tests/tests/chains/ecash/ecash_sign.rs | 72 +++++ rust/tw_tests/tests/chains/ecash/mod.rs | 6 + rust/tw_tests/tests/chains/mod.rs | 2 + .../tests/coin_address_derivation_test.rs | 6 +- src/Bitcoin/Entry.h | 18 +- src/BitcoinCash/Entry.h | 21 ++ src/Coin.cpp | 3 + tests/chains/BitcoinCash/TWCoinTypeTests.cpp | 2 +- tests/chains/ECash/TWCoinTypeTests.cpp | 2 +- 72 files changed, 1659 insertions(+), 370 deletions(-) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoincash/TestBitcoinCashSigning.kt create mode 100644 rust/chains/tw_bitcoin/src/context.rs create mode 100644 rust/chains/tw_bitcoincash/Cargo.toml create mode 100644 rust/chains/tw_bitcoincash/src/address.rs create mode 100644 rust/chains/tw_bitcoincash/src/cash_address/cash_base32.rs create mode 100644 rust/chains/tw_bitcoincash/src/cash_address/checksum.rs create mode 100644 rust/chains/tw_bitcoincash/src/cash_address/mod.rs create mode 100644 rust/chains/tw_bitcoincash/src/cash_address/unchecked.rs create mode 100644 rust/chains/tw_bitcoincash/src/context.rs create mode 100644 rust/chains/tw_bitcoincash/src/entry.rs create mode 100644 rust/chains/tw_bitcoincash/src/lib.rs create mode 100644 rust/frameworks/tw_utxo/src/context.rs create mode 100644 rust/tw_tests/tests/chains/bitcoincash/bitcoincash_address.rs create mode 100644 rust/tw_tests/tests/chains/bitcoincash/bitcoincash_compile.rs create mode 100644 rust/tw_tests/tests/chains/bitcoincash/bitcoincash_sign.rs create mode 100644 rust/tw_tests/tests/chains/bitcoincash/mod.rs create mode 100644 rust/tw_tests/tests/chains/bitcoincash/test_cases.rs create mode 100644 rust/tw_tests/tests/chains/ecash/ecash_address.rs create mode 100644 rust/tw_tests/tests/chains/ecash/ecash_sign.rs create mode 100644 rust/tw_tests/tests/chains/ecash/mod.rs create mode 100644 src/BitcoinCash/Entry.h diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoincash/TestBitcoinCashSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoincash/TestBitcoinCashSigning.kt new file mode 100644 index 00000000000..2c826c0bd4f --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoincash/TestBitcoinCashSigning.kt @@ -0,0 +1,81 @@ +package com.trustwallet.core.app.blockchains.bitcoincash + +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.BitcoinSigHashType +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Bitcoin +import wallet.core.jni.proto.BitcoinV2 +import wallet.core.jni.proto.Common.SigningError + +class TestBitcoinCashSigning { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testSignV2P2PKH() { + val privateKey = "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384".toHexBytesInByteString() + + val utxo1 = BitcoinV2.Input.newBuilder().apply { + outPoint = BitcoinV2.OutPoint.newBuilder().apply { + hash = "e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05".toHexBytesInByteString() + vout = 2 + }.build() + value = 5151 + sighashType = BitcoinSigHashType.ALL.value() + BitcoinSigHashType.FORK.value() + receiverAddress = "bitcoincash:qzhlrcrcne07x94h99thved2pgzdtv8ccujjy73xya" + } + + val out1 = BitcoinV2.Output.newBuilder().apply { + value = 600 + // Legacy address + toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx" + } + val changeOutputExplicit = BitcoinV2.Output.newBuilder().apply { + value = 4325 + // Legacy address + toAddress = "qz0q3xmg38sr94rw8wg45vujah7kzma3cskxymnw06" + } + + val builder = BitcoinV2.TransactionBuilder.newBuilder() + .setVersion(BitcoinV2.TransactionVersion.V1) + .addInputs(utxo1) + .addOutputs(out1) + .addOutputs(changeOutputExplicit) + .setInputSelector(BitcoinV2.InputSelector.UseAll) + .setFixedDustThreshold(546) + .build() + + val input = BitcoinV2.SigningInput.newBuilder() + .addPrivateKeys(privateKey) + .setBuilder(builder) + .setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply { + p2PkhPrefix = 0 + p2ShPrefix = 5 + hrp = "bitcoincash" + }) + .build() + + val legacyInput = Bitcoin.SigningInput.newBuilder() + .setSigningV2(input) + // `CoinType` must be set to be handled correctly. + .setCoinType(CoinType.BITCOINCASH.value()) + .build() + + val outputLegacy = AnySigner.sign(legacyInput, CoinType.BITCOINCASH, Bitcoin.SigningOutput.parser()) + assertEquals(outputLegacy.error, SigningError.OK) + assert(outputLegacy.hasSigningResultV2()) + + val output = outputLegacy.signingResultV2 + assertEquals(output.error, SigningError.OK) + assertEquals( + Numeric.toHexString(output.encoded.toByteArray()), + "0x0100000001e28c2b955293159898e34c6840d99bf4d390e2ee1c6f606939f18ee1e2000d05020000006b483045022100b70d158b43cbcded60e6977e93f9a84966bc0cec6f2dfd1463d1223a90563f0d02207548d081069de570a494d0967ba388ff02641d91cadb060587ead95a98d4e3534121038eab72ec78e639d02758e7860cdec018b49498c307791f785aa3019622f4ea5bffffffff0258020000000000001976a914769bdff96a02f9135a1d19b749db6a78fe07dc9088ace5100000000000001976a9149e089b6889e032d46e3b915a3392edfd616fb1c488ac00000000" + ) + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs index 9b1194bbb68..e26b696fa59 100644 --- a/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs @@ -49,11 +49,7 @@ impl CoinEntry for {BLOCKCHAIN}Entry { } #[inline] - fn parse_address_unchecked( - &self, - _coin: &dyn CoinContext, - address: &str, - ) -> AddressResult { + fn parse_address_unchecked(&self, address: &str) -> AddressResult { {BLOCKCHAIN}Address::from_str(address) } diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index faf4c575c1b..84cac17e3b4 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -65,6 +65,7 @@ enum TWBlockchain { TWBlockchainInternetComputer = 52, TWBlockchainNativeEvmos = 53, // Cosmos TWBlockchainNativeInjective = 54, // Cosmos + TWBlockchainBitcoinCash = 55, }; TW_EXTERN_C_END diff --git a/registry.json b/registry.json index 1bb8123783a..da2c8ef4098 100644 --- a/registry.json +++ b/registry.json @@ -1453,7 +1453,7 @@ "coinId": 145, "symbol": "BCH", "decimals": 8, - "blockchain": "Bitcoin", + "blockchain": "BitcoinCash", "derivation": [ { "path": "m/44'/145'/0'/0/0", @@ -3667,7 +3667,7 @@ "coinId": 899, "symbol": "XEC", "decimals": 2, - "blockchain": "Bitcoin", + "blockchain": "BitcoinCash", "derivation": [ { "path": "m/44'/899'/0'/0/0", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 108bb84e1e0..dcb26f709ab 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1800,6 +1800,21 @@ dependencies = [ "tw_utxo", ] +[[package]] +name = "tw_bitcoincash" +version = "0.1.0" +dependencies = [ + "tw_bitcoin", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_misc", + "tw_proto", + "tw_utxo", +] + [[package]] name = "tw_coin_entry" version = "0.1.0" @@ -1829,6 +1844,7 @@ dependencies = [ "tw_aptos", "tw_binance", "tw_bitcoin", + "tw_bitcoincash", "tw_coin_entry", "tw_cosmos", "tw_ethereum", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ca41fb6099d..f6bd5522ea7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -3,6 +3,7 @@ members = [ "chains/tw_aptos", "chains/tw_binance", "chains/tw_bitcoin", + "chains/tw_bitcoincash", "chains/tw_cosmos", "chains/tw_ethereum", "chains/tw_greenfield", diff --git a/rust/chains/tw_aptos/src/entry.rs b/rust/chains/tw_aptos/src/entry.rs index f9a46c55049..e2ef01a6e2f 100644 --- a/rust/chains/tw_aptos/src/entry.rs +++ b/rust/chains/tw_aptos/src/entry.rs @@ -49,11 +49,7 @@ impl CoinEntry for AptosEntry { } #[inline] - fn parse_address_unchecked( - &self, - _coin: &dyn CoinContext, - address: &str, - ) -> AddressResult { + fn parse_address_unchecked(&self, address: &str) -> AddressResult { Address::from_str(address) } diff --git a/rust/chains/tw_binance/src/entry.rs b/rust/chains/tw_binance/src/entry.rs index 55930473d53..6552c0e6bd0 100644 --- a/rust/chains/tw_binance/src/entry.rs +++ b/rust/chains/tw_binance/src/entry.rs @@ -49,11 +49,7 @@ impl CoinEntry for BinanceEntry { } #[inline] - fn parse_address_unchecked( - &self, - _coin: &dyn CoinContext, - address: &str, - ) -> AddressResult { + fn parse_address_unchecked(&self, address: &str) -> AddressResult { BinanceAddress::from_str(address) } diff --git a/rust/chains/tw_bitcoin/src/context.rs b/rust/chains/tw_bitcoin/src/context.rs new file mode 100644 index 00000000000..8ecdd4f8e55 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/context.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +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; + +#[derive(Default)] +pub struct StandardBitcoinContext; + +impl UtxoContext for StandardBitcoinContext { + type Address = StandardBitcoinAddress; + + fn addr_to_script_pubkey( + addr: &Self::Address, + prefixes: AddressPrefixes, + ) -> SigningResult