Android & JVM loader in their own modules

This commit is contained in:
Salomon BRYS 2020-07-01 12:15:04 +02:00
parent 9e1fc5d5ff
commit 1d9d57ca0a
22 changed files with 1385 additions and 688 deletions

View File

@ -1,15 +1,29 @@
plugins {
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 {
repositories {
google()
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()
@ -29,60 +43,40 @@ kotlin {
}
}
val jvmAndAndroidMain by sourceSets.creating {
dependsOn(commonMain)
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
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["main"].defaultSourceSet.dependsOn(jvmAndAndroidMain)
compilations["test"].dependencies {
implementation(project(":jni"))
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("test-junit"))
}
}
android {
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() {
fun org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget.secp256k1CInterop(target: String) {
compilations["main"].cinterops {
val libsecp256k1 by creating {
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) }
linuxX64 {
secp256k1CInterop()
linuxX64("linux") {
secp256k1CInterop("linux")
// 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()
secp256k1CInterop("ios")
// 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)
@ -120,166 +114,43 @@ afterEvaluate {
}
}
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 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"
)
}
}
}
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")
}
}
}
}
//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
View 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")
}
}

View 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
)

View File

@ -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")
}
}

View File

@ -0,0 +1,10 @@
package fr.acinq.secp256k1.jni
import kotlin.jvm.JvmStatic
public expect object NativeSecp256k1Loader {
public fun load()
}

View File

@ -15,7 +15,7 @@
*/
package org.bitcoin
import fr.acinq.secp256k1.Secp256k1Loader.initialize
import kotlin.jvm.JvmStatic
/**
* This class holds the context reference used in native methods

View File

@ -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
}
}

View 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
View 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"))
}
}

View File

@ -5,12 +5,10 @@ pluginManagement {
gradlePluginPortal()
jcenter()
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == "com.android.library") useModule("com.android.tools.build:gradle:${requested.version}")
}
}
}
rootProject.name = "secp256k1-kmp"
include(
":native",
":jni"
)

View File

@ -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
)

View File

@ -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")
}
}

View File

@ -17,7 +17,6 @@
package fr.acinq.secp256k1
import kotlin.jvm.JvmStatic
import kotlin.jvm.Synchronized
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 {
@JvmStatic
public fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean
@JvmStatic
public fun sign(data: ByteArray, sec: ByteArray, format: SigFormat): ByteArray
@JvmStatic
public fun signatureNormalize(sig: ByteArray, format: SigFormat): Pair<ByteArray, Boolean>
@JvmStatic
public fun secKeyVerify(seckey: ByteArray): Boolean
@JvmStatic
public fun computePubkey(seckey: ByteArray, format: PubKeyFormat): ByteArray
@JvmStatic
public fun parsePubkey(pubkey: ByteArray, format: PubKeyFormat): ByteArray
@JvmStatic
public fun cleanup()
@JvmStatic
public fun privKeyNegate(privkey: ByteArray): ByteArray
@JvmStatic
public fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray
@JvmStatic
public fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray
@JvmStatic
public fun pubKeyNegate(pubkey: ByteArray): ByteArray
@JvmStatic
public fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray
@JvmStatic
public fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray
@JvmStatic
public fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray
@JvmStatic
public fun createECDHSecret(seckey: ByteArray, pubkey: ByteArray): ByteArray
@JvmStatic
public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int, format: PubKeyFormat): ByteArray
@JvmStatic
public fun randomize(seed: ByteArray): Boolean
}

View File

@ -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
}
}

View File

@ -17,15 +17,18 @@
package fr.acinq.secp256k1
import org.bitcoin.NativeSecp256k1
internal expect object Secp256k1Loader {
fun initialize()
}
import java.lang.IllegalStateException
public actual object Secp256k1 {
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)

View File

@ -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
}
}

View 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>
}

View 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)
}

View 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()
}
}