Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-27 08:49:35 +00:00
commit 6d5d95e820
7 changed files with 148 additions and 5 deletions

View file

@ -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"),

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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) {

View file

@ -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

View file

@ -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<ByteArray> {
val hashesForSign: MutableList<ByteArray> = 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))
}
}