Updated on 2026-08-14
This commit is contained in:
parent
4c64438643
commit
66a56a7344
16 changed files with 383 additions and 172 deletions
|
|
@ -1,4 +1,5 @@
|
|||
apply plugin: "kotlin"
|
||||
apply plugin: 'org.jetbrains.dokka'
|
||||
|
||||
group = 'com.github.TangemCash'
|
||||
version '0.1.0'
|
||||
|
|
@ -9,6 +10,8 @@ dependencies {
|
|||
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"
|
||||
|
|
@ -16,15 +19,19 @@ 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 {
|
||||
|
|
@ -36,3 +43,7 @@ compileTestKotlin {
|
|||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||
outputFormat = 'markdown'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ 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
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.commands
|
|||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extentions.toInt
|
||||
import com.tangem.common.extensions.toInt
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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.extensions.calculateSha256
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.common.extentions
|
||||
package com.tangem.common.extensions
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.MessageDigest
|
||||
|
|
@ -28,9 +28,9 @@ fun ByteArray.toInt(): Int {
|
|||
}
|
||||
|
||||
fun ByteArray.toDate(): Date {
|
||||
val year = ((this[0] and 0xFF.toByte()).toInt() shl 8) or (this[1] and 0xFF.toByte()).toInt()
|
||||
val month = this[2] - 1
|
||||
val day = this[3].toInt()
|
||||
val year = copyOfRange(0, 2).toInt()
|
||||
val month = if (this.size > 2) this[2] - 1 else 0
|
||||
val day = if (this.size > 3) this[3].toInt() else 0
|
||||
val cd = Calendar.getInstance()
|
||||
cd.set(year, month, day, 0, 0, 0)
|
||||
return cd.time
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.common.extentions
|
||||
package com.tangem.common.extensions
|
||||
|
||||
import java.nio.charset.Charset
|
||||
import java.security.MessageDigest
|
||||
|
|
@ -13,10 +13,8 @@ fun String.calculateSha256(): ByteArray {
|
|||
}
|
||||
|
||||
fun String.hexToBytes(): ByteArray {
|
||||
val bytes = ByteArray(this.length / 2)
|
||||
for (i in bytes.indices) {
|
||||
bytes[i] = Integer.parseInt(this.substring(2 * i, 2 * i + 2),
|
||||
16).toByte()
|
||||
return ByteArray(this.length / 2)
|
||||
{ i ->
|
||||
Integer.parseInt(this.substring(2 * i, 2 * i + 2), 16).toByte()
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.extentions.toDate
|
||||
import com.tangem.common.extentions.toHexString
|
||||
import com.tangem.common.extentions.toInt
|
||||
import com.tangem.common.extentions.toUtf8
|
||||
import com.tangem.common.extensions.toDate
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.extensions.toInt
|
||||
import com.tangem.common.extensions.toUtf8
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ object CryptoUtils {
|
|||
fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray,
|
||||
curve: EllipticCurve = EllipticCurve.Secp256k1): Boolean {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> Sepc256k1.verify(publicKey, message, signature)
|
||||
EllipticCurve.Secp256k1 -> Secp256k1.verify(publicKey, message, signature)
|
||||
EllipticCurve.Ed25519 -> Ed25519.verify(publicKey, message, signature)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ object CryptoUtils {
|
|||
curve: EllipticCurve = EllipticCurve.Secp256k1
|
||||
): ByteArray {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> Sepc256k1.generatePublicKey(privateKeyArray)
|
||||
EllipticCurve.Secp256k1 -> Secp256k1.generatePublicKey(privateKeyArray)
|
||||
EllipticCurve.Ed25519 -> Ed25519.generatePublicKey(privateKeyArray)
|
||||
}
|
||||
}
|
||||
|
|
@ -74,8 +74,8 @@ object CryptoUtils {
|
|||
*/
|
||||
fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1): ByteArray {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> signSecp256k1(this, privateKeyArray)
|
||||
EllipticCurve.Ed25519 -> signEd25519(this, privateKeyArray)
|
||||
EllipticCurve.Secp256k1 -> Secp256k1.sign(this, privateKeyArray)
|
||||
EllipticCurve.Ed25519 -> Ed25519.sign(this, privateKeyArray)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.crypto
|
||||
|
||||
import com.tangem.common.extentions.calculateSha512
|
||||
import com.tangem.common.extensions.calculateSha512
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
|
|
@ -10,36 +10,46 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
|||
import java.security.MessageDigest
|
||||
import java.security.PublicKey
|
||||
|
||||
internal fun verifyEd25519(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean {
|
||||
val messageSha512 = message.calculateSha512()
|
||||
val loadedPublicKey = loadPublicKey(publicKey)
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val signatureInstance = EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm))
|
||||
signatureInstance.initVerify(loadedPublicKey)
|
||||
object Ed25519 {
|
||||
|
||||
signatureInstance.update(messageSha512)
|
||||
internal fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean {
|
||||
val messageSha512 = message.calculateSha512()
|
||||
val loadedPublicKey = loadPublicKey(publicKey)
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val signatureInstance = EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm))
|
||||
signatureInstance.initVerify(loadedPublicKey)
|
||||
|
||||
return signatureInstance.verify(signature)
|
||||
}
|
||||
signatureInstance.update(messageSha512)
|
||||
|
||||
private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey {
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec)
|
||||
return EdDSAPublicKey(pubKey)
|
||||
}
|
||||
return signatureInstance.verify(signature)
|
||||
}
|
||||
|
||||
internal fun signEd25519(data: ByteArray, privateKeyArray: ByteArray): ByteArray {
|
||||
private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey {
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec)
|
||||
return EdDSAPublicKey(pubKey)
|
||||
}
|
||||
|
||||
val dataSha512 = data.calculateSha512()
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val signatureInstance = EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm))
|
||||
internal fun sign(data: ByteArray, privateKeyArray: ByteArray): ByteArray {
|
||||
|
||||
val privateKeySpec = EdDSAPrivateKeySpec(privateKeyArray, spec)
|
||||
val privateKey = EdDSAPrivateKey(privateKeySpec)
|
||||
val dataSha512 = data.calculateSha512()
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val signatureInstance = EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm))
|
||||
|
||||
signatureInstance.initSign(privateKey)
|
||||
signatureInstance.update(dataSha512)
|
||||
val privateKeySpec = EdDSAPrivateKeySpec(privateKeyArray, spec)
|
||||
val privateKey = EdDSAPrivateKey(privateKeySpec)
|
||||
|
||||
return signatureInstance.sign()
|
||||
signatureInstance.initSign(privateKey)
|
||||
signatureInstance.update(dataSha512)
|
||||
|
||||
return signatureInstance.sign()
|
||||
}
|
||||
|
||||
internal fun generatePublicKey(privateKeyArray: ByteArray): ByteArray {
|
||||
val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519)
|
||||
val privateKeySpec = EdDSAPrivateKeySpec(privateKeyArray, spec)
|
||||
val publicKeySpec = EdDSAPublicKeySpec(privateKeySpec.a, spec)
|
||||
val publicKey = EdDSAPublicKey(publicKeySpec)
|
||||
return publicKey.abyte
|
||||
}
|
||||
}
|
||||
118
tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt
Normal file
118
tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.crypto
|
||||
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import org.spongycastle.asn1.ASN1EncodableVector
|
||||
import org.spongycastle.asn1.ASN1Integer
|
||||
import org.spongycastle.asn1.DERSequence
|
||||
import org.spongycastle.jce.ECNamedCurveTable
|
||||
import org.spongycastle.jce.spec.ECPrivateKeySpec
|
||||
import org.spongycastle.jce.spec.ECPublicKeySpec
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyFactory
|
||||
import java.security.PublicKey
|
||||
import java.security.Signature
|
||||
|
||||
object Secp256k1 {
|
||||
|
||||
internal fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean {
|
||||
val signatureInstance = Signature.getInstance("SHA256withECDSA")
|
||||
val loadedPublicKey = loadPublicKey(publicKey)
|
||||
signatureInstance.initVerify(loadedPublicKey)
|
||||
signatureInstance.update(message)
|
||||
|
||||
val v = ASN1EncodableVector()
|
||||
val size = signature.size / 2
|
||||
v.add(calculateR(signature, size))
|
||||
v.add(calculateS(signature, size))
|
||||
val sigDer = DERSequence(v).encoded
|
||||
|
||||
return signatureInstance.verify(sigDer)
|
||||
}
|
||||
|
||||
private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey {
|
||||
|
||||
val spec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
val factory = KeyFactory.getInstance("EC", "SC")
|
||||
|
||||
val p1 = spec.curve.decodePoint(publicKeyArray)
|
||||
val keySpec = ECPublicKeySpec(p1, spec)
|
||||
|
||||
return factory.generatePublic(keySpec)
|
||||
}
|
||||
|
||||
private fun calculateR(signature: ByteArray, size: Int): ASN1Integer =
|
||||
ASN1Integer(BigInteger(1, signature.copyOfRange(0, size)))
|
||||
|
||||
private fun calculateS(signature: ByteArray, size: Int): ASN1Integer =
|
||||
ASN1Integer(BigInteger(1, signature.copyOfRange(size, size * 2)))
|
||||
|
||||
|
||||
internal fun sign(data: ByteArray, privateKeyArray: ByteArray): ByteArray {
|
||||
|
||||
val spec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
val factory = KeyFactory.getInstance("EC", "SC")
|
||||
|
||||
val keySpecP = ECPrivateKeySpec(BigInteger(1, privateKeyArray), spec)
|
||||
|
||||
val signature = Signature.getInstance("SHA256withECDSA")
|
||||
|
||||
val privateKey = factory.generatePrivate(keySpecP)
|
||||
signature.initSign(privateKey)
|
||||
signature.update(data)
|
||||
|
||||
val enc = signature.sign()
|
||||
checkSignatureForErrors(enc)
|
||||
|
||||
val res = toByte64(enc)
|
||||
|
||||
if (!verify(generatePublicKey(privateKeyArray), data, res)) {
|
||||
throw Exception("Signature self verify failed - ,enc:" + enc.toHexString() + ",res:" + res.toHexString())
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
private fun checkSignatureForErrors(enc: ByteArray) {
|
||||
if (enc[0].toInt() != 0x30) throw Exception("bad encoding 1")
|
||||
if (enc[1].toInt() and 0x80 != 0) throw Exception("unsupported length encoding 1")
|
||||
if (enc[2].toInt() != 0x02) throw Exception("bad encoding 2")
|
||||
if (enc[3].toInt() and 0x80 != 0) throw Exception("unsupported length encoding 2")
|
||||
var rLength = enc[3].toInt()
|
||||
if (enc[4 + rLength].toInt() != 0x02) throw Exception("bad encoding 3")
|
||||
if (enc[5 + rLength].toInt() and 0x80 != 0)
|
||||
throw Exception("unsupported length encoding 3")
|
||||
}
|
||||
|
||||
private fun toByte64(enc: ByteArray): ByteArray {
|
||||
|
||||
var rLength = enc[3].toInt()
|
||||
var sLength = enc[5 + rLength].toInt()
|
||||
|
||||
val sPos = 6 + rLength
|
||||
val res = ByteArray(64)
|
||||
if (rLength <= 32) {
|
||||
System.arraycopy(enc, 4, res, 32 - rLength, rLength)
|
||||
rLength = 32
|
||||
} else if (rLength == 33 && enc[4].toInt() == 0) {
|
||||
rLength--
|
||||
System.arraycopy(enc, 5, res, 0, rLength)
|
||||
} else {
|
||||
throw Exception("unsupported r-length - r-length:" + rLength.toString() + ",s-length:" + sLength.toString() + ",enc:" + enc.toHexString())
|
||||
}
|
||||
if (sLength <= 32) {
|
||||
System.arraycopy(enc, sPos, res, rLength + 32 - sLength, sLength)
|
||||
sLength = 32
|
||||
} else if (sLength == 33 && enc[sPos].toInt() == 0) {
|
||||
System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1)
|
||||
} else {
|
||||
throw Exception("unsupported s-length - r-length:" + rLength.toString() + ",s-length:" + sLength.toString() + ",enc:" + enc.toHexString())
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
internal fun generatePublicKey(privateKeyArray: ByteArray): ByteArray {
|
||||
val spec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
return spec.g.multiply(BigInteger(1, privateKeyArray)).getEncoded(false)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
package com.tangem.crypto
|
||||
|
||||
import com.tangem.common.extentions.toHexString
|
||||
import org.spongycastle.asn1.ASN1EncodableVector
|
||||
import org.spongycastle.asn1.ASN1Integer
|
||||
import org.spongycastle.asn1.DERSequence
|
||||
import org.spongycastle.jce.ECNamedCurveTable
|
||||
import org.spongycastle.jce.spec.ECPrivateKeySpec
|
||||
import org.spongycastle.jce.spec.ECPublicKeySpec
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyFactory
|
||||
import java.security.PublicKey
|
||||
import java.security.Signature
|
||||
|
||||
internal fun verifySecp256k1(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean {
|
||||
val signatureInstance = Signature.getInstance("SHA256withECDSA")
|
||||
val loadedPublicKey = loadPublicKey(publicKey)
|
||||
signatureInstance.initVerify(loadedPublicKey)
|
||||
signatureInstance.update(message)
|
||||
|
||||
val v = ASN1EncodableVector()
|
||||
val size = signature.size / 2
|
||||
v.add(calculateR(signature, size))
|
||||
v.add(calculateS(signature, size))
|
||||
val sigDer = DERSequence(v).encoded
|
||||
|
||||
return signatureInstance.verify(sigDer)
|
||||
}
|
||||
|
||||
private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey {
|
||||
|
||||
val spec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
val factory = KeyFactory.getInstance("EC", "SC")
|
||||
|
||||
val p1 = spec.curve.decodePoint(publicKeyArray)
|
||||
val keySpec = ECPublicKeySpec(p1, spec)
|
||||
|
||||
return factory.generatePublic(keySpec)
|
||||
}
|
||||
|
||||
private fun calculateR(signature: ByteArray, size: Int): ASN1Integer =
|
||||
ASN1Integer(BigInteger(1, signature.copyOfRange(0, size)))
|
||||
|
||||
private fun calculateS(signature: ByteArray, size: Int): ASN1Integer =
|
||||
ASN1Integer(BigInteger(1, signature.copyOfRange(size, size * 2)))
|
||||
|
||||
|
||||
internal fun signSecp256k1(data: ByteArray, privateKeyArray: ByteArray): ByteArray {
|
||||
|
||||
val spec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
val factory = KeyFactory.getInstance("EC", "SC")
|
||||
|
||||
val keySpecP = ECPrivateKeySpec(BigInteger(1, privateKeyArray), spec)
|
||||
|
||||
val signature = Signature.getInstance("SHA256withECDSA")
|
||||
|
||||
val privateKey = factory.generatePrivate(keySpecP)
|
||||
signature.initSign(privateKey)
|
||||
signature.update(data)
|
||||
|
||||
val enc = signature.sign()
|
||||
checkSignatureForErrors(enc)
|
||||
|
||||
val res = toByte64(enc)
|
||||
|
||||
if (!verifySecp256k1(generatePublicKey(privateKeyArray), data, res)) {
|
||||
throw Exception("Signature self verify failed - ,enc:" + enc.toHexString() + ",res:" + res.toHexString())
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
private fun checkSignatureForErrors(enc: ByteArray) {
|
||||
if (enc[0].toInt() != 0x30) throw Exception("bad encoding 1")
|
||||
if (enc[1].toInt() and 0x80 != 0) throw Exception("unsupported length encoding 1")
|
||||
if (enc[2].toInt() != 0x02) throw Exception("bad encoding 2")
|
||||
if (enc[3].toInt() and 0x80 != 0) throw Exception("unsupported length encoding 2")
|
||||
var rLength = enc[3].toInt()
|
||||
if (enc[4 + rLength].toInt() != 0x02) throw Exception("bad encoding 3")
|
||||
if (enc[5 + rLength].toInt() and 0x80 != 0)
|
||||
throw Exception("unsupported length encoding 3")
|
||||
}
|
||||
|
||||
private fun toByte64(enc: ByteArray): ByteArray {
|
||||
|
||||
var rLength = enc[3].toInt()
|
||||
var sLength = enc[5 + rLength].toInt()
|
||||
|
||||
val sPos = 6 + rLength
|
||||
val res = ByteArray(64)
|
||||
if (rLength <= 32) {
|
||||
System.arraycopy(enc, 4, res, 32 - rLength, rLength)
|
||||
rLength = 32
|
||||
} else if (rLength == 33 && enc[4].toInt() == 0) {
|
||||
rLength--
|
||||
System.arraycopy(enc, 5, res, 0, rLength)
|
||||
} else {
|
||||
throw Exception("unsupported r-length - r-length:" + rLength.toString() + ",s-length:" + sLength.toString() + ",enc:" + enc.toHexString())
|
||||
}
|
||||
if (sLength <= 32) {
|
||||
System.arraycopy(enc, sPos, res, rLength + 32 - sLength, sLength)
|
||||
sLength = 32
|
||||
} else if (sLength == 33 && enc[sPos].toInt() == 0) {
|
||||
System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1)
|
||||
} else {
|
||||
throw Exception("unsupported s-length - r-length:" + rLength.toString() + ",s-length:" + sLength.toString() + ",enc:" + enc.toHexString())
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
private fun generatePublicKey(privateKeyArray: ByteArray): ByteArray {
|
||||
val spec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
return spec.g.multiply(BigInteger(1, privateKeyArray)).getEncoded(false)
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.common.extensions
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
|
||||
|
||||
class ByteArrayExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `card Id to Hex String`() {
|
||||
val hex = "cb22000000027374"
|
||||
val bytes = byteArrayOf(-53, 34, 0, 0, 0, 2, 115, 116)
|
||||
assertThat(bytes.toHexString())
|
||||
.matches(hex)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `batch Id to Hex String`() {
|
||||
val hex = "0029"
|
||||
val bytes = byteArrayOf(0, 41)
|
||||
assertThat(bytes.toHexString())
|
||||
.matches(hex)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `curve name to Utf8`() {
|
||||
val bytes = byteArrayOf(115, 101, 99, 112, 50, 53, 54, 107, 49, 0)
|
||||
val expected = "secp256k1"
|
||||
val converted = bytes.toUtf8()
|
||||
assertThat(converted)
|
||||
.matches(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `blockchain name to Utf8`() {
|
||||
val bytes = byteArrayOf(69, 84, 72)
|
||||
val expected = "ETH"
|
||||
val converted = bytes.toUtf8()
|
||||
assertThat(converted)
|
||||
.matches(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bytes to int`() {
|
||||
val bytes = byteArrayOf(0, 2, 106, 3)
|
||||
val expected = 158211
|
||||
assertThat(bytes.toInt())
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `zero to int`() {
|
||||
val bytes = byteArrayOf(0)
|
||||
val expected = 0
|
||||
assertThat(bytes.toInt())
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toDate() {
|
||||
val bytes1 = byteArrayOf(7, -30, 7, 27)
|
||||
val expected1 = Calendar.getInstance().apply { this.set(2018, 6, 27, 0, 0, 0) }.time
|
||||
val converted1 = bytes1.toDate()
|
||||
assertThat(converted1.toString())
|
||||
.isEqualTo(expected1.toString())
|
||||
|
||||
val bytes2 = byteArrayOf(7, -30, 7, 27, 30)
|
||||
val expected2 = Calendar.getInstance().apply { this.set(2018, 6, 27, 0, 0, 0) }.time
|
||||
val converted2 = bytes2.toDate()
|
||||
assertThat(converted2.toString())
|
||||
.isEqualTo(expected2.toString())
|
||||
|
||||
val bytes3 = byteArrayOf(7, -30, 7)
|
||||
val expected3 = Calendar.getInstance().apply { this.set(2018, 6, 0, 0, 0, 0) }.time
|
||||
val converted3 = bytes3.toDate()
|
||||
assertThat(converted3.toString())
|
||||
.isEqualTo(expected3.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculate sha512`() {
|
||||
val bytes = ByteArray(64) { 5 }
|
||||
val expected = byteArrayOf(
|
||||
-123, 96, 121, 57, -117, -23, -108, 57, 25, -119, -22, 97, 11, -91,
|
||||
74, -19, -88, 21, -108, -116, -100, 111, 6, -78, 114, -115, 70, -121, 29, 102, 104, 65,
|
||||
-21, -68, -111, 121, -51, 109, -94, -24, -40, 108, -25, 70, -26, 61, 38, 12, -127, -34,
|
||||
-77, -81, 81, -32, -89, -112, -31, -33, 91, 114, 89, 127, -123, -58)
|
||||
assertThat(bytes.calculateSha512())
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.common.extensions
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class StringExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `calculate SHA 256 for default PIN 1`() {
|
||||
val pin = "000000"
|
||||
val expected = byteArrayOf(-111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, -111,
|
||||
34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3)
|
||||
assertThat(pin.calculateSha256())
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculate SHA 256 for default PIN 2`() {
|
||||
val pin = "000"
|
||||
val expected = byteArrayOf(42, -55, -90, 116, 106, -54, 84, 58, -8, -33, -13, -104, -108, -49,
|
||||
-24, 23, 58, -5, -94, 30, -80, 28, 111, -82, 51, -43, 41, 71, 34, 40, 85, -17)
|
||||
assertThat(pin.calculateSha256())
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculate SHA 256 for a sample PIN 1`() {
|
||||
val pin = "999999"
|
||||
val expected = byteArrayOf(-109, 115, 119, -16, 86, 22, 15, -60, -79, 94, 11, 119, 12, 103,
|
||||
19, 106, 95, 3, -63, 82, 5, -76, -45, -65, -111, -126, 104, -2, -6, 44, 109, 10)
|
||||
assertThat(pin.calculateSha256())
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculate SHA 256 for a sample PIN 2`() {
|
||||
val pin = "999"
|
||||
val expected = byteArrayOf(-125, -49, -117, 96, -99, -26, 0, 54, -88, 39, 123, -48, -23, 97,
|
||||
53, 117, 27, -68, 7, -21, 35, 66, 86, -44, -74, 91, -119, 51, 96, 101, 27, -14)
|
||||
assertThat(pin.calculateSha256())
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `card ID hex to bytes`() {
|
||||
val cardId = "cb22000000027374"
|
||||
val expected = byteArrayOf(-53, 34, 0, 0, 0, 2, 115, 116)
|
||||
assertThat(cardId.hexToBytes())
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.crypto
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.crypto.CryptoUtils.generatePublicKey
|
||||
import com.tangem.crypto.CryptoUtils.generateRandomBytes
|
||||
import com.tangem.crypto.CryptoUtils.verify
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
|
||||
class CryptoUtilsTest {
|
||||
|
||||
@BeforeEach
|
||||
internal fun setUp() {
|
||||
CryptoUtils.initCrypto()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateRandomBytesTest() {
|
||||
val privateKey: ByteArray = generateRandomBytes(32)
|
||||
assertThat(privateKey)
|
||||
.hasLength(32)
|
||||
assertThat(privateKey.sum())
|
||||
.isNotEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
internal fun verifyEd25519Test() {
|
||||
val verified = verifySignature_withSampleData(EllipticCurve.Ed25519)
|
||||
assertThat(verified)
|
||||
.isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
internal fun verifySecp256k1Test() {
|
||||
val verified = verifySignature_withSampleData(EllipticCurve.Secp256k1)
|
||||
assertThat(verified)
|
||||
.isTrue()
|
||||
}
|
||||
|
||||
private fun verifySignature_withSampleData(curve: EllipticCurve): Boolean {
|
||||
val privateKey = ByteArray(32) { 1 }
|
||||
val publicKey = generatePublicKey(privateKey, curve)
|
||||
val message = ByteArray(64) { 5 }
|
||||
val signature = message.sign(privateKey, curve)
|
||||
return verify(publicKey, message, signature, curve)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TaskEvent.Completion -> {
|
||||
if (taskEvent.error != null) {
|
||||
if (taskEvent.error is TaskError.UserCancelledError) {
|
||||
|
|
@ -73,14 +73,8 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun createSampleHashes(): Array<ByteArray> {
|
||||
val hash1 = ByteArray(32)
|
||||
for (i in 0 until 32) {
|
||||
hash1[i] = 1
|
||||
}
|
||||
val hash2 = ByteArray(32)
|
||||
for (i in 0 until 32) {
|
||||
hash2[i] = 2
|
||||
}
|
||||
val hash1 = ByteArray(32) { 1 }
|
||||
val hash2 = ByteArray(32) { 2 }
|
||||
return arrayOf(hash1, hash2)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue