Skip to content

Commit

Permalink
Version 0.2.0: add tests and comments, implement small changes
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-pv committed Nov 18, 2019
1 parent abfa73d commit 7cbcf21
Show file tree
Hide file tree
Showing 51 changed files with 1,498 additions and 673 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.1'
classpath 'com.android.tools.build:gradle:3.5.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
}
Expand Down
16 changes: 13 additions & 3 deletions tangem-core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
apply plugin: "kotlin"
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'org.jetbrains.dokka'

group = 'com.github.TangemCash'
version '0.1.0'
group = 'com.github.Tangem'
version '0.2.0'

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "com.madgag.spongycastle:core:1.56.0.0"
implementation "com.madgag.spongycastle:prov:1.56.0.0"
implementation 'net.i2p.crypto:eddsa:0.3.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
testImplementation "com.google.truth:truth:1.0"
}

sourceCompatibility = "8"
targetCompatibility = "8"

buildscript {
ext.kotlin_version = '1.3.50'
ext.dokka_version = '0.10.0'
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
}
}
repositories {
mavenCentral()
jcenter()
}
compileKotlin {
kotlinOptions {
Expand All @@ -37,3 +43,7 @@ compileTestKotlin {
jvmTarget = "1.8"
}
}

task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputFormat = 'markdown'
}
4 changes: 4 additions & 0 deletions tangem-core/src/main/java/com/tangem/CardEnvironment.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.tangem


/**
* Contains data relating to a Tangem card. It is used in constructing all the commands,
* and commands can return modified [CardEnvironment].
*/
data class CardEnvironment(
val pin1: String = DEFAULT_PIN,
val pin2: String = DEFAULT_PIN2,
Expand Down
72 changes: 56 additions & 16 deletions tangem-core/src/main/java/com/tangem/CardManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.commands.SignCommand
import com.tangem.commands.SignResponse
import com.tangem.crypto.initCrypto
import com.tangem.crypto.CryptoUtils
import com.tangem.tasks.*
import java.util.concurrent.Executors

/**
* The main interface of Tangem SDK that allows your app to communicate with Tangem cards.
*
* @property reader is an interface that is responsible for NFC connection and
* transfer of data to and from the Tangem Card.
* Its default implementation, NfcCardReader, is in our tangem-sdk module.
* @property cardManagerDelegate An interface that allows interaction with users and shows relevant UI.
* Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module.
*/
class CardManager(
private val reader: CardReader,
private val cardManagerDelegate: CardManagerDelegate? = null) {
Expand All @@ -17,14 +26,44 @@ class CardManager(
private val cardManagerExecutor = Executors.newSingleThreadExecutor()

init {
initCrypto()
CryptoUtils.initCrypto()
}

/**
* To start using any card, you first need to read it using the scanCard() method.
* This method launches an NFC session, and once it’s connected with the card,
* it obtains the card data. Optionally, if the card contains a wallet (private and public key pair),
* it proves that the wallet owns a private key that corresponds to a public one.
*
* It launches on the new thread a [ScanTask] that will send the following events in a callback:
* [ScanEvent.OnReadEvent] after completing [com.tangem.commands.ReadCommand]
* [ScanEvent.OnVerifyEvent] after completing [com.tangem.commands.CheckWalletCommand]
* [TaskEvent.Completion] with an error field null after successful completion of a task or
* [TaskEvent.Completion] with a [TaskError] if some error occurs.
*/
fun scanCard(callback: (result: TaskEvent<ScanEvent>) -> Unit) {
val task = ScanTask()
runTask(task, callback = callback)
}

/**
* This method allows you to sign one or multiple hashes.
* Simultaneous signing of array of hashes in a single [SignCommand] is required to support
* Bitcoin-type multi-input blockchains (UTXO).
* The [SignCommand] will return a corresponding array of signatures.
*
* This method launches on the new thread [SignCommand] that will send the following events in a callback:
* [SignResponse] after completing [SignCommand]
* [TaskEvent.Completion] with an error field null after successful completion of a task or
* [TaskEvent.Completion] with a [TaskError] if some error occurs.
* Please note that Tangem cards usually protect the signing with a security delay
* that may last up to 90 seconds, depending on a card.
* It is for [CardManagerDelegate] to notify users of security delay.
* @param hashes Array of transaction hashes. It can be from one or up to ten hashes of the same length.
* @param cardId CID, Unique Tangem card ID number
* @param callback
*
*/
fun sign(hashes: Array<ByteArray>, cardId: String,
callback: (result: TaskEvent<SignResponse>) -> Unit) {
val signCommand: SignCommand
Expand All @@ -42,6 +81,9 @@ class CardManager(
runTask(task, cardId, callback)
}

/**
* Allows to run a custom task created outside of this SDK.
*/
fun <T> runTask(task: Task<T>, cardId: String? = null,
callback: (result: TaskEvent<T>) -> Unit) {
if (isBusy) {
Expand All @@ -56,26 +98,24 @@ class CardManager(
task.delegate = cardManagerDelegate

cardManagerExecutor.execute {
task.run(environment) {
when (it) {
is TaskEvent.Event -> callback(it)
is TaskEvent.Completion -> {
isBusy = false
callback(it)
}
}
task.run(environment) { taskEvent ->
if (taskEvent is TaskEvent.Completion) isBusy = false
callback(taskEvent)
}
}
}

private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
return cardEnvironmentRepository[cardId] ?: CardEnvironment()
}

fun <T : CommandResponse> runCommand(commandSerializer: CommandSerializer<T>,
/**
* Allows to run a custom command created outside of this SDK.
*/
fun <T : CommandResponse> runCommand(command: CommandSerializer<T>,
cardId: String? = null,
callback: (result: TaskEvent<T>) -> Unit) {
val task = SingleCommandTask(commandSerializer)
val task = SingleCommandTask(command)
runTask(task, cardId, callback)
}

private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
return cardEnvironmentRepository[cardId] ?: CardEnvironment()
}
}
40 changes: 35 additions & 5 deletions tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,43 @@ package com.tangem
import com.tangem.common.CompletionResult
import com.tangem.tasks.TaskError

/**
* Allows interaction with users and shows visual elements.
*
* Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module.
*/
interface CardManagerDelegate {

fun onTaskStarted()
fun showSecurityDelay(ms: Int)
fun onTaskCompleted()
fun onTaskError(error: TaskError? = null)
/**
* It is called when user is expected to scan a Tangem Card with an Android device.
*/
fun onNfcSessionStarted()

fun requestPin(callback: (result: CompletionResult<String>) -> Unit)
/**
* It is called when security delay is triggered by the card.
* A user is expected to hold the card until the security delay is over.
*/
fun onSecurityDelay(ms: Int)

/**
* It is called when user takes the card away from the Android device during the scanning
* (for example when security delay is in progress) and the TagLostException is received.
*/
fun onTagLost()

/**
* It is called when NFC session was completed and a user can take the card away from the Android device.
*/
fun onNfcSessionCompleted()

/**
* It is called when some error occur during NFC session.
*/
fun onError(error: TaskError? = null)

/**
* It is called when a user is expected to enter pin code.
*/
fun onPinRequested(callback: (result: CompletionResult<String>) -> Unit)

}
24 changes: 22 additions & 2 deletions tangem-core/src/main/java/com/tangem/CardReader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,29 @@ import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu

/**
* Allows interaction between the phone or any other terminal and Tangem card.
*
* Its default implementation, NfcCardReader, is in our tangem-sdk module.
*/
interface CardReader {
var readingActive: Boolean

/**
* Sends data to the card and receives the reply.
*
* @param apdu Data to be sent. [CommandApdu] serializes it to a [ByteArray]
* @param callback Returns response from the card,
* [ResponseApdu] Allows to convert raw data to [Tlv]
*/
fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
fun setStartSession()

/**
* Signals to [CardReader] to become ready to transceive data.
*/
fun openSession()

/**
* Signals to [CardReader] that no further NFC transition is expected.
*/
fun closeSession()
}
10 changes: 0 additions & 10 deletions tangem-core/src/main/java/com/tangem/DataStorage.kt

This file was deleted.

5 changes: 5 additions & 0 deletions tangem-core/src/main/java/com/tangem/Log.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ object Log {
}
}

/**
* Interface for logging events within the SDK.
*
* It allows to use Android logger or to choose another.
*/
interface LoggerInterface {
fun i(logTag: String, message: String)
fun e(logTag: String, message: String)
Expand Down
17 changes: 0 additions & 17 deletions tangem-core/src/main/java/com/tangem/Response.kt

This file was deleted.

38 changes: 24 additions & 14 deletions tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,49 @@ package com.tangem.commands

import com.tangem.CardEnvironment
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.extentions.calculateSha256
import com.tangem.common.extentions.hexToBytes
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.tlv.Tlv
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvTag
import com.tangem.enums.Instruction
import com.tangem.tasks.TaskError

/**
* Deserialized response from the Tangem card after [CheckWalletCommand].
*
* @property cardId Unique Tangem card ID number
* @property salt Random salt generated by the card.
* @property walletSignature Challenge and salt signed with the wallet private key.
*/
class CheckWalletResponse(
val cardId: String,
val salt: ByteArray,
val walletSignature: ByteArray
) : CommandResponse


/**
* This command proves that the wallet private key from the card corresponds to the wallet public key.
* Standard challenge/response scheme is used.
*
* @property pin1 Hashed user’s pin 1 code to access the card. Default unhashed value: ‘000000’.
* @property cardId Unique Tangem card ID number
* @property challenge Random challenge generated by application
*/
class CheckWalletCommand(
val pin1: String, val cid: String,
val challenge: ByteArray, val publicKeyChallenge: ByteArray) : CommandSerializer<CheckWalletResponse>() {

override val instruction = Instruction.CheckWallet
override val instructionCode = instruction.code
private val pin1: String,
private val cardId: String,
private val challenge: ByteArray
) : CommandSerializer<CheckWalletResponse>() {

override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = listOf(
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, cid.hexToBytes()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.Challenge, challenge)
)

return CommandApdu(instructionCode, tlvData)
return CommandApdu(Instruction.CheckWallet, tlvData)
}

override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? {
Expand All @@ -49,6 +61,4 @@ class CheckWalletCommand(
throw TaskError.SerializeCommandError()
}
}


}
Loading

0 comments on commit 7cbcf21

Please sign in to comment.