Android & JVM loader in their own modules
This commit is contained in:
parent
9e1fc5d5ff
commit
1d9d57ca0a
271
build.gradle.kts
271
build.gradle.kts
@ -1,15 +1,29 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform") version "1.4-M2-mt"
|
kotlin("multiplatform") version "1.4-M2-mt"
|
||||||
id("com.android.library") version "4.0.0"
|
// `maven-publish`
|
||||||
`maven-publish`
|
|
||||||
}
|
}
|
||||||
group = "fr.acinq.secp256k1"
|
|
||||||
version = "0.2.1-1.4-M2"
|
|
||||||
|
|
||||||
repositories {
|
buildscript {
|
||||||
jcenter()
|
repositories {
|
||||||
google()
|
google()
|
||||||
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
|
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath("com.android.tools.build:gradle:4.0.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
group = "fr.acinq.secp256k1"
|
||||||
|
version = "0.2.1-1.4-M2"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentOs = org.gradle.internal.os.OperatingSystem.current()
|
val currentOs = org.gradle.internal.os.OperatingSystem.current()
|
||||||
@ -29,60 +43,40 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val jvmAndAndroidMain by sourceSets.creating {
|
|
||||||
dependsOn(commonMain)
|
|
||||||
dependencies {
|
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jvm {
|
jvm {
|
||||||
compilations.all {
|
compilations.all {
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
(tasks[compilations["main"].processResourcesTaskName] as ProcessResources).apply{
|
compilations["main"].dependencies {
|
||||||
dependsOn("copyJni")
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
from(buildDir.resolve("jniResources"))
|
|
||||||
}
|
}
|
||||||
compilations["main"].defaultSourceSet.dependsOn(jvmAndAndroidMain)
|
|
||||||
compilations["test"].dependencies {
|
compilations["test"].dependencies {
|
||||||
|
implementation(project(":jni"))
|
||||||
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
implementation(kotlin("test-junit"))
|
implementation(kotlin("test-junit"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
fun org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget.secp256k1CInterop(target: String) {
|
||||||
publishLibraryVariants("release", "debug")
|
|
||||||
compilations.all {
|
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
sourceSets["androidMain"].dependsOn(jvmAndAndroidMain)
|
|
||||||
sourceSets["androidTest"].dependencies {
|
|
||||||
implementation(kotlin("test-junit"))
|
|
||||||
implementation("androidx.test.ext:junit:1.1.1")
|
|
||||||
implementation("androidx.test.espresso:espresso-core:3.2.0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget.secp256k1CInterop() {
|
|
||||||
compilations["main"].cinterops {
|
compilations["main"].cinterops {
|
||||||
val libsecp256k1 by creating {
|
val libsecp256k1 by creating {
|
||||||
includeDirs.headerFilterOnly(project.file("native/secp256k1/include/"))
|
includeDirs.headerFilterOnly(project.file("native/secp256k1/include/"))
|
||||||
tasks[interopProcessingTaskName].dependsOn("buildSecp256k1Ios")
|
tasks[interopProcessingTaskName].dependsOn(":native:buildSecp256k1${target.capitalize()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val nativeMain by sourceSets.creating { dependsOn(commonMain) }
|
val nativeMain by sourceSets.creating { dependsOn(commonMain) }
|
||||||
|
|
||||||
linuxX64 {
|
linuxX64("linux") {
|
||||||
secp256k1CInterop()
|
secp256k1CInterop("linux")
|
||||||
// https://youtrack.jetbrains.com/issue/KT-39396
|
// https://youtrack.jetbrains.com/issue/KT-39396
|
||||||
compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary", "$rootDir/native/build/linux/libsecp256k1.a")
|
compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary", "$rootDir/native/build/linux/libsecp256k1.a")
|
||||||
compilations["main"].defaultSourceSet.dependsOn(nativeMain)
|
compilations["main"].defaultSourceSet.dependsOn(nativeMain)
|
||||||
}
|
}
|
||||||
|
|
||||||
ios {
|
ios {
|
||||||
secp256k1CInterop()
|
secp256k1CInterop("ios")
|
||||||
// https://youtrack.jetbrains.com/issue/KT-39396
|
// https://youtrack.jetbrains.com/issue/KT-39396
|
||||||
compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary", "$rootDir/native/build/ios/libsecp256k1.a")
|
compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary", "$rootDir/native/build/ios/libsecp256k1.a")
|
||||||
compilations["main"].defaultSourceSet.dependsOn(nativeMain)
|
compilations["main"].defaultSourceSet.dependsOn(nativeMain)
|
||||||
@ -120,166 +114,43 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
//publishing {
|
||||||
defaultConfig {
|
// val snapshotName: String? by project
|
||||||
compileSdkVersion(30)
|
// val snapshotNumber: String? by project
|
||||||
minSdkVersion(21)
|
//
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
// val bintrayUsername: String? = (properties["bintrayUsername"] as String?) ?: System.getenv("BINTRAY_USER")
|
||||||
externalNativeBuild {
|
// val bintrayApiKey: String? = (properties["bintrayApiKey"] as String?) ?: System.getenv("BINTRAY_APIKEY")
|
||||||
cmake {}
|
// if (bintrayUsername == null || bintrayApiKey == null) logger.warn("Skipping bintray configuration as bintrayUsername or bintrayApiKey is not defined")
|
||||||
}
|
// else {
|
||||||
}
|
// val btRepo = if (snapshotNumber != null) "snapshots" else "libs"
|
||||||
|
// repositories {
|
||||||
compileOptions {
|
// maven {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
// name = "bintray"
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
// setUrl("https://api.bintray.com/maven/acinq/$btRepo/${project.name}/;publish=0")
|
||||||
}
|
// credentials {
|
||||||
|
// username = bintrayUsername
|
||||||
externalNativeBuild {
|
// password = bintrayApiKey
|
||||||
cmake {
|
// }
|
||||||
setPath("src/androidMain/CMakeLists.txt")
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
ndkVersion = "21.3.6528147"
|
//
|
||||||
|
// publications.withType<MavenPublication>().configureEach {
|
||||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
// if (snapshotName != null && snapshotNumber != null) version = "${project.version}-${snapshotName}-${snapshotNumber}"
|
||||||
|
// pom {
|
||||||
afterEvaluate {
|
// description.set("Bitcoin's secp256k1 library ported to Kotlin/Multiplatform for JVM, Android, iOS & Linux")
|
||||||
tasks.withType<com.android.build.gradle.tasks.factory.AndroidUnitTest>().all {
|
// url.set("https://github.com/ACINQ/secp256k1-kmp")
|
||||||
enabled = false
|
// licenses {
|
||||||
}
|
// name.set("Apache License v2.0")
|
||||||
}
|
// url.set("https://www.apache.org/licenses/LICENSE-2.0")
|
||||||
}
|
// }
|
||||||
|
// issueManagement {
|
||||||
val buildSecp256k1 by tasks.creating { group = "build" }
|
// system.set("Github")
|
||||||
sealed class Cross {
|
// url.set("https://github.com/ACINQ/secp256k1-kmp/issues")
|
||||||
abstract fun cmd(target: String, nativeDir: File): List<String>
|
// }
|
||||||
class DockCross(val cross: String) : Cross() {
|
// scm {
|
||||||
override fun cmd(target: String, nativeDir: File): List<String> = listOf("./dockcross-$cross", "bash", "-c", "CROSS=1 TARGET=$target ./build.sh")
|
// connection.set("https://github.com/ACINQ/secp256k1-kmp.git")
|
||||||
}
|
// }
|
||||||
class MultiArch(val crossTriple: String) : Cross() {
|
// }
|
||||||
override fun cmd(target: String, nativeDir: File): List<String> {
|
// }
|
||||||
val uid = Runtime.getRuntime().exec("id -u").inputStream.use { it.reader().readText() }.trim().toInt()
|
//}
|
||||||
return listOf(
|
|
||||||
"docker", "run", "--rm", "-v", "${nativeDir.absolutePath}:/workdir",
|
|
||||||
"-e", "CROSS_TRIPLE=$crossTriple", "-e", "TARGET=$target", "-e", "TO_UID=$uid", "-e", "CROSS=1",
|
|
||||||
"multiarch/crossbuild", "./build.sh"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun creatingBuildSecp256k1(target: String, cross: Cross?) = tasks.creating(Exec::class) {
|
|
||||||
group = "build"
|
|
||||||
buildSecp256k1.dependsOn(this)
|
|
||||||
|
|
||||||
inputs.files(projectDir.resolve("native/build.sh"))
|
|
||||||
outputs.dir(projectDir.resolve("native/build/$target"))
|
|
||||||
|
|
||||||
workingDir = projectDir.resolve("native")
|
|
||||||
environment("TARGET", target)
|
|
||||||
commandLine((cross?.cmd(target, workingDir) ?: emptyList()) + "./build.sh")
|
|
||||||
}
|
|
||||||
val buildSecp256k1Darwin by creatingBuildSecp256k1("darwin", if (currentOs.isMacOsX) null else Cross.MultiArch("x86_64-apple-darwin"))
|
|
||||||
val buildSecp256k1Linux by creatingBuildSecp256k1("linux", if (currentOs.isLinux) null else Cross.DockCross("linux-x64"))
|
|
||||||
val buildSecp256k1Mingw by creatingBuildSecp256k1("mingw", if (currentOs.isWindows) null else Cross.DockCross("windows-x64"))
|
|
||||||
|
|
||||||
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" } }
|
|
||||||
from(projectDir.resolve("native/build/darwin/libsecp256k1-jni.dylib")) { rename { "libsecp256k1-jni-darwin-x86_64.dylib" } }
|
|
||||||
from(projectDir.resolve("native/build/mingw/secp256k1-jni.dll")) { rename { "secp256k1-jni-mingw-x86_64.dll" } }
|
|
||||||
into(buildDir.resolve("jniResources/fr/acinq/secp256k1/native"))
|
|
||||||
}
|
|
||||||
|
|
||||||
val buildSecp256k1Ios by tasks.creating(Exec::class) {
|
|
||||||
group = "build"
|
|
||||||
buildSecp256k1.dependsOn(this)
|
|
||||||
|
|
||||||
onlyIf { currentOs.isMacOsX }
|
|
||||||
|
|
||||||
inputs.files(projectDir.resolve("native/build-ios.sh"))
|
|
||||||
outputs.dir(projectDir.resolve("native/build/ios"))
|
|
||||||
|
|
||||||
workingDir = projectDir.resolve("native")
|
|
||||||
commandLine("./build-ios.sh")
|
|
||||||
}
|
|
||||||
|
|
||||||
val buildSecp256k1Android by tasks.creating {
|
|
||||||
group = "build"
|
|
||||||
buildSecp256k1.dependsOn(this)
|
|
||||||
}
|
|
||||||
fun creatingBuildSecp256k1Android(arch: String) = tasks.creating(Exec::class) {
|
|
||||||
group = "build"
|
|
||||||
buildSecp256k1Android.dependsOn(this)
|
|
||||||
|
|
||||||
inputs.files(projectDir.resolve("native/build-android.sh"))
|
|
||||||
outputs.dir(projectDir.resolve("native/build/android/$arch"))
|
|
||||||
|
|
||||||
workingDir = projectDir.resolve("native")
|
|
||||||
|
|
||||||
val toolchain = when {
|
|
||||||
currentOs.isLinux -> "linux-x86_64"
|
|
||||||
currentOs.isMacOsX -> "darwin-x86_64"
|
|
||||||
currentOs.isWindows -> "windows-x86_64"
|
|
||||||
else -> error("No Android toolchain defined for this OS: $currentOs")
|
|
||||||
}
|
|
||||||
environment("TOOLCHAIN", toolchain)
|
|
||||||
environment("ARCH", arch)
|
|
||||||
environment("ANDROID_NDK", android.ndkDirectory)
|
|
||||||
commandLine("./build-android.sh")
|
|
||||||
}
|
|
||||||
val buildSecp256k1AndroidX86_64 by creatingBuildSecp256k1Android("x86_64")
|
|
||||||
val buildSecp256k1AndroidX86 by creatingBuildSecp256k1Android("x86")
|
|
||||||
val buildSecp256k1AndroidArm64v8a by creatingBuildSecp256k1Android("arm64-v8a")
|
|
||||||
val buildSecp256k1AndroidArmeabiv7a by creatingBuildSecp256k1Android("armeabi-v7a")
|
|
||||||
|
|
||||||
afterEvaluate {
|
|
||||||
configure(listOf("Debug", "Release").map { tasks["externalNativeBuild$it"] }) {
|
|
||||||
dependsOn(buildSecp256k1Android)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks["clean"].doLast {
|
|
||||||
delete(projectDir.resolve("native/build"))
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
val snapshotName: String? by project
|
|
||||||
val snapshotNumber: String? by project
|
|
||||||
|
|
||||||
val bintrayUsername: String? = (properties["bintrayUsername"] as String?) ?: System.getenv("BINTRAY_USER")
|
|
||||||
val bintrayApiKey: String? = (properties["bintrayApiKey"] as String?) ?: System.getenv("BINTRAY_APIKEY")
|
|
||||||
if (bintrayUsername == null || bintrayApiKey == null) logger.warn("Skipping bintray configuration as bintrayUsername or bintrayApiKey is not defined")
|
|
||||||
else {
|
|
||||||
val btRepo = if (snapshotNumber != null) "snapshots" else "libs"
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
name = "bintray"
|
|
||||||
setUrl("https://api.bintray.com/maven/acinq/$btRepo/${project.name}/;publish=0")
|
|
||||||
credentials {
|
|
||||||
username = bintrayUsername
|
|
||||||
password = bintrayApiKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
publications.withType<MavenPublication>().configureEach {
|
|
||||||
if (snapshotName != null && snapshotNumber != null) version = "${project.version}-${snapshotName}-${snapshotNumber}"
|
|
||||||
pom {
|
|
||||||
description.set("Bitcoin's secp256k1 library ported to Kotlin/Multiplatform for JVM, Android, iOS & Linux")
|
|
||||||
url.set("https://github.com/ACINQ/secp256k1-kmp")
|
|
||||||
licenses {
|
|
||||||
name.set("Apache License v2.0")
|
|
||||||
url.set("https://www.apache.org/licenses/LICENSE-2.0")
|
|
||||||
}
|
|
||||||
issueManagement {
|
|
||||||
system.set("Github")
|
|
||||||
url.set("https://github.com/ACINQ/secp256k1-kmp/issues")
|
|
||||||
}
|
|
||||||
scm {
|
|
||||||
connection.set("https://github.com/ACINQ/secp256k1-kmp.git")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
103
jni/build.gradle.kts
Normal file
103
jni/build.gradle.kts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("multiplatform") // version "1.4-M2-mt"
|
||||||
|
id("com.android.library")
|
||||||
|
`maven-publish`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
publishLibraryVariants("release", "debug")
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions.jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
sourceSets["androidMain"].dependencies {
|
||||||
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
|
}
|
||||||
|
sourceSets["androidTest"].dependencies {
|
||||||
|
implementation(kotlin("test-junit"))
|
||||||
|
implementation("androidx.test.ext:junit:1.1.1")
|
||||||
|
implementation("androidx.test.espresso:espresso-core:3.2.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets.all {
|
||||||
|
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
compileSdkVersion(30)
|
||||||
|
minSdkVersion(21)
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
setPath("src/androidMain/CMakeLists.txt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ndkVersion = "21.3.6528147"
|
||||||
|
|
||||||
|
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
tasks.withType<com.android.build.gradle.tasks.factory.AndroidUnitTest>().all {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val copyJni by tasks.creating(Sync::class) {
|
||||||
|
dependsOn(":native:buildSecp256k1Jvm")
|
||||||
|
from(rootDir.resolve("native/build/linux/libsecp256k1-jni.so")) { rename { "libsecp256k1-jni-linux-x86_64.so" } }
|
||||||
|
from(rootDir.resolve("native/build/darwin/libsecp256k1-jni.dylib")) { rename { "libsecp256k1-jni-darwin-x86_64.dylib" } }
|
||||||
|
from(rootDir.resolve("native/build/mingw/secp256k1-jni.dll")) { rename { "secp256k1-jni-mingw-x86_64.dll" } }
|
||||||
|
into(buildDir.resolve("jniResources/fr/acinq/secp256k1/jni/native"))
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
configure(listOf("Debug", "Release").map { tasks["externalNativeBuild$it"] }) {
|
||||||
|
dependsOn(":native:buildSecp256k1Android")
|
||||||
|
}
|
||||||
|
}
|
14
jni/src/androidMain/CMakeLists.txt
Normal file
14
jni/src/androidMain/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10.0)
|
||||||
|
|
||||||
|
add_library( secp256k1-jni SHARED
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../../../native/jni/src/org_bitcoin_NativeSecp256k1.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../../../native/jni/src/org_bitcoin_Secp256k1Context.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories( secp256k1-jni
|
||||||
|
PUBLIC ${CMAKE_CURRENT_LIST_DIR}/../../../native/secp256k1
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries( secp256k1-jni
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../../../native/build/android/${ANDROID_ABI}/libsecp256k1.a
|
||||||
|
)
|
@ -0,0 +1,12 @@
|
|||||||
|
package fr.acinq.secp256k1.jni
|
||||||
|
|
||||||
|
public actual object NativeSecp256k1Loader {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Synchronized
|
||||||
|
@Throws(Exception::class)
|
||||||
|
actual fun load() {
|
||||||
|
System.loadLibrary("secp256k1-jni")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package fr.acinq.secp256k1.jni
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmStatic
|
||||||
|
|
||||||
|
|
||||||
|
public expect object NativeSecp256k1Loader {
|
||||||
|
|
||||||
|
public fun load()
|
||||||
|
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.bitcoin
|
package org.bitcoin
|
||||||
|
|
||||||
import fr.acinq.secp256k1.Secp256k1Loader.initialize
|
import kotlin.jvm.JvmStatic
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds the context reference used in native methods
|
* This class holds the context reference used in native methods
|
@ -0,0 +1,211 @@
|
|||||||
|
package fr.acinq.secp256k1.jni
|
||||||
|
|
||||||
|
import java.io.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the system properties, org.sqlite.lib.path, org.sqlite.lib.name,
|
||||||
|
* appropriately so that the SQLite JDBC driver can find *.dll, *.jnilib and
|
||||||
|
* *.so files, according to the current OS (win, linux, mac).
|
||||||
|
* The library files are automatically extracted from this project's package (JAR).
|
||||||
|
* usage: call [.initialize] before using SQLite JDBC driver.
|
||||||
|
*
|
||||||
|
* @author leo
|
||||||
|
*/
|
||||||
|
public actual object NativeSecp256k1Loader {
|
||||||
|
private var extracted = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads secp256k1 native library.
|
||||||
|
*
|
||||||
|
* @return True if secp256k1 native library is successfully loaded; false otherwise.
|
||||||
|
* @throws Exception if loading fails
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Synchronized
|
||||||
|
@Throws(Exception::class)
|
||||||
|
public actual fun load() {
|
||||||
|
// only cleanup before the first extract
|
||||||
|
if (!extracted) {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
loadSecp256k1NativeLibrary()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val tempDir: File
|
||||||
|
get() = File(
|
||||||
|
System.getProperty(
|
||||||
|
"fr.acinq.secp256k1.tmpdir",
|
||||||
|
System.getProperty("java.io.tmpdir")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deleted old native libraries e.g. on Windows the DLL file is not removed
|
||||||
|
* on VM-Exit (bug #80)
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun cleanup() {
|
||||||
|
val tempFolder = tempDir.absolutePath
|
||||||
|
val dir = File(tempFolder)
|
||||||
|
val nativeLibFiles = dir.listFiles(object : FilenameFilter {
|
||||||
|
private val searchPattern = "secp256k1-"
|
||||||
|
override fun accept(dir: File, name: String): Boolean {
|
||||||
|
return name.startsWith(searchPattern) && !name.endsWith(".lck")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (nativeLibFiles != null) {
|
||||||
|
for (nativeLibFile in nativeLibFiles) {
|
||||||
|
val lckFile = File(nativeLibFile.absolutePath + ".lck")
|
||||||
|
if (!lckFile.exists()) {
|
||||||
|
try {
|
||||||
|
nativeLibFile.delete()
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
System.err.println("Failed to delete old native lib" + e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun InputStream.contentsEquals(other: InputStream): Boolean {
|
||||||
|
val bufThis = this as? BufferedInputStream
|
||||||
|
?: BufferedInputStream(this)
|
||||||
|
val bufOther = other as? BufferedInputStream
|
||||||
|
?: BufferedInputStream(other)
|
||||||
|
var ch = bufThis.read()
|
||||||
|
while (ch != -1) {
|
||||||
|
val ch2 = bufOther.read()
|
||||||
|
if (ch != ch2) return false
|
||||||
|
ch = bufThis.read()
|
||||||
|
}
|
||||||
|
val ch2 = bufOther.read()
|
||||||
|
return ch2 == -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts and loads the specified library file to the target folder
|
||||||
|
*
|
||||||
|
* @param libDir Library path.
|
||||||
|
* @param libFileName Library name.
|
||||||
|
* @param targetDirectory Target folder.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private fun extractAndLoadLibraryFile(libDir: String, libFileName: String, targetDirectory: String): Boolean {
|
||||||
|
val libPath = "$libDir/$libFileName"
|
||||||
|
// Include architecture name in temporary filename in order to avoid conflicts
|
||||||
|
// when multiple JVMs with different architectures running at the same time
|
||||||
|
val uuid = UUID.randomUUID().toString()
|
||||||
|
val extractedLibFileName = String.format("secp256k1-%s-%s", uuid, libFileName)
|
||||||
|
val extractedLckFileName = "$extractedLibFileName.lck"
|
||||||
|
val extractedLibFile = File(targetDirectory, extractedLibFileName)
|
||||||
|
val extractedLckFile = File(targetDirectory, extractedLckFileName)
|
||||||
|
return try {
|
||||||
|
// Extract a native library file into the target directory
|
||||||
|
val reader = NativeSecp256k1Loader::class.java.getResourceAsStream(libPath)
|
||||||
|
if (!extractedLckFile.exists()) {
|
||||||
|
FileOutputStream(extractedLckFile).close()
|
||||||
|
}
|
||||||
|
val writer = FileOutputStream(extractedLibFile)
|
||||||
|
try {
|
||||||
|
val buffer = ByteArray(8192)
|
||||||
|
var bytesRead = reader.read(buffer)
|
||||||
|
while (bytesRead != -1) {
|
||||||
|
writer.write(buffer, 0, bytesRead)
|
||||||
|
bytesRead = reader.read(buffer)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Delete the extracted lib file on JVM exit.
|
||||||
|
extractedLibFile.deleteOnExit()
|
||||||
|
extractedLckFile.deleteOnExit()
|
||||||
|
writer.close()
|
||||||
|
reader.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set executable (x) flag to enable Java to load the native library
|
||||||
|
extractedLibFile.setReadable(true)
|
||||||
|
extractedLibFile.setWritable(true, true)
|
||||||
|
extractedLibFile.setExecutable(true)
|
||||||
|
|
||||||
|
// Check whether the contents are properly copied from the resource folder
|
||||||
|
NativeSecp256k1Loader::class.java.getResourceAsStream(libPath).use { nativeIn ->
|
||||||
|
FileInputStream(extractedLibFile).use { extractedLibIn ->
|
||||||
|
if (!nativeIn.contentsEquals(extractedLibIn)) {
|
||||||
|
throw RuntimeException(
|
||||||
|
String.format(
|
||||||
|
"Failed to write a native library file at %s",
|
||||||
|
extractedLibFile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNativeLibrary(targetDirectory, extractedLibFileName)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
System.err.println(e.message)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads native library using the given path and name of the library.
|
||||||
|
*
|
||||||
|
* @param path Path of the native library.
|
||||||
|
* @param name Name of the native library.
|
||||||
|
* @return True for successfully loading; false otherwise.
|
||||||
|
*/
|
||||||
|
private fun loadNativeLibrary(path: String, name: String): Boolean {
|
||||||
|
val libPath = File(path, name)
|
||||||
|
return if (libPath.exists()) {
|
||||||
|
try {
|
||||||
|
System.load(File(path, name).absolutePath)
|
||||||
|
true
|
||||||
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
|
System.err.println("Failed to load native library:$name. osinfo: ${OSInfo.nativeSuffix}")
|
||||||
|
System.err.println(e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads secp256k1 native library using given path and name of the library.
|
||||||
|
*
|
||||||
|
* @throws
|
||||||
|
*/
|
||||||
|
private fun loadSecp256k1NativeLibrary() {
|
||||||
|
if (extracted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loading library from fr.acinq.secp256k1.lib.path library path */
|
||||||
|
val libraryPath = System.getProperty("fr.acinq.secp256k1.lib.path")
|
||||||
|
val libraryName = System.getProperty("fr.acinq.secp256k1.lib.name") ?: System.mapLibraryName("secp256k1-jni-${OSInfo.nativeSuffix}")
|
||||||
|
if (libraryPath != null) {
|
||||||
|
if (loadNativeLibrary(libraryPath, libraryName)) {
|
||||||
|
extracted = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the os-dependent library from the jar file
|
||||||
|
val packagePath = NativeSecp256k1Loader::class.java.getPackage().name.replace("\\.".toRegex(), "/")
|
||||||
|
val embeddedLibraryPath = "/$packagePath/native"
|
||||||
|
val hasNativeLib = NativeSecp256k1Loader::class.java.getResource("$embeddedLibraryPath/$libraryName") != null
|
||||||
|
if (!hasNativeLib) {
|
||||||
|
error("No native library found: at $embeddedLibraryPath/$libraryName")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try extracting the library from jar
|
||||||
|
if (extractAndLoadLibraryFile(embeddedLibraryPath, libraryName, tempDir.absolutePath)) {
|
||||||
|
extracted = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
extracted = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
214
jni/src/jvmMain/kotlin/fr/acinq/secp256k1/jni/OSInfo.kt
Normal file
214
jni/src/jvmMain/kotlin/fr/acinq/secp256k1/jni/OSInfo.kt
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package fr.acinq.secp256k1.jni
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides OS name and architecture name.
|
||||||
|
*
|
||||||
|
* @author leo
|
||||||
|
*/
|
||||||
|
@Suppress("DuplicatedCode")
|
||||||
|
internal object OSInfo {
|
||||||
|
private val archMapping = HashMap<String, String>()
|
||||||
|
private const val X86 = "x86"
|
||||||
|
private const val X86_64 = "x86_64"
|
||||||
|
private const val IA64_32 = "ia64_32"
|
||||||
|
private const val IA64 = "ia64"
|
||||||
|
private const val PPC = "ppc"
|
||||||
|
private const val PPC64 = "ppc64"
|
||||||
|
|
||||||
|
@JvmStatic val nativeSuffix: String get() = "$os-$arch"
|
||||||
|
|
||||||
|
@JvmStatic val os: String get() = translateOSName(System.getProperty("os.name"))
|
||||||
|
|
||||||
|
@JvmStatic val hardwareName: String get() =
|
||||||
|
try {
|
||||||
|
val p = Runtime.getRuntime().exec("uname -m")
|
||||||
|
p.waitFor()
|
||||||
|
val input = p.inputStream
|
||||||
|
input.use {
|
||||||
|
val b = ByteArrayOutputStream()
|
||||||
|
val buf = ByteArray(32)
|
||||||
|
var readLen = it.read(buf, 0, buf.size)
|
||||||
|
while (readLen >= 0) {
|
||||||
|
b.write(buf, 0, readLen)
|
||||||
|
readLen = it.read(buf, 0, buf.size)
|
||||||
|
}
|
||||||
|
b.toString()
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
System.err.println("Error while running uname -m: " + e.message)
|
||||||
|
"unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun resolveArmArchType(): String {
|
||||||
|
if (System.getProperty("os.name").contains("Linux")) {
|
||||||
|
val armType = hardwareName
|
||||||
|
// armType (uname -m) can be armv5t, armv5te, armv5tej, armv5tejl, armv6, armv7, armv7l, aarch64, i686// ignored: fall back to "arm" arch (soft-float ABI)
|
||||||
|
// ignored: fall back to "arm" arch (soft-float ABI)
|
||||||
|
// determine if first JVM found uses ARM hard-float ABI
|
||||||
|
when {
|
||||||
|
armType.startsWith("armv6") -> {
|
||||||
|
// Raspberry PI
|
||||||
|
return "armv6"
|
||||||
|
}
|
||||||
|
armType.startsWith("armv7") -> {
|
||||||
|
// Generic
|
||||||
|
return "armv7"
|
||||||
|
}
|
||||||
|
armType.startsWith("armv5") -> {
|
||||||
|
// Use armv5, soft-float ABI
|
||||||
|
return "arm"
|
||||||
|
}
|
||||||
|
armType == "aarch64" -> {
|
||||||
|
// Use arm64
|
||||||
|
return "arm64"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java 1.8 introduces a system property to determine armel or armhf
|
||||||
|
// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8005545
|
||||||
|
|
||||||
|
// For java7, we stil need to if run some shell commands to determine ABI of JVM
|
||||||
|
else -> {
|
||||||
|
val abi = System.getProperty("sun.arch.abi")
|
||||||
|
if (abi != null && abi.startsWith("gnueabihf")) {
|
||||||
|
return "armv7"
|
||||||
|
}
|
||||||
|
|
||||||
|
// For java7, we stil need to if run some shell commands to determine ABI of JVM
|
||||||
|
val javaHome = System.getProperty("java.home")
|
||||||
|
try {
|
||||||
|
// determine if first JVM found uses ARM hard-float ABI
|
||||||
|
var exitCode = Runtime.getRuntime().exec("which readelf").waitFor()
|
||||||
|
if (exitCode == 0) {
|
||||||
|
val cmdarray = arrayOf(
|
||||||
|
"/bin/sh", "-c", "find '" + javaHome +
|
||||||
|
"' -name 'libjvm.so' | head -1 | xargs readelf -A | " +
|
||||||
|
"grep 'Tag_ABI_VFP_args: VFP registers'"
|
||||||
|
)
|
||||||
|
exitCode = Runtime.getRuntime().exec(cmdarray).waitFor()
|
||||||
|
if (exitCode == 0) {
|
||||||
|
return "armv7"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println(
|
||||||
|
"WARNING! readelf not found. Cannot check if running on an armhf system, " +
|
||||||
|
"armel architecture will be presumed."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// ignored: fall back to "arm" arch (soft-float ABI)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
// ignored: fall back to "arm" arch (soft-float ABI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java 1.8 introduces a system property to determine armel or armhf
|
||||||
|
// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8005545
|
||||||
|
val abi = System.getProperty("sun.arch.abi")
|
||||||
|
if (abi != null && abi.startsWith("gnueabihf")) {
|
||||||
|
return "armv7"
|
||||||
|
}
|
||||||
|
|
||||||
|
// For java7, we stil need to if run some shell commands to determine ABI of JVM
|
||||||
|
val javaHome = System.getProperty("java.home")
|
||||||
|
try {
|
||||||
|
// determine if first JVM found uses ARM hard-float ABI
|
||||||
|
var exitCode = Runtime.getRuntime().exec("which readelf").waitFor()
|
||||||
|
if (exitCode == 0) {
|
||||||
|
val cmdarray = arrayOf(
|
||||||
|
"/bin/sh", "-c", "find '" + javaHome +
|
||||||
|
"' -name 'libjvm.so' | head -1 | xargs readelf -A | " +
|
||||||
|
"grep 'Tag_ABI_VFP_args: VFP registers'"
|
||||||
|
)
|
||||||
|
exitCode = Runtime.getRuntime().exec(cmdarray).waitFor()
|
||||||
|
if (exitCode == 0) {
|
||||||
|
return "armv7"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println(
|
||||||
|
"WARNING! readelf not found. Cannot check if running on an armhf system, " +
|
||||||
|
"armel architecture will be presumed."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// ignored: fall back to "arm" arch (soft-float ABI)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
// ignored: fall back to "arm" arch (soft-float ABI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use armv5, soft-float ABI
|
||||||
|
return "arm"
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Android
|
||||||
|
@JvmStatic
|
||||||
|
val arch: String?
|
||||||
|
get() {
|
||||||
|
val systemOsArch = System.getProperty("os.arch")
|
||||||
|
val osArch =
|
||||||
|
if (systemOsArch.startsWith("arm")) {
|
||||||
|
resolveArmArchType()
|
||||||
|
} else {
|
||||||
|
val lc = systemOsArch.toLowerCase(Locale.US)
|
||||||
|
if (archMapping.containsKey(lc)) return archMapping[lc]
|
||||||
|
systemOsArch
|
||||||
|
}
|
||||||
|
return translateArchNameToFolderName(osArch)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun translateOSName(osName: String): String =
|
||||||
|
when {
|
||||||
|
osName.contains("Windows") -> "mingw"
|
||||||
|
osName.contains("Mac") || osName.contains("Darwin") -> "darwin"
|
||||||
|
osName.contains("Linux") -> "linux"
|
||||||
|
osName.contains("AIX") -> "aix"
|
||||||
|
else -> osName.replace("\\W".toRegex(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun translateArchNameToFolderName(archName: String): String = archName.replace("\\W".toRegex(), "")
|
||||||
|
|
||||||
|
init {
|
||||||
|
// x86 mappings
|
||||||
|
archMapping[X86] = X86
|
||||||
|
archMapping["i386"] = X86
|
||||||
|
archMapping["i486"] = X86
|
||||||
|
archMapping["i586"] = X86
|
||||||
|
archMapping["i686"] = X86
|
||||||
|
archMapping["pentium"] = X86
|
||||||
|
|
||||||
|
// x86_64 mappings
|
||||||
|
archMapping[X86_64] = X86_64
|
||||||
|
archMapping["amd64"] = X86_64
|
||||||
|
archMapping["em64t"] = X86_64
|
||||||
|
archMapping["universal"] = X86_64 // Needed for openjdk7 in Mac
|
||||||
|
|
||||||
|
// Itenium 64-bit mappings
|
||||||
|
archMapping[IA64] = IA64
|
||||||
|
archMapping["ia64w"] = IA64
|
||||||
|
|
||||||
|
// Itenium 32-bit mappings, usually an HP-UX construct
|
||||||
|
archMapping[IA64_32] = IA64_32
|
||||||
|
archMapping["ia64n"] = IA64_32
|
||||||
|
|
||||||
|
// PowerPC mappings
|
||||||
|
archMapping[PPC] = PPC
|
||||||
|
archMapping["power"] = PPC
|
||||||
|
archMapping["powerpc"] = PPC
|
||||||
|
archMapping["power_pc"] = PPC
|
||||||
|
archMapping["power_rs"] = PPC
|
||||||
|
|
||||||
|
// TODO: PowerPC 64bit mappings
|
||||||
|
archMapping[PPC64] = PPC64
|
||||||
|
archMapping["power64"] = PPC64
|
||||||
|
archMapping["powerpc64"] = PPC64
|
||||||
|
archMapping["power_pc64"] = PPC64
|
||||||
|
archMapping["power_rs64"] = PPC64
|
||||||
|
}
|
||||||
|
}
|
88
native/build.gradle.kts
Normal file
88
native/build.gradle.kts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
evaluationDependsOn(":jni")
|
||||||
|
|
||||||
|
val currentOs = org.gradle.internal.os.OperatingSystem.current()
|
||||||
|
|
||||||
|
val buildSecp256k1 by tasks.creating { group = "build" }
|
||||||
|
|
||||||
|
sealed class Cross {
|
||||||
|
abstract fun cmd(target: String, nativeDir: File): List<String>
|
||||||
|
class DockCross(val cross: String) : Cross() {
|
||||||
|
override fun cmd(target: String, nativeDir: File): List<String> = listOf("./dockcross-$cross", "bash", "-c", "CROSS=1 TARGET=$target ./build.sh")
|
||||||
|
}
|
||||||
|
class MultiArch(val crossTriple: String) : Cross() {
|
||||||
|
override fun cmd(target: String, nativeDir: File): List<String> {
|
||||||
|
val uid = Runtime.getRuntime().exec("id -u").inputStream.use { it.reader().readText() }.trim().toInt()
|
||||||
|
return listOf(
|
||||||
|
"docker", "run", "--rm", "-v", "${nativeDir.absolutePath}:/workdir",
|
||||||
|
"-e", "CROSS_TRIPLE=$crossTriple", "-e", "TARGET=$target", "-e", "TO_UID=$uid", "-e", "CROSS=1",
|
||||||
|
"multiarch/crossbuild", "./build.sh"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val buildSecp256k1Jvm by tasks.creating {
|
||||||
|
group = "build"
|
||||||
|
buildSecp256k1.dependsOn(this)
|
||||||
|
}
|
||||||
|
fun creatingBuildSecp256k1(target: String, cross: Cross?) = tasks.creating(Exec::class) {
|
||||||
|
group = "build"
|
||||||
|
buildSecp256k1Jvm.dependsOn(this)
|
||||||
|
|
||||||
|
inputs.files(projectDir.resolve("build.sh"))
|
||||||
|
outputs.dir(projectDir.resolve("build/$target"))
|
||||||
|
|
||||||
|
workingDir = projectDir
|
||||||
|
environment("TARGET", target)
|
||||||
|
commandLine((cross?.cmd(target, workingDir) ?: emptyList()) + "./build.sh")
|
||||||
|
}
|
||||||
|
val buildSecp256k1Darwin by creatingBuildSecp256k1("darwin", if (currentOs.isMacOsX) null else Cross.MultiArch("x86_64-apple-darwin"))
|
||||||
|
val buildSecp256k1Linux by creatingBuildSecp256k1("linux", if (currentOs.isLinux) null else Cross.DockCross("linux-x64"))
|
||||||
|
val buildSecp256k1Mingw by creatingBuildSecp256k1("mingw", if (currentOs.isWindows) null else Cross.DockCross("windows-x64"))
|
||||||
|
|
||||||
|
val buildSecp256k1Ios by tasks.creating(Exec::class) {
|
||||||
|
group = "build"
|
||||||
|
buildSecp256k1.dependsOn(this)
|
||||||
|
|
||||||
|
onlyIf { currentOs.isMacOsX }
|
||||||
|
|
||||||
|
inputs.files(projectDir.resolve("build-ios.sh"))
|
||||||
|
outputs.dir(projectDir.resolve("build/ios"))
|
||||||
|
|
||||||
|
workingDir = projectDir
|
||||||
|
commandLine("./build-ios.sh")
|
||||||
|
}
|
||||||
|
|
||||||
|
val buildSecp256k1Android by tasks.creating {
|
||||||
|
group = "build"
|
||||||
|
buildSecp256k1.dependsOn(this)
|
||||||
|
}
|
||||||
|
fun creatingBuildSecp256k1Android(arch: String) = tasks.creating(Exec::class) {
|
||||||
|
group = "build"
|
||||||
|
buildSecp256k1Android.dependsOn(this)
|
||||||
|
|
||||||
|
inputs.files(projectDir.resolve("build-android.sh"))
|
||||||
|
outputs.dir(projectDir.resolve("build/android/$arch"))
|
||||||
|
|
||||||
|
workingDir = projectDir
|
||||||
|
|
||||||
|
val toolchain = when {
|
||||||
|
currentOs.isLinux -> "linux-x86_64"
|
||||||
|
currentOs.isMacOsX -> "darwin-x86_64"
|
||||||
|
currentOs.isWindows -> "windows-x86_64"
|
||||||
|
else -> error("No Android toolchain defined for this OS: $currentOs")
|
||||||
|
}
|
||||||
|
environment("TOOLCHAIN", toolchain)
|
||||||
|
environment("ARCH", arch)
|
||||||
|
environment("ANDROID_NDK", (project(":jni").extensions["android"] as com.android.build.gradle.LibraryExtension).ndkDirectory)
|
||||||
|
commandLine("./build-android.sh")
|
||||||
|
}
|
||||||
|
val buildSecp256k1AndroidX86_64 by creatingBuildSecp256k1Android("x86_64")
|
||||||
|
val buildSecp256k1AndroidX86 by creatingBuildSecp256k1Android("x86")
|
||||||
|
val buildSecp256k1AndroidArm64v8a by creatingBuildSecp256k1Android("arm64-v8a")
|
||||||
|
val buildSecp256k1AndroidArmeabiv7a by creatingBuildSecp256k1Android("armeabi-v7a")
|
||||||
|
|
||||||
|
val clean by tasks.creating {
|
||||||
|
doLast {
|
||||||
|
delete(projectDir.resolve("build"))
|
||||||
|
}
|
||||||
|
}
|
@ -5,12 +5,10 @@ pluginManagement {
|
|||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
resolutionStrategy {
|
|
||||||
eachPlugin {
|
|
||||||
if (requested.id.id == "com.android.library") useModule("com.android.tools.build:gradle:${requested.version}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rootProject.name = "secp256k1-kmp"
|
rootProject.name = "secp256k1-kmp"
|
||||||
|
|
||||||
|
include(
|
||||||
|
":native",
|
||||||
|
":jni"
|
||||||
|
)
|
@ -1,14 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.10.0)
|
|
||||||
|
|
||||||
add_library( secp256k1-jni SHARED
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../native/jni/src/org_bitcoin_NativeSecp256k1.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../native/jni/src/org_bitcoin_Secp256k1Context.c
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories( secp256k1-jni
|
|
||||||
PUBLIC ${CMAKE_CURRENT_LIST_DIR}/../../native/secp256k1
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries( secp256k1-jni
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/../../native/build/android/${ANDROID_ABI}/libsecp256k1.a
|
|
||||||
)
|
|
@ -1,15 +0,0 @@
|
|||||||
package fr.acinq.secp256k1
|
|
||||||
|
|
||||||
import java.io.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal actual object Secp256k1Loader {
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Synchronized
|
|
||||||
@Throws(Exception::class)
|
|
||||||
actual fun initialize() {
|
|
||||||
System.loadLibrary("secp256k1-jni")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -17,7 +17,6 @@
|
|||||||
package fr.acinq.secp256k1
|
package fr.acinq.secp256k1
|
||||||
|
|
||||||
import kotlin.jvm.JvmStatic
|
import kotlin.jvm.JvmStatic
|
||||||
import kotlin.jvm.Synchronized
|
|
||||||
|
|
||||||
public enum class SigFormat(internal val size: Int) { COMPACT(64), DER(72) }
|
public enum class SigFormat(internal val size: Int) { COMPACT(64), DER(72) }
|
||||||
|
|
||||||
@ -25,37 +24,54 @@ public enum class PubKeyFormat(internal val size: Int) { COMPRESSED(33), UNCOMPR
|
|||||||
|
|
||||||
public expect object Secp256k1 {
|
public expect object Secp256k1 {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean
|
public fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun sign(data: ByteArray, sec: ByteArray, format: SigFormat): ByteArray
|
public fun sign(data: ByteArray, sec: ByteArray, format: SigFormat): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun signatureNormalize(sig: ByteArray, format: SigFormat): Pair<ByteArray, Boolean>
|
public fun signatureNormalize(sig: ByteArray, format: SigFormat): Pair<ByteArray, Boolean>
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun secKeyVerify(seckey: ByteArray): Boolean
|
public fun secKeyVerify(seckey: ByteArray): Boolean
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun computePubkey(seckey: ByteArray, format: PubKeyFormat): ByteArray
|
public fun computePubkey(seckey: ByteArray, format: PubKeyFormat): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun parsePubkey(pubkey: ByteArray, format: PubKeyFormat): ByteArray
|
public fun parsePubkey(pubkey: ByteArray, format: PubKeyFormat): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun cleanup()
|
public fun cleanup()
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun privKeyNegate(privkey: ByteArray): ByteArray
|
public fun privKeyNegate(privkey: ByteArray): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray
|
public fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray
|
public fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun pubKeyNegate(pubkey: ByteArray): ByteArray
|
public fun pubKeyNegate(pubkey: ByteArray): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray
|
public fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray
|
public fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray
|
public fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun createECDHSecret(seckey: ByteArray, pubkey: ByteArray): ByteArray
|
public fun createECDHSecret(seckey: ByteArray, pubkey: ByteArray): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int, format: PubKeyFormat): ByteArray
|
public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int, format: PubKeyFormat): ByteArray
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
public fun randomize(seed: ByteArray): Boolean
|
public fun randomize(seed: ByteArray): Boolean
|
||||||
}
|
}
|
@ -1,228 +0,0 @@
|
|||||||
package fr.acinq.secp256k1
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------
|
|
||||||
* Copyright 2008 Taro L. Saito
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*--------------------------------------------------------------------------*/ /**
|
|
||||||
* Provides OS name and architecture name.
|
|
||||||
*
|
|
||||||
* @author leo
|
|
||||||
*/
|
|
||||||
@Suppress("DuplicatedCode")
|
|
||||||
internal object OSInfo {
|
|
||||||
private val archMapping = HashMap<String, String>()
|
|
||||||
private const val X86 = "x86"
|
|
||||||
private const val X86_64 = "x86_64"
|
|
||||||
private const val IA64_32 = "ia64_32"
|
|
||||||
private const val IA64 = "ia64"
|
|
||||||
private const val PPC = "ppc"
|
|
||||||
private const val PPC64 = "ppc64"
|
|
||||||
|
|
||||||
@JvmStatic val nativeSuffix: String get() = "$os-$arch"
|
|
||||||
|
|
||||||
@JvmStatic val os: String get() = translateOSName(System.getProperty("os.name"))
|
|
||||||
|
|
||||||
@JvmStatic val hardwareName: String get() =
|
|
||||||
try {
|
|
||||||
val p = Runtime.getRuntime().exec("uname -m")
|
|
||||||
p.waitFor()
|
|
||||||
val input = p.inputStream
|
|
||||||
input.use {
|
|
||||||
val b = ByteArrayOutputStream()
|
|
||||||
val buf = ByteArray(32)
|
|
||||||
var readLen = it.read(buf, 0, buf.size)
|
|
||||||
while (readLen >= 0) {
|
|
||||||
b.write(buf, 0, readLen)
|
|
||||||
readLen = it.read(buf, 0, buf.size)
|
|
||||||
}
|
|
||||||
b.toString()
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
System.err.println("Error while running uname -m: " + e.message)
|
|
||||||
"unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
private fun resolveArmArchType(): String {
|
|
||||||
if (System.getProperty("os.name").contains("Linux")) {
|
|
||||||
val armType = hardwareName
|
|
||||||
// armType (uname -m) can be armv5t, armv5te, armv5tej, armv5tejl, armv6, armv7, armv7l, aarch64, i686// ignored: fall back to "arm" arch (soft-float ABI)
|
|
||||||
// ignored: fall back to "arm" arch (soft-float ABI)
|
|
||||||
// determine if first JVM found uses ARM hard-float ABI
|
|
||||||
when {
|
|
||||||
armType.startsWith("armv6") -> {
|
|
||||||
// Raspberry PI
|
|
||||||
return "armv6"
|
|
||||||
}
|
|
||||||
armType.startsWith("armv7") -> {
|
|
||||||
// Generic
|
|
||||||
return "armv7"
|
|
||||||
}
|
|
||||||
armType.startsWith("armv5") -> {
|
|
||||||
// Use armv5, soft-float ABI
|
|
||||||
return "arm"
|
|
||||||
}
|
|
||||||
armType == "aarch64" -> {
|
|
||||||
// Use arm64
|
|
||||||
return "arm64"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Java 1.8 introduces a system property to determine armel or armhf
|
|
||||||
// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8005545
|
|
||||||
|
|
||||||
// For java7, we stil need to if run some shell commands to determine ABI of JVM
|
|
||||||
else -> {
|
|
||||||
val abi = System.getProperty("sun.arch.abi")
|
|
||||||
if (abi != null && abi.startsWith("gnueabihf")) {
|
|
||||||
return "armv7"
|
|
||||||
}
|
|
||||||
|
|
||||||
// For java7, we stil need to if run some shell commands to determine ABI of JVM
|
|
||||||
val javaHome = System.getProperty("java.home")
|
|
||||||
try {
|
|
||||||
// determine if first JVM found uses ARM hard-float ABI
|
|
||||||
var exitCode = Runtime.getRuntime().exec("which readelf").waitFor()
|
|
||||||
if (exitCode == 0) {
|
|
||||||
val cmdarray = arrayOf(
|
|
||||||
"/bin/sh", "-c", "find '" + javaHome +
|
|
||||||
"' -name 'libjvm.so' | head -1 | xargs readelf -A | " +
|
|
||||||
"grep 'Tag_ABI_VFP_args: VFP registers'"
|
|
||||||
)
|
|
||||||
exitCode = Runtime.getRuntime().exec(cmdarray).waitFor()
|
|
||||||
if (exitCode == 0) {
|
|
||||||
return "armv7"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.err.println(
|
|
||||||
"WARNING! readelf not found. Cannot check if running on an armhf system, " +
|
|
||||||
"armel architecture will be presumed."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
// ignored: fall back to "arm" arch (soft-float ABI)
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
// ignored: fall back to "arm" arch (soft-float ABI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Java 1.8 introduces a system property to determine armel or armhf
|
|
||||||
// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8005545
|
|
||||||
val abi = System.getProperty("sun.arch.abi")
|
|
||||||
if (abi != null && abi.startsWith("gnueabihf")) {
|
|
||||||
return "armv7"
|
|
||||||
}
|
|
||||||
|
|
||||||
// For java7, we stil need to if run some shell commands to determine ABI of JVM
|
|
||||||
val javaHome = System.getProperty("java.home")
|
|
||||||
try {
|
|
||||||
// determine if first JVM found uses ARM hard-float ABI
|
|
||||||
var exitCode = Runtime.getRuntime().exec("which readelf").waitFor()
|
|
||||||
if (exitCode == 0) {
|
|
||||||
val cmdarray = arrayOf(
|
|
||||||
"/bin/sh", "-c", "find '" + javaHome +
|
|
||||||
"' -name 'libjvm.so' | head -1 | xargs readelf -A | " +
|
|
||||||
"grep 'Tag_ABI_VFP_args: VFP registers'"
|
|
||||||
)
|
|
||||||
exitCode = Runtime.getRuntime().exec(cmdarray).waitFor()
|
|
||||||
if (exitCode == 0) {
|
|
||||||
return "armv7"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.err.println(
|
|
||||||
"WARNING! readelf not found. Cannot check if running on an armhf system, " +
|
|
||||||
"armel architecture will be presumed."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
// ignored: fall back to "arm" arch (soft-float ABI)
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
// ignored: fall back to "arm" arch (soft-float ABI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Use armv5, soft-float ABI
|
|
||||||
return "arm"
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Android
|
|
||||||
@JvmStatic
|
|
||||||
val arch: String?
|
|
||||||
get() {
|
|
||||||
val systemOsArch = System.getProperty("os.arch")
|
|
||||||
val osArch =
|
|
||||||
if (systemOsArch.startsWith("arm")) {
|
|
||||||
resolveArmArchType()
|
|
||||||
} else {
|
|
||||||
val lc = systemOsArch.toLowerCase(Locale.US)
|
|
||||||
if (archMapping.containsKey(lc)) return archMapping[lc]
|
|
||||||
systemOsArch
|
|
||||||
}
|
|
||||||
return translateArchNameToFolderName(osArch)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun translateOSName(osName: String): String =
|
|
||||||
when {
|
|
||||||
osName.contains("Windows") -> "mingw"
|
|
||||||
osName.contains("Mac") || osName.contains("Darwin") -> "darwin"
|
|
||||||
osName.contains("Linux") -> "linux"
|
|
||||||
osName.contains("AIX") -> "aix"
|
|
||||||
else -> osName.replace("\\W".toRegex(), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun translateArchNameToFolderName(archName: String): String = archName.replace("\\W".toRegex(), "")
|
|
||||||
|
|
||||||
init {
|
|
||||||
// x86 mappings
|
|
||||||
archMapping[X86] = X86
|
|
||||||
archMapping["i386"] = X86
|
|
||||||
archMapping["i486"] = X86
|
|
||||||
archMapping["i586"] = X86
|
|
||||||
archMapping["i686"] = X86
|
|
||||||
archMapping["pentium"] = X86
|
|
||||||
|
|
||||||
// x86_64 mappings
|
|
||||||
archMapping[X86_64] = X86_64
|
|
||||||
archMapping["amd64"] = X86_64
|
|
||||||
archMapping["em64t"] = X86_64
|
|
||||||
archMapping["universal"] = X86_64 // Needed for openjdk7 in Mac
|
|
||||||
|
|
||||||
// Itenium 64-bit mappings
|
|
||||||
archMapping[IA64] = IA64
|
|
||||||
archMapping["ia64w"] = IA64
|
|
||||||
|
|
||||||
// Itenium 32-bit mappings, usually an HP-UX construct
|
|
||||||
archMapping[IA64_32] = IA64_32
|
|
||||||
archMapping["ia64n"] = IA64_32
|
|
||||||
|
|
||||||
// PowerPC mappings
|
|
||||||
archMapping[PPC] = PPC
|
|
||||||
archMapping["power"] = PPC
|
|
||||||
archMapping["powerpc"] = PPC
|
|
||||||
archMapping["power_pc"] = PPC
|
|
||||||
archMapping["power_rs"] = PPC
|
|
||||||
|
|
||||||
// TODO: PowerPC 64bit mappings
|
|
||||||
archMapping[PPC64] = PPC64
|
|
||||||
archMapping["power64"] = PPC64
|
|
||||||
archMapping["powerpc64"] = PPC64
|
|
||||||
archMapping["power_pc64"] = PPC64
|
|
||||||
archMapping["power_rs64"] = PPC64
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,15 +17,18 @@
|
|||||||
package fr.acinq.secp256k1
|
package fr.acinq.secp256k1
|
||||||
|
|
||||||
import org.bitcoin.NativeSecp256k1
|
import org.bitcoin.NativeSecp256k1
|
||||||
|
import java.lang.IllegalStateException
|
||||||
internal expect object Secp256k1Loader {
|
|
||||||
fun initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
public actual object Secp256k1 {
|
public actual object Secp256k1 {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Secp256k1Loader.initialize()
|
try {
|
||||||
|
val cls = Class.forName("fr.acinq.secp256k1.jni.NativeSecp256k1Loader")
|
||||||
|
val load = cls.getMethod("load")
|
||||||
|
load.invoke(null)
|
||||||
|
} catch (ex: ClassNotFoundException) {
|
||||||
|
throw IllegalStateException("Could not load native Secp256k1 JNI library. Have you added the JNI dependency?", ex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public actual fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean = NativeSecp256k1.verify(data, signature, pub)
|
public actual fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean = NativeSecp256k1.verify(data, signature, pub)
|
@ -1,218 +0,0 @@
|
|||||||
package fr.acinq.secp256k1
|
|
||||||
|
|
||||||
import java.io.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------
|
|
||||||
* Copyright 2007 Taro L. Saito
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*--------------------------------------------------------------------------*/ /**
|
|
||||||
* Set the system properties, org.sqlite.lib.path, org.sqlite.lib.name,
|
|
||||||
* appropriately so that the SQLite JDBC driver can find *.dll, *.jnilib and
|
|
||||||
* *.so files, according to the current OS (win, linux, mac).
|
|
||||||
* The library files are automatically extracted from this project's package (JAR).
|
|
||||||
* usage: call [.initialize] before using SQLite JDBC driver.
|
|
||||||
*
|
|
||||||
* @author leo
|
|
||||||
*/
|
|
||||||
internal actual object Secp256k1Loader {
|
|
||||||
private var extracted = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads secp256k1 native library.
|
|
||||||
*
|
|
||||||
* @return True if secp256k1 native library is successfully loaded; false otherwise.
|
|
||||||
* @throws Exception if loading fails
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
@Synchronized
|
|
||||||
@Throws(Exception::class)
|
|
||||||
actual fun initialize() {
|
|
||||||
// only cleanup before the first extract
|
|
||||||
if (!extracted) {
|
|
||||||
cleanup()
|
|
||||||
}
|
|
||||||
loadSecp256k1NativeLibrary()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val tempDir: File
|
|
||||||
get() = File(System.getProperty("fr.acinq.secp256k1.tmpdir", System.getProperty("java.io.tmpdir")))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deleted old native libraries e.g. on Windows the DLL file is not removed
|
|
||||||
* on VM-Exit (bug #80)
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
fun cleanup() {
|
|
||||||
val tempFolder = tempDir.absolutePath
|
|
||||||
val dir = File(tempFolder)
|
|
||||||
val nativeLibFiles = dir.listFiles(object : FilenameFilter {
|
|
||||||
private val searchPattern = "secp256k1-"
|
|
||||||
override fun accept(dir: File, name: String): Boolean {
|
|
||||||
return name.startsWith(searchPattern) && !name.endsWith(".lck")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (nativeLibFiles != null) {
|
|
||||||
for (nativeLibFile in nativeLibFiles) {
|
|
||||||
val lckFile = File(nativeLibFile.absolutePath + ".lck")
|
|
||||||
if (!lckFile.exists()) {
|
|
||||||
try {
|
|
||||||
nativeLibFile.delete()
|
|
||||||
} catch (e: SecurityException) {
|
|
||||||
System.err.println("Failed to delete old native lib" + e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
private fun InputStream.contentsEquals(other: InputStream): Boolean {
|
|
||||||
val bufThis = this as? BufferedInputStream ?: BufferedInputStream(this)
|
|
||||||
val bufOther = other as? BufferedInputStream ?: BufferedInputStream(other)
|
|
||||||
var ch = bufThis.read()
|
|
||||||
while (ch != -1) {
|
|
||||||
val ch2 = bufOther.read()
|
|
||||||
if (ch != ch2) return false
|
|
||||||
ch = bufThis.read()
|
|
||||||
}
|
|
||||||
val ch2 = bufOther.read()
|
|
||||||
return ch2 == -1
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts and loads the specified library file to the target folder
|
|
||||||
*
|
|
||||||
* @param libDir Library path.
|
|
||||||
* @param libFileName Library name.
|
|
||||||
* @param targetDirectory Target folder.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private fun extractAndLoadLibraryFile(libDir: String, libFileName: String, targetDirectory: String): Boolean {
|
|
||||||
val libPath = "$libDir/$libFileName"
|
|
||||||
// Include architecture name in temporary filename in order to avoid conflicts
|
|
||||||
// when multiple JVMs with different architectures running at the same time
|
|
||||||
val uuid = UUID.randomUUID().toString()
|
|
||||||
val extractedLibFileName = String.format("secp256k1-%s-%s", uuid, libFileName)
|
|
||||||
val extractedLckFileName = "$extractedLibFileName.lck"
|
|
||||||
val extractedLibFile = File(targetDirectory, extractedLibFileName)
|
|
||||||
val extractedLckFile = File(targetDirectory, extractedLckFileName)
|
|
||||||
return try {
|
|
||||||
// Extract a native library file into the target directory
|
|
||||||
val reader = Secp256k1Loader::class.java.getResourceAsStream(libPath)
|
|
||||||
if (!extractedLckFile.exists()) {
|
|
||||||
FileOutputStream(extractedLckFile).close()
|
|
||||||
}
|
|
||||||
val writer = FileOutputStream(extractedLibFile)
|
|
||||||
try {
|
|
||||||
val buffer = ByteArray(8192)
|
|
||||||
var bytesRead = reader.read(buffer)
|
|
||||||
while (bytesRead != -1) {
|
|
||||||
writer.write(buffer, 0, bytesRead)
|
|
||||||
bytesRead = reader.read(buffer)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
// Delete the extracted lib file on JVM exit.
|
|
||||||
extractedLibFile.deleteOnExit()
|
|
||||||
extractedLckFile.deleteOnExit()
|
|
||||||
writer.close()
|
|
||||||
reader.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set executable (x) flag to enable Java to load the native library
|
|
||||||
extractedLibFile.setReadable(true)
|
|
||||||
extractedLibFile.setWritable(true, true)
|
|
||||||
extractedLibFile.setExecutable(true)
|
|
||||||
|
|
||||||
// Check whether the contents are properly copied from the resource folder
|
|
||||||
Secp256k1Loader::class.java.getResourceAsStream(libPath).use { nativeIn ->
|
|
||||||
FileInputStream(extractedLibFile).use { extractedLibIn ->
|
|
||||||
if (!nativeIn.contentsEquals(extractedLibIn)) {
|
|
||||||
throw RuntimeException(
|
|
||||||
String.format(
|
|
||||||
"Failed to write a native library file at %s",
|
|
||||||
extractedLibFile
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadNativeLibrary(targetDirectory, extractedLibFileName)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
System.err.println(e.message)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads native library using the given path and name of the library.
|
|
||||||
*
|
|
||||||
* @param path Path of the native library.
|
|
||||||
* @param name Name of the native library.
|
|
||||||
* @return True for successfully loading; false otherwise.
|
|
||||||
*/
|
|
||||||
private fun loadNativeLibrary(path: String, name: String): Boolean {
|
|
||||||
val libPath = File(path, name)
|
|
||||||
return if (libPath.exists()) {
|
|
||||||
try {
|
|
||||||
System.load(File(path, name).absolutePath)
|
|
||||||
true
|
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
|
||||||
System.err.println("Failed to load native library:$name. osinfo: ${OSInfo.nativeSuffix}")
|
|
||||||
System.err.println(e)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads secp256k1 native library using given path and name of the library.
|
|
||||||
*
|
|
||||||
* @throws
|
|
||||||
*/
|
|
||||||
private fun loadSecp256k1NativeLibrary() {
|
|
||||||
if (extracted) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try loading library from fr.acinq.secp256k1.lib.path library path */
|
|
||||||
val libraryPath = System.getProperty("fr.acinq.secp256k1.lib.path")
|
|
||||||
val libraryName = System.getProperty("fr.acinq.secp256k1.lib.name") ?: System.mapLibraryName("secp256k1-jni-${OSInfo.nativeSuffix}")
|
|
||||||
if (libraryPath != null) {
|
|
||||||
if (loadNativeLibrary(libraryPath, libraryName)) {
|
|
||||||
extracted = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the os-dependent library from the jar file
|
|
||||||
val packagePath = Secp256k1Loader::class.java.getPackage().name.replace("\\.".toRegex(), "/")
|
|
||||||
val embeddedLibraryPath = "/$packagePath/native"
|
|
||||||
val hasNativeLib = Secp256k1Loader::class.java.getResource("$embeddedLibraryPath/$libraryName") != null
|
|
||||||
if (!hasNativeLib) {
|
|
||||||
error("No native library found: at $embeddedLibraryPath/$libraryName")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try extracting the library from jar
|
|
||||||
if (extractAndLoadLibraryFile(embeddedLibraryPath, libraryName, tempDir.absolutePath)) {
|
|
||||||
extracted = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
extracted = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
565
src/jvmMain/kotlin/org/bitcoin/NativeSecp256k1.kt
Normal file
565
src/jvmMain/kotlin/org/bitcoin/NativeSecp256k1.kt
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
* Copyright 2014-2016 the libsecp256k1 contributors
|
||||||
|
*
|
||||||
|
* 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 org.bitcoin
|
||||||
|
|
||||||
|
import org.bitcoin.NativeSecp256k1Util.AssertFailException
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import java.util.concurrent.locks.Lock
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* This class holds native methods to handle ECDSA verification.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* You can find an example library that can be used for this at https://github.com/bitcoin/secp256k1
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* To build secp256k1 for use with bitcoinj, run
|
||||||
|
* `./configure --enable-jni --enable-experimental --enable-module-ecdh`
|
||||||
|
* and `make` then copy `.libs/libsecp256k1.so` to your system library path
|
||||||
|
* or point the JVM to the folder containing it with -Djava.library.path
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public object NativeSecp256k1 {
|
||||||
|
private val rwl = ReentrantReadWriteLock()
|
||||||
|
private val r: Lock = rwl.readLock()
|
||||||
|
private val w: Lock = rwl.writeLock()
|
||||||
|
private val nativeECDSABuffer = ThreadLocal<ByteBuffer?>()
|
||||||
|
|
||||||
|
private fun pack(vararg buffers: ByteArray): ByteBuffer {
|
||||||
|
var size = 0
|
||||||
|
for (i in buffers.indices) {
|
||||||
|
size += buffers[i].size
|
||||||
|
}
|
||||||
|
|
||||||
|
val byteBuff = nativeECDSABuffer.get()?.takeIf { it.capacity() >= size }
|
||||||
|
?: ByteBuffer.allocateDirect(size).also {
|
||||||
|
it.order(ByteOrder.nativeOrder())
|
||||||
|
nativeECDSABuffer.set(it)
|
||||||
|
}
|
||||||
|
byteBuff.rewind()
|
||||||
|
for (i in buffers.indices) {
|
||||||
|
byteBuff.put(buffers[i])
|
||||||
|
}
|
||||||
|
return byteBuff
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the given secp256k1 signature in native code.
|
||||||
|
* Calling when enabled == false is undefined (probably library not loaded)
|
||||||
|
*
|
||||||
|
* @param data The data which was signed, must be exactly 32 bytes
|
||||||
|
* @param signature The signature
|
||||||
|
* @param pub The public key which did the signing
|
||||||
|
* @return true if the signature is valid
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean {
|
||||||
|
require(data.size == 32 && signature.size <= 520 && pub.size <= 520)
|
||||||
|
val byteBuff = pack(data, signature, pub)
|
||||||
|
r.lock()
|
||||||
|
return try {
|
||||||
|
secp256k1_ecdsa_verify(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext(),
|
||||||
|
signature.size,
|
||||||
|
pub.size
|
||||||
|
) == 1
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 Create an ECDSA signature.
|
||||||
|
*
|
||||||
|
* @param data Message hash, 32 bytes
|
||||||
|
* @param sec Secret key, 32 bytes
|
||||||
|
* @param compact True for compact signature, false for DER
|
||||||
|
* @return a signature, or an empty array is signing failed
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun sign(data: ByteArray, sec: ByteArray, compact: Boolean): ByteArray {
|
||||||
|
require(data.size == 32 && sec.size <= 32)
|
||||||
|
val byteBuff = pack(data, sec)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_ecdsa_sign(
|
||||||
|
byteBuff,
|
||||||
|
compact,
|
||||||
|
Secp256k1Context.getContext()
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val sigArr = retByteArray[0]
|
||||||
|
val sigLen = BigInteger(byteArrayOf(retByteArray[1][0])).toInt()
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(
|
||||||
|
sigArr.size,
|
||||||
|
sigLen,
|
||||||
|
"Got bad signature length."
|
||||||
|
)
|
||||||
|
return if (retVal == 0) ByteArray(0) else sigArr
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun signatureNormalize(sig: ByteArray, compact: Boolean): Pair<ByteArray, Boolean> {
|
||||||
|
require(sig.size == 64 || sig.size in 70..73)
|
||||||
|
val byteBuff = pack(sig)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_ecdsa_normalize(
|
||||||
|
byteBuff,
|
||||||
|
sig.size,
|
||||||
|
compact,
|
||||||
|
Secp256k1Context.getContext()
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val sigArr = retByteArray[0]
|
||||||
|
val sigLen = BigInteger(byteArrayOf(retByteArray[1][0])).toInt()
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
val retBool = BigInteger(byteArrayOf(retByteArray[1][2])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(
|
||||||
|
sigArr.size,
|
||||||
|
sigLen,
|
||||||
|
"Got bad signature length."
|
||||||
|
)
|
||||||
|
return (if (retVal == 0) ByteArray(0) else sigArr) to (retBool == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 Seckey Verify - returns 1 if valid, 0 if invalid
|
||||||
|
*
|
||||||
|
* @param seckey ECDSA Secret key, 32 bytes
|
||||||
|
* @return true if seckey is valid
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun secKeyVerify(seckey: ByteArray): Boolean {
|
||||||
|
require(seckey.size == 32)
|
||||||
|
val byteBuff = pack(seckey)
|
||||||
|
r.lock()
|
||||||
|
return try {
|
||||||
|
secp256k1_ec_seckey_verify(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext()
|
||||||
|
) == 1
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 Compute Pubkey - computes public key from secret key
|
||||||
|
*
|
||||||
|
* @param seckey ECDSA Secret key, 32 bytes
|
||||||
|
* @throws AssertFailException if parameters are not valid
|
||||||
|
* @return the corresponding public key (uncompressed)
|
||||||
|
*/
|
||||||
|
//TODO add a 'compressed' arg
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun computePubkey(seckey: ByteArray, compressed: Boolean): ByteArray {
|
||||||
|
require(seckey.size == 32)
|
||||||
|
val byteBuff = pack(seckey)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_ec_pubkey_create(
|
||||||
|
byteBuff,
|
||||||
|
compressed,
|
||||||
|
Secp256k1Context.getContext()
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val pubArr = retByteArray[0]
|
||||||
|
val pubLen = BigInteger(byteArrayOf(retByteArray[1][0])).toInt()
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(pubArr.size, pubLen, "Got bad pubkey length.")
|
||||||
|
return if (retVal == 0) ByteArray(0) else pubArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pubkey public key
|
||||||
|
* @return the input public key (uncompressed) if valid, or an empty array
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun parsePubkey(pubkey: ByteArray, compressed: Boolean): ByteArray {
|
||||||
|
require(pubkey.size == 33 || pubkey.size == 65)
|
||||||
|
val byteBuff = pack(pubkey)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_ec_pubkey_parse(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext(),
|
||||||
|
pubkey.size,
|
||||||
|
compressed
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val pubArr = retByteArray[0]
|
||||||
|
BigInteger(byteArrayOf(retByteArray[1][0])).toInt()
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(
|
||||||
|
pubArr.size,
|
||||||
|
if (compressed) 33 else 65,
|
||||||
|
"Got bad pubkey length."
|
||||||
|
)
|
||||||
|
return if (retVal == 0) ByteArray(0) else pubArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 Cleanup - This destroys the secp256k1 context object
|
||||||
|
* This should be called at the end of the program for proper cleanup of the context.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Synchronized
|
||||||
|
public fun cleanup() {
|
||||||
|
w.lock()
|
||||||
|
try {
|
||||||
|
secp256k1_destroy_context(Secp256k1Context.getContext())
|
||||||
|
} finally {
|
||||||
|
w.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun privKeyNegate(privkey: ByteArray): ByteArray {
|
||||||
|
require(privkey.size == 32)
|
||||||
|
val byteBuff = pack(privkey)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_privkey_negate(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext()
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val privArr = retByteArray[0]
|
||||||
|
val privLen: Int = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() and 0xFF
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(
|
||||||
|
privArr.size,
|
||||||
|
privLen,
|
||||||
|
"Got bad privkey length."
|
||||||
|
)
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return privArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 PrivKey Tweak-Mul - Tweak privkey by multiplying to it
|
||||||
|
*
|
||||||
|
* @param privkey 32-byte seckey
|
||||||
|
* @param tweak some bytes to tweak with
|
||||||
|
* @return privkey * tweak
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||||
|
require(privkey.size == 32)
|
||||||
|
val byteBuff = pack(privkey, tweak)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_privkey_tweak_mul(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext()
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val privArr = retByteArray[0]
|
||||||
|
val privLen: Int = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() and 0xFF
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(
|
||||||
|
privArr.size,
|
||||||
|
privLen,
|
||||||
|
"Got bad privkey length."
|
||||||
|
)
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return privArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 PrivKey Tweak-Add - Tweak privkey by adding to it
|
||||||
|
*
|
||||||
|
* @param privkey 32-byte seckey
|
||||||
|
* @param tweak some bytes to tweak with
|
||||||
|
* @return privkey + tweak
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||||
|
require(privkey.size == 32)
|
||||||
|
val byteBuff = pack(privkey, tweak)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_privkey_tweak_add(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext()
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val privArr = retByteArray[0]
|
||||||
|
val privLen: Int = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() and 0xFF
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(privArr.size, privLen, "Got bad pubkey length.")
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return privArr
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun pubKeyNegate(pubkey: ByteArray): ByteArray {
|
||||||
|
require(pubkey.size == 33 || pubkey.size == 65)
|
||||||
|
val byteBuff = pack(pubkey)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_pubkey_negate(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext(),
|
||||||
|
pubkey.size
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val pubArr = retByteArray[0]
|
||||||
|
val pubLen: Int = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() and 0xFF
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(pubArr.size, pubLen, "Got bad pubkey length.")
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return pubArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 PubKey Tweak-Add - Tweak pubkey by adding to it
|
||||||
|
*
|
||||||
|
* @param tweak some bytes to tweak with
|
||||||
|
* @param pubkey 32-byte seckey
|
||||||
|
* @return pubkey + tweak
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||||
|
require(pubkey.size == 33 || pubkey.size == 65)
|
||||||
|
val byteBuff = pack(pubkey, tweak)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_pubkey_tweak_add(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext(),
|
||||||
|
pubkey.size
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val pubArr = retByteArray[0]
|
||||||
|
val pubLen: Int = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() and 0xFF
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(pubArr.size, pubLen, "Got bad pubkey length.")
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return pubArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 PubKey Tweak-Mul - Tweak pubkey by multiplying to it
|
||||||
|
*
|
||||||
|
* @param tweak some bytes to tweak with
|
||||||
|
* @param pubkey 32-byte seckey
|
||||||
|
* @return pubkey * tweak
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray {
|
||||||
|
require(pubkey.size == 33 || pubkey.size == 65)
|
||||||
|
val byteBuff = pack(pubkey, tweak)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_pubkey_tweak_mul(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext(),
|
||||||
|
pubkey.size
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val pubArr = retByteArray[0]
|
||||||
|
val pubLen: Int = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() and 0xFF
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(pubArr.size, pubLen, "Got bad pubkey length.")
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return pubArr
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray {
|
||||||
|
require(pubkey1.size == 33 || pubkey1.size == 65)
|
||||||
|
require(pubkey2.size == 33 || pubkey2.size == 65)
|
||||||
|
val byteBuff = pack(pubkey1, pubkey2)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_ec_pubkey_add(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext(),
|
||||||
|
pubkey1.size,
|
||||||
|
pubkey2.size
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val pubArr = retByteArray[0]
|
||||||
|
val pubLen: Int = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() and 0xFF
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(pubkey1.size, pubLen, "Got bad pubkey length.")
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return pubArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 create ECDH secret - constant time ECDH calculation
|
||||||
|
*
|
||||||
|
* @param seckey byte array of secret key used in exponentiaion
|
||||||
|
* @param pubkey byte array of public key used in exponentiaion
|
||||||
|
* @return ecdh(sedckey, pubkey)
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun createECDHSecret(seckey: ByteArray, pubkey: ByteArray): ByteArray {
|
||||||
|
require(seckey.size <= 32 && pubkey.size <= 65)
|
||||||
|
val byteBuff = pack(seckey, pubkey)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_ecdh(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext(),
|
||||||
|
pubkey.size
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val resArr = retByteArray[0]
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][0])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(resArr.size, 32, "Got bad result length.")
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return resArr
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int, compressed: Boolean): ByteArray {
|
||||||
|
require(sig.size == 64)
|
||||||
|
require(message.size == 32)
|
||||||
|
val byteBuff = pack(sig, message)
|
||||||
|
val retByteArray: Array<ByteArray>
|
||||||
|
r.lock()
|
||||||
|
retByteArray = try {
|
||||||
|
secp256k1_ecdsa_recover(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext(),
|
||||||
|
recid,
|
||||||
|
compressed
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
r.unlock()
|
||||||
|
}
|
||||||
|
val resArr = retByteArray[0]
|
||||||
|
val retVal = BigInteger(byteArrayOf(retByteArray[1][0])).toInt()
|
||||||
|
NativeSecp256k1Util.assertEquals(
|
||||||
|
resArr.size,
|
||||||
|
if (compressed) 33 else 65,
|
||||||
|
"Got bad result length."
|
||||||
|
)
|
||||||
|
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
|
||||||
|
return resArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libsecp256k1 randomize - updates the context randomization
|
||||||
|
*
|
||||||
|
* @param seed 32-byte random seed
|
||||||
|
* @return true if successful
|
||||||
|
* @throws AssertFailException in case of failure
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Synchronized
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
public fun randomize(seed: ByteArray): Boolean {
|
||||||
|
require(seed.size == 32)
|
||||||
|
val byteBuff = pack(seed)
|
||||||
|
w.lock()
|
||||||
|
return try {
|
||||||
|
secp256k1_context_randomize(
|
||||||
|
byteBuff,
|
||||||
|
Secp256k1Context.getContext()
|
||||||
|
) == 1
|
||||||
|
} finally {
|
||||||
|
w.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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>
|
||||||
|
@JvmStatic private external fun secp256k1_privkey_tweak_mul(byteBuff: ByteBuffer, context: Long): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_pubkey_negate(byteBuff: ByteBuffer, context: Long, pubLen: Int): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_pubkey_tweak_add(byteBuff: ByteBuffer, context: Long, pubLen: Int): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_pubkey_tweak_mul(byteBuff: ByteBuffer, context: Long, pubLen: Int): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_destroy_context(context: Long)
|
||||||
|
@JvmStatic private external fun secp256k1_ecdsa_verify(byteBuff: ByteBuffer, context: Long, sigLen: Int, pubLen: Int): Int
|
||||||
|
@JvmStatic private external fun secp256k1_ecdsa_sign(byteBuff: ByteBuffer, compact: Boolean, context: Long): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_ecdsa_normalize(byteBuff: ByteBuffer, sigLen: Int, compact: Boolean, context: Long): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_ec_seckey_verify(byteBuff: ByteBuffer, context: Long): Int
|
||||||
|
@JvmStatic private external fun secp256k1_ec_pubkey_create(byteBuff: ByteBuffer, compressed: Boolean, context: Long): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_ec_pubkey_parse(byteBuff: ByteBuffer, context: Long, inputLen: Int, compressed: Boolean): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_ec_pubkey_add(byteBuff: ByteBuffer, context: Long, lent1: Int, len2: Int): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_ecdh(byteBuff: ByteBuffer, context: Long, inputLen: Int): Array<ByteArray>
|
||||||
|
@JvmStatic private external fun secp256k1_ecdsa_recover(byteBuff: ByteBuffer, context: Long, recid: Int, compressed: Boolean): Array<ByteArray>
|
||||||
|
}
|
28
src/jvmMain/kotlin/org/bitcoin/NativeSecp256k1Util.kt
Normal file
28
src/jvmMain/kotlin/org/bitcoin/NativeSecp256k1Util.kt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2016 the libsecp256k1 contributors
|
||||||
|
*
|
||||||
|
* 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 org.bitcoin
|
||||||
|
|
||||||
|
import kotlin.jvm.Throws
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
internal object NativeSecp256k1Util {
|
||||||
|
@Throws(AssertFailException::class)
|
||||||
|
fun assertEquals(val1: Int, val2: Int, message: String) {
|
||||||
|
if (val1 != val2) throw AssertFailException("FAIL: $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssertFailException(message: String?) : Exception(message)
|
||||||
|
}
|
39
src/jvmMain/kotlin/org/bitcoin/Secp256k1Context.kt
Normal file
39
src/jvmMain/kotlin/org/bitcoin/Secp256k1Context.kt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2016 the libsecp256k1 contributors
|
||||||
|
*
|
||||||
|
* 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 org.bitcoin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class holds the context reference used in native methods
|
||||||
|
* to handle ECDSA operations.
|
||||||
|
*/
|
||||||
|
public object Secp256k1Context {
|
||||||
|
@JvmStatic
|
||||||
|
public val isEnabled: Boolean //true if the library is loaded
|
||||||
|
private val context: Long //ref to pointer to context obj
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
public fun getContext(): Long {
|
||||||
|
return if (!isEnabled) -1 else context //sanity check
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic private external fun secp256k1_init_context(): Long
|
||||||
|
|
||||||
|
init { //static initializer
|
||||||
|
isEnabled = true
|
||||||
|
context =
|
||||||
|
secp256k1_init_context()
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user