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

org.kin.stellarfork.StrKey.kt Maven / Gradle / Ivy

package org.kin.stellarfork

import org.kin.stellarfork.codec.Base32
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.Arrays

object StrKey {

    @JvmStatic
    fun encodeStellarAccountId(data: ByteArray): String =
        String(encodeCheck(VersionByte.ACCOUNT_ID, data))

    @JvmStatic
    fun decodeStellarAccountId(data: String): ByteArray =
        decodeCheck(VersionByte.ACCOUNT_ID, data.toCharArray())

    @JvmStatic
    fun encodeStellarSecretSeed(data: ByteArray): CharArray =
        encodeCheck(VersionByte.SEED, data)

    @JvmStatic
    fun decodeStellarSecretSeed(data: CharArray): ByteArray =
        decodeCheck(VersionByte.SEED, data)

    @JvmStatic
    fun encodePreAuthTx(data: ByteArray): String =
        String(encodeCheck(VersionByte.PRE_AUTH_TX, data))

    @JvmStatic
    fun decodePreAuthTx(data: String): ByteArray =
        decodeCheck(VersionByte.PRE_AUTH_TX, data.toCharArray())

    @JvmStatic
    fun encodeSha256Hash(data: ByteArray): String =
        String(encodeCheck(VersionByte.SHA256_HASH, data))

    @JvmStatic
    fun decodeSha256Hash(data: String): ByteArray =
        decodeCheck(VersionByte.SHA256_HASH, data.toCharArray())

    @JvmStatic
    fun encodeCheck(versionByte: VersionByte, data: ByteArray): CharArray {
        return try {
            val outputStream = ByteArrayOutputStream().apply {
                write(versionByte.getValue())
                write(data)
            }
            val payload = outputStream.toByteArray()
            val checksum = calculateChecksum(payload)
            outputStream.write(checksum)
            val unencoded = outputStream.toByteArray()

            Base32().encode(unencoded)?.let { bytesEncoded ->
                val charsEncoded = CharArray(bytesEncoded.size)
                for (i in bytesEncoded.indices) {
                    charsEncoded[i] = bytesEncoded[i].toChar()
                }
                if (VersionByte.SEED == versionByte) {
                    Arrays.fill(unencoded, 0.toByte())
                    Arrays.fill(payload, 0.toByte())
                    Arrays.fill(bytesEncoded, 0.toByte())
                }
                charsEncoded
            } ?: CharArray(0)
        } catch (e: IOException) {
            throw AssertionError(e)
        }
    }

    @JvmStatic
    fun decodeCheck(versionByte: VersionByte, encoded: CharArray): ByteArray {
        val bytes = ByteArray(encoded.size)
        encoded.indices.forEach { i ->
            require(encoded[i].toInt() <= 127) { "Illegal characters in encoded char array." }
            bytes[i] = encoded[i].toByte()
        }
        Base32().decode(bytes)?.let { decoded ->
            val decodedVersionByte = decoded[0]
            val payload = decoded.copyOfRange(0, decoded.size - 2)
            val data = payload.copyOfRange(1, payload.size)
            val checksum = decoded.copyOfRange(decoded.size - 2, decoded.size)
            if (decodedVersionByte.toInt() != versionByte.getValue()) {
                throw Exception("Version byte is invalid")
            }
            val expectedChecksum = calculateChecksum(payload)
            if (!expectedChecksum.contentEquals(checksum)) {
                throw Exception("Checksum invalid")
            }
            if (VersionByte.SEED.getValue() == decodedVersionByte.toInt()) {
                Arrays.fill(bytes, 0.toByte())
                Arrays.fill(decoded, 0.toByte())
                Arrays.fill(payload, 0.toByte())
            }
            return data
        }

        return ByteArray(0)
    }

    @JvmStatic
    internal fun calculateChecksum(bytes: ByteArray): ByteArray {
        // This code calculates CRC16-XModem checksum
        // Ported from https://github.com/alexgorbatchev/node-crc
        var crc = 0x0000
        var count = bytes.size
        var i = 0
        var code: Int
        while (count > 0) {
            code = crc ushr 8 and 0xFF
            code = code xor (bytes[i++].toInt() and 0xFF)
            code = code xor (code ushr 4)
            crc = crc shl 8 and 0xFFFF
            crc = crc xor code
            code = code shl 5 and 0xFFFF
            crc = crc xor code
            code = code shl 7 and 0xFFFF
            crc = crc xor code
            count--
        }
        // little-endian
        return byteArrayOf(crc.toByte(), (crc ushr 8).toByte())
    }

    enum class VersionByte(
        private val value: Byte // X
    ) {
        ACCOUNT_ID((6 shl 3).toByte()),  // G
        SEED((18 shl 3).toByte()),  // S
        PRE_AUTH_TX((19 shl 3).toByte()),  // T
        SHA256_HASH((23 shl 3).toByte());

        fun getValue(): Int {
            return value.toInt()
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy