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,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="fr.acinq.secp256k1">
</manifest>

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

@@ -231,7 +231,11 @@ public object NativeSecp256k1 {
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.")
NativeSecp256k1Util.assertEquals(
pubArr.size,
if (compressed) 33 else 65,
"Got bad pubkey length."
)
return if (retVal == 0) ByteArray(0) else pubArr
}
@@ -508,7 +512,11 @@ public object NativeSecp256k1 {
}
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(
resArr.size,
if (compressed) 33 else 65,
"Got bad result length."
)
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
return resArr
}

View File

@@ -15,8 +15,6 @@
*/
package org.bitcoin
import fr.acinq.secp256k1.Secp256k1Loader.initialize
/**
* This class holds the context reference used in native methods
* to handle ECDSA operations.
@@ -35,6 +33,7 @@ public object Secp256k1Context {
init { //static initializer
isEnabled = true
context = secp256k1_init_context()
context =
secp256k1_init_context()
}
}