Updated on 2026-08-14
This commit is contained in:
parent
0ee3346f20
commit
e85071fc7f
10 changed files with 119 additions and 58 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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? {
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue