All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nftco.flow.sdk.crypto.Crypto.kt Maven / Gradle / Ivy

The newest version!
package com.nftco.flow.sdk.crypto

import com.nftco.flow.sdk.*
import com.nftco.flow.sdk.Signer
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.ECPointUtil
import org.bouncycastle.jce.interfaces.ECPrivateKey
import org.bouncycastle.jce.interfaces.ECPublicKey
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.spec.ECNamedCurveSpec
import org.bouncycastle.jce.spec.ECPrivateKeySpec
import java.math.BigInteger
import java.security.*
import java.security.spec.ECGenParameterSpec
import java.security.spec.ECPublicKeySpec
import kotlin.experimental.and
import kotlin.math.max

data class KeyPair(
    val private: PrivateKey,
    val public: PublicKey
)

data class PrivateKey(
    val key: java.security.PrivateKey,
    val ecCoupleComponentSize: Int,
    val hex: String
)

data class PublicKey(
    val key: java.security.PublicKey,
    val hex: String
)

object Crypto {

    init {
        Security.addProvider(BouncyCastleProvider())
    }

    @JvmStatic
    @JvmOverloads
    fun generateKeyPair(algo: SignatureAlgorithm = SignatureAlgorithm.ECDSA_P256): KeyPair {
        val generator = KeyPairGenerator.getInstance("EC", "BC")
        generator.initialize(ECGenParameterSpec(algo.curve), SecureRandom())
        val keyPair = generator.generateKeyPair()
        val privateKey = keyPair.private
        val publicKey = keyPair.public
        return KeyPair(
            private = PrivateKey(
                key = keyPair.private,
                ecCoupleComponentSize = if (privateKey is ECPrivateKey) {
                    privateKey.parameters.n.bitLength() / 8
                } else {
                    0
                },
                hex = if (privateKey is ECPrivateKey) {
                    privateKey.d.toByteArray().bytesToHex()
                } else {
                    throw IllegalArgumentException("PrivateKey must be an ECPublicKey")
                }
            ),
            public = PublicKey(
                key = publicKey,
                hex = if (publicKey is ECPublicKey) {
                    (publicKey.q.xCoord.encoded + publicKey.q.yCoord.encoded).bytesToHex()
                } else {
                    throw IllegalArgumentException("PublicKey must be an ECPublicKey")
                }
            )
        )
    }

    @JvmStatic
    @JvmOverloads
    fun decodePrivateKey(key: String, algo: SignatureAlgorithm = SignatureAlgorithm.ECDSA_P256): PrivateKey {
        val ecParameterSpec = ECNamedCurveTable.getParameterSpec(algo.curve)
        val keyFactory = KeyFactory.getInstance(algo.algorithm, "BC")
        val ecPrivateKeySpec = ECPrivateKeySpec(BigInteger(key, 16), ecParameterSpec)
        val pk = keyFactory.generatePrivate(ecPrivateKeySpec)
        return PrivateKey(
            key = pk,
            ecCoupleComponentSize = if (pk is ECPrivateKey) {
                pk.parameters.n.bitLength() / 8
            } else {
                0
            },
            hex = if (pk is ECPrivateKey) {
                pk.d.toByteArray().bytesToHex()
            } else {
                throw IllegalArgumentException("PrivateKey must be an ECPublicKey")
            }
        )
    }

    @JvmStatic
    @JvmOverloads
    fun decodePublicKey(key: String, algo: SignatureAlgorithm = SignatureAlgorithm.ECDSA_P256): PublicKey {
        val ecParameterSpec = ECNamedCurveTable.getParameterSpec(algo.curve)
        val keyFactory = KeyFactory.getInstance("EC", "BC")
        val params = ECNamedCurveSpec(
            algo.curve,
            ecParameterSpec.curve, ecParameterSpec.g, ecParameterSpec.n
        )
        val point = ECPointUtil.decodePoint(params.curve, byteArrayOf(0x04) + key.hexToBytes())
        val pubKeySpec = ECPublicKeySpec(point, params)
        val publicKey = keyFactory.generatePublic(pubKeySpec)
        return PublicKey(
            key = publicKey,
            hex = if (publicKey is ECPublicKey) {
                (publicKey.q.xCoord.encoded + publicKey.q.yCoord.encoded).bytesToHex()
            } else {
                throw IllegalArgumentException("PublicKey must be an ECPublicKey")
            }
        )
    }

    @JvmStatic
    @JvmOverloads
    fun getSigner(privateKey: PrivateKey, hashAlgo: HashAlgorithm = HashAlgorithm.SHA3_256): Signer {
        return SignerImpl(privateKey, hashAlgo)
    }

    @JvmStatic
    @JvmOverloads
    fun getHasher(hashAlgo: HashAlgorithm = HashAlgorithm.SHA3_256): Hasher {
        return HasherImpl(hashAlgo)
    }

    @JvmStatic
    fun normalizeSignature(signature: ByteArray, ecCoupleComponentSize: Int): ByteArray {
        val (r, s) = extractRS(signature)

        val nLen = ecCoupleComponentSize
        val paddedSignature = ByteArray(2 * nLen)

        val rBytes = r.toByteArray()
        val sBytes = s.toByteArray()

        // occasionally R/S bytes representation has leading zeroes, so make sure we trim them appropriately
        rBytes.copyInto(paddedSignature, max(nLen - rBytes.size, 0), max(0, rBytes.size - nLen))
        sBytes.copyInto(paddedSignature, max(2 * nLen - sBytes.size, nLen), max(0, sBytes.size - nLen))

        return paddedSignature
    }

    @JvmStatic
    fun extractRS(signature: ByteArray): Pair {
        val startR = if ((signature[1] and 0x80.toByte()) != 0.toByte()) 3 else 2
        val lengthR = signature[startR + 1].toInt()
        val startS = startR + 2 + lengthR
        val lengthS = signature[startS + 1].toInt()
        return Pair(
            BigInteger(signature.copyOfRange(startR + 2, startR + 2 + lengthR)),
            BigInteger(signature.copyOfRange(startS + 2, startS + 2 + lengthS))
        )
    }
}

internal class HasherImpl(
    private val hashAlgo: HashAlgorithm
) : Hasher {

    override fun hash(bytes: ByteArray): ByteArray {
        val digest = MessageDigest.getInstance(hashAlgo.algorithm)
        return digest.digest(bytes)
    }
}

internal class SignerImpl(
    private val privateKey: PrivateKey,
    private val hashAlgo: HashAlgorithm,
    override val hasher: Hasher = HasherImpl(hashAlgo)
) : Signer {

    override fun sign(bytes: ByteArray): ByteArray {

        val ecdsaSign = Signature.getInstance(hashAlgo.id)
        ecdsaSign.initSign(privateKey.key)
        ecdsaSign.update(bytes)

        val signature = ecdsaSign.sign()
        if (privateKey.ecCoupleComponentSize <= 0) {
            return signature
        }

        return Crypto.normalizeSignature(signature, privateKey.ecCoupleComponentSize)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy