Native & iOS implementation
This commit is contained in:
parent
54abe2a397
commit
08b6d16836
103
build.gradle.kts
103
build.gradle.kts
@ -15,57 +15,78 @@ val currentOs = org.gradle.internal.os.OperatingSystem.current()
|
||||
kotlin {
|
||||
explicitApi()
|
||||
|
||||
val commonMain by sourceSets.getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-common"))
|
||||
}
|
||||
}
|
||||
val commonTest by sourceSets.getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
}
|
||||
|
||||
jvm {
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
(tasks[compilations["main"].processResourcesTaskName] as ProcessResources).apply{
|
||||
dependsOn("copyJni")
|
||||
from(buildDir.resolve("jniResources"))
|
||||
}
|
||||
compilations["main"].dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
}
|
||||
compilations["test"].dependencies {
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
|
||||
// linuxX64()
|
||||
// ios()
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-common"))
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
}
|
||||
}
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
implementation(kotlin("test-junit"))
|
||||
}
|
||||
}
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test-junit"))
|
||||
fun org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget.secp256k1CInterop() {
|
||||
compilations["main"].cinterops {
|
||||
val libsecp256k1 by creating {
|
||||
includeDirs.headerFilterOnly(project.file("native/secp256k1/include/"))
|
||||
// includeDirs("/usr/local/lib")
|
||||
tasks[interopProcessingTaskName].dependsOn("buildSecp256k1Ios")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val nativeMain by sourceSets.creating { dependsOn(commonMain) }
|
||||
|
||||
linuxX64 {
|
||||
secp256k1CInterop()
|
||||
// https://youtrack.jetbrains.com/issue/KT-39396
|
||||
compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary", "$rootDir/native/build/linux/libsecp256k1.a")
|
||||
compilations["main"].defaultSourceSet.dependsOn(nativeMain)
|
||||
}
|
||||
|
||||
ios {
|
||||
secp256k1CInterop()
|
||||
// https://youtrack.jetbrains.com/issue/KT-39396
|
||||
compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary", "$rootDir/native/build/ios/libsecp256k1.a")
|
||||
compilations["main"].defaultSourceSet.dependsOn(nativeMain)
|
||||
}
|
||||
|
||||
sourceSets.all {
|
||||
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val buildSecp256k1 by tasks.creating { group = "build" }
|
||||
fun creatingBuildSecp256k1(target: String, cross: String? = null, env: String = "", configuration: Task.() -> Unit = {}) = tasks.creating {
|
||||
fun creatingBuildSecp256k1(target: String, cross: String? = null, env: String = "", configuration: Task.() -> Unit = {}) = tasks.creating(Exec::class) {
|
||||
group = "build"
|
||||
buildSecp256k1.dependsOn(this)
|
||||
|
||||
inputs.files(projectDir.resolve("native/build.sh"))
|
||||
outputs.dir(projectDir.resolve("native/build/$target"))
|
||||
|
||||
doLast {
|
||||
exec {
|
||||
workingDir = projectDir.resolve("native")
|
||||
environment("TARGET", target)
|
||||
if (cross == null) commandLine("./build.sh")
|
||||
else commandLine("./dockcross-$cross", "bash", "-c", "TARGET=$target ./build.sh")
|
||||
}
|
||||
}
|
||||
workingDir = projectDir.resolve("native")
|
||||
environment("TARGET", target)
|
||||
if (cross == null) commandLine("./build.sh")
|
||||
else commandLine("./dockcross-$cross", "bash", "-c", "TARGET=$target ./build.sh")
|
||||
|
||||
configuration()
|
||||
}
|
||||
@ -74,6 +95,17 @@ val buildSecp256k1Darwin by creatingBuildSecp256k1("darwin")
|
||||
val buildSecp256k1Linux by creatingBuildSecp256k1("linux", cross = if (currentOs.isMacOsX) "linux-x64" else null)
|
||||
val buildSecp256k1Mingw by creatingBuildSecp256k1("mingw", cross = "windows-x64", env = "CONF_OPTS=--host=x86_64-w64-mingw32")
|
||||
|
||||
val buildSecp256k1Ios by tasks.creating(Exec::class) {
|
||||
group = "build"
|
||||
buildSecp256k1.dependsOn(this)
|
||||
|
||||
inputs.files(projectDir.resolve("native/build-ios.sh"))
|
||||
outputs.dir(projectDir.resolve("native/build/ios"))
|
||||
|
||||
workingDir = projectDir.resolve("native")
|
||||
commandLine("./build-ios.sh")
|
||||
}
|
||||
|
||||
val copyJni by tasks.creating(Sync::class) {
|
||||
dependsOn(buildSecp256k1)
|
||||
from(projectDir.resolve("native/build/linux/libsecp256k1-jni.so")) { rename { "libsecp256k1-jni-linux-x86_64.so" } }
|
||||
@ -81,8 +113,3 @@ val copyJni by tasks.creating(Sync::class) {
|
||||
from(projectDir.resolve("native/build/mingw/secp256k1-jni.dll")) { rename { "secp256k1-jni-mingw-x86_64.dll" } }
|
||||
into(buildDir.resolve("jniResources/fr/acinq/secp256k1/native"))
|
||||
}
|
||||
|
||||
(tasks[kotlin.jvm().compilations["main"].processResourcesTaskName] as ProcessResources).apply{
|
||||
dependsOn(copyJni)
|
||||
from(buildDir.resolve("jniResources"))
|
||||
}
|
||||
|
@ -1 +1,13 @@
|
||||
# gradle
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
org.gradle.parallel=true
|
||||
|
||||
# kotlin
|
||||
kotlin.code.style=official
|
||||
kotlin.incremental.multiplatform = true
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
#kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
kotlin.native.enableDependencyPropagation=false
|
||||
|
||||
# https://github.com/gradle/gradle/issues/11412
|
||||
systemProp.org.gradle.internal.publish.checksums.insecure=true
|
||||
|
0
native/build-ios.sh
Normal file → Executable file
0
native/build-ios.sh
Normal file → Executable file
@ -35,8 +35,6 @@ public expect object Secp256k1 {
|
||||
|
||||
public fun cleanup()
|
||||
|
||||
public fun cloneContext(): Long
|
||||
|
||||
public fun privKeyNegate(privkey: ByteArray): ByteArray
|
||||
|
||||
public fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray
|
||||
|
@ -2,7 +2,7 @@ package fr.acinq.secp256k1
|
||||
|
||||
import kotlin.jvm.JvmStatic
|
||||
|
||||
object Hex {
|
||||
internal object Hex {
|
||||
private val hexCode = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
|
||||
|
||||
@JvmStatic
|
@ -39,8 +39,6 @@ public actual object Secp256k1 {
|
||||
|
||||
public actual fun cleanup(): Unit = NativeSecp256k1.cleanup()
|
||||
|
||||
public actual fun cloneContext(): Long = NativeSecp256k1.cloneContext()
|
||||
|
||||
public actual fun privKeyNegate(privkey: ByteArray): ByteArray = NativeSecp256k1.privKeyNegate(privkey)
|
||||
|
||||
public actual fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray = NativeSecp256k1.privKeyTweakMul(privkey, tweak)
|
||||
|
@ -225,16 +225,6 @@ public object NativeSecp256k1 {
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun cloneContext(): Long {
|
||||
r.lock()
|
||||
return try {
|
||||
secp256k1_ctx_clone(Secp256k1Context.getContext())
|
||||
} finally {
|
||||
r.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(AssertFailException::class)
|
||||
public fun privKeyNegate(privkey: ByteArray): ByteArray {
|
||||
@ -343,7 +333,7 @@ public object NativeSecp256k1 {
|
||||
@Throws(AssertFailException::class)
|
||||
public fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||
require(pubkey.size == 33 || pubkey.size == 65)
|
||||
val byteBuff = pack(pubkey, tweak!!)
|
||||
val byteBuff = pack(pubkey, tweak)
|
||||
val retByteArray: Array<ByteArray>
|
||||
r.lock()
|
||||
retByteArray = try {
|
||||
@ -476,7 +466,6 @@ public object NativeSecp256k1 {
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic private external fun secp256k1_ctx_clone(context: Long): Long
|
||||
@JvmStatic private external fun secp256k1_context_randomize(byteBuff: ByteBuffer, context: Long): Int
|
||||
@JvmStatic private external fun secp256k1_privkey_negate(byteBuff: ByteBuffer, context: Long): Array<ByteArray>
|
||||
@JvmStatic private external fun secp256k1_privkey_tweak_add(byteBuff: ByteBuffer, context: Long): Array<ByteArray>
|
||||
|
12
src/nativeInterop/cinterop/libsecp256k1.def
Normal file
12
src/nativeInterop/cinterop/libsecp256k1.def
Normal file
@ -0,0 +1,12 @@
|
||||
package = secp256k1
|
||||
|
||||
headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h
|
||||
headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1.h
|
||||
|
||||
staticLibraries.linux = libsecp256k1.a
|
||||
libraryPaths.linux = c/secp256k1/build/linux/
|
||||
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/local/lib
|
||||
|
||||
staticLibraries.ios = libsecp256k1.a
|
||||
libraryPaths.ios = c/secp256k1/build/ios/ /usr/local/lib
|
||||
linkerOpts.ios = -framework Security -framework Foundation
|
238
src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt
Normal file
238
src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt
Normal file
@ -0,0 +1,238 @@
|
||||
package fr.acinq.secp256k1
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.size_tVar
|
||||
import secp256k1.*
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
public actual object Secp256k1 {
|
||||
|
||||
private const val SIG_FORMAT_UNKNOWN = 0
|
||||
private const val SIG_FORMAT_COMPACT = 1
|
||||
private const val SIG_FORMAT_DER = 2
|
||||
|
||||
private val ctx: CPointer<secp256k1_context> by lazy {
|
||||
secp256k1_context_create((SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_SIGN or SECP256K1_FLAGS_BIT_CONTEXT_VERIFY).toUInt())
|
||||
?: error("Could not create segp256k1 context")
|
||||
}
|
||||
|
||||
private fun Int.requireSuccess() = require(this == 1) { "secp256k1 native function call failed" }
|
||||
|
||||
private fun MemScope.allocSignature(input: ByteArray): secp256k1_ecdsa_signature {
|
||||
val sigFormat = when (input.size) {
|
||||
64 -> SIG_FORMAT_COMPACT
|
||||
70, 71, 72, 73 -> SIG_FORMAT_DER
|
||||
else -> SIG_FORMAT_UNKNOWN
|
||||
}
|
||||
val sig = alloc<secp256k1_ecdsa_signature>()
|
||||
val nativeBytes = toNat(input)
|
||||
val result = when (sigFormat) {
|
||||
SIG_FORMAT_COMPACT -> secp256k1_ecdsa_signature_parse_compact(ctx, sig.ptr, nativeBytes)
|
||||
SIG_FORMAT_DER -> secp256k1_ecdsa_signature_parse_der(ctx, sig.ptr, nativeBytes, input.size.toULong())
|
||||
else -> 0
|
||||
}
|
||||
require(result == 1) { "cannot parse signature (size = ${input.size}, format = $sigFormat sig = ${Hex.encode(input)}" }
|
||||
return sig
|
||||
}
|
||||
|
||||
private fun MemScope.allocPublicKey(pubkey: ByteArray): secp256k1_pubkey {
|
||||
val natPub = toNat(pubkey)
|
||||
val pub = alloc<secp256k1_pubkey>()
|
||||
secp256k1_ec_pubkey_parse(ctx, pub.ptr, natPub, pubkey.size.convert()).requireSuccess()
|
||||
return pub
|
||||
}
|
||||
|
||||
private fun MemScope.serializePubkey(pubkey: secp256k1_pubkey, len: Int): ByteArray {
|
||||
val serialized = allocArray<UByteVar>(len)
|
||||
val outputLen = alloc<size_tVar>()
|
||||
outputLen.value = len.convert()
|
||||
secp256k1_ec_pubkey_serialize(ctx, serialized, outputLen.ptr, pubkey.ptr, (if (len == 33) SECP256K1_EC_COMPRESSED else SECP256K1_EC_UNCOMPRESSED).convert()).requireSuccess()
|
||||
return serialized.readBytes(outputLen.value.convert())
|
||||
}
|
||||
|
||||
private fun DeferScope.toNat(bytes: ByteArray): CPointer<UByteVar> {
|
||||
val ubytes = bytes.asUByteArray()
|
||||
val pinned = ubytes.pin()
|
||||
this.defer { pinned.unpin() }
|
||||
return pinned.addressOf(0)
|
||||
}
|
||||
|
||||
public actual fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean {
|
||||
require(data.size == 32)
|
||||
require(pub.size == 33 || pub.size == 65)
|
||||
memScoped {
|
||||
val pubkey = allocPublicKey(pub)
|
||||
val natData = toNat(data)
|
||||
val parsedSig = allocSignature(signature)
|
||||
return secp256k1_ecdsa_verify(ctx, parsedSig.ptr, natData, pubkey.ptr) == 1
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun sign(data: ByteArray, sec: ByteArray): ByteArray {
|
||||
require(sec.size == 32)
|
||||
require(data.size == 32)
|
||||
memScoped {
|
||||
val natSec = toNat(sec)
|
||||
val natData = toNat(data)
|
||||
val natSig = alloc<secp256k1_ecdsa_signature>()
|
||||
val result = secp256k1_ecdsa_sign(ctx, natSig.ptr, natData, natSec, null, null)
|
||||
if (result == 0) return ByteArray(0)
|
||||
val natOutput = allocArray<UByteVar>(72)
|
||||
val outputLen = alloc<size_tVar>()
|
||||
outputLen.value = 72.convert()
|
||||
secp256k1_ecdsa_signature_serialize_der(ctx, natOutput, outputLen.ptr, natSig.ptr).requireSuccess()
|
||||
return natOutput.readBytes(outputLen.value.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun signCompact(data: ByteArray, sec: ByteArray): ByteArray {
|
||||
require(data.size == 32)
|
||||
require(sec.size <= 32)
|
||||
memScoped {
|
||||
val natSec = toNat(sec)
|
||||
val natData = toNat(data)
|
||||
val natSig = alloc<secp256k1_ecdsa_signature>()
|
||||
secp256k1_ecdsa_sign(ctx, natSig.ptr, natData, natSec, null, null).requireSuccess()
|
||||
val natCompact = allocArray<UByteVar>(64)
|
||||
secp256k1_ecdsa_signature_serialize_compact(ctx, natCompact, natSig.ptr).requireSuccess()
|
||||
return natCompact.readBytes(64)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun secKeyVerify(seckey: ByteArray): Boolean {
|
||||
require(seckey.size == 32)
|
||||
memScoped {
|
||||
val natSec = toNat(seckey)
|
||||
return secp256k1_ec_seckey_verify(ctx, natSec) == 1
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun computePubkey(seckey: ByteArray): ByteArray {
|
||||
require(seckey.size == 32)
|
||||
memScoped {
|
||||
val natSec = toNat(seckey)
|
||||
val pubkey = alloc<secp256k1_pubkey>()
|
||||
val result = secp256k1_ec_pubkey_create(ctx, pubkey.ptr, natSec)
|
||||
if (result == 0) return ByteArray(0)
|
||||
return serializePubkey(pubkey, 65)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun parsePubkey(pubkey: ByteArray): ByteArray {
|
||||
require(pubkey.size == 33 || pubkey.size == 65)
|
||||
memScoped {
|
||||
val nPubkey = allocPublicKey(pubkey)
|
||||
return serializePubkey(nPubkey, 65)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun cleanup() {
|
||||
secp256k1_context_destroy(ctx)
|
||||
}
|
||||
|
||||
public actual fun privKeyNegate(privkey: ByteArray): ByteArray {
|
||||
require(privkey.size == 32)
|
||||
memScoped {
|
||||
val negated = privkey.copyOf()
|
||||
val negPriv = toNat(negated)
|
||||
secp256k1_ec_privkey_negate(ctx, negPriv).requireSuccess()
|
||||
return negated
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||
require(privkey.size == 32)
|
||||
memScoped {
|
||||
val multiplied = privkey.copyOf()
|
||||
val natMul = toNat(multiplied)
|
||||
val natTweak = toNat(tweak)
|
||||
secp256k1_ec_privkey_tweak_mul(ctx, natMul, natTweak).requireSuccess()
|
||||
return multiplied
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||
require(privkey.size == 32)
|
||||
memScoped {
|
||||
val added = privkey.copyOf()
|
||||
val natAdd = toNat(added)
|
||||
val natTweak = toNat(tweak)
|
||||
secp256k1_ec_privkey_tweak_add(ctx, natAdd, natTweak).requireSuccess()
|
||||
return added
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun pubKeyNegate(pubkey: ByteArray): ByteArray {
|
||||
require(pubkey.size == 33 || pubkey.size == 65)
|
||||
memScoped {
|
||||
val nPubkey = allocPublicKey(pubkey)
|
||||
secp256k1_ec_pubkey_negate(ctx, nPubkey.ptr).requireSuccess()
|
||||
return serializePubkey(nPubkey, pubkey.size)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||
require(pubkey.size == 33 || pubkey.size == 65)
|
||||
memScoped {
|
||||
val nPubkey = allocPublicKey(pubkey)
|
||||
val nTweak = toNat(tweak)
|
||||
secp256k1_ec_pubkey_tweak_add(ctx, nPubkey.ptr, nTweak).requireSuccess()
|
||||
return serializePubkey(nPubkey, 65)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||
require(pubkey.size == 33 || pubkey.size == 65)
|
||||
memScoped {
|
||||
val nPubkey = allocPublicKey(pubkey)
|
||||
val nTweak = toNat(tweak)
|
||||
secp256k1_ec_pubkey_tweak_mul(ctx, nPubkey.ptr, nTweak).requireSuccess()
|
||||
return serializePubkey(nPubkey, 65)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray {
|
||||
require(pubkey1.size == 33 || pubkey2.size == 65)
|
||||
memScoped {
|
||||
val nPubkey1 = allocPublicKey(pubkey1)
|
||||
val nPubkey2 = allocPublicKey(pubkey2)
|
||||
val combined = nativeHeap.alloc<secp256k1_pubkey>()
|
||||
secp256k1_ec_pubkey_combine(ctx, combined.ptr, cValuesOf(nPubkey1.ptr, nPubkey2.ptr), 2.convert()).requireSuccess()
|
||||
return serializePubkey(combined, 65)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun createECDHSecret(seckey: ByteArray, pubkey: ByteArray): ByteArray {
|
||||
require(seckey.size == 32)
|
||||
require(pubkey.size == 33 || pubkey.size == 65)
|
||||
memScoped {
|
||||
val nPubkey = allocPublicKey(pubkey)
|
||||
val nSeckey = toNat(seckey)
|
||||
val output = allocArray<UByteVar>(32)
|
||||
secp256k1_ecdh(ctx, output, nPubkey.ptr, nSeckey, null, null).requireSuccess()
|
||||
return output.readBytes(32)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray {
|
||||
require(sig.size == 64)
|
||||
require(message.size == 32)
|
||||
memScoped {
|
||||
val nSig = toNat(sig)
|
||||
val rSig = nativeHeap.alloc<secp256k1_ecdsa_recoverable_signature>()
|
||||
secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, rSig.ptr, nSig, recid).requireSuccess()
|
||||
val nMessage = toNat(message)
|
||||
val pubkey = nativeHeap.alloc<secp256k1_pubkey>()
|
||||
secp256k1_ecdsa_recover(ctx, pubkey.ptr, rSig.ptr, nMessage).requireSuccess()
|
||||
return serializePubkey(pubkey, 65)
|
||||
}
|
||||
}
|
||||
|
||||
public actual fun randomize(seed: ByteArray): Boolean {
|
||||
require(seed.size == 32)
|
||||
memScoped {
|
||||
val nSeed = toNat(seed)
|
||||
return secp256k1_context_randomize(ctx, nSeed) == 1
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user