12 Commits

Author SHA1 Message Date
Fabrice Drouin
75f45e9191 Set version to 0.6.4 (#53) 2022-04-11 15:17:42 +02:00
Fabrice Drouin
118c72064c Update secp256k1 sources (#52)
We're now at 8746600eec5e7fcd35dabd480839a3a4bdfee87b, same as bitcoin core at 747cdf1d652d8587e9f2e3d4436c3ecdbf56d0a5
2022-04-11 13:34:59 +02:00
Fabrice Drouin
7af7b7760e Document how to add custom JNI bindings (#50)
This is how we add Linux Arm64 JNI bindings.
2022-04-04 11:26:18 +02:00
Fabrice Drouin
4df49dd8f6 Document publishing process (#46)
Document publishing process
2022-03-29 18:42:01 +02:00
Fabrice Drouin
48c3e4723b Set version to 0.6.4-SNAPSHOT (#49) 2022-03-24 16:26:17 +01:00
Fabrice Drouin
df183e88b2 Set version to 0.6.3 (#45) 2022-03-23 19:15:00 +01:00
Fabrice Drouin
de3fc7fe11 CI: fix windows tests (#48)
Github Actions modified their windows runners which broke our build. As recommended, we now uses `msys2` on windows and install the packages that we need.
2022-03-23 15:45:45 +01:00
sstone
d074a03f2d Set version to 0.6.3-SNAPSHOT 2022-01-04 13:52:26 +01:00
Fabrice Drouin
5942ccc977 Set version to 0.6.2 (#41) 2022-01-04 11:27:49 +01:00
Fabrice Drouin
c3602dc649 Update secp256k1 sources (#40)
We're now at 0559fc6e41b65af6e52c32eb9b1286494412a162, same as bitcoin core at 98a2ddcd6ed01a38cd0dad7c1abc7023a60d3fd0
2021-12-20 19:35:23 +01:00
Fabrice Drouin
ac7d4983d5 Export Schnorr signature API (#32)
Implement Schnorr signatures (BIP 340)
2021-11-23 17:38:46 +01:00
sstone
5ee01ea526 Set version to 0.6.2-SNAPSHOT 2021-11-08 17:05:20 +01:00
21 changed files with 453 additions and 54 deletions

View File

@@ -29,11 +29,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Android environment
if: matrix.os != 'windows-latest'
shell: bash
run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV
- name: Cached Android NDK
if: matrix.os != 'windows-latest'
uses: actions/cache@v2
with:
path: ${{ format('{0}/ndk/{1}', env.ANDROID_HOME, env.ANDROID_NDK_VERSION) }}
@@ -43,6 +45,19 @@ jobs:
run: |
echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
rm.exe "C:/WINDOWS/system32/bash.EXE"
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Install Automake (windows)
if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
@@ -54,18 +69,19 @@ jobs:
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: bash
shell: msys2 {0}
run: |
$ANDROID_HOME\\tools\\bin\\sdkmanager.bat "ndk;$ANDROID_NDK_VERSION"
echo "skip.android=true" > local.properties
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Check JVM
shell: bash
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'
@@ -87,27 +103,15 @@ jobs:
script: ./gradlew connectedCheck
- name: Publish Linux
if: matrix.os == 'ubuntu-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew publishLinuxPublicationToBintrayRepository :jni:jvm:linux:publishJvmPublicationToBintrayRepository
run: ./gradlew publishLinuxPublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
- name: Publish Windows
if: matrix.os == 'windows-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew :jni:jvm:mingw:publishJvmPublicationToBintrayRepository
shell: msys2 {0}
run: ./gradlew :jni:jvm:mingw:publishToMavenLocal
- name: Publish MacOS
if: matrix.os == 'macOS-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew publish
run: ./gradlew publishToMavenLocal
- name: Copy artifact files
run: |
@@ -120,9 +124,3 @@ jobs:
path: |
maven-local
!maven-local/**/maven-metadata-local.xml
# - name: Discard
# if: ${{ failure() || cancelled() }}
# env:
# BINTRAY_USER: ${{ secrets.bintray_user }}
# BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
# run: ./gradlew postBintrayDiscard

View File

@@ -54,6 +54,19 @@ jobs:
run: |
echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
rm.exe "C:/WINDOWS/system32/bash.EXE"
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Install Automake (windows)
if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
@@ -63,15 +76,21 @@ jobs:
shell: bash
run: |
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: |
echo "skip.android=true" > local.properties
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Check JVM
shell: bash
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'
@@ -97,7 +116,7 @@ jobs:
run: ./gradlew publishLinuxPublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
- name: Publish Windows
if: matrix.os == 'windows-latest'
shell: bash
shell: msys2 {0}
run: ./gradlew :jni:jvm:mingw:publishToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
- name: Publish MacOS
if: matrix.os == 'macOS-latest'

View File

@@ -44,11 +44,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Android environment
if: matrix.os != 'windows-latest'
shell: bash
run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV
- name: Cached Android NDK
if: matrix.os != 'windows-latest'
uses: actions/cache@v2
with:
path: ${{ format('{0}/ndk/{1}', env.ANDROID_HOME, env.ANDROID_NDK_VERSION) }}
@@ -58,6 +60,19 @@ jobs:
run: |
echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
rm.exe "C:/WINDOWS/system32/bash.EXE"
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Install Automake (windows)
if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
@@ -69,18 +84,19 @@ jobs:
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: bash
shell: msys2 {0}
run: |
$ANDROID_HOME\\tools\\bin\\sdkmanager.bat "ndk;$ANDROID_NDK_VERSION"
echo "skip.android=true" > local.properties
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Check JVM
shell: bash
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'

View File

@@ -22,7 +22,7 @@ buildscript {
allprojects {
group = "fr.acinq.secp256k1"
version = "0.6.1"
version = "0.6.4"
repositories {
google()

View File

@@ -151,6 +151,22 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1compact_1to_1der
(JNIEnv *, jclass, jlong, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_schnorrsig_sign
* Signature: (J[B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1sign
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_schnorrsig_verify
* Signature: (J[B[B[B)I
*/
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1verify
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray);
#ifdef __cplusplus
}
#endif

View File

@@ -4,6 +4,7 @@
#include "include/secp256k1.h"
#include "include/secp256k1_ecdh.h"
#include "include/secp256k1_recovery.h"
#include "include/secp256k1_schnorrsig.h"
#include "fr_acinq_secp256k1_Secp256k1CFunctions.h"
#define SIG_FORMAT_UNKNOWN 0
@@ -306,9 +307,9 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
if (jseckey == NULL) return 0;
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes");
seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0);
result = secp256k1_ec_privkey_negate(ctx, (unsigned char*)seckey);
result = secp256k1_ec_seckey_negate(ctx, (unsigned char*)seckey);
(*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0);
CHECKRESULT(!result, "secp256k1_ec_privkey_negate failed");
CHECKRESULT(!result, "secp256k1_ec_seckey_negate failed");
return jseckey;
}
@@ -368,10 +369,10 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes");
seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0);
tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0);
result = secp256k1_ec_privkey_tweak_add(ctx, (unsigned char*)seckey, (unsigned char*)tweak);
result = secp256k1_ec_seckey_tweak_add(ctx, (unsigned char*)seckey, (unsigned char*)tweak);
(*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0);
(*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0);
CHECKRESULT(!result, "secp256k1_ec_privkey_tweak_add failed");
CHECKRESULT(!result, "secp256k1_ec_seckey_tweak_add failed");
return jseckey;
}
@@ -436,8 +437,8 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes");
seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0);
tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0);
result = secp256k1_ec_privkey_tweak_mul(ctx, (unsigned char*)seckey, (unsigned char*)tweak);
CHECKRESULT(!result, "secp256k1_ec_privkey_tweak_mul failed");
result = secp256k1_ec_seckey_tweak_mul(ctx, (unsigned char*)seckey, (unsigned char*)tweak);
CHECKRESULT(!result, "secp256k1_ec_seckey_tweak_mul failed");
(*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0);
(*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0);
return jseckey;
@@ -666,3 +667,86 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
(*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0);
return jsig;
}
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_schnorrsig_sign
* Signature: (J[B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1sign
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey, jbyteArray jauxrand32)
{
secp256k1_context* ctx = (secp256k1_context *)jctx;
jbyte *seckey, *msg, *sig, *auxrand32 = NULL;
secp256k1_keypair keypair;
unsigned char signature[64];
int result = 0;
jbyteArray jsig;
if (jctx == 0) return NULL;
if (jmsg == NULL) return NULL;
if (jseckey == NULL) return NULL;
CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes");
CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes");
if (jauxrand32 != 0) {
CHECKRESULT((*penv)->GetArrayLength(penv, jauxrand32) != 32, "auxiliary random data must be 32 bytes");
}
seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0);
result = secp256k1_keypair_create(ctx, &keypair, seckey);
(*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0);
CHECKRESULT(!result, "secp256k1_keypair_create failed");
msg = (*penv)->GetByteArrayElements(penv, jmsg, 0);
if (jauxrand32 != 0) {
auxrand32 = (*penv)->GetByteArrayElements(penv, jauxrand32, 0);
}
result = secp256k1_schnorrsig_sign32(ctx, signature, (unsigned char*)msg, &keypair, auxrand32);
(*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0);
if (auxrand32 != 0) {
(*penv)->ReleaseByteArrayElements(penv, jauxrand32, auxrand32, 0);
}
CHECKRESULT(!result, "secp256k1_schnorrsig_sign failed");
jsig = (*penv)->NewByteArray(penv, 64);
sig = (*penv)->GetByteArrayElements(penv, jsig, 0);
memcpy(sig, signature, 64);
(*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0);
return jsig;
}
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_schnorrsig_verify
* Signature: (J[B[B[B)I
*/
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1verify
(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey)
{
secp256k1_context* ctx = (secp256k1_context *)jctx;
jbyte *pub, *msg, *sig;
secp256k1_xonly_pubkey pubkey;
int result = 0;
if (jctx == 0) return 0;
if (jsig == NULL) return 0;
if (jmsg == NULL) return 0;
if (jpubkey == NULL) return 0;
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, jmsg) != 32, "message must be 32 bytes");
pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0);
result = secp256k1_xonly_pubkey_parse(ctx, &pubkey, (unsigned char*)pub);
(*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0);
CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed");
sig = (*penv)->GetByteArrayElements(penv, jsig, 0);
msg = (*penv)->GetByteArrayElements(penv, jmsg, 0);
result = secp256k1_schnorrsig_verify(ctx, (unsigned char*)sig, (unsigned char*)msg, 32, &pubkey);
(*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0);
(*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0);
return result;
}

View File

@@ -64,4 +64,8 @@ public class Secp256k1CFunctions {
public static native byte[] secp256k1_ecdsa_recover(long ctx, byte[] sig, byte[] msg32, int recid);
public static native byte[] secp256k1_compact_to_der(long ctx, byte[] sig);
public static native byte[] secp256k1_schnorrsig_sign(long ctx, byte[] msg, byte[] seckey, byte[] aux_rand32);
public static native int secp256k1_schnorrsig_verify(long ctx, byte[] sig, byte[] msg, byte[] pubkey);
}

View File

@@ -84,6 +84,14 @@ public object NativeSecp256k1 : Secp256k1 {
return Secp256k1CFunctions.secp256k1_compact_to_der(Secp256k1Context.getContext(), sig)
}
override fun verifySchnorr(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean {
return Secp256k1CFunctions.secp256k1_schnorrsig_verify(Secp256k1Context.getContext(), signature, data, pub) == 1
}
override fun signSchnorr(data: ByteArray, sec: ByteArray, auxrand32: ByteArray?): ByteArray {
return Secp256k1CFunctions.secp256k1_schnorrsig_sign(Secp256k1Context.getContext(), data, sec, auxrand32)
}
override fun cleanup() {
return Secp256k1CFunctions.secp256k1_context_destroy(Secp256k1Context.getContext())
}

View File

@@ -35,7 +35,7 @@ export STRIP=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-st
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
./configure CFLAGS=-fpic --host=$TARGET --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
make clean
make

View File

@@ -6,7 +6,7 @@ cp xconfigure.sh secp256k1
cd secp256k1
./autogen.sh
sh xconfigure.sh --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
sh xconfigure.sh --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
mkdir -p ../build/ios
cp -v _build/universal/* ../build/ios/

View File

@@ -7,7 +7,7 @@ if (includeAndroid) {
}
val currentOs = OperatingSystem.current()
val bash = if (currentOs.isWindows) "bash.exe" else "bash"
val bash = "bash"
val buildSecp256k1 by tasks.creating { group = "build" }
@@ -27,7 +27,7 @@ val buildSecp256k1Host by tasks.creating(Exec::class) {
workingDir = projectDir
environment("TARGET", target)
commandLine(bash, "build.sh")
commandLine(bash, "-l", "build.sh")
}
val buildSecp256k1Ios by tasks.creating(Exec::class) {

View File

@@ -23,7 +23,7 @@ else
fi
./autogen.sh
./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
make clean
make

42
publishing/PUBLISHING.md Normal file
View File

@@ -0,0 +1,42 @@
# Publishing secp256k1-kmp artifacts
## snapshots
Snapshots are published to the Sonatype snapshot repository (https://oss.sonatype.org/content/repositories/snapshots/).
To publish snapshot, you must add your sonatype credentials for the `ossrh` server to your local maven settings (typically in $HOME/.m2/settings.xml)
- Download `snapshot.zip` generated by the `Publish snapshot` github action
- unzip `snapshot.zip` in the `publishing` directory
- add additional JNI bindings (optional, see below)
- edit `secp256k1-kmp-snapshot-deploy.sh` and update the `VERSION` environment variable if needed
- run `secp256k1-kmp-snapshot-deploy.sh`
## releases
Releases are published to the Sonatype staging repository. If all items are valid they will be published to `maven central` repository.
You must edit `secp256k1-kmp-staging-upload.sh` and add your sonatype credentials. You must also have a valid GPG key.
- Download `release.zip` generated by the `Publish release` github action (which is triggered every time you publish a github release)
- unzip `release.zip` in the `publishing` directory
- add additional JNI bindings (optional, see below)
- edit `secp256k1-kmp-staging-upload.sh` and update the `VERSION` environment variable if needed
- sign all artifacts with a valid gpg key: `find release -type f -print -exec gpg -ab {} \;`
- run `secp256k1-kmp-staging-upload.sh`
- log into sonatype, close and publish your staging repository. Artifacts will be available on Maven Central within a few hours.
## 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
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)
- git clone --recursive https://github.com/ACINQ/secp256k1-kmp.git
- cd secp256k1-kmp
- TARGET=linux ./native/build.sh
- mkdir -p jni/jvm/build/linux
- TARGET=linux ./jni/jvm/build.sh
- 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
- 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.6.3 ./secp256k1-kmp-add-linuxarm64.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 linux jar
if [ -e fr/acinq/secp256k1/jni/native/linux-aarch64/libsecp256k1-jni.so ]
then
jar -uf $1/fr/acinq/secp256k1/secp256k1-kmp-jni-jvm-linux/$VERSION/secp256k1-kmp-jni-jvm-linux-$VERSION.jar fr || exit
else
libsecp256k1-jni.so for arch64 is missing
exit 1
fi

View File

@@ -0,0 +1,53 @@
#!/bin/bash -x
GROUP_ID=fr.acinq.secp256k1
ARTIFACT_ID_BASE=secp256k1-kmp
VERSION=0.6.3-SNAPSHOT
cd snapshot
pushd .
cd fr/acinq/secp256k1/secp256k1-kmp/$VERSION
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
-DpomFile=$ARTIFACT_ID_BASE-$VERSION.pom \
-Dfile=$ARTIFACT_ID_BASE-$VERSION.jar \
-Dfiles=$ARTIFACT_ID_BASE-$VERSION.module,$ARTIFACT_ID_BASE-$VERSION-kotlin-tooling-metadata.json \
-Dtypes=module,json \
-Dclassifiers=,kotlin-tooling-metadata \
-Dsources=$ARTIFACT_ID_BASE-$VERSION-sources.jar \
-Djavadoc=$ARTIFACT_ID_BASE-$VERSION-javadoc.jar
popd
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
do
cd fr/acinq/secp256k1/secp256k1-kmp-$i/$VERSION
if [ $i == iosarm64 ] || [ $i == iosx64 ] || [ $i == linux ]; 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.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
-Dtypes=module,klib \
-Dclassifiers=,cinterop-libsecp256k1 \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
elif [ $i == jni-android ]; 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.aar \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
-Dtypes=module \
-Dclassifiers= \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
else
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.jar \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
-Dtypes=module \
-Dclassifiers= \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
fi
popd
pushd .
done

View File

@@ -0,0 +1,28 @@
#!/bin/bash -x
#
# first you must sign all files:
# find release -type f -print -exec gpg -ab {} \;
VERSION=0.6.2
for i in secp256k1-kmp \
secp256k1-kmp-iosarm64 \
secp256k1-kmp-iosx64 \
secp256k1-kmp-jni-android \
secp256k1-kmp-jni-common \
secp256k1-kmp-jni-jvm \
secp256k1-kmp-jni-jvm-darwin \
secp256k1-kmp-jni-jvm-extract \
secp256k1-kmp-jni-jvm-linux \
secp256k1-kmp-jni-jvm-mingw \
secp256k1-kmp-jvm \
secp256k1-kmp-linux
do
pushd .
cd release/fr/acinq/secp256k1/$i/$VERSION
pwd
jar -cvf bundle.jar *
# use correct sonatype credentials here
curl -v -XPOST -u USER:PASSWORD --upload-file bundle.jar https://oss.sonatype.org/service/local/staging/bundle_upload
popd
done

View File

@@ -38,6 +38,24 @@ public interface Secp256k1 {
public fun sign(message: ByteArray, privkey: ByteArray): ByteArray
/**
* Verify a Schnorr signature.
*
* @param signature 64 bytes signature.
* @param message message signed.
* @param pubkey signer's x-only public key (32 bytes).
*/
public fun verifySchnorr(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean
/**
* Create a Schnorr signature.
*
* @param message message to sign.
* @param privkey signer's private key.
* @param auxrand32 32 bytes of fresh randomness (optional).
*/
public fun signSchnorr(data: ByteArray, sec: ByteArray, auxrand32: ByteArray?): ByteArray
/**
* Convert an ECDSA signature to a normalized lower-S form (bitcoin standardness rule).
* Returns the normalized signature and a boolean set to true if the input signature was not normalized.
*

View File

@@ -1,7 +1,7 @@
package = secp256k1
headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h
headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1.h
headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h
headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1.h
libraryPaths.linux = c/secp256k1/build/linux/
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/local/lib

View File

@@ -118,7 +118,7 @@ public object Secp256k1Native : Secp256k1 {
memScoped {
val negated = privkey.copyOf()
val negPriv = toNat(negated)
secp256k1_ec_privkey_negate(ctx, negPriv).requireSuccess("secp256k1_ec_privkey_negate() failed")
secp256k1_ec_seckey_negate(ctx, negPriv).requireSuccess("secp256k1_ec_seckey_negate() failed")
return negated
}
}
@@ -129,7 +129,7 @@ public object Secp256k1Native : Secp256k1 {
val added = privkey.copyOf()
val natAdd = toNat(added)
val natTweak = toNat(tweak)
secp256k1_ec_privkey_tweak_add(ctx, natAdd, natTweak).requireSuccess("secp256k1_ec_privkey_tweak_add() failed")
secp256k1_ec_seckey_tweak_add(ctx, natAdd, natTweak).requireSuccess("secp256k1_ec_seckey_tweak_add() failed")
return added
}
}
@@ -222,9 +222,41 @@ public object Secp256k1Native : Secp256k1 {
}
}
override fun verifySchnorr(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean {
require(signature.size == 64)
require(data.size == 32)
require(pub.size == 32)
memScoped {
val nPub = toNat(pub)
val pubkey = alloc<secp256k1_xonly_pubkey>()
secp256k1_xonly_pubkey_parse(ctx, pubkey.ptr, nPub).requireSuccess("secp256k1_xonly_pubkey_parse() failed")
val nData = toNat(data)
val nSig = toNat(signature)
return secp256k1_schnorrsig_verify(ctx, nSig, nData, 32, pubkey.ptr) == 1
}
}
override fun signSchnorr(data: ByteArray, sec: ByteArray, auxrand32: ByteArray?): ByteArray {
require(sec.size == 32)
require(data.size == 32)
auxrand32?.let { require(it.size == 32) }
memScoped {
val nSec = toNat(sec)
val nData = toNat(data)
val nAuxrand32 = auxrand32?.let { toNat(it) }
val nSig = allocArray<UByteVar>(64)
val keypair = alloc<secp256k1_keypair>()
secp256k1_keypair_create(ctx, keypair.ptr, nSec).requireSuccess("secp256k1_keypair_create() failed")
secp256k1_schnorrsig_sign32(ctx, nSig, nData, keypair.ptr, nAuxrand32).requireSuccess("secp256k1_ecdsa_sign() failed")
return nSig.readBytes(64)
}
}
public override fun cleanup() {
secp256k1_context_destroy(ctx)
}
}
internal actual fun getSecpk256k1(): Secp256k1 = Secp256k1Native

View File

@@ -287,6 +287,71 @@ class Secp256k1Test {
)
}
@Test
fun testSchnorrSignature() {
val seckey = Hex.decode("0000000000000000000000000000000000000000000000000000000000000003")
val msg = Hex.decode("0000000000000000000000000000000000000000000000000000000000000000")
val auxrand32 = Hex.decode("0000000000000000000000000000000000000000000000000000000000000000")
val sig = Secp256k1.signSchnorr(msg, seckey, auxrand32)
assertEquals(
"E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0",
Hex.encode(sig).uppercase(),
)
val pubkey = Secp256k1.pubkeyCreate(seckey).drop(1).take(32).toByteArray()
assertEquals(
"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
Hex.encode(pubkey).uppercase()
)
assertTrue(Secp256k1.verifySchnorr(sig, msg, pubkey))
}
@Test
fun testSchnorrTestVectors() {
// BIP340 test vectors copied from https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
val bip340TestVectors = """index,secret key,public key,aux_rand,message,signature,verification result,comment
0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE,
1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE,
2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE,
3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n
4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE,
5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve
6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false
7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message
8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value
9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0
10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1
11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve
12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size
13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order
14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size"""
bip340TestVectors.split('\n').drop(1).forEach {
val testData = it.split(',')
val index = testData[0]
val seckey = Hex.decode(testData[1])
val pubkey = Hex.decode(testData[2])
val auxrand = if (testData[3].isEmpty()) null else Hex.decode(testData[3])
val msg = Hex.decode(testData[4])
val sig = testData[5]
val expected = when (testData[6]) {
"FALSE" -> false
else -> true
}
val comment = testData[7]
if (seckey.isNotEmpty()) {
val ourSig = Secp256k1.signSchnorr(msg, seckey, auxrand)
assertEquals(Hex.encode(ourSig).uppercase(), sig)
}
val result = try {
Secp256k1.verifySchnorr(Hex.decode(sig), msg, pubkey)
} catch (t: Throwable) {
false
}
assertEquals(expected, result, "test [$index, $comment] failed")
}
}
@Test
fun fuzzEcdsaSignVerify() {
val random = Random.Default
@@ -308,5 +373,4 @@ class Secp256k1Test {
assertTrue(Secp256k1.verify(der, message, pub))
}
}
}