Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-10 17:47:05 +03:00
parent 0ee3346f20
commit e85071fc7f
10 changed files with 119 additions and 58 deletions

View file

@ -47,6 +47,9 @@ dependencies {
implementation 'org.bitcoinj:bitcoinj-core:0.15.2'
implementation 'com.github.stellar:java-stellar-sdk:0.13.0'
implementation "com.madgag.spongycastle:core:1.58.0.0"
implementation "com.madgag.spongycastle:prov:1.58.0.0"
ext.kethereum_version = '0.79.5'
implementation "com.github.walleth.kethereum:functions:$kethereum_version"
implementation "com.github.walleth.kethereum:keccak_shortcut:$kethereum_version"

View file

@ -33,18 +33,18 @@ class BitcoinAddressValidator {
if (testNet && firstLettersNonTestNet.contains(address.first())) return false
if (address.length !in 26..35) return false
val decoded = address.decodeBase58() ?: return false
val hash = sha256(decoded, 0, 21, 2)
val hash = recursiveSha256(decoded, 0, 21, 2)
return hash.sliceArray(0..3).contentEquals(decoded.sliceArray(21..24))
} else {
return validateSegwitAddress(address, testNet)
}
}
private fun sha256(data: ByteArray, start: Int, len: Int, recursion: Int): ByteArray {
private fun recursiveSha256(data: ByteArray, start: Int, len: Int, recursion: Int): ByteArray {
if (recursion == 0) return data
val md = MessageDigest.getInstance("SHA-256")
md.update(data.sliceArray(start until start + len))
return sha256(md.digest(), 0, 32, recursion - 1)
return recursiveSha256(md.digest(), 0, 32, recursion - 1)
}
private fun String.decodeBase58(): ByteArray? {

View file

@ -58,6 +58,18 @@ class BitcoinTransactionBuilder(private val testNet: Boolean) {
val signature = TransactionSignature(r, canonicalS)
return ScriptBuilder.createInputScript(signature, ECKey.fromPublicOnly(publicKey))
}
fun getEstimateSize(transactionData: TransactionData, walletPublicKey: ByteArray): Result<Int> {
val buildTransactionResult = buildToSign(transactionData)
when (buildTransactionResult) {
is Result.Failure -> return buildTransactionResult
is Result.Success -> {
val hashes = buildTransactionResult.data
val finalTransaction = buildToSend(ByteArray(64 * hashes.size) { 1 }, walletPublicKey)
return Result.Success(finalTransaction.size)
}
}
}
}
internal fun TransactionData.toBitcoinJTransaction(networkParameters: NetworkParameters?,

View file

@ -18,7 +18,6 @@ class BitcoinWalletManager(
walletConfig: WalletConfig,
isTestNet: Boolean = false
) : WalletManager,
TransactionEstimator,
TransactionSender,
FeeProvider {
@ -48,9 +47,9 @@ class BitcoinWalletManager(
null,
"unknown",
currencyWallet.address))
} else {
currencyWallet.pendingTransactions.clear()
}
} else {
currencyWallet.pendingTransactions.clear()
}
}
@ -58,18 +57,6 @@ class BitcoinWalletManager(
Log.e(this::class.java.simpleName, error?.message ?: "")
}
override suspend fun getEstimateSize(transactionData: TransactionData): Result<Int> {
val buildTransactionResult = transactionBuilder.buildToSign(transactionData)
when (buildTransactionResult) {
is Result.Failure -> return buildTransactionResult
is Result.Success -> {
val hashes = buildTransactionResult.data
val finalTransaction = transactionBuilder.buildToSend(ByteArray(64 * hashes.size) {1}, walletPublicKey)
return Result.Success(finalTransaction.size)
}
}
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val buildTransactionResult = transactionBuilder.buildToSign(transactionData)
when (buildTransactionResult) {
@ -90,10 +77,11 @@ class BitcoinWalletManager(
when (val result = networkManager.getFee()) {
is Result.Failure -> return result
is Result.Success -> {
val sizeResult = getEstimateSize(
val sizeResult = transactionBuilder.getEstimateSize(
TransactionData(amount,
Amount(1.toBigDecimal().divide(SATOSHI_IN_BTC), blockchain),
address, destination)
address, destination),
walletPublicKey
)
when (sizeResult) {
is Result.Failure -> return sizeResult

View file

@ -5,7 +5,6 @@ import co.nstant.`in`.cbor.CborDecoder
import co.nstant.`in`.cbor.CborEncoder
import co.nstant.`in`.cbor.model.Array
import co.nstant.`in`.cbor.model.ByteString
import co.nstant.`in`.cbor.model.DataItem
import co.nstant.`in`.cbor.model.UnsignedInteger
import com.tangem.blockchain.cardano.crypto.Blake2b
import com.tangem.blockchain.common.extensions.decodeBase58

View file

@ -9,6 +9,7 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.extensions.decodeBase58
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.math.BigDecimal
class CardanoTransactionBuilder() {
var unspentOutputs: List<UnspentOutput> = listOf()
@ -143,6 +144,21 @@ class CardanoTransactionBuilder() {
return fullAmount - (amount + fee)
}
fun getEstimateSize(transactionData: TransactionData, walletPublicKey: ByteArray): Int {
val dummyFeeValue = BigDecimal.valueOf(0.1)
val dummyFee = transactionData.amount.copy(value = dummyFeeValue)
val dummyAmount =
transactionData.amount.copy(value = transactionData.amount.value!! - dummyFeeValue)
val dummyTransactionData = transactionData.copy(
amount = dummyAmount,
fee = dummyFee
)
buildToSign(dummyTransactionData)
return buildToSend(ByteArray(64), walletPublicKey).size
}
companion object {
private const val PROTOCOL_MAGIC: Long = 764824073
}

View file

@ -17,7 +17,6 @@ class CardanoWalletManager(
private val walletPublicKey: ByteArray,
walletConfig: WalletConfig
) : WalletManager,
TransactionEstimator,
TransactionSender,
FeeProvider {
@ -47,21 +46,6 @@ class CardanoWalletManager(
Log.e(this::class.java.simpleName, error?.message ?: "")
}
override suspend fun getEstimateSize(transactionData: TransactionData): Int {
val dummyFeeValue = BigDecimal.valueOf(0.1)
val dummyFee = transactionData.amount.copy(value = dummyFeeValue)
val dummyAmount =
transactionData.amount.copy(value = transactionData.amount.value!! - dummyFeeValue)
val dummyTransactionData = transactionData.copy(
amount = dummyAmount,
fee = dummyFee
)
transactionBuilder.buildToSign(dummyTransactionData)
return transactionBuilder.buildToSend(ByteArray(64), walletPublicKey).size
}
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val transactionHash = transactionBuilder.buildToSign(transactionData)
@ -77,8 +61,9 @@ class CardanoWalletManager(
override suspend fun getFee(amount: Amount, source: String, destination: String): Result<List<Amount>> {
val a = 0.155381
val b = 0.000043946
val size = getEstimateSize(TransactionData(amount, null, source, destination))
val size = transactionBuilder.getEstimateSize(
TransactionData(amount, null, source, destination), walletPublicKey
)
val fee = (a + b * size).toBigDecimal()
return Result.Success(listOf(Amount(blockchain.currency, fee, source, blockchain.decimals)))
}

View file

@ -13,10 +13,6 @@ interface WalletManager {
suspend fun update()
}
interface TransactionEstimator {
suspend fun getEstimateSize(transactionData: TransactionData): Result<Int>
}
interface TransactionSender {
suspend fun send(transactionData: TransactionData, signer: TransactionSigner) : SimpleResult
}

View file

@ -14,35 +14,40 @@ object WalletManagerFactory {
val blockchainName: String = card.cardData?.blockchainName ?: return null
when {
blockchainName.contains("btc") || blockchainName.contains("bitcoin") -> {
blockchainName == Blockchain.Bitcoin.id -> {
return BitcoinWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, true)
)
}
blockchainName == Blockchain.BitcoinTestnet.id -> {
return BitcoinWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, true),
isTestNet = isTestNet(blockchainName))
isTestNet = true
)
}
blockchainName.contains("eth") -> {
val chain = if (isTestNet(blockchainName)) {
Chain.EthereumClassicTestnet
} else {
Chain.Mainnet
}
blockchainName == Blockchain.Ethereum.id -> {
val chain = Chain.Mainnet
return EthereumWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, true),
chain = chain)
chain = chain
)
}
blockchainName.contains("xlm") -> {
blockchainName == Blockchain.Stellar.id -> {
val token = getToken(card)
return StellarWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
walletConfig = WalletConfig(true, token == null),
token = token,
isTestNet = isTestNet(blockchainName))
token = token
)
}
blockchainName.contains("cardano") -> {
blockchainName == Blockchain.Cardano.id -> {
return CardanoWalletManager(
cardId = card.cardId,
walletPublicKey = walletPublicKey,
@ -53,8 +58,6 @@ object WalletManagerFactory {
}
}
private fun isTestNet(blockchainName: String) = blockchainName.contains("test")
private fun getToken(card: Card): Token? {
val symbol = card.cardData?.tokenSymbol ?: return null
val contractAddress = card.cardData?.tokenContractAddress ?: return null

View file

@ -0,0 +1,59 @@
package com.tangem.blockchain.common
import com.google.common.truth.Truth
import com.tangem.CardEnvironment
import com.tangem.blockchain.bitcoin.BitcoinWalletManager
import com.tangem.blockchain.cardano.CardanoWalletManager
import com.tangem.blockchain.ethereum.EthereumWalletManager
import com.tangem.blockchain.stellar.StellarWalletManager
import com.tangem.commands.ReadCommand
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.extensions.hexToBytes
import org.junit.Test
internal class WalletManagerFactoryTest {
@Test
fun createBitcoinWalletManager() {
val data = "0108bb00000000000304200754414e47454d00020102800a322e3432642053444b000341040876bdec26b89bd2159a668b9af3d9fe86370f318717c92b8d6c1186fb3648c32a5f9321998cc2d042901c91d40601e79a641e1cbcebe7a2358be6054e1b6e5d0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034254438640e17ceec48c5be36240c98019f95ad8b6e56acfebe60d11979c6279f715d607d76a860a137da8d109e805753f3f56b0130709f4bbf4cb9974b4c57b8469bf4b873041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b310008040000006407010009020bb8604104752a727e14bba5bd73b6714d72500f61ffd11026ad1196d2e1c54577cbeeac3d11fc68a64700f8d533f4e311964ea8fb3aa26c588295f2133868d69c3e62869362040000005c6304000000090f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)
.isInstanceOf(BitcoinWalletManager::class.java)
}
@Test
fun createEthereumWalletManager() {
val data = "0108bb00000000000536200754414e47454d00020102800a322e3432642053444b000341046c8aea0d5a850b0a608acf9a0c453c39ea86131e88bfa78800de3cfb5bf1007aeaa7b9ffc184212255758605c2461be343c0a661d73cabafa4c9c175b3f0e59a0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034554488640431b6244acfeac479becdff201a7f720a7d70a97edc4e019fb678596baf52dfe9d0e8faf08ceb4443b82d4e66815541f2dc8ec6dd3ff83eb42f06e5eab07f25f3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b3100080400000064070100090205dc60410464dddc3f356744aaecfa07427f9eb996ff537d65f20fb5be3abccf0354352a6b5f8a1942e0f8ddeea3a170eda78d060be8162ad60e94e4e91fbbdf0a7054785562040000005b6304000000090f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)
.isInstanceOf(EthereumWalletManager::class.java)
}
@Test
fun createStellarWalletManager() {
val data = "0108bb00000000000379200754414e47454d00020102800a322e3432642053444b0003410487d7bb51b189213e3cedc3fcfa3fc047b3b71b7805b5b215e14639b3a8ebb1952c9dd5ea4354441b6ada4e8b8327674bb102ddae69df55be69643a2c916edf650a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b008403584c4d86409a4bc2baf0e5836887da21167cf33458d5249d1a610bced0e31dc053f23729ed24d715912bf89e6804669430dfe396ed83274e0031f6803e2bdb8c041fa993413041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc6020e078212d58b2b9d0edc9c936830d10081cd38b90c31778c56dfb1171027e294e62040000003863040000002c0f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)
.isInstanceOf(StellarWalletManager::class.java)
}
@Test
fun createCardanoWalletManager() {
val data = "0108bb00000000000502200754414e47454d00020102800a322e3432642053444b0003410402c1e39257d60583489da2d67d35d1cc2a1c005cc05c1021f44838edcaf25d5615cad7c9d11c2e23f5efa93e50904d33c88808d0e169060508df840992e31f4d0a04041e76310c658102ffff8a0101820407e30b0d830b54414e47454d2053444b00840743415244414e4f8640f24ef5c8c6eba0ff97560d5b013edb4a452594270db9647bd0a3543df8104dec75731d4db3ebe0fc493f2afee00195e560b51e3c41189b7c61ba7895d6434b9d3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc60208a71161cfdf1e0a85d8e7ff372aa4a01136046292aceb5f9ad7ebdb98d3f60a86204000000646304000000000f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)
.isInstanceOf(CardanoWalletManager::class.java)
}
}