13 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
11 changed files with 926 additions and 663 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.6.21-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)
@@ -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`
} }
@@ -16,13 +16,13 @@ buildscript {
dependencies { dependencies {
classpath("com.android.tools.build:gradle:7.3.1") 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.9.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)

File diff suppressed because it is too large Load Diff

View File

@@ -2,52 +2,64 @@
GROUP_ID=fr.acinq.secp256k1 GROUP_ID=fr.acinq.secp256k1
ARTIFACT_ID_BASE=secp256k1-kmp ARTIFACT_ID_BASE=secp256k1-kmp
VERSION=0.9.0-SNAPSHOT
if [[ -z "${VERSION}" ]]; then
echo "VERSION is not defined"
exit 1
fi
cd snapshot cd snapshot
pushd . pushd .
cd fr/acinq/secp256k1/secp256k1-kmp/$VERSION cd fr/acinq/secp256k1/secp256k1-kmp/$VERSION
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-$VERSION.pom \ -DpomFile=$ARTIFACT_ID_BASE-$VERSION.pom \
-Dfile=$ARTIFACT_ID_BASE-$VERSION.jar \ -Dfile=$ARTIFACT_ID_BASE-$VERSION.jar \
-Dfiles=$ARTIFACT_ID_BASE-$VERSION.module,$ARTIFACT_ID_BASE-$VERSION-kotlin-tooling-metadata.json \ -Dfiles=$ARTIFACT_ID_BASE-$VERSION.module,$ARTIFACT_ID_BASE-$VERSION-kotlin-tooling-metadata.json \
-Dtypes=module,json \ -Dtypes=module,json \
-Dclassifiers=,kotlin-tooling-metadata \ -Dclassifiers=,kotlin-tooling-metadata \
-Dsources=$ARTIFACT_ID_BASE-$VERSION-sources.jar \ -Dsources=$ARTIFACT_ID_BASE-$VERSION-sources.jar \
-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 ]; then
if [ $i == iosarm64 ] || [ $i == iosx64 ] || [ $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 \ -Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION-metadata.jar,$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \ -Dtypes=jar,module,klib \
-Dtypes=module,klib \ -Dclassifiers=metadata,,cinterop-libsecp256k1 \
-Dclassifiers=,cinterop-libsecp256k1 \ -Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \ -Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar elif [ $i == linux ]; then
elif [ $i == jni-android ]; 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.aar \ -Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \ -Dtypes=module,klib \
-Dtypes=module \ -Dclassifiers=,cinterop-libsecp256k1 \
-Dclassifiers= \ -Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \ -Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar elif [ $i == jni-android ]; then
else 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.aar \
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.jar \ -Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \ -Dtypes=module \
-Dtypes=module \ -Dclassifiers= \
-Dclassifiers= \ -Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \ -Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar else
fi mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
popd -DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
pushd . -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 done

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,167 +110,209 @@ 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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPubkey = allocPublicKey(pubkey) memScoped {
val nMessage = toNat(message) val nPubkey = allocPublicKey(pubkey)
val nSig = allocSignature(signature) val nMessage = toNat(message)
return secp256k1_ecdsa_verify(ctx, nSig.ptr, nMessage, nPubkey.ptr) == 1 val nSig = allocSignature(signature)
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPrivkey = toNat(privkey) memScoped {
val nMessage = toNat(message) val nPrivkey = toNat(privkey)
val nSig = alloc<secp256k1_ecdsa_signature>() val nMessage = toNat(message)
secp256k1_ecdsa_sign(ctx, nSig.ptr, nMessage, nPrivkey, null, null).requireSuccess("secp256k1_ecdsa_sign() failed") val nSig = alloc<secp256k1_ecdsa_signature>()
return serializeSignature(nSig) secp256k1_ecdsa_sign(ctx, nSig.ptr, nMessage, nPrivkey, null, null).requireSuccess(callbackHandler, "secp256k1_ecdsa_sign() failed")
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)}" }
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nSig = allocSignature(sig) memScoped {
val isHighS = secp256k1_ecdsa_signature_normalize(ctx, nSig.ptr, nSig.ptr) val nSig = allocSignature(sig)
return Pair(serializeSignature(nSig), isHighS == 1) val isHighS = secp256k1_ecdsa_signature_normalize(ctx, nSig.ptr, nSig.ptr)
callbackHandler.checkForErrors()
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
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPrivkey = toNat(privkey) memScoped {
return secp256k1_ec_seckey_verify(ctx, nPrivkey) == 1 val nPrivkey = toNat(privkey)
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPrivkey = toNat(privkey) memScoped {
val nPubkey = alloc<secp256k1_pubkey>() val nPrivkey = toNat(privkey)
secp256k1_ec_pubkey_create(ctx, nPubkey.ptr, nPrivkey).requireSuccess("secp256k1_ec_pubkey_create() failed") val nPubkey = alloc<secp256k1_pubkey>()
return serializePubkey(nPubkey) secp256k1_ec_pubkey_create(ctx, nPubkey.ptr, nPrivkey).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_create() failed")
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPubkey = allocPublicKey(pubkey) memScoped {
return serializePubkey(nPubkey) val nPubkey = allocPublicKey(pubkey)
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val negated = privkey.copyOf() memScoped {
val negPriv = toNat(negated) val negated = privkey.copyOf()
secp256k1_ec_seckey_negate(ctx, negPriv).requireSuccess("secp256k1_ec_seckey_negate() failed") val negPriv = toNat(negated)
return negated secp256k1_ec_seckey_negate(ctx, negPriv).requireSuccess(callbackHandler, "secp256k1_ec_seckey_negate() failed")
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val added = privkey.copyOf() memScoped {
val natAdd = toNat(added) val added = privkey.copyOf()
val natTweak = toNat(tweak) val natAdd = toNat(added)
secp256k1_ec_seckey_tweak_add(ctx, natAdd, natTweak).requireSuccess("secp256k1_ec_seckey_tweak_add() failed") val natTweak = toNat(tweak)
return added secp256k1_ec_seckey_tweak_add(ctx, natAdd, natTweak).requireSuccess(callbackHandler, "secp256k1_ec_seckey_tweak_add() failed")
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val multiplied = privkey.copyOf() memScoped {
val natMul = toNat(multiplied) val multiplied = privkey.copyOf()
val natTweak = toNat(tweak) val natMul = toNat(multiplied)
secp256k1_ec_privkey_tweak_mul(ctx, natMul, natTweak).requireSuccess("secp256k1_ec_privkey_tweak_mul() failed") val natTweak = toNat(tweak)
return multiplied secp256k1_ec_privkey_tweak_mul(ctx, natMul, natTweak).requireSuccess(callbackHandler, "secp256k1_ec_privkey_tweak_mul() failed")
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPubkey = allocPublicKey(pubkey) memScoped {
secp256k1_ec_pubkey_negate(ctx, nPubkey.ptr).requireSuccess("secp256k1_ec_pubkey_negate() failed") val nPubkey = allocPublicKey(pubkey)
return serializePubkey(nPubkey) secp256k1_ec_pubkey_negate(ctx, nPubkey.ptr).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_negate() failed")
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPubkey = allocPublicKey(pubkey) memScoped {
val nTweak = toNat(tweak) val nPubkey = allocPublicKey(pubkey)
secp256k1_ec_pubkey_tweak_add(ctx, nPubkey.ptr, nTweak).requireSuccess("secp256k1_ec_pubkey_tweak_add() failed") val nTweak = toNat(tweak)
return serializePubkey(nPubkey) secp256k1_ec_pubkey_tweak_add(ctx, nPubkey.ptr, nTweak).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_tweak_add() failed")
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPubkey = allocPublicKey(pubkey) memScoped {
val nTweak = toNat(tweak) val nPubkey = allocPublicKey(pubkey)
secp256k1_ec_pubkey_tweak_mul(ctx, nPubkey.ptr, nTweak).requireSuccess("secp256k1_ec_pubkey_tweak_mul() failed") val nTweak = toNat(tweak)
return serializePubkey(nPubkey) secp256k1_ec_pubkey_tweak_mul(ctx, nPubkey.ptr, nTweak).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_tweak_mul() failed")
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) }
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPubkeys = pubkeys.map { allocPublicKey(it).ptr } memScoped {
val combined = alloc<secp256k1_pubkey>() val nPubkeys = pubkeys.map { allocPublicKey(it).ptr }
secp256k1_ec_pubkey_combine(ctx, combined.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess("secp256k1_ec_pubkey_combine() failed") val combined = alloc<secp256k1_pubkey>()
return serializePubkey(combined) secp256k1_ec_pubkey_combine(ctx, combined.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess(callbackHandler, "secp256k1_ec_pubkey_combine() failed")
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPubkey = allocPublicKey(pubkey) memScoped {
val nPrivkey = toNat(privkey) val nPubkey = allocPublicKey(pubkey)
val output = allocArray<UByteVar>(32) val nPrivkey = toNat(privkey)
secp256k1_ecdh(ctx, output, nPubkey.ptr, nPrivkey, null, null).requireSuccess("secp256k1_ecdh() failed") val output = allocArray<UByteVar>(32)
return output.readBytes(32) secp256k1_ecdh(ctx, output, nPubkey.ptr, nPrivkey, null, null).requireSuccess(callbackHandler, "secp256k1_ecdh() failed")
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)
memScoped { // we do not check that recid is valid, which should trigger our illegal callback handler to throw a Secp256k1IllegalCallbackException
val nSig = toNat(sig) // require(recid in 0..3)
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") CallbackHandler(ctx).use { callbackHandler ->
val nMessage = toNat(message) memScoped {
val pubkey = alloc<secp256k1_pubkey>() val nSig = toNat(sig)
secp256k1_ecdsa_recover(ctx, pubkey.ptr, rSig.ptr, nMessage).requireSuccess("secp256k1_ecdsa_recover() failed") val rSig = alloc<secp256k1_ecdsa_recoverable_signature>()
return serializePubkey(pubkey) 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 pubkey = alloc<secp256k1_pubkey>()
secp256k1_ecdsa_recover(ctx, pubkey.ptr, rSig.ptr, nMessage).requireSuccess(callbackHandler, "secp256k1_ecdsa_recover() failed")
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)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nSig = allocSignature(sig) memScoped {
val natOutput = allocArray<UByteVar>(73) val nSig = allocSignature(sig)
val len = alloc<size_tVar>() val natOutput = allocArray<UByteVar>(73)
len.value = 73.convert() val len = alloc<size_tVar>()
secp256k1_ecdsa_signature_serialize_der(ctx, natOutput, len.ptr, nSig.ptr).requireSuccess("secp256k1_ecdsa_signature_serialize_der() failed") len.value = 73.convert()
return natOutput.readBytes(len.value.toInt()) 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())
}
} }
} }
@@ -226,13 +320,15 @@ public object Secp256k1Native : Secp256k1 {
require(signature.size == 64) require(signature.size == 64)
require(data.size == 32) require(data.size == 32)
require(pub.size == 32) require(pub.size == 32)
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nPub = toNat(pub) memScoped {
val pubkey = alloc<secp256k1_xonly_pubkey>() val nPub = toNat(pub)
secp256k1_xonly_pubkey_parse(ctx, pubkey.ptr, nPub).requireSuccess("secp256k1_xonly_pubkey_parse() failed") val pubkey = alloc<secp256k1_xonly_pubkey>()
val nData = toNat(data) secp256k1_xonly_pubkey_parse(ctx, pubkey.ptr, nPub).requireSuccess(callbackHandler, "secp256k1_xonly_pubkey_parse() failed")
val nSig = toNat(signature) val nData = toNat(data)
return secp256k1_schnorrsig_verify(ctx, nSig, nData, 32, pubkey.ptr) == 1 val nSig = toNat(signature)
return secp256k1_schnorrsig_verify(ctx, nSig, nData, 32u, pubkey.ptr) == 1
}
} }
} }
@@ -240,15 +336,17 @@ 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) }
memScoped { CallbackHandler(ctx).use { callbackHandler ->
val nSec = toNat(sec) memScoped {
val nData = toNat(data) val nSec = toNat(sec)
val nAuxrand32 = auxrand32?.let { toNat(it) } val nData = toNat(data)
val nSig = allocArray<UByteVar>(64) val nAuxrand32 = auxrand32?.let { toNat(it) }
val keypair = alloc<secp256k1_keypair>() val nSig = allocArray<UByteVar>(64)
secp256k1_keypair_create(ctx, keypair.ptr, nSec).requireSuccess("secp256k1_keypair_create() failed") val keypair = alloc<secp256k1_keypair>()
secp256k1_schnorrsig_sign32(ctx, nSig, nData, keypair.ptr, nAuxrand32).requireSuccess("secp256k1_ecdsa_sign() failed") secp256k1_keypair_create(ctx, keypair.ptr, nSec).requireSuccess(callbackHandler, "secp256k1_keypair_create() failed")
return nSig.readBytes(64) secp256k1_schnorrsig_sign32(ctx, nSig, nData, keypair.ptr, nAuxrand32).requireSuccess(callbackHandler, "secp256k1_ecdsa_sign() failed")
return nSig.readBytes(64)
}
} }
} }

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