commonMain.fr.acinq.bitcoin.PrivateKey.kt Maven / Gradle / Ivy
/*
* 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.secp256k1.Hex
import fr.acinq.secp256k1.Secp256k1
import kotlin.jvm.JvmField
import kotlin.jvm.JvmStatic
/**
* A bitcoin private key.
* A private key is valid if it is not 0 and less than the secp256k1 curve order when interpreted as an integer (most significant byte first).
* The probability of choosing a 32-byte string uniformly at random which is an invalid private key is negligible, so this condition is not checked by default.
* However, if you receive a private key from an external, untrusted source, you should call `isValid()` before actually using it.
*/
public data class PrivateKey(@JvmField val value: ByteVector32) {
public constructor(data: ByteArray) : this(
when {
data.size == 32 -> ByteVector32(data.copyOf())
data.size == 33 && data.last() == 1.toByte() -> ByteVector32(data.copyOf(32))
else -> throw RuntimeException("invalid private key length")
}
)
public constructor(data: ByteVector) : this(data.toByteArray())
/**
* A private key is valid if it is not 0 and less than the secp256k1 curve order when interpreted as an integer (most significant byte first).
* The probability of choosing a 32-byte string uniformly at random which is an invalid private key is negligible.
*/
public fun isValid(): Boolean = Crypto.isPrivKeyValid(value.toByteArray())
public operator fun plus(that: PrivateKey): PrivateKey = PrivateKey(Secp256k1.privKeyTweakAdd(value.toByteArray(), that.value.toByteArray()))
public operator fun unaryMinus(): PrivateKey = PrivateKey(Secp256k1.privKeyNegate(value.toByteArray()))
public operator fun minus(that: PrivateKey): PrivateKey = plus(-that)
public operator fun times(that: PrivateKey): PrivateKey =
PrivateKey(Secp256k1.privKeyTweakMul(value.toByteArray(), that.value.toByteArray()))
public fun tweak(tweak: ByteVector32): PrivateKey {
val key = if (publicKey().isEven()) this else -this
return key + PrivateKey(tweak)
}
public fun publicKey(): PublicKey {
val pub = Secp256k1.pubkeyCreate(value.toByteArray())
return PublicKey(PublicKey.compress(pub))
}
public fun xOnlyPublicKey(): XonlyPublicKey = XonlyPublicKey(publicKey())
public fun compress(): ByteArray = value.toByteArray() + 1.toByte()
public fun toBase58(prefix: Byte): String = Base58Check.encode(prefix, compress())
public fun toHex(): String = value.toHex()
/**
* We avoid accidentally logging private keys.
* You should use an explicit method if you want to convert the private key to a string representation.
*/
override fun toString(): String = ""
public companion object {
@JvmStatic
public fun isCompressed(data: ByteArray): Boolean {
return when {
data.size == 32 -> false
data.size == 33 && data.last() == 1.toByte() -> true
else -> throw IllegalArgumentException("invalid private key ${Hex.encode(data)}")
}
}
@JvmStatic
public fun fromBase58(value: String, prefix: Byte): Pair {
require(setOf(Base58.Prefix.SecretKey, Base58.Prefix.SecretKeyTestnet).contains(prefix)) { "invalid base 58 prefix for a private key" }
val (prefix1, data) = Base58Check.decode(value)
require(prefix1 == prefix) { "prefix $prefix1 does not match expected prefix $prefix" }
return Pair(PrivateKey(data), isCompressed(data))
}
@JvmStatic
public fun fromHex(hex: String): PrivateKey = PrivateKey(Hex.decode(hex))
}
}