Android implementation

This commit is contained in:
Salomon BRYS 2020-06-26 20:50:32 +02:00
parent 08b6d16836
commit ff37b86ff3
15 changed files with 303 additions and 72 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ build/
.gradle
local.properties
.gradletasknamecache
.cxx
# OS
.DS_Store

View File

@ -1,11 +1,13 @@
plugins {
kotlin("multiplatform") version "1.4-M2-mt"
id("com.android.library") version "4.0.0"
}
group = "fr.acinq.phoenix"
version = "1.0-1.4-M2"
repositories {
jcenter()
google()
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
maven("https://dl.bintray.com/kotlin/kotlin-eap")
}
@ -27,6 +29,13 @@ kotlin {
}
}
val jvmAndAndroidMain by sourceSets.creating {
dependsOn(commonMain)
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
jvm {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
@ -35,14 +44,24 @@ kotlin {
dependsOn("copyJni")
from(buildDir.resolve("jniResources"))
}
compilations["main"].dependencies {
implementation(kotlin("stdlib-jdk8"))
}
compilations["main"].defaultSourceSet.dependsOn(jvmAndAndroidMain)
compilations["test"].dependencies {
implementation(kotlin("test-junit"))
}
}
android {
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 {
val libsecp256k1 by creating {
@ -75,6 +94,26 @@ kotlin {
}
android {
defaultConfig {
compileSdkVersion(30)
minSdkVersion(21)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {}
}
}
externalNativeBuild {
cmake {
setPath("src/androidMain/CMakeLists.txt")
}
}
ndkVersion = "21.3.6528147"
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
}
val buildSecp256k1 by tasks.creating { group = "build" }
fun creatingBuildSecp256k1(target: String, cross: String? = null, env: String = "", configuration: Task.() -> Unit = {}) = tasks.creating(Exec::class) {
group = "build"
@ -95,6 +134,14 @@ val buildSecp256k1Darwin by creatingBuildSecp256k1("darwin")
val buildSecp256k1Linux by creatingBuildSecp256k1("linux", cross = if (currentOs.isMacOsX) "linux-x64" else null)
val buildSecp256k1Mingw by creatingBuildSecp256k1("mingw", cross = "windows-x64", env = "CONF_OPTS=--host=x86_64-w64-mingw32")
val 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)
@ -106,10 +153,42 @@ val buildSecp256k1Ios by tasks.creating(Exec::class) {
commandLine("./build-ios.sh")
}
val copyJni by tasks.creating(Sync::class) {
dependsOn(buildSecp256k1)
from(projectDir.resolve("native/build/linux/libsecp256k1-jni.so")) { rename { "libsecp256k1-jni-linux-x86_64.so" } }
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 buildSecp256k1Android by tasks.creating { group = "build" }
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.isMacOsX -> "darwin-x86_64"
else -> error("Cannot build for Android on this OS")
}
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"))
}
afterEvaluate {
tasks.withType<com.android.build.gradle.tasks.factory.AndroidUnitTest>().all {
enabled = false
}
}

View File

@ -11,3 +11,6 @@ kotlin.native.enableDependencyPropagation=false
# https://github.com/gradle/gradle/issues/11412
systemProp.org.gradle.internal.publish.checksums.insecure = true
# Android
android.useAndroidX = true

48
native/build-android.sh Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -e
ANDROID_NDK=/Users/salomonbrys/Library/Android/sdk/ndk/21.3.6528147
TOOLCHAIN=darwin-x86_64
[[ -z "$ANDROID_NDK" ]] && echo "Please set the ANDROID_NDK variable" && exit 1
[[ -z "$ARCH" ]] && echo "Please set the ARCH variable" && exit 1
[[ -z "$TOOLCHAIN" ]] && echo "Please set the TOOLCHAIN variable" && exit 1
if [ "$ARCH" == "x86_64" ]; then
SYS=x86_64
elif [ "$ARCH" == "x86" ]; then
SYS=i686
elif [ "$ARCH" == "arm64-v8a" ]; then
SYS=aarch64
elif [ "$ARCH" == "armeabi-v7a" ]; then
SYS=armv7a
else
echo "Unsupported ARCH: $ARCH"
exit 1
fi
TARGET=$SYS-linux-android
TOOLTARGET=$TARGET
if [ "$SYS" == "armv7a" ]; then
TARGET=armv7a-linux-androideabi
TOOLTARGET=arm-linux-androideabi
fi
export CC=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/${TARGET}21-clang
export LD=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ld
export AR=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ar
export AS=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-as
export RANLIB=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ranlib
export STRIP=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-strip
cd secp256k1
./autogen.sh
./configure CFLAGS=-fpic --host=$TARGET --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
make clean
make
cd ..
mkdir -p build/android/$ARCH
cp -v secp256k1/.libs/libsecp256k1.a build/android/$ARCH

View File

@ -19,9 +19,9 @@ make
cd ..
mkdir -p build/$TARGET
cp -r secp256k1/.libs/libsecp256k1.a build/$TARGET/
cp -v secp256k1/.libs/libsecp256k1.a build/$TARGET/
GCC=gcc
CC=gcc
JNI_HEADERS=$TARGET
if [ "$TARGET" == "linux" ]; then
@ -31,11 +31,9 @@ elif [ "$TARGET" == "darwin" ]; then
ADD_LIB=-lgmp
elif [ "$TARGET" == "mingw" ]; then
OUTFILE=secp256k1-jni.dll
GCC=/usr/src/mxe/usr/bin/x86_64-w64-mingw32.static-gcc
CC=/usr/src/mxe/usr/bin/x86_64-w64-mingw32.static-gcc
JNI_HEADERS=linux
GCC_OPTS="-fpic"
CC_OPTS="-fpic"
fi
echo $GCC -shared $GCC_OPTS -o build/$TARGET/$OUTFILE jni/src/org_bitcoin_NativeSecp256k1.c jni/src/org_bitcoin_Secp256k1Context.c -Ijni/headers/ -Ijni/headers/$JNI_HEADERS/ -Isecp256k1/ -lsecp256k1 -Lbuild/$TARGET/ $ADD_LIB
$GCC -shared $GCC_OPTS -o build/$TARGET/$OUTFILE jni/src/org_bitcoin_NativeSecp256k1.c jni/src/org_bitcoin_Secp256k1Context.c -Ijni/headers/ -Ijni/headers/$JNI_HEADERS/ -Isecp256k1/ -lsecp256k1 -Lbuild/$TARGET/ $ADD_LIB
$CC -shared $CC_OPTS -o build/$TARGET/$OUTFILE jni/src/org_bitcoin_NativeSecp256k1.c jni/src/org_bitcoin_Secp256k1Context.c -Ijni/headers/ -Ijni/headers/$JNI_HEADERS/ -Isecp256k1/ -lsecp256k1 -Lbuild/$TARGET/ $ADD_LIB

View File

@ -1,6 +1,7 @@
pluginManagement {
repositories {
mavenCentral()
google()
gradlePluginPortal()
maven {
url = uri("https://dl.bintray.com/kotlin/kotlin-eap")
@ -9,6 +10,11 @@ pluginManagement {
maven("https://plugins.gradle.org/m2/")
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == "com.android.library") useModule("com.android.tools.build:gradle:${requested.version}")
}
}
}
rootProject.name = "secp256k1-kmp"

View File

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

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,15 @@
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

@ -0,0 +1,4 @@
package fr.acinq.secp256k1
class Placeholder {}

View File

@ -17,7 +17,10 @@
package fr.acinq.secp256k1
import org.bitcoin.NativeSecp256k1
import java.math.BigInteger
internal expect object Secp256k1Loader {
fun initialize()
}
public actual object Secp256k1 {

View File

@ -78,7 +78,12 @@ public object NativeSecp256k1 {
val byteBuff = pack(data, signature, pub)
r.lock()
return try {
secp256k1_ecdsa_verify(byteBuff, Secp256k1Context.getContext(), signature.size, pub.size) == 1
secp256k1_ecdsa_verify(
byteBuff,
Secp256k1Context.getContext(),
signature.size,
pub.size
) == 1
} finally {
r.unlock()
}
@ -100,14 +105,21 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_ecdsa_sign(byteBuff, Secp256k1Context.getContext())
secp256k1_ecdsa_sign(
byteBuff,
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.")
NativeSecp256k1Util.assertEquals(
sigArr.size,
sigLen,
"Got bad signature length."
)
return if (retVal == 0) ByteArray(0) else sigArr
}
@ -130,14 +142,21 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_ecdsa_sign_compact(byteBuff, Secp256k1Context.getContext())
secp256k1_ecdsa_sign_compact(
byteBuff,
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.")
NativeSecp256k1Util.assertEquals(
sigArr.size,
sigLen,
"Got bad signature length."
)
return if (retVal == 0) ByteArray(0) else sigArr
}
@ -153,7 +172,10 @@ public object NativeSecp256k1 {
val byteBuff = pack(seckey)
r.lock()
return try {
secp256k1_ec_seckey_verify(byteBuff, Secp256k1Context.getContext()) == 1
secp256k1_ec_seckey_verify(
byteBuff,
Secp256k1Context.getContext()
) == 1
} finally {
r.unlock()
}
@ -175,7 +197,10 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext())
secp256k1_ec_pubkey_create(
byteBuff,
Secp256k1Context.getContext()
)
} finally {
r.unlock()
}
@ -199,12 +224,16 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_ec_pubkey_parse(byteBuff, Secp256k1Context.getContext(), pubkey.size)
secp256k1_ec_pubkey_parse(
byteBuff,
Secp256k1Context.getContext(),
pubkey.size
)
} finally {
r.unlock()
}
val pubArr = retByteArray[0]
val pubLen = BigInteger(byteArrayOf(retByteArray[1][0])).toInt()
BigInteger(byteArrayOf(retByteArray[1][0])).toInt()
val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt()
NativeSecp256k1Util.assertEquals(pubArr.size, 65, "Got bad pubkey length.")
return if (retVal == 0) ByteArray(0) else pubArr
@ -233,14 +262,21 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_privkey_negate(byteBuff, Secp256k1Context.getContext())
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(
privArr.size,
privLen,
"Got bad privkey length."
)
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
return privArr
}
@ -257,18 +293,25 @@ public object NativeSecp256k1 {
@Throws(AssertFailException::class)
public fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32)
val byteBuff = pack(privkey, tweak!!)
val byteBuff = pack(privkey, tweak)
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_privkey_tweak_mul(byteBuff, Secp256k1Context.getContext())
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(
privArr.size,
privLen,
"Got bad privkey length."
)
NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.")
return privArr
}
@ -285,11 +328,14 @@ public object NativeSecp256k1 {
@Throws(AssertFailException::class)
public fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32)
val byteBuff = pack(privkey, tweak!!)
val byteBuff = pack(privkey, tweak)
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_privkey_tweak_add(byteBuff, Secp256k1Context.getContext())
secp256k1_privkey_tweak_add(
byteBuff,
Secp256k1Context.getContext()
)
} finally {
r.unlock()
}
@ -309,7 +355,11 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_pubkey_negate(byteBuff, Secp256k1Context.getContext(), pubkey.size)
secp256k1_pubkey_negate(
byteBuff,
Secp256k1Context.getContext(),
pubkey.size
)
} finally {
r.unlock()
}
@ -337,7 +387,11 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_pubkey_tweak_add(byteBuff, Secp256k1Context.getContext(), pubkey.size)
secp256k1_pubkey_tweak_add(
byteBuff,
Secp256k1Context.getContext(),
pubkey.size
)
} finally {
r.unlock()
}
@ -361,11 +415,15 @@ public object NativeSecp256k1 {
@Throws(AssertFailException::class)
public fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65)
val byteBuff = pack(pubkey, tweak!!)
val byteBuff = pack(pubkey, tweak)
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_pubkey_tweak_mul(byteBuff, Secp256k1Context.getContext(), pubkey.size)
secp256k1_pubkey_tweak_mul(
byteBuff,
Secp256k1Context.getContext(),
pubkey.size
)
} finally {
r.unlock()
}
@ -386,7 +444,12 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_ec_pubkey_add(byteBuff, Secp256k1Context.getContext(), pubkey1.size, pubkey2.size)
secp256k1_ec_pubkey_add(
byteBuff,
Secp256k1Context.getContext(),
pubkey1.size,
pubkey2.size
)
} finally {
r.unlock()
}
@ -414,7 +477,11 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_ecdh(byteBuff, Secp256k1Context.getContext(), pubkey.size)
secp256k1_ecdh(
byteBuff,
Secp256k1Context.getContext(),
pubkey.size
)
} finally {
r.unlock()
}
@ -434,7 +501,11 @@ public object NativeSecp256k1 {
val retByteArray: Array<ByteArray>
r.lock()
retByteArray = try {
secp256k1_ecdsa_recover(byteBuff, Secp256k1Context.getContext(), recid)
secp256k1_ecdsa_recover(
byteBuff,
Secp256k1Context.getContext(),
recid
)
} finally {
r.unlock()
}
@ -460,7 +531,10 @@ public object NativeSecp256k1 {
val byteBuff = pack(seed)
w.lock()
return try {
secp256k1_context_randomize(byteBuff, Secp256k1Context.getContext()) == 1
secp256k1_context_randomize(
byteBuff,
Secp256k1Context.getContext()
) == 1
} finally {
w.unlock()
}

View File

@ -34,23 +34,7 @@ public object Secp256k1Context {
@JvmStatic private external fun secp256k1_init_context(): Long
init { //static initializer
var isEnabled = true
var contextRef: Long = -1
try {
if ("The Android Project" == System.getProperty("java.vm.vendor")) {
System.loadLibrary("secp256k1")
} else {
initialize()
}
contextRef = secp256k1_init_context()
} catch (e: UnsatisfiedLinkError) {
println("Cannot load secp256k1 native library: $e")
isEnabled = false
} catch (e: Exception) {
println("Cannot load secp256k1 native library: $e")
isEnabled = false
}
this.isEnabled = isEnabled
this.context = contextRef
isEnabled = true
context = secp256k1_init_context()
}
}

View File

@ -26,7 +26,7 @@ import java.util.*
*
* @author leo
*/
public object Secp256k1Loader {
internal actual object Secp256k1Loader {
private var extracted = false
/**
@ -38,13 +38,12 @@ public object Secp256k1Loader {
@JvmStatic
@Synchronized
@Throws(Exception::class)
public fun initialize(): Boolean {
actual fun initialize() {
// only cleanup before the first extract
if (!extracted) {
cleanup()
}
loadSecp256k1NativeLibrary()
return extracted
}
private val tempDir: File
@ -55,7 +54,7 @@ public object Secp256k1Loader {
* on VM-Exit (bug #80)
*/
@JvmStatic
public fun cleanup() {
fun cleanup() {
val tempFolder = tempDir.absolutePath
val dir = File(tempFolder)
val nativeLibFiles = dir.listFiles(object : FilenameFilter {