24 Commits

Author SHA1 Message Date
sstone
0364ec762e Use local secp256k1 callbacks in kotlin native code 2023-12-12 12:08:13 +01:00
sstone
aadffabe42 Set version to 0.12.0-SNAPSHOT 2023-12-11 19:08:54 +01:00
sstone
8e17e7030a Implement custom secp256k1 error callbacks
These callbacks are only triggered either by arguments do not match explicit rquirements of the sepc256k1 library, or by hardware failures, memory corruption
or bug in secp256k1, and not by misuse of the library.
In theory we do not need to implement them, except to find bugs in our own code, but the default callbacks print a message to stderr and call abort() which
is not nice especially on mobile apps.

=> Here we introduce 2 specific exceptions, Secp256k1ErrorCallbackException and Secp256k1IllegalCallbackException, which are thrown when the error callback or illegal callback are called.
2023-12-11 19:05:46 +01:00
sstone
929e2cda40 Check that the recovery id is valid
It must be 0,1,2 or 3, this is an explicit requirement of the secp256k1 library.
2023-12-11 11:54:07 +01:00
sstone
41eac9273f Reformat JNI c code (no functional changes) 2023-12-11 10:46:43 +01:00
Fabrice Drouin
161da89ee1 Set version to 0.11.0 (#86) 2023-09-28 09:50:24 +02:00
Fabrice Drouin
3706a546a2 Use secp256k1 0.4.0 (#85) 2023-09-18 14:05:36 +02:00
Fabrice Drouin
ffcaaf1b64 Set version to 0.10.1 (#84) 2023-06-28 13:03:19 +02:00
Fabrice Drouin
6ef94df247 Use secp256k1 0.3.2 (#83) 2023-06-28 10:43:05 +02:00
Fabrice Drouin
5169073a92 Update README.md 2023-05-15 11:15:30 +02:00
Fabrice Drouin
317e086cba Set version to 0.10.0 (#82) 2023-05-11 18:29:50 +02:00
Fabrice Drouin
7c7aabba80 Upgrade to Kotlin 1.8 (#81)
* Upgrade to Kotlin 1.8

* Update snapshot deployment script

Kotlin 1.8 creates a new metadata jar for ios modules.
2023-05-11 17:53:41 +02:00
Fabrice Drouin
b6823cbda6 Update CI build (#80) 2023-04-25 09:55:48 +02:00
Fabrice Drouin
d50d9060c2 Set version to 0.9.0 (#78) 2023-04-13 09:36:07 +02:00
Fabrice Drouin
6fedb1577c Update build for macos M1 (#77) 2023-04-13 09:15:43 +02:00
Fabrice Drouin
94bb2d67cf Use secp256k1 0.3.1 (#76) 2023-04-11 19:10:51 +02:00
Fabrice Drouin
bf05a001fe Update Android build plugin and tools (#75) 2023-04-11 18:42:06 +02:00
Fabrice Drouin
d9e5fda600 Set version to 0.8.0 (#73) 2023-03-09 11:41:08 +01:00
Fabrice Drouin
8c984678be Use secp256k1 0.3.0 (#72)
* Use secp256k1 0.3.0

* Set version to 0.8.0-SNAPSHOT
2023-03-09 11:10:41 +01:00
gandlafbtc
840de25c5f remove kotlin from dependencies in readme example (#71)
The dependencies wouldn't resolve unless I removed kotlin(...)
2023-02-01 17:49:51 +01:00
Fabrice Drouin
cec3fb385f Set version to 0.7.1 (#70) 2023-01-04 15:33:55 +01:00
Fabrice Drouin
d59def1c79 Use secp256k1 0.2.0 (#67) 2022-12-13 19:33:28 +01:00
Fabrice Drouin
52d73951e6 Set version to 0.7.1-SNAPSHOT (#68) 2022-12-13 19:33:06 +01:00
Fabrice Drouin
08669500b6 Update README.md
Upgrade to a kotlin 1.6.21 badge
2022-09-22 10:29:21 +02:00
20 changed files with 993 additions and 695 deletions

View File

@@ -33,7 +33,7 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
- name: Cached Android NDK - name: Cached Android NDK
if: matrix.os != 'windows-latest' if: matrix.os != 'windows-latest'
uses: actions/cache@v2 uses: actions/cache@v2
@@ -96,10 +96,9 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: 27 api-level: 27
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }} ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404 cmake: 3.22.1
script: ./gradlew connectedCheck script: ./gradlew connectedCheck
- name: Publish Linux - name: Publish Linux
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'

View File

@@ -42,7 +42,7 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
- name: Cached Android NDK - name: Cached Android NDK
if: matrix.os != 'windows-latest' if: matrix.os != 'windows-latest'
uses: actions/cache@v2 uses: actions/cache@v2
@@ -105,10 +105,9 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: 27 api-level: 27
-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }} ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404 cmake: 3.22.1
script: ./gradlew connectedCheck script: ./gradlew connectedCheck
- name: Publish Linux - name: Publish Linux
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'

View File

@@ -48,7 +48,7 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
- name: Cached Android NDK - name: Cached Android NDK
if: matrix.os != 'windows-latest' if: matrix.os != 'windows-latest'
uses: actions/cache@v2 uses: actions/cache@v2
@@ -111,8 +111,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: 27 api-level: 27
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }} ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404 cmake: 3.22.1
script: ./gradlew connectedCheck script: ./gradlew connectedCheck

View File

@@ -1,4 +1,4 @@
[![Kotlin](https://img.shields.io/badge/Kotlin-1.5.31-blue.svg?style=flat&logo=kotlin)](http://kotlinlang.org) [![Kotlin](https://img.shields.io/badge/Kotlin-1.8.21-blue.svg?style=flat&logo=kotlin)](http://kotlinlang.org)
[![Maven Central](https://img.shields.io/maven-central/v/fr.acinq.secp256k1/secp256k1-kmp)](https://search.maven.org/search?q=g:fr.acinq.secp256k1%20a:secp256k1-kmp*) [![Maven Central](https://img.shields.io/maven-central/v/fr.acinq.secp256k1/secp256k1-kmp)](https://search.maven.org/search?q=g:fr.acinq.secp256k1%20a:secp256k1-kmp*)
![Github Actions](https://github.com/ACINQ/secp256k1-kmp/actions/workflows/test.yml/badge.svg) ![Github Actions](https://github.com/ACINQ/secp256k1-kmp/actions/workflows/test.yml/badge.svg)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ACINQ/secp256k1-kmp/blob/master/LICENSE) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ACINQ/secp256k1-kmp/blob/master/LICENSE)
@@ -30,19 +30,19 @@ kotlin {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
implementation(kotlin("stdlib-common")) implementation(kotlin("stdlib-common"))
implementation(kotlin("fr.acinq.secp256k1:secp256k1-kmp:$secp256k1_version")) implementation("fr.acinq.secp256k1:secp256k1-kmp:$secp256k1_version")
} }
} }
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
implementation(kotlin("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:$secp256k1_version")) implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:$secp256k1_version")
} }
} }
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
implementation(kotlin("fr.acinq.secp256k1:secp256k1-kmp-jni-android:$secp256k1_version")) implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-android:$secp256k1_version")
} }
} }
} }
@@ -58,9 +58,9 @@ Native targets include libsecp256k1, called through KMP's c-interop, simply add
The JVM library uses JNI bindings for libsecp256k1, which is much faster than BouncyCastle. It will extract and load native bindings for your operating system in a temporary directory. The JVM library uses JNI bindings for libsecp256k1, which is much faster than BouncyCastle. It will extract and load native bindings for your operating system in a temporary directory.
JNI libraries are included for: JNI libraries are included for:
- Linux 64 bits - Linux 64 bits (x86_64 and arm64)
- Windows 64 bits - Windows 64 bits (x86_64)
- Macos 64 bits - Macos 64 bits (x86_64 and arm64)
Along this library, you **must** specify which JNI native library to use in your dependency manager: Along this library, you **must** specify which JNI native library to use in your dependency manager:

View File

@@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.dokka.Platform import org.jetbrains.dokka.Platform
plugins { plugins {
kotlin("multiplatform") version "1.6.21" kotlin("multiplatform") version "1.8.21"
id("org.jetbrains.dokka") version "1.6.21" id("org.jetbrains.dokka") version "1.8.10"
`maven-publish` `maven-publish`
} }
@@ -15,14 +15,14 @@ buildscript {
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:4.2.2") classpath("com.android.tools.build:gradle:7.3.1")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.6.21") classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.8.10")
} }
} }
allprojects { allprojects {
group = "fr.acinq.secp256k1" group = "fr.acinq.secp256k1"
version = "0.7.0" version = "0.12.0-SNAPSHOT"
repositories { repositories {
google() google()
@@ -157,6 +157,7 @@ allprojects {
Platform.js -> "js" Platform.js -> "js"
Platform.native -> "native" Platform.native -> "native"
Platform.common -> "common" Platform.common -> "common"
Platform.wasm -> "wasm"
} }
displayName.set(platformName) displayName.set(platformName)

View File

@@ -15,12 +15,9 @@ dependencies {
android { android {
defaultConfig { defaultConfig {
compileSdkVersion(30) compileSdk = 33
minSdkVersion(21) minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {}
}
} }
compileOptions { compileOptions {
@@ -30,10 +27,12 @@ android {
externalNativeBuild { externalNativeBuild {
cmake { cmake {
version = "3.22.1"
path("src/main/CMakeLists.txt") path("src/main/CMakeLists.txt")
} }
} }
ndkVersion = "21.4.7075529"
ndkVersion = "25.2.9519653"
afterEvaluate { afterEvaluate {
tasks.withType<com.android.build.gradle.tasks.factory.AndroidUnitTest>().all { tasks.withType<com.android.build.gradle.tasks.factory.AndroidUnitTest>().all {
@@ -43,8 +42,8 @@ android {
} }
afterEvaluate { afterEvaluate {
configure(listOf("Debug", "Release").map { tasks["externalNativeBuild$it"] }) { tasks.filter { it.name.startsWith("configureCMake") }.forEach {
dependsOn(":native:buildSecp256k1Android") it.dependsOn(":native:buildSecp256k1Android")
} }
} }

View File

@@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.10.0) cmake_minimum_required(VERSION 3.10.0)
project(secp256k1jni)
add_library( secp256k1-jni SHARED add_library( secp256k1-jni SHARED
${CMAKE_CURRENT_LIST_DIR}/../../../c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c ${CMAKE_CURRENT_LIST_DIR}/../../../c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c
) )

View File

@@ -1,6 +1,9 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#ifdef WIN32
#define SECP256K1_STATIC // needed on windows when linking to a static version of secp256k1
#endif
#include "include/secp256k1.h" #include "include/secp256k1.h"
#include "include/secp256k1_ecdh.h" #include "include/secp256k1_ecdh.h"
#include "include/secp256k1_recovery.h" #include "include/secp256k1_recovery.h"
@@ -14,34 +17,87 @@
void JNI_ThrowByName(JNIEnv *penv, const char *name, const char *msg) void JNI_ThrowByName(JNIEnv *penv, const char *name, const char *msg)
{ {
jclass cls = (*penv)->FindClass(penv, name); jclass cls = (*penv)->FindClass(penv, name);
if (cls != NULL) { if (cls != NULL)
{
(*penv)->ThrowNew(penv, cls, msg); (*penv)->ThrowNew(penv, cls, msg);
(*penv)->DeleteLocalRef(penv, cls); (*penv)->DeleteLocalRef(penv, cls);
} }
} }
/**
* secp256k1 uses callbacks for errors that are either hw pbs or bugs in the calling library, for example
* passing parameters with values that are explicitly defined as illegal in the API, and should never be called for normal operations
* But if they are, default behaviour is to print an error to stderr and abort which is not what we want especially in mobile apps
* => we set up string pointers in every method, and custom callback that will set them to the message passed in by sec256k1's callbacks, which
* we turn into specific Sec256k1 exceptions
*/
#define SETUP_ERROR_CALLBACKS \
char *error_callback_message = NULL; \
char *illegal_callback_message = NULL; \
secp256k1_context_set_error_callback(ctx, my_error_callback_fn, &error_callback_message); \
secp256k1_context_set_illegal_callback(ctx, my_illegal_callback_fn, &illegal_callback_message);
#define CHECKRESULT(errorcheck, message) { \ #define CHECKRESULT(errorcheck, message) \
if (errorcheck) { \ { \
if (error_callback_message) \
{ \
JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1ErrorCallbackException", error_callback_message); \
return 0; \
} \
if (illegal_callback_message) \
{ \
JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1IllegalCallbackException", illegal_callback_message); \
return 0; \
} \
if (errorcheck) \
{ \
JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1Exception", message); \ JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1Exception", message); \
return 0; \ return 0; \
} \ } \
} }
#define CHECKRESULT1(errorcheck, message, dosomething) { \ #define CHECKRESULT1(errorcheck, message, dosomething) \
if (errorcheck) { \ { \
if (error_callback_message) \
{ \
dosomething; \ dosomething; \
JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1ErrorCallbackException", error_callback_message); \
return 0; \
} \
if (illegal_callback_message) \
{ \
dosomething; \
JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1IllegalCallbackException", illegal_callback_message); \
return 0; \
} \
if (errorcheck) \
{ \
JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1Exception", message); \ JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1Exception", message); \
return 0; \ return 0; \
} \ } \
} }
void my_illegal_callback_fn(const char *str, void *data)
{
if (data != NULL)
{
*(char **)data = str;
}
}
void my_error_callback_fn(const char *str, void *data)
{
if (data != NULL)
{
*(char **)data = str;
}
}
/* /*
* Class: fr_acinq_bitcoin_Secp256k1Bindings * Class: fr_acinq_bitcoin_Secp256k1Bindings
* Method: secp256k1_context_create * Method: secp256k1_context_create
* Signature: (I)J * Signature: (I)J
*/ */
JNIEXPORT jlong JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1create JNIEXPORT jlong JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1create(JNIEnv *penv, jclass clazz, jint flags)
(JNIEnv *penv, jclass clazz, jint flags)
{ {
return (jlong)secp256k1_context_create(flags); return (jlong)secp256k1_context_create(flags);
} }
@@ -51,10 +107,10 @@ JNIEXPORT jlong JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1c
* Method: secp256k1_context_destroy * Method: secp256k1_context_destroy
* Signature: (J)V * Signature: (J)V
*/ */
JNIEXPORT void JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1destroy JNIEXPORT void JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1destroy(JNIEnv *penv, jclass clazz, jlong ctx)
(JNIEnv *penv, jclass clazz, jlong ctx) {
if (ctx != 0)
{ {
if (ctx != 0) {
secp256k1_context_destroy((secp256k1_context *)ctx); secp256k1_context_destroy((secp256k1_context *)ctx);
} }
} }
@@ -64,16 +120,20 @@ JNIEXPORT void JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1co
* Method: secp256k1_ec_seckey_verify * Method: secp256k1_ec_seckey_verify
* Signature: (J[B)I * Signature: (J[B)I
*/ */
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1seckey_1verify JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1seckey_1verify(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *seckey; jbyte *seckey;
int result = 0; int result = 0;
if (jctx == 0) return 0; if (jctx == 0)
if (jseckey == NULL) return 0; return 0;
if ((*penv)->GetArrayLength(penv, jseckey) != 32) return 0; if (jseckey == NULL)
return 0;
if ((*penv)->GetArrayLength(penv, jseckey) != 32)
return 0;
SETUP_ERROR_CALLBACKS
seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0);
result = secp256k1_ec_seckey_verify(ctx, (unsigned char *)seckey); result = secp256k1_ec_seckey_verify(ctx, (unsigned char *)seckey);
@@ -86,8 +146,7 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec
* Method: secp256k1_ec_pubkey_parse * Method: secp256k1_ec_pubkey_parse
* Signature: (J[B)[B * Signature: (J[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1parse JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1parse(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *pubkeyBytes; jbyte *pubkeyBytes;
@@ -95,8 +154,12 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
size_t size; size_t size;
int result = 0; int result = 0;
if (jctx == 0) return 0; if (jctx == 0)
if (jpubkey == NULL) return 0; return 0;
if (jpubkey == NULL)
return 0;
SETUP_ERROR_CALLBACKS
size = (*penv)->GetArrayLength(penv, jpubkey); size = (*penv)->GetArrayLength(penv, jpubkey);
CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); CHECKRESULT((size != 33) && (size != 65), "invalid public key size");
@@ -120,8 +183,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ec_pubkey_create * Method: secp256k1_ec_pubkey_create
* Signature: (J[B)[B * Signature: (J[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1create JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1create(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *seckey, *pubkey; jbyte *seckey, *pubkey;
@@ -130,8 +192,12 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
size_t len; size_t len;
jbyteArray jpubkey = 0; jbyteArray jpubkey = 0;
if (jseckey == NULL) return NULL; if (jseckey == NULL)
if (jctx == 0) return NULL; return NULL;
if (jctx == 0)
return NULL;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes");
seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0);
@@ -152,8 +218,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ecdsa_sign * Method: secp256k1_ecdsa_sign
* Signature: (J[B[B)[B * Signature: (J[B[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1sign JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1sign(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *seckey, *msg, *sig; jbyte *seckey, *msg, *sig;
@@ -161,9 +226,14 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
int result = 0; int result = 0;
jbyteArray jsig; jbyteArray jsig;
if (jctx == 0) return NULL; if (jctx == 0)
if (jmsg == NULL) return NULL; return NULL;
if (jseckey == NULL) return NULL; if (jmsg == NULL)
return NULL;
if (jseckey == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes");
CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message key must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message key must be 32 bytes");
@@ -185,8 +255,10 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
int GetSignatureFormat(size_t size) int GetSignatureFormat(size_t size)
{ {
if (size == 64) return SIG_FORMAT_COMPACT; if (size == 64)
if (size < 64) return SIG_FORMAT_UNKNOWN; return SIG_FORMAT_COMPACT;
if (size < 64)
return SIG_FORMAT_UNKNOWN;
return SIG_FORMAT_DER; return SIG_FORMAT_DER;
} }
@@ -195,8 +267,7 @@ int GetSignatureFormat(size_t size)
* Method: secp256k1_ecdsa_verify * Method: secp256k1_ecdsa_verify
* Signature: (J[B[B[B)I * Signature: (J[B[B[B)I
*/ */
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1verify JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1verify(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *pub, *msg, *sig; jbyte *pub, *msg, *sig;
@@ -205,10 +276,16 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec
size_t sigSize, pubSize; size_t sigSize, pubSize;
int result = 0; int result = 0;
if (jctx == 0) return 0; if (jctx == 0)
if (jsig == NULL) return 0; return 0;
if (jmsg == NULL) return 0; if (jsig == NULL)
if (jpubkey == NULL) return 0; return 0;
if (jmsg == NULL)
return 0;
if (jpubkey == NULL)
return 0;
SETUP_ERROR_CALLBACKS
sigSize = (*penv)->GetArrayLength(penv, jsig); sigSize = (*penv)->GetArrayLength(penv, jsig);
int sigFormat = GetSignatureFormat(sigSize); int sigFormat = GetSignatureFormat(sigSize);
@@ -220,7 +297,8 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec
CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes");
sig = (*penv)->GetByteArrayElements(penv, jsig, 0); sig = (*penv)->GetByteArrayElements(penv, jsig, 0);
switch(sigFormat) { switch (sigFormat)
{
case SIG_FORMAT_COMPACT: case SIG_FORMAT_COMPACT:
result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature, (unsigned char *)sig); result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature, (unsigned char *)sig);
(*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0);
@@ -249,8 +327,7 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec
* Method: secp256k1_ecdsa_signature_normalize * Method: secp256k1_ecdsa_signature_normalize
* Signature: (J[B[B)I * Signature: (J[B[B)I
*/ */
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1signature_1normalize JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1signature_1normalize(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsigin, jbyteArray jsigout)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsigin, jbyteArray jsigout)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *sig; jbyte *sig;
@@ -260,9 +337,14 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec
int return_value = 0; int return_value = 0;
int sigFormat = SIG_FORMAT_UNKNOWN; int sigFormat = SIG_FORMAT_UNKNOWN;
if (jctx == 0) return 0; if (jctx == 0)
if (jsigin == NULL) return 0; return 0;
if (jsigout == NULL) return 0; if (jsigin == NULL)
return 0;
if (jsigout == NULL)
return 0;
SETUP_ERROR_CALLBACKS
size = (*penv)->GetArrayLength(penv, jsigin); size = (*penv)->GetArrayLength(penv, jsigin);
sigFormat = GetSignatureFormat(size); sigFormat = GetSignatureFormat(size);
@@ -270,7 +352,8 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec
CHECKRESULT((*penv)->GetArrayLength(penv, jsigout) != 64, "output signature length must be 64 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jsigout) != 64, "output signature length must be 64 bytes");
sig = (*penv)->GetByteArrayElements(penv, jsigin, 0); sig = (*penv)->GetByteArrayElements(penv, jsigin, 0);
switch(sigFormat) { switch (sigFormat)
{
case SIG_FORMAT_COMPACT: case SIG_FORMAT_COMPACT:
result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature_in, (unsigned char *)sig); result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature_in, (unsigned char *)sig);
(*penv)->ReleaseByteArrayElements(penv, jsigin, sig, 0); (*penv)->ReleaseByteArrayElements(penv, jsigin, sig, 0);
@@ -296,15 +379,19 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec
* Method: secp256k1_ec_privkey_negate * Method: secp256k1_ec_privkey_negate
* Signature: (J[B)[B * Signature: (J[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1negate JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1negate(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *seckey; jbyte *seckey;
int result = 0; int result = 0;
if (jctx == 0) return 0; if (jctx == 0)
if (jseckey == NULL) return 0; return 0;
if (jseckey == NULL)
return 0;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes");
seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0);
result = secp256k1_ec_seckey_negate(ctx, (unsigned char *)seckey); result = secp256k1_ec_seckey_negate(ctx, (unsigned char *)seckey);
@@ -318,8 +405,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ec_pubkey_negate * Method: secp256k1_ec_pubkey_negate
* Signature: (J[B)[B * Signature: (J[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1negate JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1negate(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *pub; jbyte *pub;
@@ -327,8 +413,12 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
size_t size; size_t size;
int result = 0; int result = 0;
if (jctx == 0) return 0; if (jctx == 0)
if (jpubkey == NULL) return 0; return 0;
if (jpubkey == NULL)
return 0;
SETUP_ERROR_CALLBACKS
size = (*penv)->GetArrayLength(penv, jpubkey); size = (*penv)->GetArrayLength(penv, jpubkey);
CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); CHECKRESULT((size != 33) && (size != 65), "invalid public key size");
@@ -354,16 +444,20 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ec_privkey_tweak_add * Method: secp256k1_ec_privkey_tweak_add
* Signature: (J[B[B)[B * Signature: (J[B[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1tweak_1add JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1tweak_1add(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jtweak)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jtweak)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *seckey, *tweak; jbyte *seckey, *tweak;
int result = 0; int result = 0;
if (jctx == 0) return NULL; if (jctx == 0)
if (jseckey == NULL) return NULL; return NULL;
if (jtweak == NULL) return NULL; if (jseckey == NULL)
return NULL;
if (jtweak == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes");
CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes");
@@ -381,8 +475,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ec_pubkey_tweak_add * Method: secp256k1_ec_pubkey_tweak_add
* Signature: (J[B[B)[B * Signature: (J[B[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1add JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1add(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey, jbyteArray jtweak)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey, jbyteArray jtweak)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *pub, *tweak; jbyte *pub, *tweak;
@@ -390,9 +483,14 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
size_t size; size_t size;
int result = 0; int result = 0;
if (jctx == 0) return NULL; if (jctx == 0)
if (jpubkey == NULL) return NULL; return NULL;
if (jtweak == NULL) return NULL; if (jpubkey == NULL)
return NULL;
if (jtweak == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
size = (*penv)->GetArrayLength(penv, jpubkey); size = (*penv)->GetArrayLength(penv, jpubkey);
CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); CHECKRESULT((size != 33) && (size != 65), "invalid public key size");
@@ -422,16 +520,20 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ec_privkey_tweak_mul * Method: secp256k1_ec_privkey_tweak_mul
* Signature: (J[B[B)[B * Signature: (J[B[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1tweak_1mul JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1tweak_1mul(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jtweak)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jtweak)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *seckey, *tweak; jbyte *seckey, *tweak;
int result = 0; int result = 0;
if (jctx == 0) return NULL; if (jctx == 0)
if (jseckey == NULL) return NULL; return NULL;
if (jtweak == NULL) return NULL; if (jseckey == NULL)
return NULL;
if (jtweak == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes");
CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes");
@@ -449,8 +551,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ec_pubkey_tweak_mul * Method: secp256k1_ec_pubkey_tweak_mul
* Signature: (J[B[B)[B * Signature: (J[B[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1mul JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1mul(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey, jbyteArray jtweak)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey, jbyteArray jtweak)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *pub, *tweak; jbyte *pub, *tweak;
@@ -458,9 +559,14 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
size_t size; size_t size;
int result = 0; int result = 0;
if (jctx == 0) return NULL; if (jctx == 0)
if (jpubkey == NULL) return NULL; return NULL;
if (jtweak == NULL) return NULL; if (jpubkey == NULL)
return NULL;
if (jtweak == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
size = (*penv)->GetArrayLength(penv, jpubkey); size = (*penv)->GetArrayLength(penv, jpubkey);
CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); CHECKRESULT((size != 33) && (size != 65), "invalid public key size");
@@ -487,8 +593,10 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
void free_pubkeys(secp256k1_pubkey **pubkeys, size_t count) void free_pubkeys(secp256k1_pubkey **pubkeys, size_t count)
{ {
size_t i; size_t i;
for(i = 0; i < count; i++) { for (i = 0; i < count; i++)
if (pubkeys[i] != NULL) free(pubkeys[i]); {
if (pubkeys[i] != NULL)
free(pubkeys[i]);
} }
free(pubkeys); free(pubkeys);
} }
@@ -498,8 +606,7 @@ void free_pubkeys(secp256k1_pubkey **pubkeys, size_t count)
* Method: secp256k1_ec_pubkey_combine * Method: secp256k1_ec_pubkey_combine
* Signature: (J[[B)[B * Signature: (J[[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1combine JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1combine(JNIEnv *penv, jclass clazz, jlong jctx, jobjectArray jpubkeys)
(JNIEnv *penv, jclass clazz, jlong jctx, jobjectArray jpubkeys)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *pub; jbyte *pub;
@@ -510,13 +617,18 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
size_t i; size_t i;
int result = 0; int result = 0;
if (jctx == 0) return NULL; if (jctx == 0)
if (jpubkeys == NULL) return NULL; return NULL;
if (jpubkeys == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
count = (*penv)->GetArrayLength(penv, jpubkeys); count = (*penv)->GetArrayLength(penv, jpubkeys);
pubkeys = calloc(count, sizeof(secp256k1_pubkey *)); pubkeys = calloc(count, sizeof(secp256k1_pubkey *));
for(i = 0; i < count; i++) { for (i = 0; i < count; i++)
{
pubkeys[i] = calloc(1, sizeof(secp256k1_pubkey)); pubkeys[i] = calloc(1, sizeof(secp256k1_pubkey));
jpubkey = (jbyteArray)(*penv)->GetObjectArrayElement(penv, jpubkeys, i); jpubkey = (jbyteArray)(*penv)->GetObjectArrayElement(penv, jpubkeys, i);
size = (*penv)->GetArrayLength(penv, jpubkey); size = (*penv)->GetArrayLength(penv, jpubkey);
@@ -544,8 +656,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ecdh * Method: secp256k1_ecdh
* Signature: (J[B[B)[B * Signature: (J[B[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdh JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdh(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jpubkey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jpubkey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *seckeyBytes, *pubkeyBytes, *output; jbyte *seckeyBytes, *pubkeyBytes, *output;
@@ -554,9 +665,14 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
size_t size; size_t size;
int result; int result;
if (jctx == 0) return NULL; if (jctx == 0)
if (jseckey == NULL) return NULL; return NULL;
if (jpubkey == NULL) return NULL; if (jseckey == NULL)
return NULL;
if (jpubkey == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "invalid private key size"); CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "invalid private key size");
@@ -581,8 +697,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_ecdsa_recover * Method: secp256k1_ecdsa_recover
* Signature: (J[B[BI)[B * Signature: (J[B[BI)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1recover JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1recover(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jint recid)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jint recid)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *sig, *msg, *pub; jbyte *sig, *msg, *pub;
@@ -594,16 +709,25 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
size_t sigSize, size; size_t sigSize, size;
int result; int result;
if (jctx == 0) return NULL; if (jctx == 0)
if (jsig == NULL) return NULL; return NULL;
if (jmsg == NULL) return NULL; if (jsig == NULL)
return NULL;
if (jmsg == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
// we do not check that recid is valid, which should trigger our illegal callback handler to throw a Secp256k1IllegalCallbackException
// CHECKRESULT(recid < 0 || recid > 3, "recid must be 0, 1, 2 or 3")
sigSize = (*penv)->GetArrayLength(penv, jsig); sigSize = (*penv)->GetArrayLength(penv, jsig);
int sigFormat = GetSignatureFormat(sigSize); int sigFormat = GetSignatureFormat(sigSize);
CHECKRESULT(sigFormat == SIG_FORMAT_UNKNOWN, "invalid signature size"); CHECKRESULT(sigFormat == SIG_FORMAT_UNKNOWN, "invalid signature size");
CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes");
sig = (*penv)->GetByteArrayElements(penv, jsig, 0); sig = (*penv)->GetByteArrayElements(penv, jsig, 0);
switch(sigFormat) { switch (sigFormat)
{
case SIG_FORMAT_COMPACT: case SIG_FORMAT_COMPACT:
result = secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &signature, (unsigned char *)sig, recid); result = secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &signature, (unsigned char *)sig, recid);
(*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0);
@@ -638,18 +762,22 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_compact_to_der * Method: secp256k1_compact_to_der
* Signature: (J[B)[B * Signature: (J[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1compact_1to_1der JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1compact_1to_1der(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *sig; jbyte *sig;
secp256k1_ecdsa_signature signature;; secp256k1_ecdsa_signature signature;
unsigned char der[73]; unsigned char der[73];
size_t size; size_t size;
int result = 0; int result = 0;
if (jctx == 0) return 0; if (jctx == 0)
if (jsig == NULL) return 0; return 0;
if (jsig == NULL)
return 0;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jsig) != 64, "invalid signature size"); CHECKRESULT((*penv)->GetArrayLength(penv, jsig) != 64, "invalid signature size");
size = (*penv)->GetArrayLength(penv, jsig); size = (*penv)->GetArrayLength(penv, jsig);
@@ -673,8 +801,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_schnorrsig_sign * Method: secp256k1_schnorrsig_sign
* Signature: (J[B[B[B)[B * Signature: (J[B[B[B)[B
*/ */
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1sign JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1sign(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey, jbyteArray jauxrand32)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey, jbyteArray jauxrand32)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *seckey, *msg, *sig, *auxrand32 = NULL; jbyte *seckey, *msg, *sig, *auxrand32 = NULL;
@@ -683,13 +810,19 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
int result = 0; int result = 0;
jbyteArray jsig; jbyteArray jsig;
if (jctx == 0) return NULL; if (jctx == 0)
if (jmsg == NULL) return NULL; return NULL;
if (jseckey == NULL) return NULL; if (jmsg == NULL)
return NULL;
if (jseckey == NULL)
return NULL;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes");
CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes");
if (jauxrand32 != 0) { if (jauxrand32 != 0)
{
CHECKRESULT((*penv)->GetArrayLength(penv, jauxrand32) != 32, "auxiliary random data must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jauxrand32) != 32, "auxiliary random data must be 32 bytes");
} }
seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0);
@@ -698,13 +831,15 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
CHECKRESULT(!result, "secp256k1_keypair_create failed"); CHECKRESULT(!result, "secp256k1_keypair_create failed");
msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); msg = (*penv)->GetByteArrayElements(penv, jmsg, 0);
if (jauxrand32 != 0) { if (jauxrand32 != 0)
{
auxrand32 = (*penv)->GetByteArrayElements(penv, jauxrand32, 0); auxrand32 = (*penv)->GetByteArrayElements(penv, jauxrand32, 0);
} }
result = secp256k1_schnorrsig_sign32(ctx, signature, (unsigned char *)msg, &keypair, auxrand32); result = secp256k1_schnorrsig_sign32(ctx, signature, (unsigned char *)msg, &keypair, auxrand32);
(*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0);
if (auxrand32 != 0) { if (auxrand32 != 0)
{
(*penv)->ReleaseByteArrayElements(penv, jauxrand32, auxrand32, 0); (*penv)->ReleaseByteArrayElements(penv, jauxrand32, auxrand32, 0);
} }
CHECKRESULT(!result, "secp256k1_schnorrsig_sign failed"); CHECKRESULT(!result, "secp256k1_schnorrsig_sign failed");
@@ -721,18 +856,23 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
* Method: secp256k1_schnorrsig_verify * Method: secp256k1_schnorrsig_verify
* Signature: (J[B[B[B)I * Signature: (J[B[B[B)I
*/ */
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1verify JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1verify(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey)
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey)
{ {
secp256k1_context *ctx = (secp256k1_context *)jctx; secp256k1_context *ctx = (secp256k1_context *)jctx;
jbyte *pub, *msg, *sig; jbyte *pub, *msg, *sig;
secp256k1_xonly_pubkey pubkey; secp256k1_xonly_pubkey pubkey;
int result = 0; int result = 0;
if (jctx == 0) return 0; if (jctx == 0)
if (jsig == NULL) return 0; return 0;
if (jmsg == NULL) return 0; if (jsig == NULL)
if (jpubkey == NULL) return 0; return 0;
if (jmsg == NULL)
return 0;
if (jpubkey == NULL)
return 0;
SETUP_ERROR_CALLBACKS
CHECKRESULT((*penv)->GetArrayLength(penv, jsig) != 64, "signature must be 64 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jsig) != 64, "signature must be 64 bytes");
CHECKRESULT((*penv)->GetArrayLength(penv, jpubkey) != 32, "public key must be 32 bytes"); CHECKRESULT((*penv)->GetArrayLength(penv, jpubkey) != 32, "public key must be 32 bytes");

View File

@@ -12,8 +12,12 @@ dependencies {
val copyJni by tasks.creating(Sync::class) { val copyJni by tasks.creating(Sync::class) {
onlyIf { org.gradle.internal.os.OperatingSystem.current().isMacOsX } onlyIf { org.gradle.internal.os.OperatingSystem.current().isMacOsX }
dependsOn(":jni:jvm:buildNativeHost") dependsOn(":jni:jvm:buildNativeHost")
val arch = when (System.getProperty("os.arch")) {
"aarch64" -> "aarch64"
else -> "x86_64"
}
from(rootDir.resolve("jni/jvm/build/darwin/libsecp256k1-jni.dylib")) from(rootDir.resolve("jni/jvm/build/darwin/libsecp256k1-jni.dylib"))
into(buildDir.resolve("jniResources/fr/acinq/secp256k1/jni/native/darwin-x86_64")) into(buildDir.resolve("jniResources/fr/acinq/secp256k1/jni/native/darwin-$arch"))
} }
(tasks["processResources"] as ProcessResources).apply { (tasks["processResources"] as ProcessResources).apply {

View File

@@ -19,18 +19,16 @@ else
fi fi
TARGET=$SYS-linux-android TARGET=$SYS-linux-android
TOOLTARGET=$TARGET
if [ "$SYS" == "armv7a" ]; then if [ "$SYS" == "armv7a" ]; then
TARGET=armv7a-linux-androideabi TARGET=armv7a-linux-androideabi
TOOLTARGET=arm-linux-androideabi
fi fi
export CC=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/${TARGET}21-clang export CC=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/${TARGET}21-clang
export LD=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ld export LD=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/ld
export AR=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ar export AR=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/llvm-ar
export AS=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-as export AS=$CC
export RANLIB=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ranlib export RANLIB=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/llvm-ranlib
export STRIP=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-strip export STRIP=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/llvm-strip
cd secp256k1 cd secp256k1

View File

@@ -16,7 +16,7 @@ if [ "$TARGET" == "mingw" ]; then
elif [ "$TARGET" == "linux" ]; then elif [ "$TARGET" == "linux" ]; then
CONF_OPTS="CFLAGS=-fPIC" CONF_OPTS="CFLAGS=-fPIC"
elif [ "$TARGET" == "darwin" ]; then elif [ "$TARGET" == "darwin" ]; then
CONF_OPTS="--host=x86_64-w64-darwin" CONF_OPTS=""
else else
echo "Unknown TARGET=$TARGET" echo "Unknown TARGET=$TARGET"
exit 1 exit 1

View File

@@ -27,7 +27,9 @@ You must edit `secp256k1-kmp-staging-upload.sh` and add your sonatype credential
## Adding custom JNI bindings ## Adding custom JNI bindings
Github CI currently generates JNI bindings for Windows x64, Linux x64 and iOS x64. But it is possible to add custom bindings to JNI packages before Github CI currently generates JNI bindings for Windows x64, Linux x64 and iOS x64. But it is possible to add custom bindings to JNI packages before
they are published to maven central. This is how we add linux arm64 bindings: they are published to maven central.
This is how we add linux arm64 bindings:
- compile JNI bindings for Linux Arm64 (on a Linux Arm64 machine, cross-compilation is not supported) - compile JNI bindings for Linux Arm64 (on a Linux Arm64 machine, cross-compilation is not supported)
- git clone --recursive https://github.com/ACINQ/secp256k1-kmp.git - git clone --recursive https://github.com/ACINQ/secp256k1-kmp.git
- cd secp256k1-kmp - cd secp256k1-kmp
@@ -37,6 +39,19 @@ they are published to maven central. This is how we add linux arm64 bindings:
- JNI library is: jni/jvm/build/linux/libsecp256k1-jni.so - JNI library is: jni/jvm/build/linux/libsecp256k1-jni.so
- copy libsecp256k1-jni.so to fr/acinq/secp256k1/jni/native/linux-aarch64/libsecp256k1-jni.so - copy libsecp256k1-jni.so to fr/acinq/secp256k1/jni/native/linux-aarch64/libsecp256k1-jni.so
- run `secp256k1-kmp-add-linuxarm64.sh` and specify either `release` or `snapshot` and the `VERSION` environment variable, for example: - run `secp256k1-kmp-add-linuxarm64.sh` and specify either `release` or `snapshot` and the `VERSION` environment variable, for example:
- VERSION=0.6.4-SNAPSHOT ./secp256k1-kmp-add-linuxarm64.sh snapshot - VERSION=0.9.0-SNAPSHOT ./secp256k1-kmp-add-linuxarm64.sh snapshot
- VERSION=0.6.3 ./secp256k1-kmp-add-linuxarm64.sh release - VERSION=0.9.0 ./secp256k1-kmp-add-linuxarm64.sh release
This is how we add macos arm64 (M1/M2) bindings:
- compile JNI bindings for macos Arm64 (on a macos Arm64 machine, cross-compilation is not supported)
- git clone --recursive https://github.com/ACINQ/secp256k1-kmp.git
- cd secp256k1-kmp
- TARGET=darwin ./native/build.sh
- mkdir -p jni/jvm/build/darwin
- TARGET=darwin ./jni/jvm/build.sh
- JNI library is: jni/jvm/build/darwin/libsecp256k1-jni.dylib
- copy libsecp256k1-jni.dylib to fr/acinq/secp256k1/jni/native/darwin-aarch64/libsecp256k1-jni.dylib
- run `secp256k1-kmp-add-darwinaarch64.sh` and specify either `release` or `snapshot` and the `VERSION` environment variable, for example:
- VERSION=0.9.0-SNAPSHOT ./secp256k1-kmp-add-darwinaarch64.sh snapshot
- VERSION=0.9.0 ./secp256k1-kmp-add-darwinaarch64.sh release

View File

@@ -0,0 +1,17 @@
#!/bin/bash -x
if [ $# -eq 0 ]
then
echo "specify either snapshot or release"
exit 1
fi
# add aarch64 (ARM64) library to the darwin jar
if [ -e fr/acinq/secp256k1/jni/native/darwin-aarch64/libsecp256k1-jni.dylib ]
then
jar -uf $1/fr/acinq/secp256k1/secp256k1-kmp-jni-jvm-darwin/$VERSION/secp256k1-kmp-jni-jvm-darwin-$VERSION.jar fr || exit
else
libsecp256k1-jni.dylib for arch64 is missing
exit 1
fi

View File

@@ -2,7 +2,11 @@
GROUP_ID=fr.acinq.secp256k1 GROUP_ID=fr.acinq.secp256k1
ARTIFACT_ID_BASE=secp256k1-kmp ARTIFACT_ID_BASE=secp256k1-kmp
VERSION=0.6.3-SNAPSHOT
if [[ -z "${VERSION}" ]]; then
echo "VERSION is not defined"
exit 1
fi
cd snapshot cd snapshot
pushd . pushd .
@@ -17,10 +21,18 @@ mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/conte
-Djavadoc=$ARTIFACT_ID_BASE-$VERSION-javadoc.jar -Djavadoc=$ARTIFACT_ID_BASE-$VERSION-javadoc.jar
popd popd
pushd . pushd .
for i in iosarm64 iosx64 jni-android jni-common jni-jvm-darwin jni-jvm-extract jni-jvm-linux jni-jvm-mingw jni-jvm jvm linux for i in iosarm64 iosx64 jni-android jni-common jni-jvm-darwin jni-jvm-extract jni-jvm-linux jni-jvm-mingw jni-jvm jvm linux; do
do
cd fr/acinq/secp256k1/secp256k1-kmp-$i/$VERSION cd fr/acinq/secp256k1/secp256k1-kmp-$i/$VERSION
if [ $i == iosarm64 ] || [ $i == iosx64 ] || [ $i == linux ]; then if [ $i == iosarm64 ] || [ $i == iosx64 ]; then
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION-metadata.jar,$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
-Dtypes=jar,module,klib \
-Dclassifiers=metadata,,cinterop-libsecp256k1 \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
elif [ $i == linux ]; then
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \ mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \ -DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \ -Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \

View File

@@ -3,6 +3,13 @@ pluginManagement {
google() google()
gradlePluginPortal() gradlePluginPortal()
} }
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == "com.android" || requested.id.name == "kotlin-android-extensions") {
useModule("com.android.tools.build:gradle:7.3.1")
}
}
}
} }
rootProject.name = "secp256k1-kmp" rootProject.name = "secp256k1-kmp"

View File

@@ -166,7 +166,17 @@ public interface Secp256k1 {
internal expect fun getSecpk256k1(): Secp256k1 internal expect fun getSecpk256k1(): Secp256k1
public class Secp256k1Exception : RuntimeException { public open class Secp256k1Exception : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)
}
public class Secp256k1ErrorCallbackException : Secp256k1Exception {
public constructor() : super()
public constructor(message: String?) : super(message)
}
public class Secp256k1IllegalCallbackException : Secp256k1Exception {
public constructor() : super() public constructor() : super()
public constructor(message: String?) : super(message) public constructor(message: String?) : super(message)
} }

View File

@@ -4,15 +4,67 @@ import kotlinx.cinterop.*
import platform.posix.size_tVar import platform.posix.size_tVar
import secp256k1.* import secp256k1.*
@OptIn(ExperimentalUnsignedTypes::class) private typealias Secp256k1CallbackHandler = (String) -> Unit
@OptIn(ExperimentalStdlibApi::class)
private class CallbackHandler(ctx: CPointer<secp256k1_context>) : AutoCloseable {
var illegalCallBackMessage: String? = null
val illegalHandler: Secp256k1CallbackHandler = { x: String -> illegalCallBackMessage = x }
val illegalCallbackRef = StableRef.create(illegalHandler)
var errorCallBackMessage: String? = null
val errorHandler: Secp256k1CallbackHandler = { x: String -> errorCallBackMessage = x }
val errorCallbackRef = StableRef.create(errorHandler)
init {
secp256k1_context_set_error_callback(
ctx, staticCFunction { buffer: CPointer<ByteVar>?, data: COpaquePointer? ->
if (data != null) {
val callback = data.asStableRef<Secp256k1CallbackHandler>().get()
callback(buffer?.toKString() ?: "error callback triggered")
}
},
errorCallbackRef.asCPointer()
)
secp256k1_context_set_illegal_callback(
ctx, staticCFunction { buffer: CPointer<ByteVar>?, data: COpaquePointer? ->
if (data != null) {
val callback = data.asStableRef<Secp256k1CallbackHandler>().get()
callback(buffer?.toKString() ?: "illegal callback triggered")
}
},
illegalCallbackRef.asCPointer()
)
}
fun checkForErrors() {
errorCallBackMessage?.let { throw Secp256k1ErrorCallbackException(it) }
illegalCallBackMessage?.let { throw Secp256k1IllegalCallbackException(it) }
}
override fun close() {
// StableRef instances have to be disposed of manually
illegalCallbackRef.dispose()
errorCallbackRef.dispose()
}
}
@OptIn(ExperimentalUnsignedTypes::class, ExperimentalStdlibApi::class)
public object Secp256k1Native : Secp256k1 { public object Secp256k1Native : Secp256k1 {
private val ctx: CPointer<secp256k1_context> by lazy { private val ctx: CPointer<secp256k1_context> by lazy {
secp256k1_context_create((SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_SIGN or SECP256K1_FLAGS_BIT_CONTEXT_VERIFY).toUInt()) secp256k1_context_create((SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_SIGN or SECP256K1_FLAGS_BIT_CONTEXT_VERIFY).toUInt())
?: error("Could not create secp256k1 context") ?: error("Could not create secp256k1 context")
} }
private fun Int.requireSuccess(message: String): Int = if (this != 1) throw Secp256k1Exception(message) else this private fun Int.requireSuccess(message: String): Int {
return if (this != 1) throw Secp256k1Exception(message) else this
}
private fun Int.requireSuccess(callbackHandler: CallbackHandler, message: String): Int {
callbackHandler.checkForErrors()
return if (this != 1) throw Secp256k1Exception(message) else this
}
private fun MemScope.allocSignature(input: ByteArray): secp256k1_ecdsa_signature { private fun MemScope.allocSignature(input: ByteArray): secp256k1_ecdsa_signature {
val sig = alloc<secp256k1_ecdsa_signature>() val sig = alloc<secp256k1_ecdsa_signature>()
@@ -58,181 +110,225 @@ public object Secp256k1Native : Secp256k1 {
public override fun verify(signature: ByteArray, message: ByteArray, pubkey: ByteArray): Boolean { public override fun verify(signature: ByteArray, message: ByteArray, pubkey: ByteArray): Boolean {
require(message.size == 32) require(message.size == 32)
require(pubkey.size == 33 || pubkey.size == 65) require(pubkey.size == 33 || pubkey.size == 65)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPubkey = allocPublicKey(pubkey) val nPubkey = allocPublicKey(pubkey)
val nMessage = toNat(message) val nMessage = toNat(message)
val nSig = allocSignature(signature) val nSig = allocSignature(signature)
return secp256k1_ecdsa_verify(ctx, nSig.ptr, nMessage, nPubkey.ptr) == 1 val verify = secp256k1_ecdsa_verify(ctx, nSig.ptr, nMessage, nPubkey.ptr)
callbackHandler.checkForErrors()
return verify == 1
}
} }
} }
public override fun sign(message: ByteArray, privkey: ByteArray): ByteArray { public override fun sign(message: ByteArray, privkey: ByteArray): ByteArray {
require(privkey.size == 32) require(privkey.size == 32)
require(message.size == 32) require(message.size == 32)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPrivkey = toNat(privkey) val nPrivkey = toNat(privkey)
val nMessage = toNat(message) val nMessage = toNat(message)
val nSig = alloc<secp256k1_ecdsa_signature>() val nSig = alloc<secp256k1_ecdsa_signature>()
secp256k1_ecdsa_sign(ctx, nSig.ptr, nMessage, nPrivkey, null, null).requireSuccess("secp256k1_ecdsa_sign() failed") secp256k1_ecdsa_sign(ctx, nSig.ptr, nMessage, nPrivkey, null, null).requireSuccess(callbackHandler, "secp256k1_ecdsa_sign() failed")
return serializeSignature(nSig) return serializeSignature(nSig)
} }
} }
}
public override fun signatureNormalize(sig: ByteArray): Pair<ByteArray, Boolean> { public override fun signatureNormalize(sig: ByteArray): Pair<ByteArray, Boolean> {
require(sig.size >= 64) { "invalid signature ${Hex.encode(sig)}" } require(sig.size >= 64) { "invalid signature ${Hex.encode(sig)}" }
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nSig = allocSignature(sig) val nSig = allocSignature(sig)
val isHighS = secp256k1_ecdsa_signature_normalize(ctx, nSig.ptr, nSig.ptr) val isHighS = secp256k1_ecdsa_signature_normalize(ctx, nSig.ptr, nSig.ptr)
callbackHandler.checkForErrors()
return Pair(serializeSignature(nSig), isHighS == 1) return Pair(serializeSignature(nSig), isHighS == 1)
} }
} }
}
public override fun secKeyVerify(privkey: ByteArray): Boolean { public override fun secKeyVerify(privkey: ByteArray): Boolean {
if (privkey.size != 32) return false if (privkey.size != 32) return false
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPrivkey = toNat(privkey) val nPrivkey = toNat(privkey)
return secp256k1_ec_seckey_verify(ctx, nPrivkey) == 1 val result = secp256k1_ec_seckey_verify(ctx, nPrivkey) == 1
callbackHandler.checkForErrors()
return result
}
} }
} }
public override fun pubkeyCreate(privkey: ByteArray): ByteArray { public override fun pubkeyCreate(privkey: ByteArray): ByteArray {
require(privkey.size == 32) require(privkey.size == 32)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPrivkey = toNat(privkey) val nPrivkey = toNat(privkey)
val nPubkey = alloc<secp256k1_pubkey>() val nPubkey = alloc<secp256k1_pubkey>()
secp256k1_ec_pubkey_create(ctx, nPubkey.ptr, nPrivkey).requireSuccess("secp256k1_ec_pubkey_create() failed") secp256k1_ec_pubkey_create(ctx, nPubkey.ptr, nPrivkey).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_create() failed")
return serializePubkey(nPubkey) return serializePubkey(nPubkey)
} }
} }
}
public override fun pubkeyParse(pubkey: ByteArray): ByteArray { public override fun pubkeyParse(pubkey: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65) require(pubkey.size == 33 || pubkey.size == 65)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPubkey = allocPublicKey(pubkey) val nPubkey = allocPublicKey(pubkey)
return serializePubkey(nPubkey) val result = serializePubkey(nPubkey)
callbackHandler.checkForErrors()
return result
}
} }
} }
public override fun privKeyNegate(privkey: ByteArray): ByteArray { public override fun privKeyNegate(privkey: ByteArray): ByteArray {
require(privkey.size == 32) require(privkey.size == 32)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val negated = privkey.copyOf() val negated = privkey.copyOf()
val negPriv = toNat(negated) val negPriv = toNat(negated)
secp256k1_ec_seckey_negate(ctx, negPriv).requireSuccess("secp256k1_ec_seckey_negate() failed") secp256k1_ec_seckey_negate(ctx, negPriv).requireSuccess(callbackHandler, "secp256k1_ec_seckey_negate() failed")
return negated return negated
} }
} }
}
public override fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray { public override fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32) require(privkey.size == 32)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val added = privkey.copyOf() val added = privkey.copyOf()
val natAdd = toNat(added) val natAdd = toNat(added)
val natTweak = toNat(tweak) val natTweak = toNat(tweak)
secp256k1_ec_seckey_tweak_add(ctx, natAdd, natTweak).requireSuccess("secp256k1_ec_seckey_tweak_add() failed") secp256k1_ec_seckey_tweak_add(ctx, natAdd, natTweak).requireSuccess(callbackHandler, "secp256k1_ec_seckey_tweak_add() failed")
return added return added
} }
} }
}
public override fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray { public override fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32) require(privkey.size == 32)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val multiplied = privkey.copyOf() val multiplied = privkey.copyOf()
val natMul = toNat(multiplied) val natMul = toNat(multiplied)
val natTweak = toNat(tweak) val natTweak = toNat(tweak)
secp256k1_ec_privkey_tweak_mul(ctx, natMul, natTweak).requireSuccess("secp256k1_ec_privkey_tweak_mul() failed") secp256k1_ec_privkey_tweak_mul(ctx, natMul, natTweak).requireSuccess(callbackHandler, "secp256k1_ec_privkey_tweak_mul() failed")
return multiplied return multiplied
} }
} }
}
public override fun pubKeyNegate(pubkey: ByteArray): ByteArray { public override fun pubKeyNegate(pubkey: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65) require(pubkey.size == 33 || pubkey.size == 65)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPubkey = allocPublicKey(pubkey) val nPubkey = allocPublicKey(pubkey)
secp256k1_ec_pubkey_negate(ctx, nPubkey.ptr).requireSuccess("secp256k1_ec_pubkey_negate() failed") secp256k1_ec_pubkey_negate(ctx, nPubkey.ptr).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_negate() failed")
return serializePubkey(nPubkey) return serializePubkey(nPubkey)
} }
} }
}
public override fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray { public override fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65) require(pubkey.size == 33 || pubkey.size == 65)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPubkey = allocPublicKey(pubkey) val nPubkey = allocPublicKey(pubkey)
val nTweak = toNat(tweak) val nTweak = toNat(tweak)
secp256k1_ec_pubkey_tweak_add(ctx, nPubkey.ptr, nTweak).requireSuccess("secp256k1_ec_pubkey_tweak_add() failed") secp256k1_ec_pubkey_tweak_add(ctx, nPubkey.ptr, nTweak).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_tweak_add() failed")
return serializePubkey(nPubkey) return serializePubkey(nPubkey)
} }
} }
}
public override fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray { public override fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65) require(pubkey.size == 33 || pubkey.size == 65)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPubkey = allocPublicKey(pubkey) val nPubkey = allocPublicKey(pubkey)
val nTweak = toNat(tweak) val nTweak = toNat(tweak)
secp256k1_ec_pubkey_tweak_mul(ctx, nPubkey.ptr, nTweak).requireSuccess("secp256k1_ec_pubkey_tweak_mul() failed") secp256k1_ec_pubkey_tweak_mul(ctx, nPubkey.ptr, nTweak).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_tweak_mul() failed")
return serializePubkey(nPubkey) return serializePubkey(nPubkey)
} }
} }
}
public override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray { public override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray {
pubkeys.forEach { require(it.size == 33 || it.size == 65) } pubkeys.forEach { require(it.size == 33 || it.size == 65) }
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPubkeys = pubkeys.map { allocPublicKey(it).ptr } val nPubkeys = pubkeys.map { allocPublicKey(it).ptr }
val combined = alloc<secp256k1_pubkey>() val combined = alloc<secp256k1_pubkey>()
secp256k1_ec_pubkey_combine(ctx, combined.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess("secp256k1_ec_pubkey_combine() failed") secp256k1_ec_pubkey_combine(ctx, combined.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_combine() failed")
return serializePubkey(combined) return serializePubkey(combined)
} }
} }
}
public override fun ecdh(privkey: ByteArray, pubkey: ByteArray): ByteArray { public override fun ecdh(privkey: ByteArray, pubkey: ByteArray): ByteArray {
require(privkey.size == 32) require(privkey.size == 32)
require(pubkey.size == 33 || pubkey.size == 65) require(pubkey.size == 33 || pubkey.size == 65)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPubkey = allocPublicKey(pubkey) val nPubkey = allocPublicKey(pubkey)
val nPrivkey = toNat(privkey) val nPrivkey = toNat(privkey)
val output = allocArray<UByteVar>(32) val output = allocArray<UByteVar>(32)
secp256k1_ecdh(ctx, output, nPubkey.ptr, nPrivkey, null, null).requireSuccess("secp256k1_ecdh() failed") secp256k1_ecdh(ctx, output, nPubkey.ptr, nPrivkey, null, null).requireSuccess(callbackHandler, "secp256k1_ecdh() failed")
return output.readBytes(32) return output.readBytes(32)
} }
} }
}
public override fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray { public override fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray {
require(sig.size == 64) require(sig.size == 64)
require(message.size == 32) require(message.size == 32)
// we do not check that recid is valid, which should trigger our illegal callback handler to throw a Secp256k1IllegalCallbackException
// require(recid in 0..3)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nSig = toNat(sig) val nSig = toNat(sig)
val rSig = alloc<secp256k1_ecdsa_recoverable_signature>() val rSig = alloc<secp256k1_ecdsa_recoverable_signature>()
secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, rSig.ptr, nSig, recid).requireSuccess("secp256k1_ecdsa_recoverable_signature_parse_compact() failed") secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, rSig.ptr, nSig, recid).requireSuccess(callbackHandler, "secp256k1_ecdsa_recoverable_signature_parse_compact() failed")
val nMessage = toNat(message) val nMessage = toNat(message)
val pubkey = alloc<secp256k1_pubkey>() val pubkey = alloc<secp256k1_pubkey>()
secp256k1_ecdsa_recover(ctx, pubkey.ptr, rSig.ptr, nMessage).requireSuccess("secp256k1_ecdsa_recover() failed") secp256k1_ecdsa_recover(ctx, pubkey.ptr, rSig.ptr, nMessage).requireSuccess(callbackHandler, "secp256k1_ecdsa_recover() failed")
return serializePubkey(pubkey) return serializePubkey(pubkey)
} }
} }
}
public override fun compact2der(sig: ByteArray): ByteArray { public override fun compact2der(sig: ByteArray): ByteArray {
require(sig.size == 64) require(sig.size == 64)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nSig = allocSignature(sig) val nSig = allocSignature(sig)
val natOutput = allocArray<UByteVar>(73) val natOutput = allocArray<UByteVar>(73)
val len = alloc<size_tVar>() val len = alloc<size_tVar>()
len.value = 73.convert() len.value = 73.convert()
secp256k1_ecdsa_signature_serialize_der(ctx, natOutput, len.ptr, nSig.ptr).requireSuccess("secp256k1_ecdsa_signature_serialize_der() failed") secp256k1_ecdsa_signature_serialize_der(ctx, natOutput, len.ptr, nSig.ptr).requireSuccess(callbackHandler, "secp256k1_ecdsa_signature_serialize_der() failed")
return natOutput.readBytes(len.value.toInt()) return natOutput.readBytes(len.value.toInt())
} }
} }
}
override fun verifySchnorr(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean { override fun verifySchnorr(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean {
require(signature.size == 64) require(signature.size == 64)
require(data.size == 32) require(data.size == 32)
require(pub.size == 32) require(pub.size == 32)
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nPub = toNat(pub) val nPub = toNat(pub)
val pubkey = alloc<secp256k1_xonly_pubkey>() val pubkey = alloc<secp256k1_xonly_pubkey>()
secp256k1_xonly_pubkey_parse(ctx, pubkey.ptr, nPub).requireSuccess("secp256k1_xonly_pubkey_parse() failed") secp256k1_xonly_pubkey_parse(ctx, pubkey.ptr, nPub).requireSuccess(callbackHandler, "secp256k1_xonly_pubkey_parse() failed")
val nData = toNat(data) val nData = toNat(data)
val nSig = toNat(signature) val nSig = toNat(signature)
return secp256k1_schnorrsig_verify(ctx, nSig, nData, 32, pubkey.ptr) == 1 return secp256k1_schnorrsig_verify(ctx, nSig, nData, 32u, pubkey.ptr) == 1
}
} }
} }
@@ -240,17 +336,19 @@ public object Secp256k1Native : Secp256k1 {
require(sec.size == 32) require(sec.size == 32)
require(data.size == 32) require(data.size == 32)
auxrand32?.let { require(it.size == 32) } auxrand32?.let { require(it.size == 32) }
CallbackHandler(ctx).use { callbackHandler ->
memScoped { memScoped {
val nSec = toNat(sec) val nSec = toNat(sec)
val nData = toNat(data) val nData = toNat(data)
val nAuxrand32 = auxrand32?.let { toNat(it) } val nAuxrand32 = auxrand32?.let { toNat(it) }
val nSig = allocArray<UByteVar>(64) val nSig = allocArray<UByteVar>(64)
val keypair = alloc<secp256k1_keypair>() val keypair = alloc<secp256k1_keypair>()
secp256k1_keypair_create(ctx, keypair.ptr, nSec).requireSuccess("secp256k1_keypair_create() failed") secp256k1_keypair_create(ctx, keypair.ptr, nSec).requireSuccess(callbackHandler, "secp256k1_keypair_create() failed")
secp256k1_schnorrsig_sign32(ctx, nSig, nData, keypair.ptr, nAuxrand32).requireSuccess("secp256k1_ecdsa_sign() failed") secp256k1_schnorrsig_sign32(ctx, nSig, nData, keypair.ptr, nAuxrand32).requireSuccess(callbackHandler, "secp256k1_ecdsa_sign() failed")
return nSig.readBytes(64) return nSig.readBytes(64)
} }
} }
}
public override fun cleanup() { public override fun cleanup() {
secp256k1_context_destroy(ctx) secp256k1_context_destroy(ctx)

View File

@@ -1,7 +0,0 @@
package fr.acinq.secp256k1
import org.junit.Test
import kotlin.test.assertEquals
class AndroidTest {}

View File

@@ -275,6 +275,11 @@ class Secp256k1Test {
val pub0 = Secp256k1.ecdsaRecover(sig, message, 0) val pub0 = Secp256k1.ecdsaRecover(sig, message, 0)
val pub1 = Secp256k1.ecdsaRecover(sig, message, 1) val pub1 = Secp256k1.ecdsaRecover(sig, message, 1)
assertTrue(pub.contentEquals(pub0) || pub.contentEquals(pub1)) assertTrue(pub.contentEquals(pub0) || pub.contentEquals(pub1))
// this is a special case, ecdsaRecover explicitly does not check that recid is valid, which triggers our illegal callback handler
assertFailsWith(Secp256k1IllegalCallbackException::class) {
Secp256k1.ecdsaRecover(sig, message, 4)
}
} }
@Test @Test