Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-11 13:27:59 +03:00
commit 903fc32bc9
51 changed files with 944 additions and 309 deletions

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

@ -15,35 +15,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,
@ -61,8 +66,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