diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 3f3a033388..3a76e07b04 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -9,6 +9,7 @@ public enum Blockchain { Unknown("", "", 1.0, R.drawable.ic_logo_unknown, ""), Bitcoin("BTC", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"), BitcoinTestNet("BTC/test", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin_testnet, "Bitcoin Testnet"), + BitcoinDual("BTC/dual", "BTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Bitcoin"), Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"), EthereumId("ETH/ID", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum ID"), EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"), diff --git a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java index 8f0f0011fe..bccbfd5a1e 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiBlockcypher.java @@ -70,6 +70,7 @@ public class ServerApiBlockcypher { network = "test3"; } if (blockchainID.equals(Blockchain.Token.getID())) blockchain = "eth"; + if (blockchainID.equals(Blockchain.BitcoinDual.getID())) blockchain = "btc"; switch (method) { case BLOCKCYPHER_ADDRESS: diff --git a/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt b/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt index 76f14bc2bc..7c8ec384e4 100644 --- a/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/id/IdFragment.kt @@ -364,7 +364,7 @@ class IdFragment : BaseFragment(), NfcAdapter.ReaderCallback, updateViews() // Bitcoin, Litecoin, BitcoinCash, Stellar - if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || + if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.BitcoinDual || ctx.blockchain == Blockchain.Litecoin || ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet || ctx.blockchain == Blockchain.StellarAsset) { ctx.coinData.setIsBalanceEqual(true) diff --git a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt index 50597ac664..ca0505d7aa 100644 --- a/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt +++ b/app/src/main/java/com/tangem/ui/fragment/wallet/LoadedWalletFragment.kt @@ -707,7 +707,7 @@ class LoadedWalletFragment : BaseFragment(), NavigationResultListener, NfcAdapte updateViews() // Bitcoin, Litecoin, BitcoinCash, Stellar - if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || + if (ctx.blockchain == Blockchain.Bitcoin || ctx.blockchain == Blockchain.BitcoinTestNet || ctx.blockchain == Blockchain.BitcoinDual || ctx.blockchain == Blockchain.Litecoin || ctx.blockchain == Blockchain.BitcoinCash || ctx.blockchain == Blockchain.Stellar || ctx.blockchain == Blockchain.StellarTestNet || ctx.blockchain == Blockchain.StellarAsset) { ctx.coinData.setIsBalanceEqual(true) diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt index 3d93e06d50..1570ea6621 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt @@ -5,6 +5,7 @@ import com.tangem.data.Blockchain import com.tangem.wallet.bch.BtcCashEngine import com.tangem.wallet.binance.BinanceEngine import com.tangem.wallet.btc.BtcEngine +import com.tangem.wallet.btcmultisig.BtcMultisigEngine import com.tangem.wallet.cardano.CardanoData import com.tangem.wallet.cardano.CardanoEngine import com.tangem.wallet.ducatus.DucatusEngine @@ -56,6 +57,7 @@ object CoinEngineFactory { Blockchain.Eos -> EosEngine() Blockchain.Ducatus -> DucatusEngine() Blockchain.Tezos -> TezosEngine() + Blockchain.BitcoinDual -> BtcMultisigEngine() else -> null } } @@ -101,6 +103,8 @@ object CoinEngineFactory { DucatusEngine(context) else if (Blockchain.Tezos == context.blockchain) TezosEngine(context) + else if (Blockchain.BitcoinDual == context.blockchain) + BtcMultisigEngine(context) else return null } catch (e: Exception) { diff --git a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java index 4159d44d5b..4bfa331eda 100644 --- a/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/wallet/btc/BtcEngine.java @@ -181,7 +181,7 @@ public class BtcEngine extends CoinEngine { return false; } - if (ctx.getBlockchain() != Blockchain.BitcoinTestNet && ctx.getBlockchain() != Blockchain.Bitcoin) { + if (ctx.getBlockchain() != Blockchain.BitcoinTestNet && ctx.getBlockchain() != Blockchain.Bitcoin && ctx.getBlockchain() != Blockchain.BitcoinDual) { return false; } @@ -190,7 +190,7 @@ public class BtcEngine extends CoinEngine { } } else { try { - if (ctx.getBlockchain() == Blockchain.Bitcoin) { + if (ctx.getBlockchain() == Blockchain.Bitcoin || ctx.getBlockchain() == Blockchain.BitcoinDual) { SegwitAddress.fromBech32(new MainNetParams(), address); } else if (ctx.getBlockchain() == Blockchain.BitcoinTestNet) { SegwitAddress.fromBech32(new TestNet3Params(), address); @@ -213,7 +213,7 @@ public class BtcEngine extends CoinEngine { @Override public Uri getWalletExplorerUri() { - return Uri.parse((ctx.getBlockchain() == Blockchain.Bitcoin ? "https://www.blockchain.com/btc/address/" : "https://live.blockcypher.com/btc-testnet/address/") + ctx.getCoinData().getWallet()); + return Uri.parse((ctx.getBlockchain() == Blockchain.Bitcoin || ctx.getBlockchain() == Blockchain.BitcoinDual ? "https://www.blockchain.com/btc/address/" : "https://live.blockcypher.com/btc-testnet/address/") + ctx.getCoinData().getWallet()); } @Override diff --git a/app/src/main/java/com/tangem/wallet/btcmultisig/BtcMultisigEngine.kt b/app/src/main/java/com/tangem/wallet/btcmultisig/BtcMultisigEngine.kt new file mode 100644 index 0000000000..4f87b26d94 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/btcmultisig/BtcMultisigEngine.kt @@ -0,0 +1,137 @@ +package com.tangem.wallet.btcmultisig + +import com.google.common.primitives.UnsignedBytes +import com.tangem.tangem_card.data.TangemCard +import com.tangem.tangem_card.reader.CardProtocol.TangemException +import com.tangem.tangem_card.tasks.SignTask +import com.tangem.tangem_card.util.Util +import com.tangem.wallet.TangemContext +import com.tangem.wallet.btc.BtcEngine +import org.bitcoinj.core.* +import org.bitcoinj.crypto.TransactionSignature +import org.bitcoinj.params.MainNetParams +import org.bitcoinj.script.Script +import org.bitcoinj.script.ScriptBuilder +import java.math.BigInteger + +class BtcMultisigEngine : BtcEngine { + val networkParameters = MainNetParams.get() + + constructor(): super() + + constructor(context: TangemContext): super(context) + + override fun defineWallet() { + try { + val wallet = calculateAddress(ctx.card.walletPublicKeyRar) + ctx.coinData.wallet = wallet + } catch (e: Exception) { + ctx.coinData.wallet = "ERROR" + throw TangemException("Can't define wallet address") + } + } + + override fun calculateAddress(compressedPublicKey: ByteArray): String? { + if (ctx.card.issuerData != null && ctx.card.issuerData.size == 33) { + val script = createMultisigOutputScript() + val scriptHash = Utils.sha256hash160(script.program) + val address = LegacyAddress.fromScriptHash(networkParameters, scriptHash) + return address.toBase58() + } else { + return Util.byteArrayToHexString(compressedPublicKey) + } + } + + override fun constructTransaction(amountValue: Amount, feeValue: Amount, IncFee: Boolean, targetAddress: String): SignTask.TransactionToSign { + val fee = convertToInternalAmount(feeValue).longValueExact() + var amount = convertToInternalAmount(amountValue).longValueExact() + var change = coinData.balanceInInternalUnits.longValueExact() - amount + + if (IncFee) { + amount -= fee + } else { + change -= fee + } + + val transaction = Transaction(networkParameters) + + for (utxo in coinData.unspentTransactions) { + transaction.addInput( + Sha256Hash.wrap(utxo.txID), + utxo.outputN.toLong(), + Script(Util.hexToBytes(utxo.script)) + ) + } + + transaction.addOutput( + Coin.valueOf(amount), + Address.fromString(networkParameters, targetAddress) + ) + if (change != 0L) { + transaction.addOutput( + Coin.valueOf(change), + Address.fromString(networkParameters, coinData.wallet) + ) + } + + return object : SignTask.TransactionToSign { + override fun isSigningMethodSupported(signingMethod: TangemCard.SigningMethod): Boolean { + return signingMethod == TangemCard.SigningMethod.Sign_Hash + } + + override fun getHashesToSign(): Array { + val hashesForSign: MutableList = MutableList(transaction.inputs.size) { byteArrayOf() } + for (input in transaction.inputs) { + val index = input.index + val outputScript = createMultisigOutputScript() + hashesForSign[index] = transaction.hashForSignature(index, outputScript, Transaction.SigHash.ALL, false).bytes + } + return hashesForSign.toTypedArray() + } + + @Throws(java.lang.Exception::class) + override fun getRawDataToSign(): ByteArray { + throw java.lang.Exception("Signing of raw transaction not supported for " + this.javaClass.simpleName) + } + + override fun getHashAlgToSign(): String { + return "sha-256x2" + } + + @Throws(java.lang.Exception::class) + override fun getIssuerTransactionSignature(dataToSignByIssuer: ByteArray): ByteArray { + throw java.lang.Exception("Issuer validation not supported!") + } + + + override fun onSignCompleted(signFromCard: ByteArray): ByteArray { + for (index in transaction.inputs.indices) { + transaction.inputs[index].scriptSig = + createMultisigInputScript(index, signFromCard) + } + val txForSend = transaction.bitcoinSerialize() + notifyOnNeedSendTransaction(txForSend) + return txForSend + } + } + } + + private fun createMultisigInputScript(index: Int, signedTransaction: ByteArray): Script { + val r = BigInteger(1, signedTransaction.copyOfRange(index * 64, 32 + index * 64)) + val s = BigInteger(1, signedTransaction.copyOfRange(32 + index * 64, 64 + index * 64)) + val canonicalS = ECKey.ECDSASignature(r, s).toCanonicalised().s + val signature = TransactionSignature(r, canonicalS) + + val outputScript = createMultisigOutputScript() + return ScriptBuilder.createP2SHMultiSigInputScript(mutableListOf(signature), outputScript) + } + + private fun createMultisigOutputScript(): Script { + val publicKeys = mutableListOf(ctx.card.walletPublicKeyRar, ctx.card.issuerData) + publicKeys.sortWith(UnsignedBytes.lexicographicalComparator()) + val publicEcKeys = + MutableList(publicKeys.size) { i -> ECKey.fromPublicOnly(publicKeys[i]) } + + return Script(Script.createMultiSigOutputScript(1, publicEcKeys)) + } +} \ No newline at end of file