Updated on 2026-08-14
This commit is contained in:
commit
85f4e2e2c6
12 changed files with 89 additions and 39 deletions
|
|
@ -76,8 +76,8 @@ dependencies {
|
|||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'com.tangem:blockchain:develop-38'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-47'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-47'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-70'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-70'
|
||||
|
||||
// WebView
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 415eb6e9ccfcdc3573391b869adeba433f790087
|
||||
Subproject commit 0cf3af21e6f441e6323a1ba127ee7518168797df
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.content.Context
|
||||
|
||||
fun Context.readFile(fileName: String): String =
|
||||
this.openFileInput(fileName).bufferedReader().readText()
|
||||
|
||||
fun Context.rewriteFile(content: String, fileName: String) {
|
||||
this.openFileOutput(fileName, Context.MODE_PRIVATE).use {
|
||||
it.write(content.toByteArray(), 0, content.length)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.readAssetAsString(fileName: String): String {
|
||||
return this.assets.open("$fileName.json").bufferedReader().readText()
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.google.firebase.remoteconfig.ktx.remoteConfig
|
|||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.readAssetAsString
|
||||
import com.tangem.tap.domain.configurable.Loader
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -22,8 +23,8 @@ class FeaturesLocalLoader(
|
|||
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
|
||||
val valuesAdapter: JsonAdapter<ConfigValueModel> = moshi.adapter(ConfigValueModel::class.java)
|
||||
|
||||
val jsonFeatures = readAssetAsString(Loader.featuresName)
|
||||
val jsonConfigValues = readAssetAsString(Loader.configValuesName)
|
||||
val jsonFeatures = context.readAssetAsString(Loader.featuresName)
|
||||
val jsonConfigValues = context.readAssetAsString(Loader.configValuesName)
|
||||
|
||||
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
|
||||
} catch (ex: Exception) {
|
||||
|
|
@ -32,10 +33,6 @@ class FeaturesLocalLoader(
|
|||
}
|
||||
onComplete(config)
|
||||
}
|
||||
|
||||
private fun readAssetAsString(fileName: String): String {
|
||||
return context.assets.open("$fileName.json").bufferedReader().readText()
|
||||
}
|
||||
}
|
||||
|
||||
class FeaturesRemoteLoader(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class CreateWalletAndRescanTask : CardSessionRunnable<Card> {
|
|||
val firmwareVersion = card.firmwareVersion
|
||||
|
||||
val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
|
||||
CreateWalletCommand(card.supportedCurves.first(), false)
|
||||
CreateWalletCommand(card.supportedCurves.first())
|
||||
} else {
|
||||
CreateWalletsTask()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class CreateWalletsTask(curves: List<EllipticCurve> = emptyList()) : CardSession
|
|||
callback: (result: CompletionResult<Card>) -> Unit
|
||||
) {
|
||||
|
||||
CreateWalletCommand(curve, false).run(session) { result ->
|
||||
CreateWalletCommand(curve).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
if (index == curves.lastIndex) {
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
|||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card = session.environment.card?.setWallets(emptyList())
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1, false)
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1)
|
||||
.run(session) { callback(it) }
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1, false)
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1)
|
||||
.run(session) { callback(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.domain.twins
|
|||
|
||||
import com.tangem.Message
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.KeyPair
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.tap.domain.extensions.getSingleWallet
|
|||
|
||||
class CreateSecondTwinWalletTask(
|
||||
private val firstPublicKey: String,
|
||||
private val issuerKeys: KeyPair,
|
||||
private val preparingMessage: Message,
|
||||
private val creatingWalletMessage: Message
|
||||
) : CardSessionRunnable<CreateWalletResponse> {
|
||||
|
|
@ -36,13 +38,13 @@ class CreateSecondTwinWalletTask(
|
|||
|
||||
private fun finishTask(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
session.setInitialMessage(creatingWalletMessage)
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1, false).run(session) { result ->
|
||||
CreateWalletCommand(EllipticCurve.Secp256k1).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
session.environment.card = session.environment.card?.updateWallet(result.data.wallet)
|
||||
|
||||
WriteProtectedIssuerDataTask(
|
||||
firstPublicKey.hexToBytes(), TwinCardsManager.issuerKeys
|
||||
firstPublicKey.hexToBytes(), issuerKeys
|
||||
).run(session) { writeResult ->
|
||||
when (writeResult) {
|
||||
is CompletionResult.Success -> callback(result)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.tap.domain.twins
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
|
|
@ -10,10 +13,12 @@ import com.tangem.common.extensions.hexToBytes
|
|||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.extensions.readAssetAsString
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.network.createMoshi
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
|
||||
class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
||||
class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse, context: Context) {
|
||||
|
||||
private val currentCardId: String = scanNoteResponse.card.cardId
|
||||
private val secondCardId: String? = TwinsHelper.getTwinsCardId(currentCardId)
|
||||
|
|
@ -21,9 +26,13 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
|||
private var currentCardPublicKey: String? = null
|
||||
private var secondCardPublicKey: String? = null
|
||||
|
||||
private val issuerKeyPair: KeyPair = getIssuerKeys(
|
||||
context, scanNoteResponse.card.issuer.publicKey.toHexString()
|
||||
)
|
||||
|
||||
suspend fun createFirstWallet(message: Message): SimpleResult {
|
||||
val response = tangemSdkManager.runTaskAsync(
|
||||
CreateFirstTwinWalletTask(), currentCardId, message
|
||||
CreateFirstTwinWalletTask(), currentCardId, message
|
||||
)
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
@ -46,13 +55,17 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
|||
|
||||
|
||||
suspend fun createSecondWallet(
|
||||
initialMessage: Message,
|
||||
preparingMessage: Message,
|
||||
creatingWalletMessage: Message
|
||||
initialMessage: Message,
|
||||
preparingMessage: Message,
|
||||
creatingWalletMessage: Message,
|
||||
): SimpleResult {
|
||||
val response = tangemSdkManager.runTaskAsync(
|
||||
CreateSecondTwinWalletTask(currentCardPublicKey!!, preparingMessage, creatingWalletMessage),
|
||||
secondCardId, initialMessage
|
||||
CreateSecondTwinWalletTask(
|
||||
firstPublicKey = currentCardPublicKey!!,
|
||||
issuerKeys = issuerKeyPair,
|
||||
preparingMessage = preparingMessage,
|
||||
creatingWalletMessage = creatingWalletMessage),
|
||||
secondCardId, initialMessage
|
||||
)
|
||||
when (response) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
@ -75,8 +88,8 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
|||
|
||||
suspend fun complete(message: Message): Result<ScanNoteResponse> {
|
||||
val response = tangemSdkManager.runTaskAsync(
|
||||
FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeys),
|
||||
currentCardId, message
|
||||
FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeyPair),
|
||||
currentCardId, message
|
||||
)
|
||||
return when (response) {
|
||||
is CompletionResult.Success -> Result.Success(response.data)
|
||||
|
|
@ -94,13 +107,6 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val issuerKeys = KeyPair(
|
||||
privateKey = "F9F4C50636C9E6FC65F92655BD5C21C85A5F6A34DCD0F1E75FCEA1980FE242F5".hexToBytes(),
|
||||
publicKey = ("048196AA4B410AC44A3B9CCE18E7BE226AEA070ACC83A9CF67540F" +
|
||||
"AC49AF25129F6A538A28AD6341358E3C4F9963064F" +
|
||||
"7E365372A651D374E5C23CDD37FD099BF2").hexToBytes()
|
||||
)
|
||||
|
||||
fun verifyTwinPublicKey(issuerData: ByteArray, cardWalletPublicKey: ByteArray?): Boolean {
|
||||
if (issuerData.size < 65) return false
|
||||
val publicKey = issuerData.sliceArray(0 until 65)
|
||||
|
|
@ -108,5 +114,30 @@ class TwinCardsManager(private val scanNoteResponse: ScanNoteResponse) {
|
|||
return (cardWalletPublicKey != null &&
|
||||
CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey))
|
||||
}
|
||||
|
||||
private fun getIssuerKeys(context: Context, publicKey: String): KeyPair {
|
||||
val issuer = getIssuers(context).first { it.publicKey == publicKey }
|
||||
return KeyPair(
|
||||
publicKey = issuer.publicKey.hexToBytes(),
|
||||
privateKey = issuer.privateKey.hexToBytes()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAdapter(): JsonAdapter<List<Issuer>> {
|
||||
return createMoshi().adapter(
|
||||
Types.newParameterizedType(List::class.java, Issuer::class.java)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getIssuers(context: Context): List<Issuer> {
|
||||
val file = context.readAssetAsString("tangem-app-config/issuers")
|
||||
return getAdapter().fromJson(file)!!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Issuer(
|
||||
val id: String,
|
||||
val privateKey: String,
|
||||
val publicKey: String,
|
||||
)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.card.Card
|
||||
|
|
@ -63,7 +64,9 @@ sealed class DetailsAction : Action {
|
|||
object Confirm : CreateTwinWalletAction()
|
||||
}
|
||||
|
||||
data class LaunchFirstStep(val message: Message) : CreateTwinWalletAction() {
|
||||
data class LaunchFirstStep(
|
||||
val message: Message, val context: Context
|
||||
) : CreateTwinWalletAction() {
|
||||
object Success : CreateTwinWalletAction()
|
||||
object Failure : CreateTwinWalletAction()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class CreateTwinWalletMiddleware {
|
|||
}
|
||||
is DetailsAction.CreateTwinWalletAction.LaunchFirstStep -> {
|
||||
store.state.globalState.scanNoteResponse?.let {
|
||||
twinsManager = TwinCardsManager(it)
|
||||
twinsManager = TwinCardsManager(it, action.context)
|
||||
}
|
||||
scope.launch {
|
||||
val result = twinsManager?.createFirstWallet(action.message)
|
||||
|
|
|
|||
|
|
@ -97,11 +97,12 @@ class CreateTwinWalletFragment : Fragment(R.layout.fragment_details_twin_cards),
|
|||
v_step_3.setBackgroundColor(defaultColor)
|
||||
btn_tap.setOnClickListener {
|
||||
store.dispatch(DetailsAction.CreateTwinWalletAction.LaunchFirstStep(
|
||||
Message(getString(
|
||||
R.string.details_twins_recreate_title_format,
|
||||
twinCardNumberString
|
||||
)
|
||||
)))
|
||||
Message(getString(
|
||||
R.string.details_twins_recreate_title_format,
|
||||
twinCardNumberString
|
||||
)),
|
||||
requireContext()
|
||||
))
|
||||
}
|
||||
btn_tap.text = getString(R.string.details_twins_recreate_button_format,
|
||||
twinCardNumber.number.toString())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue