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

commonMain.fr.acinq.bitcoin.Crypto.kt Maven / Gradle / Ivy

Go to download

A simple Kotlin Multiplatform library which implements most of the bitcoin protocol

The newest version!
/*
 * Copyright 2020 ACINQ SAS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.acinq.bitcoin

import fr.acinq.bitcoin.ScriptFlags.SCRIPT_VERIFY_DERSIG
import fr.acinq.bitcoin.ScriptFlags.SCRIPT_VERIFY_LOW_S
import fr.acinq.bitcoin.ScriptFlags.SCRIPT_VERIFY_STRICTENC
import fr.acinq.bitcoin.crypto.Digest
import fr.acinq.bitcoin.crypto.hmac
import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.secp256k1.Secp256k1
import kotlin.jvm.JvmStatic

public object Crypto {
    @JvmStatic
    public fun sha1(input: ByteVector): ByteArray = Digest.sha1().hash(input.toByteArray())

    @JvmStatic
    public fun sha256(input: ByteArray, offset: Int, len: Int): ByteArray =
        Digest.sha256().hash(input, offset, len)

    @JvmStatic
    public fun sha256(input: ByteArray): ByteArray =
        sha256(input, 0, input.size)

    @JvmStatic
    public fun sha256(input: ByteVector): ByteArray =
        sha256(input.toByteArray(), 0, input.size())

    @JvmStatic
    public fun ripemd160(input: ByteArray, offset: Int, len: Int): ByteArray =
        Digest.ripemd160().hash(input, offset, len)

    @JvmStatic
    public fun ripemd160(input: ByteArray): ByteArray =
        ripemd160(input, 0, input.size)

    @JvmStatic
    public fun ripemd160(input: ByteVector): ByteArray =
        ripemd160(input.toByteArray(), 0, input.size())

    @JvmStatic
    public fun hash256(input: ByteArray, offset: Int, len: Int): ByteArray =
        Digest.sha256().let { it.hash(it.hash(input, offset, len)) }

    @JvmStatic
    public fun hash256(input: ByteArray): ByteArray =
        hash256(input, 0, input.size)

    @JvmStatic
    public fun hash256(input: ByteVector): ByteArray =
        hash256(input.toByteArray(), 0, input.size())

    @JvmStatic
    public fun hash160(input: ByteArray, offset: Int, len: Int): ByteArray =
        Digest.ripemd160().hash(Digest.sha256().hash(input, offset, len))

    @JvmStatic
    public fun hash160(input: ByteArray): ByteArray = hash160(input, 0, input.size)

    @JvmStatic
    public fun hash160(input: ByteVector): ByteArray =
        hash160(input.toByteArray(), 0, input.size())

    @JvmStatic
    public fun hmac512(key: ByteArray, data: ByteArray): ByteArray {
        return Digest.sha512().hmac(key, data, 128)
    }

    /**
     * Computes ecdh using secp256k1's variant: sha256(priv * pub serialized in compressed format)
     *
     * @param priv private value
     * @param pub  public value
     * @return ecdh(priv, pub) as computed by libsecp256k1
     */
    @JvmStatic
    public fun ecdh(priv: PrivateKey, pub: PublicKey): ByteArray {
        return Secp256k1.ecdh(priv.value.toByteArray(), pub.value.toByteArray())
    }

    @JvmStatic
    public fun isPrivKeyValid(key: ByteArray): Boolean = Secp256k1.secKeyVerify(key)

    @JvmStatic
    public fun isPubKeyValid(key: ByteArray): Boolean = try {
        Secp256k1.pubkeyParse(key)
        true
    } catch (e: Throwable) {
        false
    }

    @JvmStatic
    public fun isPubKeyCompressedOrUncompressed(key: ByteArray): Boolean {
        return isPubKeyCompressed(key) || isPubKeyUncompressed(key)
    }

    @JvmStatic
    public fun isPubKeyCompressed(key: ByteArray): Boolean = when {
        key.size == 33 && (key[0] == 2.toByte() || key[0] == 3.toByte()) -> true
        else -> false
    }

    @JvmStatic
    public fun isPubKeyUncompressed(key: ByteArray): Boolean = when {
        key.size == 65 && key[0] == 4.toByte() -> true
        else -> false
    }

    /**
     * Sign data with a private key, using RCF6979 deterministic signatures
     *
     * @param data       data to sign
     * @param privateKey private key. If you are using bitcoin "compressed" private keys make sure to only use the first 32 bytes of
     *                   the key (there is an extra "1" appended to the key)
     * @return a (r, s) ECDSA signature pair
     */
    @JvmStatic
    public fun sign(data: ByteArray, privateKey: PrivateKey): ByteVector64 {
        val bin = Secp256k1.sign(data, privateKey.value.toByteArray())
        return ByteVector64(bin)
    }

    @JvmStatic
    public fun sign(data: ByteVector32, privateKey: PrivateKey): ByteVector64 =
        sign(data.toByteArray(), privateKey)

    /**
     * @param data      data
     * @param signature signature
     * @param publicKey public key
     * @return true is signature is valid for this data with this public key
     */
    @JvmStatic
    public fun verifySignature(data: ByteArray, signature: ByteVector64, publicKey: PublicKey): Boolean {
        return Secp256k1.verify(
            signature.toByteArray(),
            data,
            publicKey.value.toByteArray()
        )
    }

    /**
     * Specify how private keys are tweaked when creating Schnorr signatures
     */
    public sealed class SchnorrTweak {
        /**
         * private key is used as-is
         */
        public data object NoTweak : SchnorrTweak()
    }

    public sealed class TaprootTweak : SchnorrTweak() {
        /**
         * private key is tweaked with H_TapTweak(public key) (this is used for key path spending when no scripts are present)
         */
        public data object NoScriptTweak : TaprootTweak()

        /**
         * private key is tweaked with H_TapTweak(public key || merkle_root) (this is used for key path spending, with specific Merkle root of the script tree).
         */
        public data class ScriptTweak(val merkleRoot: ByteVector32) : TaprootTweak() {
            public constructor(scriptTree: ScriptTree) : this(scriptTree.hash())
        }
    }

    /**
     * @param data data to sign (32 bytes)
     * @param privateKey private key
     * @param schnorrTweak specify how to tweak the private key.
     * @param auxrand32 optional auxiliary random data
     * @return the Schnorr signature of data with private key (optionally tweaked with the tapscript merkle root)
     */
    @JvmStatic
    public fun signSchnorr(data: ByteVector32, privateKey: PrivateKey, schnorrTweak: SchnorrTweak, auxrand32: ByteVector32? = null): ByteVector64 {
        val priv = when (schnorrTweak) {
            SchnorrTweak.NoTweak -> privateKey
            is TaprootTweak.NoScriptTweak -> privateKey.tweak(privateKey.xOnlyPublicKey().tweak(schnorrTweak))
            is TaprootTweak.ScriptTweak -> privateKey.tweak(privateKey.xOnlyPublicKey().tweak(schnorrTweak))
        }
        val sig = Secp256k1.signSchnorr(data.toByteArray(), priv.value.toByteArray(), auxrand32?.toByteArray()).byteVector64()
        require(verifySignatureSchnorr(data, sig, priv.xOnlyPublicKey())) { "Cannot create Schnorr signature" }
        return sig
    }

    @JvmStatic
    public fun verifySignatureSchnorr(data: ByteVector32, signature: ByteVector, publicKey: XonlyPublicKey): Boolean {
        return Secp256k1.verifySchnorr(signature.toByteArray(), data.toByteArray(), publicKey.value.toByteArray())
    }

    private fun padLeft(data: ByteArray, size: Int): ByteArray = when {
        data.size == size -> data
        data.size < size -> ByteArray(size - data.size) + data
        else -> throw RuntimeException("cannot pad left: byte array is too big (${data.size} > $size)")
    }

    private fun dropZeroAndFixSize(input: ByteArray, size: Int) = padLeft(input.dropWhile { it == 0.toByte() }.toByteArray(), size)

    @JvmStatic
    public fun compact2der(signature: ByteVector64): ByteVector {
        val normalized = Secp256k1.signatureNormalize(signature.toByteArray()).first
        val der = Secp256k1.compact2der(normalized)
        return ByteVector(der)
    }

    @JvmStatic
    public fun der2compact(signature: ByteArray): ByteVector64 {
        val (r, s) = decodeSignatureLax(ByteArrayInput(signature))
        val lax = dropZeroAndFixSize(r, 32) + dropZeroAndFixSize(s, 32)
        return ByteVector64(Secp256k1.signatureNormalize(lax).first)
    }

    @JvmStatic
    public fun normalize(signature: ByteArray): Pair {
        val (r, s) = decodeSignatureLax(ByteArrayInput(signature))
        val compact = dropZeroAndFixSize(r, 32) + dropZeroAndFixSize(s, 32)
        return Secp256k1.signatureNormalize(compact).let { ByteVector64(it.first) to it.second }
    }

    @JvmStatic
    public fun isDERSignature(sig: ByteArray): Boolean {
        // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash]
        // * total-length: 1-byte length descriptor of everything that follows,
        //   excluding the sighash byte.
        // * R-length: 1-byte length descriptor of the R value that follows.
        // * R: arbitrary-length big-endian encoded R value. It must use the shortest
        //   possible encoding for a positive integers (which means no null bytes at
        //   the start, except a single one when the next byte has its highest bit set).
        // * S-length: 1-byte length descriptor of the S value that follows.
        // * S: arbitrary-length big-endian encoded S value. The same rules apply.
        // * sighash: 1-byte value indicating what data is hashed (not part of the DER
        //   signature)

        // Minimum and maximum size constraints.
        if (sig.size < 9) return false
        if (sig.size > 73) return false

        // A signature is of type 0x30 (compound).
        if (sig[0] != 0x30.toByte()) return false

        // Make sure the length covers the entire signature.
        if (sig[1] != (sig.size - 3).toByte()) return false

        // Extract the length of the R element.
        val lenR = sig[3]

        // Make sure the length of the S element is still inside the signature.
        if (5 + lenR >= sig.size) return false

        // Extract the length of the S element.
        val lenS = sig[5 + lenR]

        // Verify that the length of the signature matches the sum of the length
        // of the elements.
        if (lenR + lenS + 7 != sig.size) return false

        // Check whether the R element is an integer.
        if (sig[2] != 0x02.toByte()) return false

        // Zero-length integers are not allowed for R.
        if (lenR == 0.toByte()) return false

        // Negative numbers are not allowed for R.
        if ((sig[4].toInt() and 0x80) != 0) return false

        // Null bytes at the start of R are not allowed, unless R would
        // otherwise be interpreted as a negative number.
        if (lenR > 1 && (sig[4] == 0x00.toByte()) && (sig[5].toInt() and 0x80) == 0) return false

        // Check whether the S element is an integer.
        if (sig[lenR + 4] != 0x02.toByte()) return false

        // Zero-length integers are not allowed for S.
        if (lenS == 0.toByte()) return false

        // Negative numbers are not allowed for S.
        if ((sig[lenR + 6].toInt() and 0x80) != 0) return false

        // Null bytes at the start of S are not allowed, unless S would otherwise be
        // interpreted as a negative number.
        if (lenS > 1 && (sig[lenR + 6] == 0x00.toByte()) && (sig[lenR + 7].toInt() and 0x80) == 0) return false

        return true
    }

    /**
     * @param sig signature (DER encoded, without a trailing sighash byte)
     * @return true if the input is a "low S" signature
     */
    @JvmStatic
    public fun isLowDERSignature(sig: ByteArray): Boolean = !Secp256k1.signatureNormalize(sig).second

    /**
     * @param sig signature (DER encoded + a trailing sighash byte)
     * @return true if the trailing sighash byte is valid
     */
    @JvmStatic
    public fun isDefinedHashTypeSignature(sig: ByteArray): Boolean = if (sig.isEmpty()) false else {
        val hashType = (sig.last().toInt() and 0xff) and (SigHash.SIGHASH_ANYONECANPAY.inv())
        !((hashType < SigHash.SIGHASH_ALL || hashType > SigHash.SIGHASH_SINGLE))
    }

    /**
     * @param sig signature in Bitcoin format (DER encoded + 1 trailing sighash byte)
     * @param flags script flags
     * @return true if the signature is properly encoded
     */
    @JvmStatic
    public fun checkSignatureEncoding(sig: ByteArray, flags: Int): Boolean {
        // Empty signature. Not strictly DER encoded, but allowed to provide a
        // compact way to provide an invalid signature for use with CHECK(MULTI)SIG
        return when {
            sig.isEmpty() -> true
            (flags and (SCRIPT_VERIFY_DERSIG or SCRIPT_VERIFY_LOW_S or SCRIPT_VERIFY_STRICTENC)) != 0 && !isDERSignature(sig) -> false
            (flags and SCRIPT_VERIFY_LOW_S) != 0 && !isLowDERSignature(sig.dropLast(1).toByteArray()) -> false // drop the sighash byte
            (flags and SCRIPT_VERIFY_STRICTENC) != 0 && !isDefinedHashTypeSignature(sig) -> false
            else -> true
        }
    }

    /**
     * @param key public key
     * @param flags script flags
     * @param sigVersion signature version (legacy or segwit)
     * @return true if the pubkey is properly encoded
     */
    @JvmStatic
    public fun checkPubKeyEncoding(key: ByteArray, flags: Int, sigVersion: Int): Boolean {
        if ((flags and SCRIPT_VERIFY_STRICTENC) != 0) {
            require(isPubKeyCompressedOrUncompressed(key)) { "invalid public key" }
        }
        // Only compressed keys are accepted in segwit
        if ((flags and ScriptFlags.SCRIPT_VERIFY_WITNESS_PUBKEYTYPE) != 0 && sigVersion == SigVersion.SIGVERSION_WITNESS_V0) {
            require(isPubKeyCompressed(key)) { "public key must be compressed in segwit" }
        }
        return true
    }

    @JvmStatic
    public fun decodeSignatureLax(input: ByteArrayInput): Pair {
        require(input.read() == 0x30)

        fun readLength(): Int {
            val len = input.read()
            return if ((len and 0x80) == 0) {
                len
            } else {
                var n = len - 0x80
                var len1 = 0
                while (n > 0) {
                    len1 = (len1 shl 8) + input.read()
                    n -= 1
                }
                len1
            }
        }

        readLength()
        require(input.read() == 0x02)
        val lenR = readLength()
        val r = ByteArray(lenR)
        input.read(r, 0, lenR)
        require(input.read() == 0x02)
        val lenS = readLength()
        val s = ByteArray(lenS)
        input.read(s, 0, lenS)
        return Pair(r, s)
    }

    /**
     * Recover public keys from a signature and the message that was signed. This method will return 2 public keys, and the signature
     * can be verified with both, but only one of them matches that private key that was used to generate the signature.
     * @param sig signature
     * @param message message that was signed
     * @return a (pub1, pub2) tuple where pub1 and pub2 are candidates public keys. If you have the recovery id  then use
     *         pub1 if the recovery id is even and pub2 if it is odd
     */
    @JvmStatic
    public fun recoverPublicKey(sig: ByteVector64, message: ByteArray): Pair {
        val p0 = recoverPublicKey(sig, message, 0)
        val p1 = recoverPublicKey(sig, message, 1)
        return Pair(p0, p1)
    }

    /**
     * Recover public keys from a signature, the message that was signed, and the recovery id (i.e. the sign of
     * the recovered public key)
     * @param sig signature
     * @param message that was signed
     * @recid recovery id
     * @return the recovered public key
     */
    @JvmStatic
    public fun recoverPublicKey(sig: ByteVector64, message: ByteArray, recid: Int): PublicKey {
        return PublicKey(PublicKey.compress(Secp256k1.ecdsaRecover(sig.toByteArray(), message, recid)))
    }

    /**
     * @param input data to be hashed
     * @param tag tag name
     * @return the tagged hash of input as defined in BIP340
     */
    @JvmStatic
    public fun taggedHash(input: ByteArray, tag: String): ByteVector32 {
        val hashedTag = sha256(tag.encodeToByteArray())
        return sha256(hashedTag + hashedTag + input).byteVector32()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy