Compare commits

..

No commits in common. "frost" and "v0.6.0" have entirely different histories.

58 changed files with 1122 additions and 7220 deletions

View File

@ -29,13 +29,11 @@ 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=25.2.9519653" >> $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) }}
@ -45,44 +43,34 @@ 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
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
java-version: 8
- name: Setup Android
if: matrix.os != 'windows-latest'
shell: bash
run: |
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: msys2 {0}
shell: bash
run: |
echo "skip.android=true" > local.properties
$ANDROID_HOME\\tools\\bin\\sdkmanager.bat "ndk;$ANDROID_NDK_VERSION"
- 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
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
shell: bash
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'
shell: bash
run: ./gradlew linuxX64Test
run: ./gradlew linuxTest
- name: Check iOS
if: matrix.os == 'macOS-latest'
shell: bash
@ -92,21 +80,34 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
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
ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.22.1
cmake: 3.10.2.4988404
script: ./gradlew connectedCheck
- name: Publish Linux
if: matrix.os == 'ubuntu-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
run: ./gradlew publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
# ./gradlew publishLinuxPublicationToBintrayRepository :jni:jvm:linux:publishJvmPublicationToBintrayRepository
run: ./gradlew publishLinuxPublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
- name: Publish Windows
if: matrix.os == 'windows-latest'
shell: msys2 {0}
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew :jni:jvm:mingw:publishJvmPublicationToBintrayRepository
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: |
@ -119,3 +120,9 @@ 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

@ -38,13 +38,11 @@ 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=25.2.9519653" >> $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) }}
@ -54,44 +52,34 @@ 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
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
java-version: 8
- name: Setup Android
if: matrix.os != 'windows-latest'
shell: bash
run: |
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: msys2 {0}
shell: bash
run: |
echo "skip.android=true" > local.properties
$ANDROID_HOME\\tools\\bin\\sdkmanager.bat "ndk;$ANDROID_NDK_VERSION"
- 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
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
shell: bash
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'
shell: bash
run: ./gradlew linuxX64Test
run: ./gradlew linuxTest
- name: Check iOS
if: matrix.os == 'macOS-latest'
shell: bash
@ -101,21 +89,34 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
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
ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.22.1
cmake: 3.10.2.4988404
script: ./gradlew connectedCheck
- name: Publish Linux
if: matrix.os == 'ubuntu-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
run: ./gradlew publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
# ./gradlew publishLinuxPublicationToBintrayRepository :jni:jvm:linux:publishJvmPublicationToBintrayRepository -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
run: ./gradlew publishLinuxPublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
- name: Publish Windows
if: matrix.os == 'windows-latest'
shell: msys2 {0}
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew :jni:jvm:mingw:publishJvmPublicationToBintrayRepository -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
run: ./gradlew :jni:jvm:mingw:publishToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
- name: Publish MacOS
if: matrix.os == 'macOS-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew publish -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
run: ./gradlew publishToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
- name: Copy artifact files
run: |
@ -128,4 +129,34 @@ 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 -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
# publish:
# name: Publish
# needs: upload
# runs-on: ubuntu-latest
# steps:
# - name: Check out
# uses: actions/checkout@v2
# with:
# submodules: 'true'
# - name: Cached Gradle
# uses: actions/cache@v2
# with:
# path: ~/.gradle
# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
# restore-keys: ${{ runner.os }}-gradle-
# - name: Set up JDK 11
# uses: actions/setup-java@v1
# with:
# java-version: 11
# - name: Publish
# env:
# BINTRAY_USER: ${{ secrets.bintray_user }}
# BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
# run: ./gradlew postBintrayPublish -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}

View File

@ -44,13 +44,11 @@ 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=25.2.9519653" >> $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) }}
@ -60,44 +58,34 @@ 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
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
java-version: 8
- name: Setup Android
if: matrix.os != 'windows-latest'
shell: bash
run: |
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: msys2 {0}
shell: bash
run: |
echo "skip.android=true" > local.properties
$ANDROID_HOME\\tools\\bin\\sdkmanager.bat "ndk;$ANDROID_NDK_VERSION"
- 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
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
shell: bash
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'
shell: bash
run: ./gradlew linuxX64Test
run: ./gradlew linuxTest
- name: Check iOS
if: matrix.os == 'macOS-latest'
shell: bash
@ -107,7 +95,8 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
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
ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.22.1
cmake: 3.10.2.4988404
script: ./gradlew connectedCheck

3
.gitignore vendored
View File

@ -13,6 +13,3 @@ local.properties
# OS
.DS_Store
jni/bin/*
jni/jvm/bin/*
/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.class

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "native/secp256k1"]
path = native/secp256k1
url = https://code.sigidli.com/frost/secp256k1-zkp.git
url = https://github.com/bitcoin-core/secp256k1.git

View File

@ -1,4 +1,4 @@
[![Kotlin](https://img.shields.io/badge/Kotlin-1.9.22-blue.svg?style=flat&logo=kotlin)](http://kotlinlang.org)
[![Kotlin](https://img.shields.io/badge/Kotlin-1.4.31-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*)
![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)
@ -30,19 +30,19 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
implementation("fr.acinq.secp256k1:secp256k1-kmp:$secp256k1_version")
implementation(kotlin("fr.acinq.secp256k1:secp256k1:$secp256k1_version"))
}
}
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib"))
implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:$secp256k1_version")
implementation(kotlin("fr.acinq.secp256k1:secp256k1-jni-jvm:$secp256k1_version"))
}
}
val androidMain by getting {
dependencies {
implementation(kotlin("stdlib"))
implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-android:$secp256k1_version")
implementation(kotlin("fr.acinq.secp256k1:secp256k1-jni-android:$secp256k1_version"))
}
}
}
@ -58,22 +58,22 @@ 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.
JNI libraries are included for:
- Linux 64 bits (x86_64 and arm64)
- Windows 64 bits (x86_64)
- Macos 64 bits (x86_64 and arm64)
- Linux 64 bits
- Windows 64 bits
- Macos 64 bits
Along this library, you **must** specify which JNI native library to use in your dependency manager:
* **For desktop or server JVMs**, you must add the dependency:
* Either the `fr.acinq.secp256k1:secp256k1-kmp-jni-jvm` dependency which imports all supported platforms.
* Either the `fr.acinq.secp256k1:secp256k1-jni-jvm` dependency which imports all supported platforms.
* Or the platform specific dependencies (note that you can add multiple as they do not conflict):
* `fr.acinq.secp256k1:secp256k1-kmp-jni-jvm-linux` for Linux
* `fr.acinq.secp256k1:secp256k1-kmp-jni-jvm-darwin` for Mac OS X
* `fr.acinq.secp256k1:secp256k1-kmp-jni-jvm-mingw` for Windows
* **For Android**, you must add the `fr.acinq.secp256k1:secp256k1-kmp-jni-android` dependency
* `fr.acinq.secp256k1:secp256k1-jni-jvm-linux` for Linux
* `fr.acinq.secp256k1:secp256k1-jni-jvm-darwin` for Mac OS X
* `fr.acinq.secp256k1:secp256k1-jni-jvm-mingw` for Windows
* **For Android**, you must add the `fr.acinq.secp256k1:secp256k1-jni-android` dependency
If you are using the JVM on an OS for which we don't provide JNI bindings (32 bits OS for example), you can use your own library native library by
adding the `fr.acinq.secp256k1:secp256k1-kmp-jni-jvm` dependency and specifying its path with `-Dfr.acinq.secp256k1.lib.path` and optionally its name with `-Dfr.acinq.secp256k1.lib.name`
adding the `fr.acinq.secp256k1:secp256k1-jni-jvm` dependency and specifying its path with `-Dfr.acinq.secp256k1.lib.path` and optionally its name with `-Dfr.acinq.secp256k1.lib.name`
(if unspecified bitcoink use the standard name for your OS i.e. libsecp256k1.so on Linux, secp256k1.dll on Windows, ...).
To compile your own JNI bindings, have a look add the `native/build.sh` and `jni/build.sh` scripts.
@ -84,58 +84,3 @@ You can also specify the temporary directory where the library will be extracted
## Usage
Please have a look at unit tests, more samples will be added soon.
## Building
**secp256k1-kmp** is a [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) wrapper for Bitcoin Core's [secp256k1 library](https://github.com/bitcoin-core/secp256k1).
To build the library you need the following:
- Window 64 bits, Linux 64 bits, or MacOs 64 Bits
- OpenJDK11 (we recommend using packages provided by https://adoptopenjdk.net/ but there are other options)
- (optional) Android SDK
It may work with other Operating Systems and JDKs, but then you're on your own (in particular we don't plan to support 32 bits Operating Systems).
To build the library and publish compiled artefacts locally (so they can be used by other projects):
```sh
./gradlew :build
./gradlew :publishToMavenLocal
```
To run all tests on all platforms:
```sh
./gradlew allTests
```
To run tests on a single platform, for example the JVM:
```sh
./gradlew jvmTest
```
If you want to skip building Android artefacts create a `local.properties` file in the project's root directory and add the following line:
```
skip.android=true
```
## Contributing to and extending the library
secp256k1-kmp follows 2 simples rules:
- copy as literally as possible what the original [secp256k1 library](https://github.com/bitcoin-core/secp256k1) does: use the same function names, parameters, options, ...
- follow JNI best practices (memory allocation, error handling, ...)
"Porting" C/C++ code that uses [secp256k1](https://github.com/bitcoin-core/secp256k1) should be a no-brainer and we should not have to document secp256k1-kmp
To extend this library and support methods that have been added to specific versions of [secp256k1](https://github.com/bitcoin-core/secp256k1) you have to:
- add new methods to the Secp256k1 interface src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt (please follow rule #1 above and try and match secp256k1's interface as much as possible)
- implement these new methods in jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt (JNI implementation) and src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt (native linux/ios/... implementation)
- update the JNI interface src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java (NativeSecp256k1 calls Secp256k1CFunctions)
- generate a new JNI header file jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h with `javac -h jni/c/headers/java jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java`
- implement the new methods in jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c
You may also need to modify build files if you need to compile [secp256k1](https://github.com/bitcoin-core/secp256k1) with custom options
We use [secp256k1](https://github.com/bitcoin-core/secp256k1) through git submodules so you may also need to change what they point to

View File

@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.dokka.Platform
plugins {
kotlin("multiplatform") version "1.9.22"
id("org.jetbrains.dokka") version "1.9.10"
kotlin("multiplatform") version "1.5.31"
id("org.jetbrains.dokka") version "1.5.30"
`maven-publish`
}
@ -15,14 +15,14 @@ buildscript {
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.1")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
classpath("com.android.tools.build:gradle:4.0.2")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.5.30")
}
}
allprojects {
group = "fr.acinq.secp256k1"
version = "frost-SNAPSHOT"
version = "0.6.0"
repositories {
google()
@ -52,37 +52,24 @@ kotlin {
}
}
val nativeMain by sourceSets.creating
val nativeMain by sourceSets.creating { dependsOn(commonMain) }
linuxX64 {
linuxX64("linux") {
secp256k1CInterop("host")
compilations["main"].defaultSourceSet.dependsOn(nativeMain)
// https://youtrack.jetbrains.com/issue/KT-39396
compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary", "$rootDir/native/build/linux/libsecp256k1.a")
}
if (currentOs.isMacOsX) {
macosX64 {
secp256k1CInterop("host")
}
macosArm64 {
secp256k1CInterop("host")
}
iosX64 {
secp256k1CInterop("ios")
}
iosArm64 {
secp256k1CInterop("ios")
}
iosSimulatorArm64 {
secp256k1CInterop("ios")
}
ios {
secp256k1CInterop("ios")
compilations["main"].defaultSourceSet.dependsOn(nativeMain)
// https://youtrack.jetbrains.com/issue/KT-39396
compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-include-binary", "$rootDir/native/build/ios/libsecp256k1.a")
}
sourceSets.all {
languageSettings.optIn("kotlin.RequiresOptIn")
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
}
}
@ -93,9 +80,9 @@ allprojects {
val currentOs = OperatingSystem.current()
val targets = when {
currentOs.isLinux -> listOf()
currentOs.isMacOsX -> listOf("linuxX64")
currentOs.isWindows -> listOf("linuxX64")
else -> listOf("linuxX64")
currentOs.isMacOsX -> listOf("linux")
currentOs.isWindows -> listOf("linux")
else -> listOf("linux")
}.mapNotNull { kotlin.targets.findByName(it) as? KotlinNativeTarget }
configure(targets) {
@ -170,7 +157,6 @@ allprojects {
Platform.js -> "js"
Platform.native -> "native"
Platform.common -> "common"
Platform.wasm -> "wasm"
}
displayName.set(platformName)

View File

@ -4,9 +4,14 @@ org.gradle.parallel = true
# kotlin
kotlin.code.style = official
kotlin.incremental.multiplatform = true
kotlin.parallel.tasks.in.project = true
#kotlin.mpp.enableGranularSourceSetsMetadata = true
kotlin.native.enableDependencyPropagation = false
kotlin.native.ignoreDisabledTargets = true
kotlin.mpp.enableCInteropCommonization=true
kotlin.native.cacheKind.macosArm64=none
# https://github.com/gradle/gradle/issues/11412
systemProp.org.gradle.internal.publish.checksums.insecure = true
# Android
android.useAndroidX = true

Binary file not shown.

View File

@ -1,6 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View File

@ -82,7 +82,6 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -130,7 +129,6 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath

192
gradlew.bat vendored
View File

@ -1,89 +1,103 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

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

View File

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

View File

@ -6,10 +6,11 @@ import fr.acinq.secp256k1.NativeSecp256k1
import java.util.*
public object NativeSecp256k1AndroidLoader {
@JvmStatic
@Synchronized
@Throws(Exception::class)
public fun load(): Secp256k1 {
fun load(): Secp256k1 {
try {
System.loadLibrary("secp256k1-jni")
return NativeSecp256k1
@ -26,4 +27,5 @@ public object NativeSecp256k1AndroidLoader {
}
}
}

View File

@ -1,3 +1,5 @@
import org.jetbrains.dokka.Platform
plugins {
kotlin("jvm")
id("org.jetbrains.dokka")
@ -20,7 +22,7 @@ dependencies {
val generateHeaders by tasks.creating(JavaCompile::class) {
group = "build"
classpath = sourceSets["main"].compileClasspath
destinationDirectory.set(file("${buildDir}/generated/jni"))
destinationDir = file("${buildDir}/generated/jni")
source = sourceSets["main"].java
options.compilerArgs = listOf(
"-h", file("${buildDir}/generated/jni").absolutePath,

View File

@ -7,43 +7,6 @@
#ifdef __cplusplus
extern "C" {
#endif
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_TYPE_CONTEXT
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_TYPE_CONTEXT 1L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_TYPE_COMPRESSION
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_TYPE_COMPRESSION 2L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_CONTEXT_VERIFY
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_CONTEXT_VERIFY 256L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_CONTEXT_SIGN
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_CONTEXT_SIGN 512L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_COMPRESSION
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_COMPRESSION 256L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_VERIFY
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_VERIFY 257L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_SIGN
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_SIGN 513L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_NONE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_NONE 1L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_EC_COMPRESSED
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_EC_COMPRESSED 258L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_EC_UNCOMPRESSED
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_EC_UNCOMPRESSED 2L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE 66L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE 132L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE 197L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE 133L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FROST_SECRET_NONCE_SIZE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FROST_SECRET_NONCE_SIZE 68L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FROST_PUBLIC_NONCE_SIZE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FROST_PUBLIC_NONCE_SIZE 66L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FROST_AGGREGATE_SHARE_SIZE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FROST_AGGREGATE_SHARE_SIZE 32L
#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FROST_SESSION_SIZE
#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FROST_SESSION_SIZE 133L
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_context_create
@ -156,6 +119,14 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1mul
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_ec_pubkey_add
* Signature: (J[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1add
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_ec_pubkey_combine
@ -188,190 +159,6 @@ 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);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_nonce_gen
* Signature: (J[B[B[B[B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_nonce_agg
* Signature: (J[[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1agg
(JNIEnv *, jclass, jlong, jobjectArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_pubkey_agg
* Signature: (J[[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1agg
(JNIEnv *, jclass, jlong, jobjectArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_pubkey_ec_tweak_add
* Signature: (J[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1ec_1tweak_1add
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_pubkey_xonly_tweak_add
* Signature: (J[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1xonly_1tweak_1add
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_nonce_process
* Signature: (J[B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1process
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_partial_sign
* Signature: (J[B[B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sign
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_partial_sig_verify
* Signature: (J[B[B[B[B[B)I
*/
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sig_1verify
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_musig_partial_sig_agg
* Signature: (J[B[[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sig_1agg
(JNIEnv *, jclass, jlong, jbyteArray, jobjectArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_shares_gen
* Signature: (J[B[BII[[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1shares_1gen
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jint, jint, jobjectArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_share_agg
* Signature: (J[[B[[[BII[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1share_1agg
(JNIEnv *, jclass, jlong, jobjectArray, jobjectArray, jint, jint, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_share_verify
* Signature: (JI[B[B[[B)I
*/
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1share_1verify
(JNIEnv *, jclass, jlong, jint, jbyteArray, jbyteArray, jobjectArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_compute_pubshare
* Signature: (JI[B[[[BI)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1compute_1pubshare
(JNIEnv *, jclass, jlong, jint, jbyteArray, jobjectArray, jint);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_pubkey_tweak
* Signature: (J[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1pubkey_1tweak
(JNIEnv *, jclass, jlong, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_pubkey_ec_tweak_add
* Signature: (J[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1pubkey_1ec_1tweak_1add
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_pubkey_xonly_tweak_add
* Signature: (J[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1pubkey_1xonly_1tweak_1add
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_nonce_gen
* Signature: (J[B[B[B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1nonce_1gen
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_nonce_process
* Signature: (J[[BI[B[B[B[[B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1nonce_1process
(JNIEnv *, jclass, jlong, jobjectArray, jint, jbyteArray, jbyteArray, jbyteArray, jobjectArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_partial_sign
* Signature: (J[B[B[B[B)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1partial_1sign
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_partial_sig_verify
* Signature: (J[B[B[B[B[B)I
*/
JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1partial_1sig_1verify
(JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray);
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_frost_partial_sig_agg
* Signature: (J[B[[BI)[B
*/
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1frost_1partial_1sig_1agg
(JNIEnv *, jclass, jlong, jbyteArray, jobjectArray, jint);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,13 @@
import org.gradle.internal.os.OperatingSystem
plugins {
`java-library`
id("org.jetbrains.dokka")
`maven-publish`
}
val currentOs = OperatingSystem.current()
dependencies {
val publishModeEnabled = rootProject.hasProperty("publishMode") // TODO: Add a -PpublishMode argument to the build script specifically for publishing a release (not when publishing local).
println("publishModeEnabled: $publishModeEnabled")
if (publishModeEnabled || currentOs.isMacOsX) {
api(project(":jni:jvm:darwin"))
}
if (publishModeEnabled || currentOs.isLinux) {
api(project(":jni:jvm:linux"))
}
if (publishModeEnabled || currentOs.isWindows) {
api(project(":jni:jvm:mingw"))
}
api(project(":jni:jvm:darwin"))
api(project(":jni:jvm:linux"))
api(project(":jni:jvm:mingw"))
}
publishing {

View File

@ -17,7 +17,6 @@ if [ "$TARGET" == "linux" ]; then
CC_OPTS="-fPIC"
elif [ "$TARGET" == "darwin" ]; then
OUTFILE=libsecp256k1-jni.dylib
CC_OPTS="-arch arm64 -arch x86_64"
elif [ "$TARGET" == "mingw" ]; then
OUTFILE=secp256k1-jni.dll
CC=x86_64-w64-mingw32-gcc

View File

@ -13,7 +13,7 @@ val copyJni by tasks.creating(Sync::class) {
onlyIf { org.gradle.internal.os.OperatingSystem.current().isMacOsX }
dependsOn(":jni:jvm:buildNativeHost")
from(rootDir.resolve("jni/jvm/build/darwin/libsecp256k1-jni.dylib"))
into(buildDir.resolve("jniResources/fr/acinq/secp256k1/jni/native/darwin"))
into(buildDir.resolve("jniResources/fr/acinq/secp256k1/jni/native/darwin-x86_64"))
}
(tasks["processResources"] as ProcessResources).apply {

View File

@ -19,8 +19,7 @@ internal object OSInfo {
private const val PPC = "ppc"
private const val PPC64 = "ppc64"
// on macos we build a universal library that contains arm64 and x64 binaries
@JvmStatic val nativeSuffix: String get() = if (os == "darwin") os else "$os-$arch"
@JvmStatic val nativeSuffix: String get() = "$os-$arch"
@JvmStatic val os: String get() = translateOSName(System.getProperty("os.name"))

View File

@ -5,56 +5,36 @@ public class Secp256k1CFunctions {
* All flags' lower 8 bits indicate what they're for. Do not use directly.
*/
public static int SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1);
public static final int SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0);
public static final int SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1);
public static int SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0);
public static int SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1);
/**
* The higher bits contain the actual data. Do not use directly.
*/
public static final int SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8);
public static final int SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9);
public static final int SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8);
public static int SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8);
public static int SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9);
public static int SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8);
/**
* Flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and
* secp256k1_context_preallocated_create.
*/
public static final int SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY);
public static final int SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN);
public static final int SECP256K1_CONTEXT_NONE = (SECP256K1_FLAGS_TYPE_CONTEXT);
public static int SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY);
public static int SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN);
public static int SECP256K1_CONTEXT_NONE = (SECP256K1_FLAGS_TYPE_CONTEXT);
/**
* Flag to pass to secp256k1_ec_pubkey_serialize.
*/
public static final int SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION);
public static final int SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION);
/**
* A musig2 public nonce is simply two elliptic curve points.
*/
public static final int SECP256K1_MUSIG_PUBLIC_NONCE_SIZE = 66;
/**
* A musig2 private nonce is basically two scalars, but should be treated as an opaque blob.
*/
public static final int SECP256K1_MUSIG_SECRET_NONCE_SIZE = 132;
/**
* When aggregating public keys, we cache information in an opaque blob (must not be interpreted).
*/
public static final int SECP256K1_MUSIG_KEYAGG_CACHE_SIZE = 197;
/**
* When creating partial signatures and aggregating them, session data is kept in an opaque blob (must not be interpreted).
*/
public static final int SECP256K1_MUSIG_SESSION_SIZE = 133;
public static int SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION);
public static int SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION);
public static native long secp256k1_context_create(int flags);
public static native void secp256k1_context_destroy(long ctx);
public static native int secp256k1_ec_seckey_verify(long ctx, byte[] seckey);
public static native byte[] secp256k1_ec_pubkey_parse(long ctx, byte[] pubkey);
public static native byte[] secp256k1_ec_pubkey_create(long ctx, byte[] seckey);
@ -77,6 +57,8 @@ public class Secp256k1CFunctions {
public static native byte[] secp256k1_ec_pubkey_tweak_mul(long ctx, byte[] pubkey, byte[] tweak);
public static native byte[] secp256k1_ec_pubkey_add(long ctx, byte[] pubkey1, byte[] pubkey2);
public static native byte[] secp256k1_ec_pubkey_combine(long ctx, byte[][] pubkeys);
public static native byte[] secp256k1_ecdh(long ctx, byte[] seckey, byte[] pubkey);
@ -84,315 +66,4 @@ 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);
public static native byte[] secp256k1_musig_nonce_gen(long ctx, byte[] session_id32, byte[] seckey, byte[] pubkey, byte[] msg32, byte[] keyagg_cache, byte[] extra_input32);
public static native byte[] secp256k1_musig_nonce_agg(long ctx, byte[][] nonces);
public static native byte[] secp256k1_musig_pubkey_agg(long ctx, byte[][] pubkeys, byte[] keyagg_cache);
public static native byte[] secp256k1_musig_pubkey_ec_tweak_add(long ctx, byte[] keyagg_cache, byte[] tweak32);
public static native byte[] secp256k1_musig_pubkey_xonly_tweak_add(long ctx, byte[] keyagg_cache, byte[] tweak32);
public static native byte[] secp256k1_musig_nonce_process(long ctx, byte[] aggnonce, byte[] msg32, byte[] keyagg_cache);
public static native byte[] secp256k1_musig_partial_sign(long ctx, byte[] secnonce, byte[] privkey, byte[] keyagg_cache, byte[] session);
public static native int secp256k1_musig_partial_sig_verify(long ctx, byte[] psig, byte[] pubnonce, byte[] pubkey, byte[] keyagg_cache, byte[] session);
public static native byte[] secp256k1_musig_partial_sig_agg(long ctx, byte[] session, byte[][] psigs);
/**
* Creates key shares
*
* To generate a key, each participant generates a share for each other
* participant. For example, in the case of 2 particpants, Alice and Bob, they
* each generate 2 shares, distribute 1 share to each other using a secure
* channel, and keep 1 for themselves.
*
* Each participant must transmit shares over secure channels to each other
* participant.
*
* Each call to this function must have a UNIQUE and uniformly RANDOM seed32
* that must NOT BE REUSED in subsequent calls to this function and
* must be KEPT SECRET (even from other participants).
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param seed32 32-byte random seed as explained above. Must be unique to this call to secp256k1_frost_shares_gen
* and must be uniformly random.
* @param threshold the minimum number of signers required to produce a signature
* @param total_signers the total number of participants
* @param ids33 array of 33-byte participant IDs
*
* @return
* [0] shares: pointer to the key shares
* [1] vss_commitment: pointer to the VSS commitment
* [2] pok64: pointer to the proof of knowledge
*/
public static native byte[] secp256k1_frost_shares_gen(long ctx, byte[] pok64, byte[] seed32, int threshold, int total_signers, byte[][] ids33);
/**
* Aggregates shares
*
* As part of the key generation protocol, each participant receives a share
* from each participant, including a share they "receive" from themselves.
* This function verifies those shares against their VSS commitments,
* aggregates the shares, and then aggregates the commitments to each
* participant's first polynomial coefficient to derive the aggregate public
* key.
*
* If this function returns an error, `secp256k1_frost_share_verify` can be
* called on each share to determine which participants submitted faulty
* shares.
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param shares all key generation shares for the partcipant's index
* @param vss_commitments coefficient commitments of all participants ordered by the x-only pubkeys of the participants
* @param totalShareCount the total number of shares
* @param threshold the minimum number of shares required to produce a signature
* @param id33 the 33-byte ID of the participant whose shares are being aggregated
*
* @return
* [0] agg_share: the aggregated share
* [1] agg_pk: the aggregated x-only public key
*/
public static native byte[] secp256k1_frost_share_agg(long ctx, byte[][] shares, byte[][][] vss_commitments, int totalShareCount, int threshold, byte[] id33);
/**
* Verifies a share received during a key generation session
*
* The signature is verified against the VSS commitment received with the
* share. This is only useful for purposes of determining which share(s) are
* invalid if share_agg returns an error.
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param threshold the minimum number of signers required to produce a signature
* @param id33 the 33-byte participant ID of the share recipient
* @param share pointer to a key generation share
* @param vss_commitment the VSS commitment associated with the share
*
* @return 0 if the arguments are invalid or the share does not verify, 1 otherwise
*/
public static native int secp256k1_frost_share_verify(long ctx, int threshold, byte[] id33, byte[] share, byte[][] vss_commitment);
/**
* Computes a public verification share used for verifying partial signatures
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param threshold the minimum number of signers required to produce a signature
* @param id33 the 33-byte participant ID of the participant whose partial signature will be verified with
* the pubshare
* @param vss_commitments coefficient commitments of all participants
* @param totalSignersCount the total number of participants
*
* @return pubshare: pointer to a struct to store the public verification share
*/
public static native byte[] secp256k1_frost_compute_pubshare(long ctx, int threshold, byte[] id33, byte[][][] vss_commitments, int totalSignersCount);
/**
* Initializes a tweak cache used for applying tweaks to a FROST key
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param public_key the aggregated x-only public key that is the output of `secp256k1_frost_share_agg`
*
* @return tweak_cache: pointer to a frost_tweak_cache struct that is required for key tweaking
*/
public static native byte[] secp256k1_frost_pubkey_tweak(long ctx, byte[] public_key);
/**
* Apply ordinary "EC" tweaking to a public key in a given tweak_cache by
* adding the generator multiplied with `tweak32` to it. This is useful for
* deriving child keys from an aggregate public key via BIP32.
*
* The tweaking method is the same as `secp256k1_ec_pubkey_tweak_add`. So after
* the following pseudocode buf and buf2 have identical contents (absent
* earlier failures).
*
* secp256k1_frost_share_agg(..., xonly_agg_pk, ...)
* secp256k1_frost_pubkey_tweak(..., tweak_cache, xonly_agg_pk)
* secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, tweak_cache, tweak32)
* secp256k1_ec_pubkey_serialize(..., buf, output_pk)
* secp256k1_frost_pubkey_get(..., ec_agg_pk, xonly_agg_pk)
* secp256k1_ec_pubkey_tweak_add(..., ec_agg_pk, tweak32)
* secp256k1_ec_pubkey_serialize(..., buf2, ec_agg_pk)
*
* This function is required if you want to _sign_ for a tweaked aggregate key.
* On the other hand, if you are only computing a public key, but not intending
* to create a signature for it, you can just use
* `secp256k1_ec_pubkey_tweak_add`.
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param tweakCache pointer to a `frost_tweak_cache` struct initialized by `frost_pubkey_tweak`
* @param tweak32 pointer to a 32-byte tweak. If the tweak is invalid according to `secp256k1_ec_seckey_verify`,
* this function returns 0. For uniformly random 32-byte arrays the chance of being invalid
* is negligible (around 1 in 2^128).
*
* @return output_pubkey: pointer to a public key to store the result. Will be set
* to an invalid value if this function returns 0. If you
* do not need it, this arg can be NULL.
*/
public static native byte[] secp256k1_frost_pubkey_ec_tweak_add(long ctx, byte[] tweakCache, byte[] tweak32);
/**
* Apply x-only tweaking to a public key in a given tweak_cache by adding the
* generator multiplied with `tweak32` to it. This is useful for creating
* Taproot outputs.
*
* The tweaking method is the same as `secp256k1_xonly_pubkey_tweak_add`. So in
* the following pseudocode xonly_pubkey_tweak_add_check (absent earlier
* failures) returns 1.
*
* secp256k1_frost_share_agg(..., agg_pk, ...)
* secp256k1_frost_pubkey_tweak(..., tweak_cache, agg_pk)
* secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, tweak_cache, tweak32)
* secp256k1_xonly_pubkey_serialize(..., buf, output_pk)
* secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., agg_pk, tweak32)
*
* This function is required if you want to _sign_ for a tweaked aggregate key.
* On the other hand, if you are only computing a public key, but not intending
* to create a signature for it, you can just use
* `secp256k1_xonly_pubkey_tweak_add`.
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param tweakCache pointer to a `frost_tweak_cache` struct initialized by `frost_pubkey_tweak`
* @param tweak32 pointer to a 32-byte tweak. If the tweak is invalid according to secp256k1_ec_seckey_verify,
* this function returns 0. For uniformly random 32-byte arrays the
* chance of being invalid is negligible (around 1 in 2^128).
*
* @return
* [0] output_pubkey: pointer to a public key to store the result. Will be set to an invalid value if this
* function returns 0. If you do not need it, this arg can be NULL.
* [1] tweak_cache: pointer to a `frost_tweak_cache` struct initialized by `frost_pubkey_tweak`
*/
public static native byte[] secp256k1_frost_pubkey_xonly_tweak_add(long ctx, byte[] tweakCache, byte[] tweak32);
/**
* Starts a signing session by generating a nonce
*
* This function outputs a secret nonce that will be required for signing and a
* corresponding public nonce that is intended to be sent to other signers.
*
* FROST, like MuSig, differs from regular Schnorr signing in that
* implementers _must_ take special care to not reuse a nonce. This can be
* ensured by following these rules:
*
* 1. Each call to this function must have a UNIQUE session_id32 that must NOT BE
* REUSED in subsequent calls to this function.
* If you do not provide a seckey, session_id32 _must_ be UNIFORMLY RANDOM
* AND KEPT SECRET (even from other signers). If you do provide a seckey,
* session_id32 can instead be a counter (that must never repeat!). However,
* it is recommended to always choose session_id32 uniformly at random.
* 2. If you already know the seckey, message or aggregate public key, they
* can be optionally provided to derive the nonce and increase
* misuse-resistance. The extra_input32 argument can be used to provide
* additional data that does not repeat in normal scenarios, such as the
* current time.
* 3. Avoid copying (or serializing) the secnonce. This reduces the possibility
* that it is used more than once for signing.
*
* Remember that nonce reuse will leak the secret key!
* Note that using the same agg_share for multiple FROST sessions is fine.
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param sessionId32 a 32-byte session_id32 as explained above. Must be unique to this call to
* secp256k1_frost_nonce_gen and must be uniformly random unless you really know what you
* are doing.
* @param share the aggregated share that will later be used for signing, if already known (can be NULL)
* @param msg32 the 32-byte message that will later be signed, if already known (can be NULL)
* @param publicKey the FROST-aggregated public key (can be NULL)
* @param extraInput32 an optional 32-byte array that is input to the nonce derivation function (can be NULL)
* @return
* [0] secnonce: pointer to a structure to store the secret nonce
* [1] pubnonce: pointer to a structure to store the public nonce
*/
public static native byte[] secp256k1_frost_nonce_gen(long ctx, byte[] sessionId32, byte[] share, byte[] msg32, byte[] publicKey, byte[] extraInput32);
/**
* Takes the public nonces of all signers and computes a session that is
* required for signing and verification of partial signatures. The participant
* IDs can be sorted before combining, but the corresponding pubnonces must be
* resorted as well. All signers must use the same sorting of pubnonces,
* otherwise signing will fail.
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param pubnonces array of pointers to public nonces sent by the signers
* @param msg32 the 32-byte message to sign
* @param publicKey the FROST-aggregated public key
* @param id33 the 33-byte ID of the participant who will use the session for signing
* @param ids33 array of the 33-byte participant IDs of the signers
* @param tweakCache pointer to frost_tweak_cache struct (can be NULL)
* @param adaptor optional pointer to an adaptor point encoded as a public key if this signing session is part
* of an adaptor signature protocol (can be NULL)
*
* @return session: pointer to a struct to store the session
*/
public static native byte[] secp256k1_frost_nonce_process(long ctx, byte[][] pubnonces, int n_pubnonces, byte[] msg32, byte[] publicKey, byte[] id33, byte[][] ids33, byte[] tweakCache, byte[] adaptor);
/**
* Produces a partial signature
*
* This function overwrites the given secnonce with zeros and will abort if given a
* secnonce that is all zeros. This is a best effort attempt to protect against nonce
* reuse. However, this is of course easily defeated if the secnonce has been
* copied (or serialized). Remember that nonce reuse will leak the secret key!
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param secnonce (IS ALSO OUTPUT)pointer to the secnonce struct created in frost_nonce_gen that has been never used in a
* partial_sign call before
* @param share the aggregated share
* @param session pointer to the session that was created with frost_nonce_process
* @param tweak_cache pointer to frost_tweak_cache struct (can be NULL)
*
* @return
* partial_sig: pointer to struct to store the partial signature
* TODO: [1] secnonce: pointer to the secnonce struct created in frost_nonce_gen that has been never used in a
* partial_sign call before
*
*/
public static native byte[] secp256k1_frost_partial_sign(long ctx, byte[] secnonce, byte[] share, byte[] session, byte[] tweak_cache);
/**
* Verifies an individual signer's partial signature
*
* The signature is verified for a specific signing session. In order to avoid
* accidentally verifying a signature from a different or non-existing signing
* session, you must ensure the following:
* 1. The `tweak_cache` argument is identical to the one used to create the
* `session` with `frost_nonce_process`.
* 2. The `pubshare` argument must be the output of
* `secp256k1_frost_compute_pubshare` for the signer's ID.
* 3. The `pubnonce` argument must be identical to the one sent by the
* signer and used to create the `session` with `frost_nonce_process`.
*
* This function can be used to assign blame for a failed signature.
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param partialSig pointer to partial signature to verify, sent by the signer associated with `pubnonce` and `pubkey`
* @param publicNonce public nonce of the signer in the signing session
* @param publicShare public verification share of the signer in the signing session that is the output of
* `secp256k1_frost_compute_pubshare`
* @param session pointer to the session that was created with `frost_nonce_process`
* @param tweakCache pointer to frost_tweak_cache struct (can be NULL)
*
* @return 0 if the arguments are invalid or the partial signature does not verify, 1 otherwise
*/
public static native int secp256k1_frost_partial_sig_verify(long ctx, byte[] partialSig, byte[] publicNonce, byte[] publicShare, byte[] session, byte[] tweakCache);
/**
* Aggregates partial signatures
*
* @param ctx pointer to a context object (not secp256k1_context_static)
* @param session pointer to the session that was created with frost_nonce_process
* @param partialSignatures array of pointers to partial signatures to aggregate
* @param n_sigs number of elements in the partial_sigs array. Must be greater than 0.
*
* @return sig64: complete (but possibly invalid) Schnorr signature
*/
public static native byte[] secp256k1_frost_partial_sig_agg(long ctx, byte[] session, byte[][] partialSignatures, int n_sigs);
}

View File

@ -14,16 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.acinq.secp256k1
public object NativeSecp256k1 : Secp256k1 {
override fun verify(signature: ByteArray, message: ByteArray, pubkey: ByteArray): Boolean {
return Secp256k1CFunctions.secp256k1_ecdsa_verify(Secp256k1Context.getContext(), signature, message, pubkey) == 1
override fun verify(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean {
return Secp256k1CFunctions.secp256k1_ecdsa_verify(Secp256k1Context.getContext(), signature, data, pub) == 1
}
override fun sign(message: ByteArray, privkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ecdsa_sign(Secp256k1Context.getContext(), message, privkey)
override fun sign(data: ByteArray, sec: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ecdsa_sign(Secp256k1Context.getContext(), data, sec)
}
override fun signatureNormalize(sig: ByteArray): Pair<ByteArray, Boolean> {
@ -32,30 +31,35 @@ public object NativeSecp256k1 : Secp256k1 {
return Pair(sigout, result == 1)
}
override fun secKeyVerify(privkey: ByteArray): Boolean {
return Secp256k1CFunctions.secp256k1_ec_seckey_verify(Secp256k1Context.getContext(), privkey) == 1
override fun secKeyVerify(seckey: ByteArray): Boolean {
val result = Secp256k1CFunctions.secp256k1_ec_seckey_verify(Secp256k1Context.getContext(), seckey);
return result == 1;
}
override fun pubkeyCreate(privkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_create(Secp256k1Context.getContext(), privkey)
override fun pubkeyCreate(seckey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_create(Secp256k1Context.getContext(), seckey)
}
override fun pubkeyParse(pubkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_parse(Secp256k1Context.getContext(), pubkey)
}
override fun cleanup() {
return Secp256k1CFunctions.secp256k1_context_destroy(Secp256k1Context.getContext())
}
override fun privKeyNegate(privkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_privkey_negate(Secp256k1Context.getContext(), privkey)
}
override fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_privkey_tweak_add(Secp256k1Context.getContext(), privkey, tweak)
}
override fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_privkey_tweak_mul(Secp256k1Context.getContext(), privkey, tweak)
}
override fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_privkey_tweak_add(Secp256k1Context.getContext(), privkey, tweak)
}
override fun pubKeyNegate(pubkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_negate(Secp256k1Context.getContext(), pubkey)
}
@ -68,12 +72,12 @@ public object NativeSecp256k1 : Secp256k1 {
return Secp256k1CFunctions.secp256k1_ec_pubkey_tweak_mul(Secp256k1Context.getContext(), pubkey, tweak)
}
override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_combine(Secp256k1Context.getContext(), pubkeys)
override fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_add(Secp256k1Context.getContext(), pubkey1, pubkey2)
}
override fun ecdh(privkey: ByteArray, pubkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ecdh(Secp256k1Context.getContext(), privkey, pubkey)
override fun ecdh(seckey: ByteArray, pubkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ecdh(Secp256k1Context.getContext(), seckey, pubkey)
}
override fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray {
@ -83,258 +87,4 @@ public object NativeSecp256k1 : Secp256k1 {
override fun compact2der(sig: ByteArray): ByteArray {
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 musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray {
return Secp256k1CFunctions.secp256k1_musig_nonce_gen(Secp256k1Context.getContext(), sessionId32, privkey, aggpubkey, msg32, keyaggCache, extraInput32)
}
override fun musigNonceAgg(pubnonces: Array<ByteArray>): ByteArray {
return Secp256k1CFunctions.secp256k1_musig_nonce_agg(Secp256k1Context.getContext(), pubnonces)
}
override fun musigPubkeyAgg(pubkeys: Array<ByteArray>, keyaggCache: ByteArray?): ByteArray {
return Secp256k1CFunctions.secp256k1_musig_pubkey_agg(Secp256k1Context.getContext(), pubkeys, keyaggCache)
}
override fun musigPubkeyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_musig_pubkey_ec_tweak_add(Secp256k1Context.getContext(), keyaggCache, tweak32)
}
override fun musigPubkeyXonlyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_musig_pubkey_xonly_tweak_add(Secp256k1Context.getContext(), keyaggCache, tweak32)
}
override fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_musig_nonce_process(Secp256k1Context.getContext(), aggnonce, msg32, keyaggCache)
}
override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray {
require(musigNonceValidate(secnonce, pubkeyCreate(privkey)))
return Secp256k1CFunctions.secp256k1_musig_partial_sign(Secp256k1Context.getContext(), secnonce, privkey, keyaggCache, session)
}
override fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): Int {
return Secp256k1CFunctions.secp256k1_musig_partial_sig_verify(Secp256k1Context.getContext(), psig, pubnonce, pubkey, keyaggCache, session)
}
override fun musigPartialSigAgg(session: ByteArray, psigs: Array<ByteArray>): ByteArray {
return Secp256k1CFunctions.secp256k1_musig_partial_sig_agg(Secp256k1Context.getContext(), session, psigs)
}
override fun frostSharesGen(
seed32: ByteArray,
threshold: Int,
totalSigners: Int,
ids33: Array<ByteArray>
): Triple<Array<ByteArray>, Array<ByteArray>, ByteArray> {
val pok64 = ByteArray(64)
val result = Secp256k1CFunctions.secp256k1_frost_shares_gen(
Secp256k1Context.getContext(),
pok64,
seed32,
threshold,
totalSigners,
ids33
)
println(Hex.encode(result))
val shares = Array(totalSigners) { index ->
val startIndex = 0 + (index*32);
val endIndex = 31 + (index*32);
result.sliceArray(
startIndex..endIndex
)
}
val sharesOffset = totalSigners*32;
val vssCommitment = Array(threshold) { index ->
val startIndex = sharesOffset + (index*65);
val endIndex = sharesOffset + 64 + (index*65);
result.sliceArray(
startIndex..endIndex
)
}
return Triple(
shares,
vssCommitment,
pok64
)
}
override fun frostShareAggregate(
totalShares: Array<ByteArray>,
vssCommitments: Array<Array<ByteArray>>,
totalShareCount: Int,
threshold: Int,
id33: ByteArray
): Pair<ByteArray, ByteArray> {
val result = Secp256k1CFunctions.secp256k1_frost_share_agg(
Secp256k1Context.getContext(),
totalShares,
vssCommitments,
totalShareCount,
threshold,
id33
)
return Pair(
result.take(Secp256k1.FROST_SERIALIZED_SHARE_SIZE).toByteArray(), // agg_share
result.takeLast(Secp256k1.SERIALIZED_X_ONLY_PUBKEY_SIZE).toByteArray() // agg_pk
)
}
override fun frostShareVerify(
threshold: Int,
id33: ByteArray,
share: ByteArray,
vssCommitment: Array<ByteArray>
): Int {
return Secp256k1CFunctions.secp256k1_frost_share_verify(
Secp256k1Context.getContext(),
threshold,
id33,
share,
vssCommitment
)
}
override fun frostComputePublicShare(
threshold: Int,
id33: ByteArray,
vssCommitments: Array<Array<ByteArray>>,
totalSignersCount: Int
): ByteArray {
return Secp256k1CFunctions.secp256k1_frost_compute_pubshare(
Secp256k1Context.getContext(),
threshold,
id33,
vssCommitments,
totalSignersCount
)
}
override fun frostPublicKeyTweak(xOnlyPublicKey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_frost_pubkey_tweak(
Secp256k1Context.getContext(),
xOnlyPublicKey
)
}
override fun frostPublicKeyEcTweakAdd(tweakCache: ByteArray, tweak32: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_frost_pubkey_ec_tweak_add(
Secp256k1Context.getContext(),
tweakCache,
tweak32
)
}
override fun frostPublicKeyXonlyTweakAdd(tweakCache: ByteArray, tweak32: ByteArray): ByteArray? {
return Secp256k1CFunctions.secp256k1_frost_pubkey_xonly_tweak_add(
Secp256k1Context.getContext(),
tweakCache,
tweak32
)
}
override fun frostNonceGen(
sessionId32: ByteArray,
share: ByteArray?,
msg32: ByteArray?,
publicKey: ByteArray?,
extraInput32: ByteArray?
): Pair<ByteArray, ByteArray> {
val result = Secp256k1CFunctions.secp256k1_frost_nonce_gen(
Secp256k1Context.getContext(),
sessionId32,
share,
msg32,
publicKey,
extraInput32
)
return Pair(
result.take(Secp256k1.FROST_SECNONCE_SIZE).toByteArray(), // secnonce
result.takeLast(Secp256k1.FROST_SERIALIZED_PUBNONCE_SIZE).toByteArray() // pubnonce
)
}
override fun frostNonceProcess(
publicNonces: Array<ByteArray>,
threshold: Int,
msg32: ByteArray,
publicKey: ByteArray,
id33: ByteArray,
ids33: Array<ByteArray>,
tweakCache: ByteArray?,
adaptor: ByteArray?
): ByteArray {
return Secp256k1CFunctions.secp256k1_frost_nonce_process(
Secp256k1Context.getContext(),
publicNonces,
threshold,
msg32,
publicKey,
id33,
ids33,
tweakCache,
adaptor
)
}
override fun frostPartialSign(
secnonce: ByteArray,
share: ByteArray,
session: ByteArray,
tweakCache: ByteArray?
): ByteArray {
return Secp256k1CFunctions.secp256k1_frost_partial_sign(
Secp256k1Context.getContext(),
secnonce,
share,
session,
tweakCache
)
}
override fun frostPartialSignatureVerify(
partialSig: ByteArray,
publicNonce: ByteArray,
publicShare: ByteArray,
session: ByteArray,
tweakCache: ByteArray?
): Int {
return Secp256k1CFunctions.secp256k1_frost_partial_sig_verify(
Secp256k1Context.getContext(),
partialSig,
publicNonce,
publicShare,
session,
tweakCache
)
}
override fun frostPartialSignatureAggregate(session: ByteArray, partialSignatures: Array<ByteArray>, threshold: Int): ByteArray {
return Secp256k1CFunctions.secp256k1_frost_partial_sig_agg(
Secp256k1Context.getContext(),
session,
partialSignatures,
threshold
)
}
override fun cleanup() {
return Secp256k1CFunctions.secp256k1_context_destroy(Secp256k1Context.getContext())
}
}

View File

@ -19,21 +19,23 @@ else
fi
TARGET=$SYS-linux-android
TOOLTARGET=$TARGET
if [ "$SYS" == "armv7a" ]; then
TARGET=armv7a-linux-androideabi
TOOLTARGET=arm-linux-androideabi
fi
export CC=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/${TARGET}21-clang
export LD=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/ld
export AR=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/llvm-ar
export AS=$CC
export RANLIB=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/llvm-ranlib
export STRIP=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/llvm-strip
export LD=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ld
export AR=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ar
export AS=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-as
export RANLIB=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-ranlib
export STRIP=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/$TOOLTARGET-strip
cd secp256k1
./autogen.sh
./configure CFLAGS=-fpic --host=$TARGET --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-module-frost --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-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
make clean
make

View File

@ -6,13 +6,10 @@ cp xconfigure.sh secp256k1
cd secp256k1
./autogen.sh
sh xconfigure.sh --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-module-frost --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-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
mkdir -p ../build/ios
cp -v _build/universal/ios/* ../build/ios/
mkdir -p ../build/iosSimulatorArm64
cp -v _build/universal/iosSimulatorArm64/* ../build/iosSimulatorArm64/
cp -v _build/universal/* ../build/ios/
rm -rf _build
make clean

View File

@ -1,13 +1,9 @@
import org.gradle.internal.os.OperatingSystem
val includeAndroid = System.getProperty("includeAndroid")?.toBoolean() ?: true
if (includeAndroid) {
evaluationDependsOn(":jni:android")
}
evaluationDependsOn(":jni:android")
val currentOs = OperatingSystem.current()
val bash = "bash"
val bash = if (currentOs.isWindows) "bash.exe" else "bash"
val buildSecp256k1 by tasks.creating { group = "build" }
@ -27,7 +23,7 @@ val buildSecp256k1Host by tasks.creating(Exec::class) {
workingDir = projectDir
environment("TARGET", target)
commandLine(bash, "-l", "build.sh")
commandLine(bash, "build.sh")
}
val buildSecp256k1Ios by tasks.creating(Exec::class) {
@ -43,39 +39,34 @@ val buildSecp256k1Ios by tasks.creating(Exec::class) {
commandLine(bash, "build-ios.sh")
}
if (includeAndroid) {
val buildSecp256k1Android by tasks.creating {
group = "build"
buildSecp256k1.dependsOn(this)
}
fun creatingBuildSecp256k1Android(arch: String) = tasks.creating(Exec::class) {
group = "build"
buildSecp256k1Android.dependsOn(this)
inputs.files(projectDir.resolve("build-android.sh"))
outputs.dir(projectDir.resolve("build/android/$arch"))
workingDir = projectDir
val toolchain = when {
currentOs.isLinux -> "linux-x86_64"
currentOs.isMacOsX -> "darwin-x86_64"
currentOs.isWindows -> "windows-x86_64"
else -> error("No Android toolchain defined for this OS: $currentOs")
}
environment("TOOLCHAIN", toolchain)
environment("ARCH", arch)
environment("ANDROID_NDK", (project(":jni:android").extensions["android"] as com.android.build.gradle.LibraryExtension).ndkDirectory)
commandLine(bash, "build-android.sh")
}
val buildSecp256k1AndroidX86_64 by creatingBuildSecp256k1Android("x86_64")
val buildSecp256k1AndroidX86 by creatingBuildSecp256k1Android("x86")
val buildSecp256k1AndroidArm64v8a by creatingBuildSecp256k1Android("arm64-v8a")
val buildSecp256k1AndroidArmeabiv7a by creatingBuildSecp256k1Android("armeabi-v7a")
val buildSecp256k1Android by tasks.creating {
group = "build"
buildSecp256k1.dependsOn(this)
}
fun creatingBuildSecp256k1Android(arch: String) = tasks.creating(Exec::class) {
group = "build"
buildSecp256k1Android.dependsOn(this)
inputs.files(projectDir.resolve("build-android.sh"))
outputs.dir(projectDir.resolve("build/android/$arch"))
workingDir = projectDir
val toolchain = when {
currentOs.isLinux -> "linux-x86_64"
currentOs.isMacOsX -> "darwin-x86_64"
currentOs.isWindows -> "windows-x86_64"
else -> error("No Android toolchain defined for this OS: $currentOs")
}
environment("TOOLCHAIN", toolchain)
environment("ARCH", arch)
environment("ANDROID_NDK", (project(":jni:android").extensions["android"] as com.android.build.gradle.LibraryExtension).ndkDirectory)
commandLine(bash, "build-android.sh")
}
val buildSecp256k1AndroidX86_64 by creatingBuildSecp256k1Android("x86_64")
val buildSecp256k1AndroidX86 by creatingBuildSecp256k1Android("x86")
val buildSecp256k1AndroidArm64v8a by creatingBuildSecp256k1Android("arm64-v8a")
val buildSecp256k1AndroidArmeabiv7a by creatingBuildSecp256k1Android("armeabi-v7a")
val clean by tasks.creating {
group = "build"

View File

@ -12,20 +12,18 @@ cd "$(dirname "$0")"
cd secp256k1
if [ "$TARGET" == "mingw" ]; then
CFLAGS="-fPIC"
CONF_OPTS=" --host=x86_64-w64-mingw32"
CONF_OPTS="CFLAGS=-fPIC --host=x86_64-w64-mingw32"
elif [ "$TARGET" == "linux" ]; then
CFLAGS="-fPIC"
CONF_OPTS="CFLAGS=-fPIC"
elif [ "$TARGET" == "darwin" ]; then
CFLAGS="-arch arm64 -arch x86_64"
LDFLAGS="-arch arm64 -arch x86_64"
CONF_OPTS="--host=x86_64-w64-darwin"
else
echo "Unknown TARGET=$TARGET"
exit 1
fi
./autogen.sh
CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" ./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-module-frost --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-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
make clean
make

@ -1 +1 @@
Subproject commit 69d5e6bdc1ec8b472138aaba9804b93d46f397bb
Subproject commit be8d9c262f46309d9b4165b0498b71d704aba8fe

View File

@ -69,22 +69,9 @@ HOST_FLAGS="${ARCH_FLAGS} -mios-simulator-version-min=${MIN_IOS_VERSION} -isysro
CHOST="x86_64-apple-darwin"
Build "$@"
## Build for iphone M1/M2/Mx simulators
SDK="iphonesimulator"
PLATFORM="arm64-sim"
PLATFORM_SIM_ARM=${PLATFORM}
ARCH_FLAGS="-arch arm64"
HOST_FLAGS="${ARCH_FLAGS} -mios-simulator-version-min=${MIN_IOS_VERSION} -isysroot $(xcrun --sdk ${SDK} --show-sdk-path)"
CHOST="arm-apple-darwin"
Build "$@"
# Create universal binary
cd "${PLATFORMS}/${PLATFORM_ARM}/lib"
LIB_NAME=`find . -iname *.a`
cd -
mkdir -p "${UNIVERSAL}/ios" &> /dev/null
mkdir -p "${UNIVERSAL}/iosSimulatorArm64" &> /dev/null
lipo -create -output "${UNIVERSAL}/ios/${LIB_NAME}" "${PLATFORMS}/${PLATFORM_ARM}/lib/${LIB_NAME}" "${PLATFORMS}/${PLATFORM_ISIM}/lib/${LIB_NAME}"
# create a specific library for arm64 simulator: it cannot be included in the lib above which already contains an arm64 lib
lipo -create -output "${UNIVERSAL}/iosSimulatorArm64/${LIB_NAME}" "${PLATFORMS}/${PLATFORM_SIM_ARM}/lib/${LIB_NAME}"
mkdir -p "${UNIVERSAL}" &> /dev/null
lipo -create -output "${UNIVERSAL}/${LIB_NAME}" "${PLATFORMS}/${PLATFORM_ARM}/lib/${LIB_NAME}" "${PLATFORMS}/${PLATFORM_ISIM}/lib/${LIB_NAME}"

View File

@ -1,57 +0,0 @@
# 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.9.0-SNAPSHOT ./secp256k1-kmp-add-linuxarm64.sh snapshot
- 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

@ -1,22 +0,0 @@
#!/bin/bash -x
if [[ -z "${VERSION}" ]]; then
echo "VERSION is not defined"
exit 1
fi
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

@ -1,22 +0,0 @@
#!/bin/bash -x
if [[ -z "${VERSION}" ]]; then
echo "VERSION is not defined"
exit 1
fi
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

@ -1,72 +0,0 @@
#!/bin/bash -x
GROUP_ID=fr.acinq.secp256k1
ARTIFACT_ID_BASE=secp256k1-kmp
if [[ -z "${VERSION}" ]]; then
echo "VERSION is not defined"
exit 1
fi
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 iossimulatorarm64 iosx64 macosarm64 macosx64 jni-android jni-common jni-jvm-darwin jni-jvm-extract jni-jvm-linux jni-jvm-mingw jni-jvm jvm linuxx64; do
cd fr/acinq/secp256k1/secp256k1-kmp-$i/$VERSION
case $i in
iosarm64 | iossimulatorarm64 | iosx64 | macosarm64 | macosx64)
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
;;
linuxx64)
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
;;
jni-android)
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
;;
*)
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
;;
esac
popd
pushd .
done

View File

@ -1,41 +0,0 @@
#!/bin/bash -x
#
# first you must sign all files:
# find release -type f -print -exec gpg -ab {} \;
if [[ -z "${VERSION}" ]]; then
echo "VERSION is not defined"
exit 1
fi
if [[ -z "${OSS_USER}" ]]; then
echo "OSS_USER is not defined"
exit 1
fi
read -p "Password : " -s OSS_PASSWORD
for i in secp256k1-kmp \
secp256k1-kmp-iosarm64 \
secp256k1-kmp-iossimulatorarm64 \
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-linuxx64
do
pushd .
cd release/fr/acinq/secp256k1/$i/$VERSION
pwd
jar -cvf bundle.jar *
# use correct sonatype credentials here
curl -v -XPOST -u $OSS_USER:$OSS_PASSWORD --upload-file bundle.jar https://oss.sonatype.org/service/local/staging/bundle_upload
popd
done

View File

@ -2,30 +2,15 @@ pluginManagement {
repositories {
google()
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == "com.android" || requested.id.name == "kotlin-android-extensions") {
useModule("com.android.tools.build:gradle:7.3.1")
}
}
jcenter()
}
}
rootProject.name = "secp256k1-kmp"
// We use a property defined in `local.properties` to know whether we should build the android application or not.
// For example, iOS developers may want to skip that most of the time.
val skipAndroid = File("$rootDir/local.properties").takeIf { it.exists() }
?.inputStream()?.use { java.util.Properties().apply { load(it) } }
?.run { getProperty("skip.android", "false")?.toBoolean() }
?: false
// Use system properties to inject the property in other gradle build files.
System.setProperty("includeAndroid", (!skipAndroid).toString())
include(
":native",
":jni",
":jni:android",
":jni:jvm",
":jni:jvm:darwin",
":jni:jvm:linux",
@ -33,10 +18,3 @@ include(
":jni:jvm:all",
":tests"
)
if (!skipAndroid) {
print("building android library")
include(":jni:android")
} else {
print("skipping android build")
}

View File

@ -20,330 +20,60 @@ import kotlin.jvm.JvmStatic
public interface Secp256k1 {
/**
* Verify an ECDSA signature.
*
* @param signature signature using either compact encoding (64 bytes) or der-encoding.
* @param message message signed.
* @param pubkey signer's public key.
*/
public fun verify(signature: ByteArray, message: ByteArray, pubkey: ByteArray): Boolean
public fun verify(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean
/**
* Create a normalized ECDSA signature.
*
* @param message message to sign.
* @param privkey signer's private key.
*/
public fun sign(message: ByteArray, privkey: ByteArray): ByteArray
public fun sign(data: ByteArray, sec: ByteArray): ByteArray
/**
* Verify a Schnorr signature.
*
* @param signature 64 bytes signature.
* @param data message signed.
* @param pub signer's x-only public key (32 bytes).
*/
public fun verifySchnorr(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean
/**
* Create a Schnorr signature.
*
* @param data message to sign.
* @param sec 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.
*
* @param sig signature that should be normalized.
*/
public fun signatureNormalize(sig: ByteArray): Pair<ByteArray, Boolean>
/**
* Verify the validity of a private key.
*/
public fun secKeyVerify(privkey: ByteArray): Boolean
public fun secKeyVerify(seckey: ByteArray): Boolean
/**
* Get the public key corresponding to the given private key.
* Returns the uncompressed public key (65 bytes).
*/
public fun pubkeyCreate(privkey: ByteArray): ByteArray
public fun pubkeyCreate(seckey: ByteArray): ByteArray
/**
* Parse a serialized public key.
* Returns the uncompressed public key (65 bytes).
*/
public fun pubkeyParse(pubkey: ByteArray): ByteArray
/**
* Negate the given private key.
*/
public fun cleanup()
public fun privKeyNegate(privkey: ByteArray): ByteArray
/**
* Tweak a private key by adding tweak to it.
*/
public fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray
/**
* Tweak a private key by multiplying it by a tweak.
*/
public fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray
/**
* Negate the given public key.
* Returns the uncompressed public key (65 bytes).
*/
public fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray
public fun pubKeyNegate(pubkey: ByteArray): ByteArray
/**
* Tweak a public key by adding tweak times the generator to it.
* Returns the uncompressed public key (65 bytes).
*/
public fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray
/**
* Tweak a public key by multiplying it by a tweak value.
* Returns the uncompressed public key (65 bytes).
*/
public fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray
/**
* Add a number of public keys together.
* Returns the uncompressed public key (65 bytes).
*/
public fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray
public fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray
/**
* Compute an elliptic curve Diffie-Hellman secret.
*/
public fun ecdh(privkey: ByteArray, pubkey: ByteArray): ByteArray
public fun ecdh(seckey: ByteArray, pubkey: ByteArray): ByteArray
/**
* Recover a public key from an ECDSA signature.
*
* @param sig ecdsa compact signature (64 bytes).
* @param message message signed.
* @param recid recoveryId (should have been provided with the signature to allow recovery).
*/
public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray
/**
* Convert a compact ECDSA signature (64 bytes) to a der-encoded ECDSA signature.
*/
public fun compact2der(sig: ByteArray): ByteArray
/**
* Serialize a public key to compact form (33 bytes).
*/
public fun pubKeyCompress(pubkey: ByteArray): ByteArray {
public fun pubKeyCompress(pubkey: ByteArray) : ByteArray {
return when {
pubkey.size == 33 && (pubkey[0] == 2.toByte() || pubkey[0] == 3.toByte()) -> pubkey
pubkey.size == 65 && pubkey[0] == 4.toByte() -> {
val compressed = pubkey.copyOf(33)
compressed[0] = if (pubkey.last() % 2 == 0) 2.toByte() else 3.toByte()
compressed
val pub1 = pubkey.copyOf(33)
pub1[0] = if (pubkey.last() % 2 == 0) 2.toByte() else 3.toByte()
pub1
}
else -> throw Secp256k1Exception("invalid public key")
}
}
/**
* Generate a secret nonce to be used in a musig2 signing session.
* This nonce must never be persisted or reused across signing sessions.
* All optional arguments exist to enrich the quality of the randomness used, which is critical for security.
*
* @param sessionId32 unique 32-byte session ID.
* @param privkey (optional) signer's private key.
* @param aggpubkey aggregated public key of all participants in the signing session.
* @param msg32 (optional) 32-byte message that will be signed, if already known.
* @param keyaggCache (optional) key aggregation cache data from the signing session.
* @param extraInput32 (optional) additional 32-byte random data.
* @return serialized version of the secret nonce and the corresponding public nonce.
*/
public fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray
/**
* Aggregate public nonces from all participants of a signing session.
*
* @param pubnonces public nonces (one per participant).
* @return 66-byte aggregate public nonce (two public keys) or throws an exception is a nonce is invalid.
*/
public fun musigNonceAgg(pubnonces: Array<ByteArray>): ByteArray
/**
* Aggregate public keys from all participants of a signing session.
*
* @param pubkeys public keys of all participants in the signing session.
* @param keyaggCache (optional) key aggregation cache data from the signing session. If an empty byte array is
* provided, it will be filled with key aggregation data that can be used for the next steps of the signing process.
* @return 32-byte x-only public key.
*/
public fun musigPubkeyAgg(pubkeys: Array<ByteArray>, keyaggCache: ByteArray?): ByteArray
/**
* Tweak the aggregated public key of a signing session.
*
* @param keyaggCache key aggregation cache filled by [musigPubkeyAgg].
* @param tweak32 private key tweak to apply.
* @return P + tweak32 * G (where P is the aggregated public key from [keyaggCache]). The key aggregation cache will
* be updated with the tweaked public key.
*/
public fun musigPubkeyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray
/**
* Tweak the aggregated public key of a signing session, treating it as an x-only public key (e.g. when using taproot).
*
* @param keyaggCache key aggregation cache filled by [musigPubkeyAgg].
* @param tweak32 private key tweak to apply.
* @return with_even_y(P) + tweak32 * G (where P is the aggregated public key from [keyaggCache]). The key aggregation
* cache will be updated with the tweaked public key.
*/
public fun musigPubkeyXonlyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray
/**
* Create a signing session context based on the public information from all participants.
*
* @param aggnonce aggregated public nonce (see [musigNonceAgg]).
* @param msg32 32-byte message that will be signed.
* @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants.
* @return signing session context that can be used to create partial signatures and aggregate them.
*/
public fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray
/**
* Check that a secret nonce was generated with a public key that matches the private key used for signing.
* @param secretnonce secret nonce.
* @param pubkey public key for the private key that will be used, with the secret nonce, to generate a partial signature.
* @return false if the secret nonce does not match the public key.
*/
public fun musigNonceValidate(secretnonce: ByteArray, pubkey: ByteArray): Boolean {
if (secretnonce.size != MUSIG2_SECRET_NONCE_SIZE) return false
if (pubkey.size != 33 && pubkey.size != 65) return false
val pk = Secp256k1.pubkeyParse(pubkey)
// this is a bit hackish but the secp256k1 library does not export methods to do this cleanly
val x = secretnonce.copyOfRange(68, 68 + 32)
x.reverse()
val y = secretnonce.copyOfRange(68 + 32, 68 + 32 + 32)
y.reverse()
val pkx = pk.copyOfRange(1, 1 + 32)
val pky = pk.copyOfRange(33, 33 + 32)
return x.contentEquals(pkx) && y.contentEquals(pky)
}
/**
* Create a partial signature.
*
* @param secnonce signer's secret nonce (see [musigNonceGen]).
* @param privkey signer's private key.
* @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants.
* @param session signing session context (see [musigNonceProcess]).
* @return 32-byte partial signature.
*/
public fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray
/**
* Verify the partial signature from one of the signing session's participants.
*
* @param psig 32-byte partial signature.
* @param pubnonce individual public nonce of the signing participant.
* @param pubkey individual public key of the signing participant.
* @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants.
* @param session signing session context (see [musigNonceProcess]).
* @return result code (1 if the partial signature is valid, 0 otherwise).
*/
public fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): Int
/**
* Aggregate partial signatures from all participants into a single schnorr signature. If some of the partial
* signatures are invalid, this function will return an invalid aggregated signature without raising an error.
* It is recommended to use [musigPartialSigVerify] to verify partial signatures first.
*
* @param session signing session context (see [musigNonceProcess]).
* @param psigs list of 32-byte partial signatures.
* @return 64-byte aggregated schnorr signature.
*/
public fun musigPartialSigAgg(session: ByteArray, psigs: Array<ByteArray>): ByteArray
public fun frostSharesGen(seed32: ByteArray, threshold: Int, totalSigners: Int, ids33: Array<ByteArray>): Triple<Array<ByteArray>,Array<ByteArray>, ByteArray>
/**
*
* total signers is deduced from the size of the totalShares array.
* threshold is deduced from the vssCommitments (all vssCommitments should have the same length... which is the threshold)
*/
public fun frostShareAggregate(totalShares: Array<ByteArray>, vssCommitments: Array<Array<ByteArray>>, totalShareCount: Int, threshold: Int, id33: ByteArray): Pair<ByteArray, ByteArray>
public fun frostShareVerify(threshold: Int, id33: ByteArray, share: ByteArray, vssCommitment: Array<ByteArray>): Int
/**
*
* total signers is deduced from the size of the vssCommitments array.
* threshold is deduced from the vssCommitments (all vssCommitments should have the same length... which is the threshold)
*/
public fun frostComputePublicShare(threshold: Int, id33: ByteArray, vssCommitments: Array<Array<ByteArray>>, totalSignersCount: Int): ByteArray
public fun frostPublicKeyTweak(xOnlyPublicKey: ByteArray): ByteArray
public fun frostPublicKeyEcTweakAdd(tweakCache: ByteArray, tweak32: ByteArray): ByteArray?
public fun frostPublicKeyXonlyTweakAdd(tweakCache: ByteArray, tweak32: ByteArray): ByteArray?
public fun frostNonceGen(sessionId32: ByteArray, share: ByteArray?, msg32: ByteArray?, publicKey: ByteArray?, extraInput32: ByteArray?): Pair<ByteArray, ByteArray>
/**
*
* threshold can be deduced from the size of the pubnonces array.
*/
public fun frostNonceProcess(publicNonces: Array<ByteArray>, threshold: Int, msg32: ByteArray, publicKey: ByteArray, id33: ByteArray, ids33: Array<ByteArray>, tweakCache: ByteArray?, adaptor: ByteArray?): ByteArray
public fun frostPartialSign(secnonce: ByteArray, share: ByteArray, session: ByteArray, tweakCache: ByteArray?): ByteArray
public fun frostPartialSignatureVerify(partialSig: ByteArray, publicNonce: ByteArray, publicShare: ByteArray, session: ByteArray, tweakCache: ByteArray?): Int
public fun frostPartialSignatureAggregate(session: ByteArray, partialSignatures: Array<ByteArray>, threshold: Int): ByteArray
/**
* Delete the secp256k1 context from dynamic memory.
*/
public fun cleanup()
public companion object : Secp256k1 by getSecpk256k1() {
@JvmStatic
public fun get(): Secp256k1 = this
// @formatter:off
public const val X_ONLY_PUBKEY_SIZE: Int = 64
public const val SERIALIZED_X_ONLY_PUBKEY_SIZE: Int = 32
public const val MUSIG2_SECRET_NONCE_SIZE: Int = 132
public const val MUSIG2_PUBLIC_NONCE_SIZE: Int = 66
public const val MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE: Int = 197
public const val MUSIG2_PUBLIC_SESSION_SIZE: Int = 133
public const val FROST_PARTIAL_SIGNATURE_SIZE: Int = 36
public const val FROST_SHARE_SIZE: Int = 36
public const val FROST_TWEAK_CACHE_SIZE: Int = 101
public const val FROST_SESSION_SIZE: Int = 133
public const val FROST_SECNONCE_SIZE: Int = 68
public const val FROST_PUBNONCE_SIZE: Int = 132
public const val FROST_SERIALIZED_PARTIAL_SIGNATURE_SIZE: Int = 32
public const val FROST_SERIALIZED_SHARE_SIZE: Int = 32
public const val FROST_SERIALIZED_PUBNONCE_SIZE: Int = 66
// @formatter:on
@JvmStatic public fun get(): Secp256k1 = this
}
}
internal expect fun getSecpk256k1(): Secp256k1
public class Secp256k1Exception : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor() : super() {}
public constructor(message: String?) : super(message) {}
}

View File

@ -1,22 +1,10 @@
package = secp256k1
headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1_musig.h secp256k1_frost.h
headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1_musig.h secp256k1_frost.h secp256k1.h
headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h
headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1.h
staticLibraries.linux = libsecp256k1.a
libraryPaths.linux = c/secp256k1/build/linux/ native/build/linux/ native/build/darwin/
libraryPaths.linux = c/secp256k1/build/linux/
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/local/lib
staticLibraries.macos_x64 = libsecp256k1.a
libraryPaths.macos_x64 = c/secp256k1/build/darwin/ native/build/darwin/
linkerOpts.macos_x64 = -framework Security -framework Foundation
staticLibraries.macos_arm64 = libsecp256k1.a
libraryPaths.macos_arm64 = c/secp256k1/build/darwin/ native/build/darwin/
linkerOpts.macos_arm64 = -framework Security -framework Foundation
staticLibraries.ios = libsecp256k1.a
libraryPaths.ios_x64 = c/secp256k1/build/ios/ /usr/local/lib native/build/ios/
libraryPaths.ios_arm64 = c/secp256k1/build/ios/ /usr/local/lib native/build/ios/
libraryPaths.ios_simulator_arm64 = c/secp256k1/build/ios/ /usr/local/lib native/build/iosSimulatorArm64/
libraryPaths.ios = c/secp256k1/build/ios/ /usr/local/lib
linkerOpts.ios = -framework Security -framework Foundation

View File

@ -1,18 +1,15 @@
package fr.acinq.secp256k1
import fr.acinq.secp256k1.Secp256k1Native.toNat
import kotlinx.cinterop.*
import kotlinx.cinterop.ptr
import platform.posix.memcpy
import platform.posix.size_tVar
import secp256k1.*
@OptIn(ExperimentalUnsignedTypes::class, ExperimentalForeignApi::class)
@OptIn(ExperimentalUnsignedTypes::class)
public object Secp256k1Native : Secp256k1 {
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())
?: error("Could not create secp256k1 context")
?: error("Could not create segp256k1 context")
}
private fun Int.requireSuccess(message: String): Int = if (this != 1) throw Secp256k1Exception(message) else this
@ -43,27 +40,6 @@ public object Secp256k1Native : Secp256k1 {
return pub
}
private fun MemScope.allocXonlyPublicKey(pubkey: ByteArray): secp256k1_xonly_pubkey {
val natPub = toNat(pubkey)
val pub = alloc<secp256k1_xonly_pubkey>()
secp256k1_xonly_pubkey_parse(ctx, pub.ptr, natPub).requireSuccess("secp256k1_xonly_pubkey_parse() failed")
return pub
}
private fun MemScope.allocPublicNonce(pubnonce: ByteArray): secp256k1_musig_pubnonce {
val nat = toNat(pubnonce)
val nPubnonce = alloc<secp256k1_musig_pubnonce>()
secp256k1_musig_pubnonce_parse(ctx, nPubnonce.ptr, nat).requireSuccess("secp256k1_musig_pubnonce_parse() failed")
return nPubnonce
}
private fun MemScope.allocPartialSig(psig: ByteArray): secp256k1_musig_partial_sig {
val nat = toNat(psig)
val nPsig = alloc<secp256k1_musig_partial_sig>()
secp256k1_musig_partial_sig_parse(ctx, nPsig.ptr, nat).requireSuccess("secp256k1_musig_partial_sig_parse() failed")
return nPsig
}
private fun MemScope.serializePubkey(pubkey: secp256k1_pubkey): ByteArray {
val serialized = allocArray<UByteVar>(65)
val outputLen = alloc<size_tVar>()
@ -72,56 +48,38 @@ public object Secp256k1Native : Secp256k1 {
return serialized.readBytes(outputLen.value.convert())
}
private fun MemScope.serializeXonlyPubkey(pubkey: secp256k1_xonly_pubkey): ByteArray {
val serialized = allocArray<UByteVar>(32)
secp256k1_xonly_pubkey_serialize(ctx, serialized, pubkey.ptr).requireSuccess("secp256k1_xonly_pubkey_serialize() failed")
return serialized.readBytes(32)
}
private fun MemScope.serializePubnonce(pubnonce: secp256k1_musig_pubnonce): ByteArray {
val serialized = allocArray<UByteVar>(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
secp256k1_musig_pubnonce_serialize(ctx, serialized, pubnonce.ptr).requireSuccess("secp256k1_musig_pubnonce_serialize() failed")
return serialized.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
}
private fun MemScope.serializeAggnonce(aggnonce: secp256k1_musig_aggnonce): ByteArray {
val serialized = allocArray<UByteVar>(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
secp256k1_musig_aggnonce_serialize(ctx, serialized, aggnonce.ptr).requireSuccess("secp256k1_musig_aggnonce_serialize() failed")
return serialized.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
}
private fun DeferScope.toNat(bytes: ByteArray): CPointer<UByteVar> {
private fun DeferScope.toNat(bytes: ByteArray): CPointer<UByteVar> {
val ubytes = bytes.asUByteArray()
val pinned = ubytes.pin()
this.defer { pinned.unpin() }
return pinned.addressOf(0)
}
public override fun verify(signature: ByteArray, message: ByteArray, pubkey: ByteArray): Boolean {
require(message.size == 32)
require(pubkey.size == 33 || pubkey.size == 65)
public override fun verify(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean {
require(data.size == 32)
require(pub.size == 33 || pub.size == 65)
memScoped {
val nPubkey = allocPublicKey(pubkey)
val nMessage = toNat(message)
val nPubkey = allocPublicKey(pub)
val nData = toNat(data)
val nSig = allocSignature(signature)
return secp256k1_ecdsa_verify(ctx, nSig.ptr, nMessage, nPubkey.ptr) == 1
return secp256k1_ecdsa_verify(ctx, nSig.ptr, nData, nPubkey.ptr) == 1
}
}
public override fun sign(message: ByteArray, privkey: ByteArray): ByteArray {
require(privkey.size == 32)
require(message.size == 32)
public override fun sign(data: ByteArray, sec: ByteArray): ByteArray {
require(sec.size == 32)
require(data.size == 32)
memScoped {
val nPrivkey = toNat(privkey)
val nMessage = toNat(message)
val nSec = toNat(sec)
val nData = toNat(data)
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, nData, nSec, null, null).requireSuccess("secp256k1_ecdsa_sign() failed")
return serializeSignature(nSig)
}
}
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 {
val nSig = allocSignature(sig)
val isHighS = secp256k1_ecdsa_signature_normalize(ctx, nSig.ptr, nSig.ptr)
@ -129,20 +87,20 @@ public object Secp256k1Native : Secp256k1 {
}
}
public override fun secKeyVerify(privkey: ByteArray): Boolean {
if (privkey.size != 32) return false
public override fun secKeyVerify(seckey: ByteArray): Boolean {
require(seckey.size == 32)
memScoped {
val nPrivkey = toNat(privkey)
return secp256k1_ec_seckey_verify(ctx, nPrivkey) == 1
val nSec = toNat(seckey)
return secp256k1_ec_seckey_verify(ctx, nSec) == 1
}
}
public override fun pubkeyCreate(privkey: ByteArray): ByteArray {
require(privkey.size == 32)
public override fun pubkeyCreate(seckey: ByteArray): ByteArray {
require(seckey.size == 32)
memScoped {
val nPrivkey = toNat(privkey)
val nSec = toNat(seckey)
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, nSec).requireSuccess("secp256k1_ec_pubkey_create() failed")
return serializePubkey(nPubkey)
}
}
@ -155,31 +113,22 @@ public object Secp256k1Native : Secp256k1 {
}
}
public override fun cleanup() {
secp256k1_context_destroy(ctx)
}
public override fun privKeyNegate(privkey: ByteArray): ByteArray {
require(privkey.size == 32)
memScoped {
val negated = privkey.copyOf()
val negPriv = toNat(negated)
secp256k1_ec_seckey_negate(ctx, negPriv).requireSuccess("secp256k1_ec_seckey_negate() failed")
secp256k1_ec_privkey_negate(ctx, negPriv).requireSuccess("secp256k1_ec_privkey_negate() failed")
return negated
}
}
public override fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32)
require(tweak.size == 32)
memScoped {
val added = privkey.copyOf()
val natAdd = toNat(added)
val natTweak = toNat(tweak)
secp256k1_ec_seckey_tweak_add(ctx, natAdd, natTweak).requireSuccess("secp256k1_ec_seckey_tweak_add() failed")
return added
}
}
public override fun privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32)
require(tweak.size == 32)
memScoped {
val multiplied = privkey.copyOf()
val natMul = toNat(multiplied)
@ -189,6 +138,17 @@ public object Secp256k1Native : Secp256k1 {
}
}
public override fun privKeyTweakAdd(privkey: ByteArray, tweak: ByteArray): ByteArray {
require(privkey.size == 32)
memScoped {
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")
return added
}
}
public override fun pubKeyNegate(pubkey: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65)
memScoped {
@ -200,7 +160,6 @@ public object Secp256k1Native : Secp256k1 {
public override fun pubKeyTweakAdd(pubkey: ByteArray, tweak: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65)
require(tweak.size == 32)
memScoped {
val nPubkey = allocPublicKey(pubkey)
val nTweak = toNat(tweak)
@ -211,7 +170,6 @@ public object Secp256k1Native : Secp256k1 {
public override fun pubKeyTweakMul(pubkey: ByteArray, tweak: ByteArray): ByteArray {
require(pubkey.size == 33 || pubkey.size == 65)
require(tweak.size == 32)
memScoped {
val nPubkey = allocPublicKey(pubkey)
val nTweak = toNat(tweak)
@ -220,25 +178,26 @@ public object Secp256k1Native : Secp256k1 {
}
}
public override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray {
require(pubkeys.isNotEmpty())
pubkeys.forEach { require(it.size == 33 || it.size == 65) }
public override fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray {
require(pubkey1.size == 33 || pubkey1.size == 65)
require(pubkey2.size == 33 || pubkey2.size == 65)
memScoped {
val nPubkeys = pubkeys.map { allocPublicKey(it).ptr }
val nPubkey1 = allocPublicKey(pubkey1)
val nPubkey2 = allocPublicKey(pubkey2)
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, cValuesOf(nPubkey1.ptr, nPubkey2.ptr), 2.convert()).requireSuccess("secp256k1_ec_pubkey_combine() failed")
return serializePubkey(combined)
}
}
public override fun ecdh(privkey: ByteArray, pubkey: ByteArray): ByteArray {
require(privkey.size == 32)
public override fun ecdh(seckey: ByteArray, pubkey: ByteArray): ByteArray {
require(seckey.size == 32)
require(pubkey.size == 33 || pubkey.size == 65)
memScoped {
val nPubkey = allocPublicKey(pubkey)
val nPrivkey = toNat(privkey)
val nSeckey = toNat(seckey)
val output = allocArray<UByteVar>(32)
secp256k1_ecdh(ctx, output, nPubkey.ptr, nPrivkey, null, null).requireSuccess("secp256k1_ecdh() failed")
secp256k1_ecdh(ctx, output, nPubkey.ptr, nSeckey, null, null).requireSuccess("secp256k1_ecdh() failed")
return output.readBytes(32)
}
}
@ -246,7 +205,6 @@ public object Secp256k1Native : Secp256k1 {
public override fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray {
require(sig.size == 64)
require(message.size == 32)
require(recid in 0..3)
memScoped {
val nSig = toNat(sig)
val rSig = alloc<secp256k1_ecdsa_recoverable_signature>()
@ -269,729 +227,6 @@ public object Secp256k1Native : Secp256k1 {
return natOutput.readBytes(len.value.toInt())
}
}
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, 32u, 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)
}
}
override fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray {
require(sessionId32.size == 32)
privkey?.let { require(it.size == 32) }
msg32?.let { require(it.size == 32) }
keyaggCache?.let { require(it.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) }
extraInput32?.let { require(it.size == 32) }
val nonce = memScoped {
val secnonce = alloc<secp256k1_musig_secnonce>()
val pubnonce = alloc<secp256k1_musig_pubnonce>()
val nPubkey = allocPublicKey(aggpubkey)
val nKeyAggCache = keyaggCache?.let {
val n = alloc<secp256k1_musig_keyagg_cache>()
memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
n
}
secp256k1_musig_nonce_gen(
ctx,
secnonce.ptr,
pubnonce.ptr,
toNat(sessionId32),
privkey?.let { toNat(it) },
nPubkey.ptr,
msg32?.let { toNat(it) },
nKeyAggCache?.ptr,
extraInput32?.let { toNat(it) }).requireSuccess("secp256k1_musig_nonce_gen() failed")
val nPubnonce = allocArray<UByteVar>(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
secp256k1_musig_pubnonce_serialize(ctx, nPubnonce, pubnonce.ptr).requireSuccess("secp256k1_musig_pubnonce_serialize failed")
secnonce.ptr.readBytes(Secp256k1.MUSIG2_SECRET_NONCE_SIZE) + nPubnonce.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
}
return nonce
}
override fun musigNonceAgg(pubnonces: Array<ByteArray>): ByteArray {
require(pubnonces.isNotEmpty())
pubnonces.forEach { require(it.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) }
memScoped {
val nPubnonces = pubnonces.map { allocPublicNonce(it).ptr }
val combined = alloc<secp256k1_musig_aggnonce>()
secp256k1_musig_nonce_agg(ctx, combined.ptr, nPubnonces.toCValues(), pubnonces.size.convert()).requireSuccess("secp256k1_musig_nonce_agg() failed")
return serializeAggnonce(combined)
}
}
override fun musigPubkeyAgg(pubkeys: Array<ByteArray>, keyaggCache: ByteArray?): ByteArray {
require(pubkeys.isNotEmpty())
pubkeys.forEach { require(it.size == 33 || it.size == 65) }
keyaggCache?.let { require(it.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) }
memScoped {
val nPubkeys = pubkeys.map { allocPublicKey(it).ptr }
val combined = alloc<secp256k1_xonly_pubkey>()
val nKeyAggCache = keyaggCache?.let {
val n = alloc<secp256k1_musig_keyagg_cache>()
memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
n
}
secp256k1_musig_pubkey_agg(ctx, null, combined.ptr, nKeyAggCache?.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess("secp256k1_musig_nonce_agg() failed")
val agg = serializeXonlyPubkey(combined)
keyaggCache?.let { blob -> nKeyAggCache?.let { memcpy(toNat(blob), it.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) } }
return agg
}
}
override fun musigPubkeyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray {
require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
require(tweak32.size == 32)
memScoped {
val nKeyAggCache = alloc<secp256k1_musig_keyagg_cache>()
memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
val nPubkey = alloc<secp256k1_pubkey>()
secp256k1_musig_pubkey_ec_tweak_add(ctx, nPubkey.ptr, nKeyAggCache.ptr, toNat(tweak32)).requireSuccess("secp256k1_musig_pubkey_ec_tweak_add() failed")
memcpy(toNat(keyaggCache), nKeyAggCache.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
return serializePubkey(nPubkey)
}
}
override fun musigPubkeyXonlyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray {
require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
require(tweak32.size == 32)
memScoped {
val nKeyAggCache = alloc<secp256k1_musig_keyagg_cache>()
memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
val nPubkey = alloc<secp256k1_pubkey>()
secp256k1_musig_pubkey_xonly_tweak_add(ctx, nPubkey.ptr, nKeyAggCache.ptr, toNat(tweak32)).requireSuccess("secp256k1_musig_pubkey_xonly_tweak_add() failed")
memcpy(toNat(keyaggCache), nKeyAggCache.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
return serializePubkey(nPubkey)
}
}
override fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray {
require(aggnonce.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
require(msg32.size == 32)
memScoped {
val nKeyAggCache = alloc<secp256k1_musig_keyagg_cache>()
memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
val nSession = alloc<secp256k1_musig_session>()
val nAggnonce = alloc<secp256k1_musig_aggnonce>()
secp256k1_musig_aggnonce_parse(ctx, nAggnonce.ptr, toNat(aggnonce)).requireSuccess("secp256k1_musig_aggnonce_parse() failed")
secp256k1_musig_nonce_process(ctx, nSession.ptr, nAggnonce.ptr, toNat(msg32), nKeyAggCache.ptr, null).requireSuccess("secp256k1_musig_nonce_process() failed")
val session = ByteArray(Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE)
memcpy(toNat(session), nSession.ptr, Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong())
return session
}
}
override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray {
require(secnonce.size == Secp256k1.MUSIG2_SECRET_NONCE_SIZE)
require(privkey.size == 32)
require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
require(session.size == Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE)
require(musigNonceValidate(secnonce, pubkeyCreate(privkey)))
memScoped {
val nSecnonce = alloc<secp256k1_musig_secnonce>()
memcpy(nSecnonce.ptr, toNat(secnonce), Secp256k1.MUSIG2_SECRET_NONCE_SIZE.toULong())
val nKeypair = alloc<secp256k1_keypair>()
secp256k1_keypair_create(ctx, nKeypair.ptr, toNat(privkey))
val nPsig = alloc<secp256k1_musig_partial_sig>()
val nKeyAggCache = alloc<secp256k1_musig_keyagg_cache>()
memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
val nSession = alloc<secp256k1_musig_session>()
memcpy(nSession.ptr, toNat(session), Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong())
secp256k1_musig_partial_sign(ctx, nPsig.ptr, nSecnonce.ptr, nKeypair.ptr, nKeyAggCache.ptr, nSession.ptr).requireSuccess("secp256k1_musig_partial_sign failed")
val psig = ByteArray(32)
secp256k1_musig_partial_sig_serialize(ctx, toNat(psig), nPsig.ptr).requireSuccess("secp256k1_musig_partial_sig_serialize() failed")
return psig
}
}
override fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): Int {
require(psig.size == 32)
require(pubnonce.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
require(pubkey.size == 33 || pubkey.size == 65)
require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
require(session.size == Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE)
memScoped {
val nPSig = allocPartialSig(psig)
val nPubnonce = allocPublicNonce(pubnonce)
val nPubkey = allocPublicKey(pubkey)
val nKeyAggCache = alloc<secp256k1_musig_keyagg_cache>()
memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong())
val nSession = alloc<secp256k1_musig_session>()
memcpy(nSession.ptr, toNat(session), Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong())
return secp256k1_musig_partial_sig_verify(ctx, nPSig.ptr, nPubnonce.ptr, nPubkey.ptr, nKeyAggCache.ptr, nSession.ptr)
}
}
override fun musigPartialSigAgg(session: ByteArray, psigs: Array<ByteArray>): ByteArray {
require(session.size == Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE)
require(psigs.isNotEmpty())
psigs.forEach { require(it.size == 32) }
memScoped {
val nSession = alloc<secp256k1_musig_session>()
memcpy(nSession.ptr, toNat(session), Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong())
val nPsigs = psigs.map { allocPartialSig(it).ptr }
val sig64 = ByteArray(64)
secp256k1_musig_partial_sig_agg(ctx, toNat(sig64), nSession.ptr, nPsigs.toCValues(), psigs.size.convert()).requireSuccess("secp256k1_musig_partial_sig_agg() failed")
return sig64
}
}
private fun MemScope.allocFrostShare(share: ByteArray): secp256k1_frost_share {
val nat = toNat(share)
val nFrostShare = alloc<secp256k1_frost_share>()
secp256k1_frost_share_parse(ctx, nFrostShare.ptr, nat).requireSuccess("secp256k1_frost_share_parse() failed")
return nFrostShare
}
private fun MemScope.allocFrostPartialSignature(partialSignature: ByteArray): secp256k1_frost_partial_sig {
val nat = toNat(partialSignature)
val nPartialSignature = alloc<secp256k1_frost_partial_sig>()
secp256k1_frost_partial_sig_parse(ctx, nPartialSignature.ptr, nat).requireSuccess("secp256k1_frost_partial_sig_parse() failed")
return nPartialSignature
}
private fun MemScope.allocFrostPublicNonce(pubnonce: ByteArray): secp256k1_frost_pubnonce {
val nat = toNat(pubnonce)
val nPublicNonce = alloc<secp256k1_frost_pubnonce>()
secp256k1_frost_pubnonce_parse(ctx, nPublicNonce.ptr, nat).requireSuccess("secp256k1_frost_pubnonce_parse() failed")
return nPublicNonce
}
override fun frostSharesGen(
seed32: ByteArray,
threshold: Int,
totalSigners: Int,
ids33: Array<ByteArray>
): Triple<Array<ByteArray>, Array<ByteArray>, ByteArray> {
require(seed32.size == 32) { "seed size should be 32" }
require(threshold > 1) { "threshold cannot be less then 1" }
require(threshold <= totalSigners) { "threshold($threshold) cannot be greater then totalSigners ($totalSigners)" }
require(ids33.size == totalSigners) { "ids33 size need to be the same as totalSigners size" }
ids33.forEach { require(it.size == 33) { "id33 size must be 33" } }
memScoped {
val nShares = allocArray<secp256k1_frost_share>(ids33.size)
val nVssCommitment = allocArray<secp256k1_pubkey>(threshold)
val pok64 = ByteArray(64)
val nIds33s = ids33.map { toNat(it) }
secp256k1_frost_shares_gen(
ctx = ctx,
shares = nShares,
vss_commitment = nVssCommitment,
pok64 = toNat(pok64),
seed32 = toNat(seed32),
threshold = threshold.convert(),
n_participants = ids33.size.convert(),
ids33 = nIds33s.toCValues()
)
return Triple(
ids33.indices.map { serializeFrostShare(nShares[it]) }.toTypedArray(),
(0 until threshold).map { serializePubkey(nVssCommitment[it]) }.toTypedArray(),
pok64
)
}
}
private fun MemScope.serializeFrostShare(nFrostShare: secp256k1_frost_share): ByteArray {
val natOutput = allocArray<UByteVar>(Secp256k1.FROST_SERIALIZED_SHARE_SIZE)
secp256k1_frost_share_serialize(ctx, natOutput, nFrostShare.ptr).requireSuccess("secp256k1_frost_share_serialize() failed")
return natOutput.readBytes(Secp256k1.FROST_SERIALIZED_SHARE_SIZE)
}
override fun frostShareAggregate(
totalShares: Array<ByteArray>,
vssCommitments: Array<Array<ByteArray>>,
totalShareCount: Int,
threshold: Int,
id33: ByteArray
): Pair<ByteArray, ByteArray> {
require(totalShares.size == totalShareCount) { "totalShare array size (${totalShares.size}) should be the same as total share count ($totalShareCount)" }
totalShares.forEach { require(it.size == 32) { "all shares should be of size 32" } }
require(vssCommitments.size == totalShareCount) { "vssCommitments array size ${vssCommitments.size} should be the same as total share count ($totalShareCount)" }
vssCommitments.forEach { vssCommitment ->
require(vssCommitment.size == threshold) { "all vss commitment array size (${vssCommitment.size}) should be the same as the threshold size ($threshold)" }
vssCommitment.forEach { publicKey ->
require(publicKey.size == 33 || publicKey.size == 65) { "vss commitment data size should be 33 or 65" }
}
}
require(threshold > 1) { "threshold should be greater then 1" }
require(threshold <= totalShareCount) { "Threshold can not be greater then the total share count" }
require(id33.size == 33) { "id size should be 33" }
memScoped {
val nAggregateShare = alloc<secp256k1_frost_share>()
val nAggregatePublicKey = alloc<secp256k1_xonly_pubkey>()
val nTotalShares = totalShares.map { allocFrostShare(it).ptr }
val nVssCommitments = allocArray<CPointerVar<secp256k1_pubkey>>(vssCommitments.size)
vssCommitments.forEachIndexed { index, commitments ->
val pubkeyArray = allocArray<secp256k1_pubkey>(commitments.size)
commitments.forEachIndexed { commitmentIndex, pubkeyData ->
if (secp256k1_ec_pubkey_parse(ctx, pubkeyArray[commitmentIndex].ptr, toNat(pubkeyData), pubkeyData.size.convert()) == 0) {
error("Failed to parse public key")
}
}
nVssCommitments[index] = pubkeyArray
}
val result = secp256k1_frost_share_agg(
ctx = ctx,
agg_share = nAggregateShare.ptr,
agg_pk = nAggregatePublicKey.ptr,
shares = nTotalShares.toCValues(),
vss_commitments = nVssCommitments,
n_shares = totalShareCount.convert(),
threshold = threshold.convert(),
id33 = toNat(id33)
)
println("Aggregate Result: $result")
return Pair(
serializeFrostShare(nAggregateShare),
serializeXonlyPubkey(nAggregatePublicKey)
)
}
}
override fun frostShareVerify(
threshold: Int,
id33: ByteArray,
share: ByteArray,
vssCommitment: Array<ByteArray>
): Int {
require(threshold > 1) { "threshold should be greater then 1" }
require(id33.size == 33) { "id size (${id33.size}) should be 33" }
require(share.size == Secp256k1.FROST_SERIALIZED_SHARE_SIZE) { "share size (${share.size}) should be of size ${Secp256k1.FROST_SERIALIZED_SHARE_SIZE}" }
require(vssCommitment.size == threshold) { "vss commitment array size (${vssCommitment.size}) should be the same as the threshold size ($threshold)" }
vssCommitment.forEach { publicKey ->
require(publicKey.size == 33 || publicKey.size == 65) { "vss commitment data size should be 33 or 65" }
}
memScoped {
val nFrostShare = allocFrostShare(share)
val nVssCommitments = allocArray<CPointerVar<secp256k1_pubkey>>(1)
val pubkeyArray = allocArray<secp256k1_pubkey>(vssCommitment.size)
vssCommitment.forEachIndexed { commitmentIndex, pubkeyData ->
if (secp256k1_ec_pubkey_parse(ctx, pubkeyArray[commitmentIndex].ptr, toNat(pubkeyData), pubkeyData.size.convert()) == 0) {
error("Failed to parse public key")
}
}
nVssCommitments[0] = pubkeyArray
return secp256k1_frost_share_verify(
ctx = ctx,
threshold = threshold.convert(),
id33 = toNat(id33),
share = nFrostShare.ptr,
vss_commitment = nVssCommitments
)
}
}
override fun frostComputePublicShare(
threshold: Int,
id33: ByteArray,
vssCommitments: Array<Array<ByteArray>>,
totalSignersCount: Int
): ByteArray {
require(threshold > 1)
require(threshold <= totalSignersCount)
require(id33.size == 33)
require(vssCommitments.size == totalSignersCount)
vssCommitments.forEach { vssCommitment ->
require(vssCommitment.size == threshold)
vssCommitment.forEach { publicKey ->
require(publicKey.size == 33 || publicKey.size == 65)
}
}
memScoped {
val nPublicShare = alloc<secp256k1_pubkey>()
val nVssCommitments = allocArray<CPointerVar<secp256k1_pubkey>>(vssCommitments.size)
vssCommitments.forEachIndexed { index, commitments ->
val pubkeyArray = allocArray<secp256k1_pubkey>(commitments.size)
commitments.forEachIndexed { commitmentIndex, pubkeyData ->
if (secp256k1_ec_pubkey_parse(ctx, pubkeyArray[commitmentIndex].ptr, toNat(pubkeyData), pubkeyData.size.convert()) == 0) {
error("Failed to parse public key")
}
}
nVssCommitments[index] = pubkeyArray
}
val result = secp256k1_frost_compute_pubshare(
ctx = ctx,
pubshare = nPublicShare.ptr,
threshold = threshold.convert(),
id33 = toNat(id33),
vss_commitments = nVssCommitments,
n_participants = totalSignersCount.convert()
)
println("Compute pubshare result: $result")
return serializePubkey(nPublicShare)
}
}
override fun frostPublicKeyTweak(xOnlyPublicKey: ByteArray): ByteArray {
require(xOnlyPublicKey.size == Secp256k1.SERIALIZED_X_ONLY_PUBKEY_SIZE) { "pubkey size (${xOnlyPublicKey.size}) should be ${Secp256k1.SERIALIZED_X_ONLY_PUBKEY_SIZE}" }
memScoped {
val nTweakCache = alloc<secp256k1_frost_tweak_cache>()
val nPublicKey = allocXonlyPublicKey(xOnlyPublicKey)
secp256k1_frost_pubkey_tweak(
ctx = ctx,
tweak_cache = nTweakCache.ptr,
agg_pk = nPublicKey.ptr
)
return serializeFrostTweakCache(nTweakCache)
}
}
override fun frostPublicKeyEcTweakAdd(tweakCache: ByteArray, tweak32: ByteArray): ByteArray {
require(tweakCache.size == Secp256k1.FROST_TWEAK_CACHE_SIZE)
require(tweak32.size == 32)
memScoped {
val nTweakCache = alloc<secp256k1_frost_tweak_cache>()
memcpy(nTweakCache.ptr, toNat(tweakCache), Secp256k1.FROST_TWEAK_CACHE_SIZE.toULong())
val nPublicKey = alloc<secp256k1_pubkey>()
secp256k1_frost_pubkey_ec_tweak_add(
ctx = ctx,
output_pubkey = nPublicKey.ptr,
tweak_cache = nTweakCache.ptr,
tweak32 = toNat(tweak32)
)
return serializePubkey(nPublicKey)
}
}
private fun MemScope.serializeFrostTweakCache(nTweakCache: secp256k1_frost_tweak_cache): ByteArray {
val natOutput = ByteArray(Secp256k1.FROST_TWEAK_CACHE_SIZE)
memcpy(toNat(natOutput), nTweakCache.ptr, Secp256k1.FROST_TWEAK_CACHE_SIZE.toULong())
return natOutput
}
override fun frostPublicKeyXonlyTweakAdd(tweakCache: ByteArray, tweak32: ByteArray): ByteArray? {
require(tweakCache.size == Secp256k1.FROST_TWEAK_CACHE_SIZE)
require(tweak32.size == 32)
memScoped {
val nPublicKey = alloc<secp256k1_pubkey>()
val nTweakCache = alloc<secp256k1_frost_tweak_cache>()
memcpy(nTweakCache.ptr, toNat(tweakCache), Secp256k1.FROST_TWEAK_CACHE_SIZE.toULong())
secp256k1_frost_pubkey_xonly_tweak_add(
ctx = ctx,
output_pubkey = nPublicKey.ptr,
tweak_cache = nTweakCache.ptr,
tweak32 = toNat(tweak32)
)
return serializePubkey(nPublicKey)
}
}
private fun MemScope.serializeFrostSecnonce(nTweakCache: secp256k1_frost_secnonce): ByteArray {
val natOutput = ByteArray(Secp256k1.FROST_SECNONCE_SIZE)
memcpy(toNat(natOutput), nTweakCache.ptr, Secp256k1.FROST_SECNONCE_SIZE.toULong())
return natOutput
}
private fun MemScope.serializeFrostPubnonce(nPubnonce: secp256k1_frost_pubnonce): ByteArray {
val natOutput = allocArray<UByteVar>(Secp256k1.FROST_SERIALIZED_PUBNONCE_SIZE)
secp256k1_frost_pubnonce_serialize(ctx, natOutput, nPubnonce.ptr).requireSuccess("secp256k1_frost_pubnonce_serialize() failed")
return natOutput.readBytes(Secp256k1.FROST_SERIALIZED_PUBNONCE_SIZE)
}
override fun frostNonceGen(
sessionId32: ByteArray,
share: ByteArray?,
msg32: ByteArray?,
publicKey: ByteArray?,
extraInput32: ByteArray?
): Pair<ByteArray, ByteArray> {
require(sessionId32.size == 32) { "session id (${sessionId32.size}) size should be 32" }
share?.let {
require(share.size == Secp256k1.FROST_SERIALIZED_SHARE_SIZE) { "share size (${share.size}) should be ${Secp256k1.FROST_SERIALIZED_SHARE_SIZE}" }
}
msg32?.let {
require(msg32.size == 32) { "msg32 (${sessionId32.size}) size should be 32" }
}
publicKey?.let {
require(publicKey.size == 32) { "public key (${publicKey.size}) should be 32" }
}
extraInput32?.let {
require(it.size == 32) { "extraInput32 (${extraInput32.size}) size should be 32" }
}
memScoped {
val nFrostSecnonce = alloc<secp256k1_frost_secnonce>()
val nPublicNonce = alloc<secp256k1_frost_pubnonce>()
val nShare = share?.let { allocFrostShare(it) }
val nPublicKey = publicKey?.let { allocXonlyPublicKey(it) }
val nExtraInput32 = extraInput32?.let {
toNat(it)
}
secp256k1_frost_nonce_gen(
ctx = ctx,
secnonce = nFrostSecnonce.ptr,
pubnonce = nPublicNonce.ptr,
session_id32 = toNat(sessionId32),
agg_share = nShare?.ptr,
msg32 = msg32?.let { toNat(it) },
agg_pk = nPublicKey?.ptr,
extra_input32 = nExtraInput32
)
return Pair(
serializeFrostSecnonce(nFrostSecnonce),
serializeFrostPubnonce(nPublicNonce)
)
}
}
private fun MemScope.serializeFrostSession(nSession: secp256k1_frost_session): ByteArray {
val natOutput = ByteArray(Secp256k1.FROST_SESSION_SIZE)
memcpy(toNat(natOutput), nSession.ptr, Secp256k1.FROST_SESSION_SIZE.toULong())
return natOutput
}
override fun frostNonceProcess(
publicNonces: Array<ByteArray>,
threshold: Int,
msg32: ByteArray,
publicKey: ByteArray,
id33: ByteArray,
ids33: Array<ByteArray>,
tweakCache: ByteArray?,
adaptor: ByteArray?
): ByteArray {
publicNonces.forEach { publicNonce ->
require(publicNonce.size == Secp256k1.FROST_SERIALIZED_PUBNONCE_SIZE) { "pubnonce size (${publicNonce.size}) size should be ${Secp256k1.FROST_SERIALIZED_PUBNONCE_SIZE}" }
}
require(msg32.size == 32) { "msg32 (${msg32.size}) size should be 32" }
require(publicKey.size == Secp256k1.SERIALIZED_X_ONLY_PUBKEY_SIZE) { "publicKey size (${publicKey.size}) size should be ${Secp256k1.SERIALIZED_X_ONLY_PUBKEY_SIZE}" }
require(ids33.size == threshold) { "ids33 array size much match public nonces array size"}
ids33.forEach {
require(it.size == 33) { "id33 (${it.size}) size should be 33" }
}
tweakCache?.let {
require(tweakCache.size == Secp256k1.FROST_TWEAK_CACHE_SIZE) { "tweak cache size (${tweakCache.size}) size should be ${Secp256k1.FROST_TWEAK_CACHE_SIZE}" }
}
adaptor?.let {
require(it.size == 33 || it.size == 65) { "adaptor public key size (${it.size}) should be 33 or 65" }
}
memScoped {
val nSession = alloc<secp256k1_frost_session>();
val nPublicNonces = publicNonces.map { allocFrostPublicNonce(it).ptr }
val nPublicKey = allocXonlyPublicKey(publicKey)
val nIds33 = ids33.map { toNat(it) }
val nTweakCache = tweakCache?.let {
alloc<secp256k1_frost_tweak_cache>()
}?.also { nTweakCache ->
memcpy(nTweakCache.ptr, toNat(tweakCache) , Secp256k1.FROST_TWEAK_CACHE_SIZE.toULong())
}
val nAdaptor = adaptor?.let {
allocPublicKey(it).ptr
}
secp256k1_frost_nonce_process(
ctx = ctx,
session = nSession.ptr,
pubnonces = nPublicNonces.toCValues(),
n_pubnonces = publicNonces.size.convert(),
msg32 = toNat(msg32),
agg_pk = nPublicKey.ptr,
my_id33 = toNat(id33),
ids33 = nIds33.toCValues(),
tweak_cache = nTweakCache?.ptr,
adaptor = nAdaptor
)
return serializeFrostSession(
nSession = nSession
)
}
}
private fun MemScope.serializeFrostPartialSignature(nPartialSignature: secp256k1_frost_partial_sig): ByteArray {
val natOutput = allocArray<UByteVar>(Secp256k1.FROST_SERIALIZED_PARTIAL_SIGNATURE_SIZE)
secp256k1_frost_partial_sig_serialize(ctx, natOutput, nPartialSignature.ptr).requireSuccess("secp256k1_frost_partial_sig_serialize() failed")
return natOutput.readBytes(Secp256k1.FROST_SERIALIZED_PARTIAL_SIGNATURE_SIZE)
}
override fun frostPartialSign(
secnonce: ByteArray,
share: ByteArray,
session: ByteArray,
tweakCache: ByteArray?
): ByteArray {
require(secnonce.size == Secp256k1.FROST_SECNONCE_SIZE) { "secnonce size (${secnonce.size}) should be of size ${Secp256k1.FROST_SECNONCE_SIZE}" }
require(share.size == Secp256k1.FROST_SERIALIZED_SHARE_SIZE) { "share size (${share.size}) should be of size ${Secp256k1.FROST_SERIALIZED_SHARE_SIZE}" }
require(session.size == Secp256k1.FROST_SESSION_SIZE) { "session size (${share.size}) should be of size ${Secp256k1.FROST_SESSION_SIZE}" }
tweakCache?.let {
require(tweakCache.size == Secp256k1.FROST_TWEAK_CACHE_SIZE) { "tweak cache size (${tweakCache.size}) size should be ${Secp256k1.FROST_TWEAK_CACHE_SIZE}" }
}
memScoped {
val nPartialSignature = alloc<secp256k1_frost_partial_sig>();
val nSecnonce = alloc<secp256k1_frost_secnonce>()
memcpy(nSecnonce.ptr, toNat(secnonce), Secp256k1.FROST_SECNONCE_SIZE.toULong())
val nShare = allocFrostShare(share)
val nSession = alloc<secp256k1_frost_session>()
memcpy(nSession.ptr, toNat(session), Secp256k1.FROST_SESSION_SIZE.toULong())
val nTweakCache = tweakCache?.let {
alloc<secp256k1_frost_tweak_cache>()
}?.also { nTweakCache ->
memcpy(nTweakCache.ptr, toNat(tweakCache) , Secp256k1.FROST_TWEAK_CACHE_SIZE.toULong())
}
secp256k1_frost_partial_sign(
ctx,
nPartialSignature.ptr,
nSecnonce.ptr,
nShare.ptr,
nSession.ptr,
nTweakCache?.ptr
)
return serializeFrostPartialSignature(nPartialSignature)
}
}
override fun frostPartialSignatureVerify(
partialSig: ByteArray,
publicNonce: ByteArray,
publicShare: ByteArray,
session: ByteArray,
tweakCache: ByteArray?
): Int {
require(partialSig.size == 32) { "partialSig (${partialSig.size}) size should be 32" }
require(publicNonce.size == Secp256k1.FROST_SERIALIZED_PUBNONCE_SIZE) { "public nonce (${publicNonce.size}) size should be ${Secp256k1.FROST_SERIALIZED_PUBNONCE_SIZE}" }
require(publicShare.size == 33 || publicShare.size == 65) { "public share size (${partialSig.size}) should be 33 or 65" }
require(session.size == Secp256k1.FROST_SESSION_SIZE) { "session size (${session.size}) size should be ${Secp256k1.FROST_SESSION_SIZE}" }
tweakCache?.let {
require(tweakCache.size == Secp256k1.FROST_TWEAK_CACHE_SIZE) { "tweak cache size (${tweakCache.size}) size should be ${Secp256k1.FROST_TWEAK_CACHE_SIZE}" }
}
memScoped {
val nPartialSignature = allocFrostPartialSignature(partialSig)
val nPublicNonce = allocFrostPublicNonce(publicNonce)
val nPublicShare = allocPublicKey(publicShare)
val nSession = alloc<secp256k1_frost_session>()
memcpy(nSession.ptr, toNat(session), Secp256k1.FROST_SESSION_SIZE.toULong())
val nTweakCache = tweakCache?.let {
alloc<secp256k1_frost_tweak_cache>()
}?.also { nTweakCache ->
memcpy(nTweakCache.ptr, toNat(tweakCache) , Secp256k1.FROST_TWEAK_CACHE_SIZE.toULong())
}
return secp256k1_frost_partial_sig_verify(
ctx,
nPartialSignature.ptr,
nPublicNonce.ptr,
nPublicShare.ptr,
nSession.ptr,
nTweakCache?.ptr
)
}
}
override fun frostPartialSignatureAggregate(
session: ByteArray,
partialSignatures: Array<ByteArray>,
threshold: Int
): ByteArray {
require(threshold > 1) { "threshold must be greater then 1" }
require(session.size == Secp256k1.FROST_SESSION_SIZE) { "session size (${session.size}) should be ${Secp256k1.FROST_SESSION_SIZE}" }
require(partialSignatures.size == threshold) { "partialSignatures array size should match the threshold size" }
partialSignatures.forEach { partialSig ->
require(partialSig.size == 32) { "partialSignatures size (${partialSig.size}) should be 32" }
}
memScoped {
val sig64 = ByteArray(64)
val nSession = alloc<secp256k1_frost_session>();
memcpy(nSession.ptr, toNat(session), Secp256k1.FROST_SESSION_SIZE.toULong())
val nPartialSignatures = partialSignatures.map { allocFrostPartialSignature(it).ptr }
secp256k1_frost_partial_sig_agg(
ctx,
toNat(sig64),
nSession.ptr,
nPartialSignatures.toCValues(),
threshold.convert()
)
return sig64
}
}
public override fun cleanup() {
secp256k1_context_destroy(ctx)
}
}
internal actual fun getSecpk256k1(): Secp256k1 = Secp256k1Native

View File

@ -1,19 +1,11 @@
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeHostTest
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest
plugins {
kotlin("multiplatform")
if (System.getProperty("includeAndroid")?.toBoolean() == true) {
id("com.android.library")
}
id("com.android.library")
}
kotlin {
explicitApi()
val includeAndroid = System.getProperty("includeAndroid")?.toBoolean() ?: true
val commonMain by sourceSets.getting {
dependencies {
implementation(rootProject)
@ -21,10 +13,7 @@ kotlin {
}
val commonTest by sourceSets.getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation("org.kodein.memory:klio-files:0.12.0")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}
}
@ -40,72 +29,42 @@ kotlin {
}
}
if (includeAndroid) {
androidTarget {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
sourceSets["androidMain"].dependencies {
implementation(project(":jni:android"))
}
sourceSets["androidUnitTest"].dependencies {
implementation(kotlin("test-junit"))
implementation("androidx.test.ext:junit:1.1.2")
implementation("androidx.test.espresso:espresso-core:3.3.0")
}
android {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
sourceSets["androidMain"].dependencies {
implementation(project(":jni:android"))
}
sourceSets["androidTest"].dependencies {
implementation(kotlin("test-junit"))
implementation("androidx.test.ext:junit:1.1.2")
implementation("androidx.test.espresso:espresso-core:3.3.0")
}
}
linuxX64()
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64("linux")
ios()
}
val includeAndroid = System.getProperty("includeAndroid")?.toBoolean() ?: true
if (includeAndroid) {
extensions.configure<com.android.build.gradle.LibraryExtension>("android") {
defaultConfig {
compileSdk = 30
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
android {
defaultConfig {
compileSdkVersion(30)
minSdkVersion(21)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
afterEvaluate {
tasks.withType<com.android.build.gradle.tasks.factory.AndroidUnitTest>().all {
enabled = false
}
afterEvaluate {
tasks.withType<com.android.build.gradle.tasks.factory.AndroidUnitTest>().all {
enabled = false
}
}
}
afterEvaluate {
tasks.withType<AbstractTestTask> {
testLogging {
events("passed", "skipped", "failed", "standard_out", "standard_error")
showExceptions = true
showStackTraces = true
}
}
tasks.withType<KotlinJvmTest> {
environment("TEST_RESOURCES_PATH", projectDir.resolve("src/commonTest/resources"))
}
tasks.withType<KotlinNativeHostTest> {
environment("TEST_RESOURCES_PATH", projectDir.resolve("src/commonTest/resources"))
}
tasks.withType<KotlinNativeSimulatorTest> {
environment("SIMCTL_CHILD_TEST_RESOURCES_PATH", projectDir.resolve("src/commonTest/resources"))
}
}

View File

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

View File

@ -1,24 +0,0 @@
package fr.acinq.secp256k1
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import org.kodein.memory.file.FileSystem
import org.kodein.memory.file.Path
import org.kodein.memory.file.openReadableFile
import org.kodein.memory.file.resolve
import org.kodein.memory.system.Environment
import org.kodein.memory.text.readString
import org.kodein.memory.use
abstract class BaseTest {
fun resourcesDir() =
Environment.findVariable("TEST_RESOURCES_PATH")?.let { Path(it) }
?: FileSystem.workingDir().resolve("src/commonTest/resources")
fun readData(filename: String): JsonElement {
val file = resourcesDir().resolve(filename)
val raw = file.openReadableFile().use { it.readString() }
val format = Json { ignoreUnknownKeys = true }
return format.parseToJsonElement(raw)
}
}

View File

@ -1,510 +0,0 @@
package fr.acinq.secp256k1
import kotlinx.serialization.json.*
import kotlin.test.*
class FrostTest: BaseTest() {
val msg32 = "this_could_be_the_hash_of_a_msg!".encodeToByteArray()
@Test
fun `frost share generation test cases`() {
val tests = readData("frost/share_gen_vectors.json")
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
tests.jsonObject["valid_share_gen_test_cases"]!!.jsonArray.forEach { validTestCases ->
println("Testing ${validTestCases.jsonObject["seed"]!!.jsonPrimitive.content}") // Hack to slow things down... :(
val keyIndices = validTestCases.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val seed32 = Hex.decode(validTestCases.jsonObject["seed"]!!.jsonPrimitive.content)
val nParticipants = keyIndices.size
val threshold = validTestCases.jsonObject["threshold"]!!.jsonPrimitive.int
val ids33 = keyIndices.map { pubkeys[it] }.toTypedArray()
val result = Secp256k1.frostSharesGen(
seed32,
threshold,
nParticipants,
ids33
)
val expected = validTestCases.jsonObject["expected"]!!;
val expectedShare = expected.jsonObject["frost_share"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val expectedVSSCommitment = expected.jsonObject["vss_commitment"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val expectedPoK64 = Hex.decode(expected.jsonObject["pok64"]!!.jsonPrimitive.content)
result.first.forEachIndexed { index, share ->
assertEquals(
expected = Hex.encode(expectedShare[index]),
actual = Hex.encode(share),
"Unexpected $index:share for $keyIndices test case"
)
}
result.second.forEachIndexed { index, vssCommitment ->
assertEquals(
expected = Hex.encode(expectedVSSCommitment[index]),
actual = Hex.encode(vssCommitment),
"Unexpected $index:vss_commitment for the $keyIndices test case"
)
}
assertEquals(
expected = Hex.encode(expectedPoK64),
actual = Hex.encode(result.third),
message = "Unexpected pok64 for $keyIndices test case"
)
}
}
@Test
fun `frost share generation signers`() {
val tests = readData("frost/share_gen_signers_vectors.json")
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val signerShareGenTestCase = tests.jsonObject["valid_signers_share_gen_test_case"]!!
val keyIndices = signerShareGenTestCase.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nParticipants = keyIndices.size
val threshold = signerShareGenTestCase.jsonObject["threshold"]!!.jsonPrimitive.int
val ids33 = keyIndices.map { pubkeys[it] }.toTypedArray()
signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.forEachIndexed { signerIndex, signer ->
val seed32 = Hex.decode(signer.jsonObject["seed"]!!.jsonPrimitive.content)
println("Testing ${signer.jsonObject["seed"]!!.jsonPrimitive.content}") // Hack to slow things down... :(
// There seems to be a bug that causes a crash if we call frost_share_gen too often
val result = Secp256k1.frostSharesGen(
seed32,
threshold,
nParticipants,
ids33
)
val expected = signer.jsonObject["expected"]!!;
val expectedShare = expected.jsonObject["frost_share"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val expectedVSSCommitment = expected.jsonObject["vss_commitment"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val expectedPoK64 = Hex.decode(expected.jsonObject["pok64"]!!.jsonPrimitive.content)
result.first.forEachIndexed { index, share ->
assertEquals(
expected = Hex.encode(expectedShare[index]),
actual = Hex.encode(share),
"Unexpected $signerIndex:signer $index:share for $keyIndices test case"
)
}
result.second.forEachIndexed { index, vssCommitment ->
assertEquals(
expected = Hex.encode(expectedVSSCommitment[index]),
actual = Hex.encode(vssCommitment),
"Unexpected $signerIndex:signer $index:vss_commitment for the $keyIndices test case"
)
}
assertEquals(
expected = Hex.encode(expectedPoK64),
actual = Hex.encode(result.third),
message = "Unexpected $signerIndex:signer pok64 for $keyIndices test case"
)
}
}
@Test
fun `frost share aggregation`() {
val shareGenTests = readData("frost/share_gen_vectors.json")
val tests = readData("frost/share_agg_vectors.json")
val expectedAggregatePublicKey = tests.jsonObject["aggregate_public_key"]!!.jsonPrimitive.content
val publicKeys = shareGenTests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val signerShareGenTestCase = shareGenTests.jsonObject["valid_signers_share_gen_test_case"]!!;
val keyIndices = signerShareGenTestCase.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nParticipants = keyIndices.size
val threshold = signerShareGenTestCase.jsonObject["threshold"]!!.jsonPrimitive.int
val ids33 = keyIndices.map { publicKeys[it] }.toTypedArray()
val vssCommitments = signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.map { signer ->
signer.jsonObject["expected"]!!.jsonObject["vss_commitment"]!!.jsonArray.map {
Hex.decode(it.jsonPrimitive.content)
}.toTypedArray()
}
signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.forEachIndexed { index, _ ->
val assignedShares = signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.map {
Hex.decode(
it.jsonObject["expected"]!!.jsonObject["frost_share"]!!.jsonArray[index].jsonPrimitive.content
)
}
val result = Secp256k1.frostShareAggregate(
assignedShares.toTypedArray(),
vssCommitments.toTypedArray(),
nParticipants,
threshold,
ids33[index]
)
val expected = tests.jsonObject["expected"]!!.jsonArray[index];
val expectedAggregateShare = expected.jsonObject["aggregate_share"]!!.jsonPrimitive.content
assertEquals(
expected = expectedAggregateShare,
actual = Hex.encode(result.first),
"Unexpected $index:aggregate_share"
)
assertEquals(
expected = expectedAggregatePublicKey,
actual = Hex.encode(result.second),
"Unexpected $index:aggregate_public_key"
)
}
}
@Test
fun `frost share verify`() {
val shareGenTests = readData("frost/share_gen_vectors.json")
val publicKeys = shareGenTests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val signerShareGenTestCase = shareGenTests.jsonObject["valid_signers_share_gen_test_case"]!!;
val keyIndices = signerShareGenTestCase.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val threshold = signerShareGenTestCase.jsonObject["threshold"]!!.jsonPrimitive.int
val ids33 = keyIndices.map { publicKeys[it] }.toTypedArray()
val vssCommitments = signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.map { signer ->
signer.jsonObject["expected"]!!.jsonObject["vss_commitment"]!!.jsonArray.map {
Hex.decode(it.jsonPrimitive.content)
}.toTypedArray()
}
signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.forEachIndexed { index, _ ->
val assignedShares = signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.map {
Hex.decode(
it.jsonObject["expected"]!!.jsonObject["frost_share"]!!.jsonArray[index].jsonPrimitive.content
)
}
assertEquals(
expected = 1,
actual = Secp256k1.frostShareVerify(
threshold,
ids33[index],
assignedShares[index],
vssCommitments[index]
),
message = "Couldn't verify share from $index signer"
)
}
}
@Test
fun `frost compute pubshare`() {
val shareGenTests = readData("frost/share_gen_vectors.json")
val tests = readData("frost/share_agg_vectors.json")
val publicKeys = shareGenTests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val signerShareGenTestCase = shareGenTests.jsonObject["valid_signers_share_gen_test_case"]!!;
val keyIndices = signerShareGenTestCase.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nParticipants = keyIndices.size
val threshold = signerShareGenTestCase.jsonObject["threshold"]!!.jsonPrimitive.int
val ids33 = keyIndices.map { publicKeys[it] }.toTypedArray()
val vssCommitments = signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.map { signer ->
signer.jsonObject["expected"]!!.jsonObject["vss_commitment"]!!.jsonArray.map {
Hex.decode(it.jsonPrimitive.content)
}.toTypedArray()
}
signerShareGenTestCase.jsonObject["signers"]!!.jsonArray.forEachIndexed { index, _ ->
val expected = tests.jsonObject["expected"]!!.jsonArray[index];
val expectedPublicShare = expected.jsonObject["public_share"]!!.jsonPrimitive.content
assertEquals(
expected = expectedPublicShare,
actual = Hex.encode(
Secp256k1.frostComputePublicShare(
threshold,
ids33[index],
vssCommitments.toTypedArray(),
nParticipants
)
),
message = "Couldn't verify share from $index signer"
)
}
}
// Frost Tweak
@Test
fun `frost pubkey tweak`() {
val tests = readData("frost/share_agg_vectors.json")
val expectedAggregatePublicKey = Hex.decode(
tests.jsonObject["aggregate_public_key"]!!.jsonPrimitive.content
)
val expectedTweakCache = tests.jsonObject["tweak_cache"]!!.jsonPrimitive.content
assertEquals(
expected = expectedTweakCache,
actual = Hex.encode(
Secp256k1.frostPublicKeyTweak(
expectedAggregatePublicKey
)
),
message = "Tweak cache incorrect."
)
}
@Test
fun `frost pubkey ec tweak add`() {
val tests = readData("frost/frost_tweak_vectors.json")
val tweakCache = Hex.decode(
tests.jsonObject["tweak_cache"]!!.jsonPrimitive.content
)
val ordinaryTweak = "this could be a BIP32 tweak.....".encodeToByteArray()
assertEquals(
expected = tests.jsonObject["ec_tweak_add"]!!.jsonPrimitive.content,
actual = Secp256k1.frostPublicKeyEcTweakAdd(
tweakCache,
ordinaryTweak
)?.let {
Hex.encode(
it
)
},
message = "EC Tweak Add incorrect."
)
}
@Test
fun `frost pubkey xonly tweak add`() {
val tests = readData("frost/frost_tweak_vectors.json")
val tweakCache = Hex.decode(
tests.jsonObject["tweak_cache"]!!.jsonPrimitive.content
)
val ordinaryTweak = "this could be a BIP32 tweak.....".encodeToByteArray()
assertEquals(
expected = tests.jsonObject["ec_tweak_add"]!!.jsonPrimitive.content,
// TODO: Return public key
actual = Secp256k1.frostPublicKeyXonlyTweakAdd(
tweakCache,
ordinaryTweak
)?.let {
Hex.encode(
it
)
},
message = "EC Tweak Add incorrect."
)
}
// Frost Sign functionality
@Test
fun `frost nonce gen`() {
val tests = readData("frost/frost_nonce_vectors.json")
val sessionId = Hex.decode(
tests.jsonObject["session_id"]!!.jsonPrimitive.content
)
val aggregatePublicKey = Hex.decode(
tests.jsonObject["aggregate_public_key"]!!.jsonPrimitive.content
)
tests.jsonObject["signers"]!!.jsonArray.forEachIndexed { index, signers ->
val aggregateShare = Hex.decode(
signers.jsonObject["aggregate_share"]!!.jsonPrimitive.content
)
val (secNonce, pubNonce) = Secp256k1.frostNonceGen(
sessionId,
aggregateShare,
msg32,
aggregatePublicKey,
null
)
val expectedSecNonce = signers.jsonObject["secnonce"]!!.jsonPrimitive.content
val expectedPubNonce = signers.jsonObject["pubnonce"]!!.jsonPrimitive.content
assertEquals(
expected = expectedSecNonce,
actual = Hex.encode(secNonce),
message = "Invalid $index:secnonce"
)
assertEquals(
expected = expectedPubNonce,
actual = Hex.encode(pubNonce),
message = "Invalid $index:pubnonce"
)
}
}
@Test
fun `frost nonce process`() {
val tests = readData("frost/frost_nonce_vectors.json")
val threshold = tests.jsonObject["threshold"]!!.jsonPrimitive.int
val pubnonces = tests.jsonObject["signers"]!!.jsonArray.take(threshold).map {
Hex.decode(it.jsonObject["pubnonce"]!!.jsonPrimitive.content)
}
val aggregatePublicKey = Hex.decode(
tests.jsonObject["aggregate_public_key"]!!.jsonPrimitive.content
)
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.take(threshold).map {
Hex.decode(it.jsonPrimitive.content)
}
val tweakCache = Hex.decode(
tests.jsonObject["tweak_cache"]!!.jsonPrimitive.content
)
tests.jsonObject["signers"]!!.jsonArray.take(threshold).forEachIndexed { signerIndex, signer ->
val session = Secp256k1.frostNonceProcess(
pubnonces.toTypedArray(),
threshold,
msg32,
aggregatePublicKey,
pubkeys[signerIndex],
pubkeys.toTypedArray(),
tweakCache,
null
)
assertEquals(
expected = signer.jsonObject["session"]!!.jsonPrimitive.content,
actual = Hex.encode(session),
message = "Invalid $signerIndex:session"
)
}
}
@Test
fun `frost partial sign`() {
val tests = readData("frost/frost_nonce_vectors.json")
val threshold = tests.jsonObject["threshold"]!!.jsonPrimitive.int
val tweakCache = Hex.decode(
tests.jsonObject["tweak_cache"]!!.jsonPrimitive.content
)
tests.jsonObject["signers"]!!.jsonArray.take(threshold).forEachIndexed { signerIndex, signer ->
val secNonce = Hex.decode(
signer.jsonObject["secnonce"]!!.jsonPrimitive.content
)
val aggregateShare = Hex.decode(
signer.jsonObject["aggregate_share"]!!.jsonPrimitive.content
)
val session = Hex.decode(
signer.jsonObject["session"]!!.jsonPrimitive.content
)
val partialSignature = Secp256k1.frostPartialSign(
secNonce,
aggregateShare,
session,
tweakCache
)
assertEquals(
expected = signer.jsonObject["partial_signature"]!!.jsonPrimitive.content,
actual = Hex.encode(partialSignature),
message = "Invalid $signerIndex:partial signature"
)
}
}
@Test
fun `frost partial signature verify`() {
val tests = readData("frost/frost_nonce_vectors.json")
val threshold = tests.jsonObject["threshold"]!!.jsonPrimitive.int
val tweakCache = Hex.decode(
tests.jsonObject["tweak_cache"]!!.jsonPrimitive.content
)
tests.jsonObject["signers"]!!.jsonArray.take(threshold).forEach { signer ->
val partialSignature = Hex.decode(
signer.jsonObject["partial_signature"]!!.jsonPrimitive.content
)
val pubNonce = Hex.decode(
signer.jsonObject["pubnonce"]!!.jsonPrimitive.content
)
val publicShare = Hex.decode(
signer.jsonObject["public_share"]!!.jsonPrimitive.content
)
val session = Hex.decode(
signer.jsonObject["session"]!!.jsonPrimitive.content
)
assertEquals(
expected = 1,
actual = Secp256k1.frostPartialSignatureVerify(
partialSignature,
pubNonce,
publicShare,
session,
tweakCache
),
message = "Failed to verify partial signature"
)
}
}
@Test
fun `frost partial signature aggregation`() {
val tests = readData("frost/frost_nonce_vectors.json")
val threshold = tests.jsonObject["threshold"]!!.jsonPrimitive.int
val partialSignatures = tests.jsonObject["signers"]!!.jsonArray.take(threshold).map {
Hex.decode(it.jsonObject["partial_signature"]!!.jsonPrimitive.content)
}
tests.jsonObject["signers"]!!.jsonArray.take(threshold).forEach { signer ->
val session = Hex.decode(
signer.jsonObject["session"]!!.jsonPrimitive.content
)
val aggregatedSignature = Secp256k1.frostPartialSignatureAggregate(
session,
partialSignatures.toTypedArray(),
threshold
)
assertEquals(
expected = tests.jsonObject["aggregate_signature"]!!.jsonPrimitive.content,
actual = Hex.encode(aggregatedSignature),
message = "Invalid aggregate partial signature"
)
}
}
}

View File

@ -1,288 +0,0 @@
package fr.acinq.secp256k1
import kotlinx.serialization.json.*
import org.kodein.memory.file.FileSystem
import org.kodein.memory.file.Path
import org.kodein.memory.file.openReadableFile
import org.kodein.memory.file.resolve
import org.kodein.memory.system.Environment
import org.kodein.memory.text.readString
import org.kodein.memory.use
import kotlin.test.*
class Musig2Test: BaseTest() {
@Test
fun `aggregate public keys`() {
val tests = readData("musig2/key_agg_vectors.json")
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
val aggkey = Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache)
assertContentEquals(expected, aggkey)
}
tests.jsonObject["error_test_cases"]!!.jsonArray.forEach {
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val tweakIndex = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }.firstOrNull()
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
when (tweakIndex) {
null -> {
// One of the public keys is invalid, so key aggregation will fail.
// Callers must verify that public keys are valid before aggregating them.
assertFails {
val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache)
}
}
else -> {
// The tweak cannot be applied, it would result in an invalid public key.
assertFails {
val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache)
if (isXonly[0])
Secp256k1.musigPubkeyXonlyTweakAdd(keyAggCache, tweaks[tweakIndex])
else
Secp256k1.musigPubkeyTweakAdd(keyAggCache, tweaks[tweakIndex])
}
}
}
}
}
/** Secret nonces in test vectors use a custom encoding. */
private fun deserializeSecretNonce(hex: String): ByteArray {
val serialized = Hex.decode(hex)
require(serialized.size == 97) { "secret nonce from test vector should be serialized using 97 bytes" }
// In test vectors, secret nonces are serialized as: <scalar_1> <scalar_2> <compressed_public_key>
val compressedPublicKey = serialized.takeLast(33).toByteArray()
// We expect secret nonces serialized as: <magic> <scalar_1> <scalar_2> <public_key_x> <public_key_y>
// Where we use a different endianness for the public key coordinates than the test vectors.
val uncompressedPublicKey = Secp256k1.pubkeyParse(compressedPublicKey)
val publicKeyX = uncompressedPublicKey.drop(1).take(32).reversed().toByteArray()
val publicKeyY = uncompressedPublicKey.takeLast(32).reversed().toByteArray()
val magic = Hex.decode("220EDCF1")
return magic + serialized.take(64) + publicKeyX + publicKeyY
}
@Test
fun `generate secret nonce`() {
val tests = readData("musig2/nonce_gen_vectors.json")
tests.jsonObject["test_cases"]!!.jsonArray.forEach {
val randprime = Hex.decode(it.jsonObject["rand_"]!!.jsonPrimitive.content)
val sk = it.jsonObject["sk"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) }
val pk = Hex.decode(it.jsonObject["pk"]!!.jsonPrimitive.content)
val keyagg = it.jsonObject["aggpk"]?.jsonPrimitive?.contentOrNull?.let {
// The test vectors directly provide an aggregated public key: we must manually create the corresponding
// key aggregation cache to correctly test.
val agg = ByteArray(1) { 2.toByte() } + Hex.decode(it)
val magic = Hex.decode("f4adbbdf")
magic + Secp256k1.pubkeyParse(agg).drop(1) + ByteArray(129) { 0x00 }
}
val msg = it.jsonObject["msg"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) }
val extraInput = it.jsonObject["extra_in"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) }
val expectedSecnonce = deserializeSecretNonce(it.jsonObject["expected_secnonce"]!!.jsonPrimitive.content)
val expectedPubnonce = Hex.decode(it.jsonObject["expected_pubnonce"]!!.jsonPrimitive.content)
// secp256k1 only supports signing 32-byte messages (when provided), which excludes some of the test vectors.
if (msg == null || msg.size == 32) {
val nonce = Secp256k1.musigNonceGen(randprime, sk, pk, msg, keyagg, extraInput)
val secnonce = nonce.copyOfRange(0, Secp256k1.MUSIG2_SECRET_NONCE_SIZE)
val pubnonce = nonce.copyOfRange(Secp256k1.MUSIG2_SECRET_NONCE_SIZE, Secp256k1.MUSIG2_SECRET_NONCE_SIZE + Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
assertContentEquals(expectedPubnonce, pubnonce)
assertContentEquals(expectedSecnonce, secnonce)
}
}
}
@Test
fun `aggregate nonces`() {
val tests = readData("musig2/nonce_agg_vectors.json")
val nonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
val nonceIndices = it.jsonObject["pnonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
val agg = Secp256k1.musigNonceAgg(nonceIndices.map { nonces[it] }.toTypedArray())
assertNotNull(agg)
assertContentEquals(expected, agg)
}
tests.jsonObject["error_test_cases"]!!.jsonArray.forEach {
val nonceIndices = it.jsonObject["pnonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
assertFails {
Secp256k1.musigNonceAgg(nonceIndices.map { nonces[it] }.toTypedArray())
}
}
}
@Test
fun sign() {
val tests = readData("musig2/sign_verify_vectors.json")
val sk = Hex.decode(tests.jsonObject["sk"]!!.jsonPrimitive.content)
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val secnonces = tests.jsonObject["secnonces"]!!.jsonArray.map { deserializeSecretNonce(it.jsonPrimitive.content) }
val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val aggnonces = tests.jsonObject["aggnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val msgs = tests.jsonObject["msgs"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int
val messageIndex = it.jsonObject["msg_index"]!!.jsonPrimitive.int
val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())
assertNotNull(aggnonce)
assertContentEquals(aggnonces[it.jsonObject["aggnonce_index"]!!.jsonPrimitive.int], aggnonce)
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
// We only support signing 32-byte messages.
if (msgs[messageIndex].size == 32) {
val session = Secp256k1.musigNonceProcess(aggnonce, msgs[messageIndex], keyagg)
assertNotNull(session)
val psig = Secp256k1.musigPartialSign(secnonces[keyIndices[signerIndex]], sk, keyagg, session)
assertContentEquals(expected, psig)
assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session))
}
}
tests.jsonObject["verify_fail_test_cases"]!!.jsonArray.forEach {
val psig = Hex.decode(it.jsonObject["sig"]!!.jsonPrimitive.content)
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int
val messageIndex = it.jsonObject["msg_index"]!!.jsonPrimitive.int
if (msgs[messageIndex].size == 32) {
val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())
assertNotNull(aggnonce)
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
val session = Secp256k1.musigNonceProcess(aggnonce, msgs[messageIndex], keyagg)
assertNotNull(session)
assertFails {
require(Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session) == 1)
}
}
}
}
@Test
fun `aggregate signatures`() {
val tests = readData("musig2/sig_agg_vectors.json")
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val psigs = tests.jsonObject["psigs"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val msg = Hex.decode(tests.jsonObject["msg"]!!.jsonPrimitive.content)
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val psigIndices = it.jsonObject["psig_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())
assertNotNull(aggnonce)
val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
assertContentEquals(Hex.decode(it.jsonObject["aggnonce"]!!.jsonPrimitive.content), aggnonce)
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
tweakIndices
.zip(isXonly)
.map { tweaks[it.first] to it.second }
.forEach {
if (it.second)
Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first)
else
Secp256k1.musigPubkeyTweakAdd(keyagg, it.first)
}
val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg)
val aggsig = Secp256k1.musigPartialSigAgg(session, psigIndices.map { psigs[it] }.toTypedArray())
assertContentEquals(expected, aggsig)
}
tests.jsonObject["error_test_cases"]!!.jsonArray.forEach {
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val psigIndices = it.jsonObject["psig_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())
assertNotNull(aggnonce)
val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
assertContentEquals(Hex.decode(it.jsonObject["aggnonce"]!!.jsonPrimitive.content), aggnonce)
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
tweakIndices
.zip(isXonly)
.map { tweaks[it.first] to it.second }
.forEach {
if (it.second)
Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first)
else
Secp256k1.musigPubkeyTweakAdd(keyagg, it.first)
}
val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg)
assertFails {
Secp256k1.musigPartialSigAgg(session, psigIndices.map { psigs[it] }.toTypedArray())
}
}
}
@Test
fun `tweak tests`() {
val tests = readData("musig2/tweak_vectors.json")
val sk = Hex.decode(tests.jsonObject["sk"]!!.jsonPrimitive.content)
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
val msg = Hex.decode(tests.jsonObject["msg"]!!.jsonPrimitive.content)
val secnonce = deserializeSecretNonce(tests.jsonObject["secnonce"]!!.jsonPrimitive.content)
val aggnonce = Hex.decode(tests.jsonObject["aggnonce"]!!.jsonPrimitive.content)
assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(arrayOf(pnonces[0], pnonces[1], pnonces[2])))
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray()))
val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
tweakIndices
.zip(isXonly)
.map { tweaks[it.first] to it.second }
.forEach {
if (it.second)
Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first)
else
Secp256k1.musigPubkeyTweakAdd(keyagg, it.first)
}
val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg)
assertNotNull(session)
val psig = Secp256k1.musigPartialSign(secnonce, sk, keyagg, session)
assertContentEquals(expected, psig)
assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session))
}
tests.jsonObject["error_test_cases"]!!.jsonArray.forEach {
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray()))
val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
assertEquals(1, tweakIndices.size)
val tweak = tweaks[tweakIndices.first()]
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }.first()
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
assertFails {
if (isXonly)
Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, tweak)
else
Secp256k1.musigPubkeyTweakAdd(keyagg, tweak)
}
}
}
}

View File

@ -3,292 +3,264 @@ package fr.acinq.secp256k1
import kotlin.random.Random
import kotlin.test.*
/**
* This class holds test cases defined for testing this library.
*/
class Secp256k1Test {
//TODO improve comments/add more tests
val random = Random.Default
fun randomBytes(length: Int): ByteArray {
val buffer = ByteArray(length)
random.nextBytes(buffer)
return buffer
@Test
fun testVerifyPos() {
var result: Boolean
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val sig: ByteArray = Hex.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".lowercase())
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
result = Secp256k1.verify(sig, data, pub)
assertTrue(result, "testVerifyPos")
val sigCompact: ByteArray = Hex.decode("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".lowercase())
result = Secp256k1.verify(sigCompact, data, pub)
assertTrue(result, "testVerifyPos")
}
@Test
fun verifyValidPrivateKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
assertTrue(Secp256k1.secKeyVerify(priv))
fun testVerifyNeg() {
var result: Boolean
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91".lowercase()) //sha256hash of "testing"
val sig: ByteArray = Hex.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".lowercase())
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
result = Secp256k1.verify(sig, data, pub)
assertFalse(result, "testVerifyNeg")
}
@Test
fun verifyInvalidPrivateKey() {
val invalidSize = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A106353001")
assertFalse(Secp256k1.secKeyVerify(invalidSize))
val curveOrder = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141".lowercase())
assertFalse(Secp256k1.secKeyVerify(curveOrder))
val zero = Hex.decode("0000000000000000000000000000000000000000000000000000000000000000".lowercase())
assertFalse(Secp256k1.secKeyVerify(zero))
fun testSecKeyVerifyPos() {
var result: Boolean
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
result = Secp256k1.secKeyVerify(sec)
assertTrue(result, "testSecKeyVerifyPos")
}
@Test
fun createValidPublicKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub = Secp256k1.pubkeyCreate(priv)
fun testSecKeyVerifyNeg() {
var result: Boolean
val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".lowercase())
result = Secp256k1.secKeyVerify(sec)
assertFalse(result, "testSecKeyVerifyNeg")
}
@Test
fun testPubKeyCreatePos() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val resultArr: ByteArray = Secp256k1.pubkeyCreate(sec)
val pubkeyString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6",
Hex.encode(pub).uppercase(),
pubkeyString,
"testPubKeyCreatePos"
)
}
@Test
fun createInvalidPublicKey() {
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyCreate(Hex.decode("0000000000000000000000000000000000000000000000000000000000000000".lowercase())) }
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyCreate(Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141".lowercase())) }
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyCreate(Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".lowercase())) }
fun testPubKeyCreateNeg() {
val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".lowercase())
assertFailsWith<Secp256k1Exception> {
Secp256k1.pubkeyCreate(sec)
}
}
@Test
fun compressPublicKey() {
fun testPubkeyCompress() {
val pub = Hex.decode("04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6")
val compressed = Secp256k1.pubKeyCompress(pub)
assertEquals("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D", Hex.encode(compressed).uppercase())
}
@Test
fun negatePublicKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub = Secp256k1.pubkeyCreate(priv)
fun testPubKeyNegatePos() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pubkey: ByteArray = Secp256k1.pubkeyCreate(sec)
val pubkeyString: String = Hex.encode(pubkey).uppercase()
assertEquals(
"04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6",
Hex.encode(pub).uppercase(),
pubkeyString,
"testPubKeyCreatePos"
)
assertEquals(
"02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D",
Hex.encode(Secp256k1.pubKeyCompress(pub)).uppercase()
)
val npub = Secp256k1.pubKeyNegate(pub)
val pubkey1: ByteArray = Secp256k1.pubKeyNegate(pubkey)
val pubkeyString1: String = Hex.encode(pubkey1).uppercase()
assertEquals(
"04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2DDEFC12B6B8E73968536514302E69ED1DDB24B999EFEE79C12D03AB17E79E1989",
Hex.encode(npub).uppercase(),
pubkeyString1,
"testPubKeyNegatePos"
)
assertEquals(
"03C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D",
Hex.encode(Secp256k1.pubKeyCompress(npub)).uppercase()
)
val nnpub = Secp256k1.pubKeyNegate(npub)
assertContentEquals(pub, nnpub)
}
@Test
fun parsePublicKey() {
val pub = Hex.decode("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D".lowercase())
val parsed1 = Secp256k1.pubkeyParse(pub)
fun testPubKeyParse() {
val pub: ByteArray = Hex.decode("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D".lowercase())
val resultArr: ByteArray = Secp256k1.pubkeyParse(pub)
val pubkeyString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6",
Hex.encode(parsed1).uppercase(),
)
val parsed2 = Secp256k1.pubkeyParse(parsed1)
assertContentEquals(parsed1, parsed2)
}
@Test
fun parseInvalidPublicKey() {
// Not a valid curve point.
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyParse(Hex.decode("02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".lowercase())) }
// Invalid first byte.
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyParse(Hex.decode("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6".lowercase())) }
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyParse(Hex.decode("03C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6".lowercase())) }
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyParse(Hex.decode("05C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6".lowercase())) }
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyParse(Hex.decode("01C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D".lowercase())) }
assertFailsWith<Secp256k1Exception> { Secp256k1.pubkeyParse(Hex.decode("04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D".lowercase())) }
}
@Test
fun combinePublicKeys() {
// Mixture of compressed and uncompressed public keys.
val pub1 = Hex.decode("041b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f70beaf8f588b541507fed6a642c5ab42dfdf8120a7f639de5122d47a69a8e8d1")
val pub2 = Hex.decode("044d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d07662a3eada2d0fe208b6d257ceb0f064284662e857f57b66b54c198bd310ded36d0")
val pub3 = Hex.decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")
val pub4 = Secp256k1.pubKeyCombine(arrayOf(pub1, pub2, pub3))
assertEquals(
"042C0B7CF95324A07D05398B240174DC0C2BE444D96B159AA6C7F7B1E668680991AE31A9C671A36543F46CEA8FCE6984608AA316AA0472A7EED08847440218CB2F",
Hex.encode(pub4).uppercase(),
pubkeyString,
"testPubKeyAdd"
)
}
@Test
fun createEcdsaSignature() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val sig = Secp256k1.sign(message, priv)
fun testPubKeyAdd() {
val pub1: ByteArray = Hex.decode("041b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f70beaf8f588b541507fed6a642c5ab42dfdf8120a7f639de5122d47a69a8e8d1".lowercase())
val pub2: ByteArray = Hex.decode("044d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d07662a3eada2d0fe208b6d257ceb0f064284662e857f57b66b54c198bd310ded36d0".lowercase())
val pub3: ByteArray = Secp256k1.pubKeyAdd(pub1, pub2)
val pubkeyString: String = Hex.encode(pub3).uppercase()
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
Hex.encode(sig).uppercase(),
"04531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE3379E92C265E71E481BA82A84675A47AC705A200FCD524E92D93B0E7386F26A5458",
pubkeyString,
"testPubKeyAdd"
)
}
@Test
fun normalizeEcdsaSignature() {
val normalizedDerSig = Hex.decode("30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".lowercase())
val (normalizedCompactSig1, wasNotNormalized1) = Secp256k1.signatureNormalize(normalizedDerSig)
assertFalse(wasNotNormalized1)
fun testSignPos() {
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val resultArr: ByteArray = Secp256k1.sign(data, sec)
val sigString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
Hex.encode(normalizedCompactSig1).uppercase(),
)
val notNormalizedDerSig = Hex.decode("30450220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A2022100E3990C925DEE3F780C50774AF120B062E0080D86D8C721C6E32F10DBACA57A58".lowercase())
val (normalizedCompactSig2, wasNotNormalized2) = Secp256k1.signatureNormalize(notNormalizedDerSig)
assertTrue(wasNotNormalized2)
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
Hex.encode(normalizedCompactSig2).uppercase(),
)
val normalizedCompactSig = Hex.decode("182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".lowercase())
val (normalizedCompactSig3, wasNotNormalized3) = Secp256k1.signatureNormalize(normalizedCompactSig)
assertFalse(wasNotNormalized3)
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
Hex.encode(normalizedCompactSig3).uppercase(),
)
val notNormalizedCompactSig = Hex.decode("182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A2E3990C925DEE3F780C50774AF120B062E0080D86D8C721C6E32F10DBACA57A58".lowercase())
val (normalizedCompactSig4, wasNotNormalized4) = Secp256k1.signatureNormalize(notNormalizedCompactSig)
assertTrue(wasNotNormalized4)
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
Hex.encode(normalizedCompactSig4).uppercase(),
sigString,
"testSignPos"
)
}
@Test
fun failToCreateEcdsaSignature() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val priv = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".lowercase())
fun testSignatureNormalize() {
val data: ByteArray = Hex.decode("30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".lowercase())
val (resultArr, isHighS) = Secp256k1.signatureNormalize(data)
val sigString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
sigString,
"testSignPos"
)
assertFalse(isHighS, "isHighS")
}
@Test
fun testSignNeg() {
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".lowercase())
assertFailsWith<Secp256k1Exception> {
Secp256k1.sign(message, priv)
Secp256k1.sign(data, sec)
}
}
@Test
fun createCompactEcdsaSignature() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val sig = Secp256k1.sign(message, priv)
fun testSignCompactPos() {
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val resultArr: ByteArray = Secp256k1.sign(data, sec)
val sigString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
Hex.encode(sig).uppercase(),
sigString,
"testSignCompactPos"
)
}
@Test
fun verifyValidEcdsaSignatures() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val sig = Hex.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".lowercase())
val pub = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
assertTrue(Secp256k1.verify(sig, message, pub))
val sigCompact = Hex.decode("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".lowercase())
assertTrue(Secp256k1.verify(sigCompact, message, pub))
}
@Test
fun verifyInvalidEcdsaSignatures() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91".lowercase()) //sha256hash of "testing"
val sig = Hex.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".lowercase())
val pub = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
assertFalse(Secp256k1.verify(sig, message, pub))
}
@Test
fun negatePrivateKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val npriv = Secp256k1.privKeyNegate(priv)
fun testPrivKeyTweakNegate() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val sec1: ByteArray = Secp256k1.privKeyNegate(sec)
assertEquals(
"981A9A7DD677A622518DA068D66D5F824E5F22F084B8A0E2F195B5662F300C11",
Hex.encode(npriv).uppercase(),
Hex.encode(sec1).uppercase(),
"testPrivKeyNegate"
)
val nnpriv: ByteArray = Secp256k1.privKeyNegate(npriv)
assertContentEquals(priv, nnpriv)
val sec2: ByteArray = Secp256k1.privKeyNegate(sec1)
assertTrue(sec.contentEquals(sec2))
}
@Test
fun addTweakToPrivateKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val tweak = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase())
val tweakedPriv = Secp256k1.privKeyTweakAdd(priv, tweak)
fun testPrivKeyTweakAdd_1() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase()) //sha256hash of "tweak"
val resultArr: ByteArray = Secp256k1.privKeyTweakAdd(sec, data)
val sigString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"A168571E189E6F9A7E2D657A4B53AE99B909F7E712D1C23CED28093CD57C88F3",
Hex.encode(tweakedPriv).uppercase(),
sigString,
"testPrivKeyAdd_1"
)
}
@Test
fun multiplyPrivateKeyWithTweak() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val tweak = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase())
val tweakedPriv = Secp256k1.privKeyTweakMul(priv, tweak)
fun testPrivKeyTweakMul_1() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase()) //sha256hash of "tweak"
val resultArr: ByteArray = Secp256k1.privKeyTweakMul(sec, data)
val sigString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"97F8184235F101550F3C71C927507651BD3F1CDB4A5A33B8986ACF0DEE20FFFC",
Hex.encode(tweakedPriv).uppercase(),
sigString,
"testPrivKeyMul_1"
)
}
@Test
fun addTweakToPublicKey() {
val pub = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val tweak = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase())
val tweakedPub = Secp256k1.pubKeyTweakAdd(pub, tweak)
fun testPrivKeyTweakAdd_2() {
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase()) //sha256hash of "tweak"
val resultArr: ByteArray = Secp256k1.pubKeyTweakAdd(pub, data)
val sigString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"0411C6790F4B663CCE607BAAE08C43557EDC1A4D11D88DFCB3D841D0C6A941AF525A268E2A863C148555C48FB5FBA368E88718A46E205FABC3DBA2CCFFAB0796EF",
Hex.encode(tweakedPub).uppercase(),
sigString,
"testPrivKeyAdd_2"
)
}
@Test
fun multiplyPublicKeyWithTweak() {
val pub = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val tweak = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase())
val tweakedPub = Secp256k1.pubKeyTweakMul(pub, tweak)
fun testPrivKeyTweakMul_2() {
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase()) //sha256hash of "tweak"
val resultArr: ByteArray = Secp256k1.pubKeyTweakMul(pub, data)
val sigString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"04E0FE6FE55EBCA626B98A807F6CAF654139E14E5E3698F01A9A658E21DC1D2791EC060D4F412A794D5370F672BC94B722640B5F76914151CFCA6E712CA48CC589",
Hex.encode(tweakedPub).uppercase(),
sigString,
"testPrivKeyMul_2"
)
}
@Test
fun createEcdhSecret() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val secret = Secp256k1.ecdh(priv, pub)
fun testCreateECDHSecret() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val resultArr: ByteArray = Secp256k1.ecdh(sec, pub)
val ecdhString: String = Hex.encode(resultArr).uppercase()
assertEquals(
"2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043",
Hex.encode(secret).uppercase(),
ecdhString,
"testCreateECDHSecret"
)
}
@Test
fun createSymmetricEcdhSecret() {
val priv1 = Hex.decode("3580a881ac24eb00530a51235c42bcb65424ba121e2e7d910a70fa531a578d21")
val pub1 = Secp256k1.pubkeyCreate(priv1)
val priv2 = Hex.decode("f6a353f7a5de654501c3495acde7450293f74d09086c2b7c9a4e524248d0daac")
val pub2 = Secp256k1.pubkeyCreate(priv2)
val secret1 = Secp256k1.ecdh(priv1, pub2)
val secret2 = Secp256k1.ecdh(priv2, pub1)
assertContentEquals(secret1, secret2)
fun testEcdsaRecover() {
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub: ByteArray = Secp256k1.pubkeyCreate(sec)
val sig: ByteArray = Secp256k1.sign(data, sec)
val pub0: ByteArray = Secp256k1.ecdsaRecover(sig, data, 0)
val pub1: ByteArray = Secp256k1.ecdsaRecover(sig, data, 1)
assertTrue(pub.contentEquals(pub0) || pub.contentEquals(pub1), "testEcdsaRecover")
}
@Test
fun recoverPublicKeyFromEcdsaSignature() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub = Secp256k1.pubkeyCreate(priv)
val sig = Secp256k1.sign(message, priv)
val pub0 = Secp256k1.ecdsaRecover(sig, message, 0)
val pub1 = Secp256k1.ecdsaRecover(sig, message, 1)
assertTrue(pub.contentEquals(pub0) || pub.contentEquals(pub1))
}
@Test
fun convertCompactEcdsaSignatureToDer() {
val compact = Hex.decode("182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".lowercase()) //sha256hash of "testing"
val der = Secp256k1.compact2der(compact)
fun testCompactToDER() {
val sig: ByteArray = Hex.decode("182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".lowercase()) //sha256hash of "testing"
val der: ByteArray = Secp256k1.compact2der(sig)
assertEquals(
"30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
Hex.encode(der).uppercase(),
@ -296,273 +268,22 @@ 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))
}
fun testFormatConversion() {
val random = Random.Default
@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 testMusig2GenerateNonce() {
val privkey = Hex.decode("0000000000000000000000000000000000000000000000000000000000000003")
val pubkey = Hex.decode("02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9")
val sessionId = Hex.decode("0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F")
val nonce = Secp256k1.musigNonceGen(sessionId, null, pubkey, null, null, null)
val pubnonce = Hex.encode(nonce.copyOfRange(132, 132 + 66)).uppercase()
assertEquals("02C96E7CB1E8AA5DAC64D872947914198F607D90ECDE5200DE52978AD5DED63C000299EC5117C2D29EDEE8A2092587C3909BE694D5CFF0667D6C02EA4059F7CD9786", pubnonce)
assertNotEquals(nonce, Secp256k1.musigNonceGen(sessionId, privkey, pubkey, null, null, null))
assertNotEquals(nonce, Secp256k1.musigNonceGen(sessionId, null, pubkey, sessionId, null, null))
}
@Test
fun testMusig2AggregateNonce() {
val nonces = listOf(
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
// The following nonces are invalid.
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
).map { Hex.decode(it) }
val agg1 = Secp256k1.musigNonceAgg(arrayOf(nonces[0], nonces[1]))
assertEquals("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8", Hex.encode(agg1).uppercase())
val agg2 = Secp256k1.musigNonceAgg(arrayOf(nonces[2], nonces[3]))
assertEquals("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000", Hex.encode(agg2).uppercase())
assertFails {
Secp256k1.musigNonceAgg(arrayOf(nonces[0], nonces[4]))
}
assertFails {
Secp256k1.musigNonceAgg(arrayOf(nonces[5], nonces[1]))
}
assertFails {
Secp256k1.musigNonceAgg(arrayOf(nonces[6], nonces[1]))
}
}
@Test
fun testMusig2AggregatePubkey() {
val pubkeys = listOf(
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"020000000000000000000000000000000000000000000000000000000000000005",
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
"04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
).map { Hex.decode(it) }
val agg1 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), null)
assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg1).uppercase())
// We provide an empty cache, which will be filled when aggregating public keys.
val keyaggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
val agg2 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), keyaggCache)
assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg2).uppercase())
assertTrue(keyaggCache.count { it.toInt() != 0 } > 100) // the cache has been filled with key aggregation data
// We can reuse the key aggregation cache to speed up computation.
val agg3 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), keyaggCache)
assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg3).uppercase())
val agg4 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[2], pubkeys[1], pubkeys[0]), null)
assertEquals("6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B", Hex.encode(agg4).uppercase())
val agg5 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[0]), null)
assertEquals("B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935", Hex.encode(agg5).uppercase())
val agg6 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), null)
assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg6).uppercase())
// If we provide the key aggregation cache for a different session, it is ignored and overwritten.
val agg7 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), keyaggCache)
assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg7).uppercase())
// If we provide random data in the key aggregation cache, it is ignored and overwritten.
val agg8 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), Random.nextBytes(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE))
assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg8).uppercase())
}
@Test
fun testMusig2TweakPubkeys() {
val pubkeys = listOf(
"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"02531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337"
).map { Hex.decode(it) }.toTypedArray()
val cache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
val agg1 = Secp256k1.musigPubkeyAgg(pubkeys, cache)
assertEquals("b6d830642403fc82511aca5ff98a5e76fcef0f89bffc1aadbe78ee74cd5a5716", Hex.encode(agg1))
val agg2 = Secp256k1.musigPubkeyTweakAdd(cache, Hex.decode("7468697320636f756c64206265206120424950333220747765616b2e2e2e2e00"))
assertEquals("04791e4f22a21f19bd9798eceab92ad2ccc18f2d6660e91ae4c0709aaebf1aa9023701f468b0eddf8973495a5327f2169d9c6a50eb6a0f87c0fbee90a4067eb230", Hex.encode(agg2))
val agg3 = Secp256k1.musigPubkeyXonlyTweakAdd(cache, Hex.decode("7468697320636f756c64206265206120746170726f6f7420747765616b2e2e00"))
assertEquals("04537a081a8d32ff700ca86aaa77a423e9b8d1480938076b645c68ee39d263c93948026928799b2d942cb5851db397015b26b1759de1b9ab2c691ced64a2eef836", Hex.encode(agg3))
}
@Test
fun testMusig2SigningSession() {
val privkeys = listOf(randomBytes(32), randomBytes(32))
val sessionId = randomBytes(32)
val msg32 = randomBytes(32)
val pubkeys = privkeys.map { Secp256k1.pubkeyCreate(it) }
val nonces = pubkeys.map { Secp256k1.musigNonceGen(sessionId, null, it, null, null, null) }
val testData = run {
val builder = StringBuilder()
builder.append("private keys\n")
privkeys.forEach { builder.append(Hex.encode(it)).append("\n") }
builder.append("sessionId ${Hex.encode(sessionId)}\n")
builder.append("msg32 ${Hex.encode(msg32)}\n")
builder.append("nonces\n")
nonces.forEach { builder.append(Hex.encode(it)).append("\n") }
builder.toString()
}
val secnonces = nonces.map { it.copyOfRange(0, 132) }
val pubnonces = nonces.map { it.copyOfRange(132, 132 + 66) }
val aggnonce = Secp256k1.musigNonceAgg(pubnonces.toTypedArray())
val keyaggCaches = (0 until 2).map { ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) }
val aggpubkey = Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[0])
assertContentEquals(aggpubkey, Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[1]), testData)
assertContentEquals(keyaggCaches[0], keyaggCaches[1], testData)
val sessions = (0 until 2).map { Secp256k1.musigNonceProcess(aggnonce, msg32, keyaggCaches[it]) }
val psigs = (0 until 2).map {
val psig = Secp256k1.musigPartialSign(secnonces[it], privkeys[it], keyaggCaches[it], sessions[it])
assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it]), testData)
assertEquals(0, Secp256k1.musigPartialSigVerify(Random.nextBytes(32), pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it]), testData)
psig
fun randomBytes(length: Int): ByteArray {
val buffer = ByteArray(length)
random.nextBytes(buffer)
return buffer
}
// signing fails if the secret nonce does not match the private key's public key
assertFails(testData) {
Secp256k1.musigPartialSign(secnonces[1], privkeys[0], keyaggCaches[0], sessions[0])
}
assertFails(testData) {
Secp256k1.musigPartialSign(secnonces[0], privkeys[1], keyaggCaches[1], sessions[1])
}
val sig = Secp256k1.musigPartialSigAgg(sessions[0], psigs.toTypedArray())
assertContentEquals(sig, Secp256k1.musigPartialSigAgg(sessions[1], psigs.toTypedArray()), testData)
assertTrue(Secp256k1.verifySchnorr(sig, msg32, aggpubkey), testData)
val invalidSig1 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(psigs[0], psigs[0]))
assertFalse(Secp256k1.verifySchnorr(invalidSig1, msg32, aggpubkey), testData)
val invalidSig2 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(Random.nextBytes(32), Random.nextBytes(32)))
assertFalse(Secp256k1.verifySchnorr(invalidSig2, msg32, aggpubkey), testData)
}
@Test
fun testInvalidArguments() {
assertFails {
Secp256k1.pubkeyCreate(ByteArray(32))
}
assertFails {
Secp256k1.pubkeyCreate(Hex.decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
}
assertFails {
Secp256k1.pubkeyParse(ByteArray(33))
}
assertFails {
Secp256k1.pubkeyParse(Hex.decode("03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
}
assertFails {
Secp256k1.pubKeyCombine(arrayOf())
}
assertFails {
Secp256k1.pubKeyCombine(arrayOf(ByteArray(0)))
}
assertFails {
Secp256k1.signSchnorr(ByteArray(0), Hex.decode("0101010101010101010101010101010101010101010101010101010101010101"), null)
}
assertFails {
Secp256k1.ecdsaRecover(
Hex.decode("01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"),
Hex.decode("0202020202020202020202020202020202020202020202020202020202020202"),
-1
)
}
}
@Test
fun fuzzMusig2SigningSession() {
repeat(1000) {
testMusig2SigningSession()
}
}
@Test
fun fuzzEcdsaSignVerify() {
repeat(200) {
val priv = randomBytes(32)
assertTrue(Secp256k1.secKeyVerify(priv))
val pub = Secp256k1.pubkeyCreate(priv)
val message = randomBytes(32)
val sig = Secp256k1.sign(message, priv)
assertTrue(Secp256k1.verify(sig, message, pub))
val data = randomBytes(32)
val sig = Secp256k1.sign(data, priv)
val der = Secp256k1.compact2der(sig)
assertTrue(Secp256k1.verify(der, message, pub))
Secp256k1.verify(der, data, pub)
}
}
}

View File

@ -1,52 +0,0 @@
{
"pubkeys": [
"023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66",
"02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
"03935f972da013f80ae011890fa89b67a27b7be6ccb24d3274d18b2d4067f261a9",
"03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
],
"threshold": 3,
"aggregate_public_key": "bd5561ef6dbff52d3f73b8cb0c065328988b71d3386d23890744a0dd6ad27c15",
"aggregate_signature": "2fd5bc8fa46b0097f48e0b5734ec557a17a72f8c5989bdf5f387f01b5edc149f02a340d5ca0348b54448aecc67c1bdd532e43f5295a5382e063fe7b4425259ac",
"session_id": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"tweak_cache": "40252e41157cd26adda0440789236d38d3718b982853060ccbb8733f2df5bf6def6155bdeefcc6b83fe690d621b793b08f5b1562a7bef628c4db33f2d0b3f3bf2f541aaa000000000000000000000000000000000000000000000000000000000000000000",
"signers": [
{
"partial_signature": "82b98b609479595b861db8704b1b1af5d34defb52fb75023a2fb53a8c66e8ba7",
"session": "5c11a803002fd5bc8fa46b0097f48e0b5734ec557a17a72f8c5989bdf5f387f01b5edc149fe6d54b152392da71ec234b35804aeb36c6829cb0a0201aa7a94ec586a1147f0c18901c3b3d604349539d6cd24a2c1e7a676a7bce95cb810dbc346133c117a7a60000000000000000000000000000000000000000000000000000000000000000",
"secnonce": "847d4625fe87e00f9562351a9b7de8fc2420caba09535db177fc4fbac5b69b84c8700ae143946a0fffff4083d6377ee19a6448a55241160fbc7c793aace02f289a2fec8f",
"pubnonce": "03203a0450540686854df68f6c1d15661772e4d05c4442ee1e437d86842779ef2202d03839fd99faf7a11ccc319a9adc965c5e094ca3728455059a4911ae96192fae",
"aggregate_share": "1cfa28492e84e945343f1167401cdce061202a59e47e050c0c2f7f0c56e8e148",
"public_share": "0493effba7e50d3885bb0c4665149abd4fd13622047412f1da4c0e3754ecb1a9183aaadfdf0f2f82e24641e6ed7a0f7ee22a4a8a47c6d2df66daad37a4880fffe2"
},
{
"partial_signature": "62bb46458d4307cc96280159873fac35285e65049196c0df03954da79e7924e5",
"session": "5c11a803002fd5bc8fa46b0097f48e0b5734ec557a17a72f8c5989bdf5f387f01b5edc149fe6d54b152392da71ec234b35804aeb36c6829cb0a0201aa7a94ec586a1147f0c69b1d2e667d72b4a220100d9229740f24d9891f55f41918a07831d382aeba5e80000000000000000000000000000000000000000000000000000000000000000",
"secnonce": "847d4625e6af707cb69026251afdfc2570a3fbdbbc7e72530354f0777fe2192c6aa8b23969172508dff48f7a21827935cce2ed019c570bb5552f9a3269a8ec34cfd23e2c",
"pubnonce": "035c8f36d2cf868b9ccab3221b3f5eca64d60469a50245d6edc3b4f4bfb4358892023e832dcc0b5b55562ef6f1536679a2e996827747d3b824ca335daf00d51ab788",
"aggregate_share": "dd82fcc1806f1a968228c794a7001c18d209871fb3441bae80fd8229f6a9b0dd",
"public_share": "048482e27b533879d4f3d68bdb2038bf9480d4ce4cc614d7133238e55179c65a175c684afb7f983e60139542b80f0f12815f3194082f07c93e1f87f3cd1b1c0d8b"
},
{
"partial_signature": "1d2e6f2fa846e78d2802f5029566f6a8f1e6c77f839fc7671f81a4f0ada0ea61",
"session": "5c11a803002fd5bc8fa46b0097f48e0b5734ec557a17a72f8c5989bdf5f387f01b5edc149fe6d54b152392da71ec234b35804aeb36c6829cb0a0201aa7a94ec586a1147f0c7c15772acc75c702d2cb960a98c793090de73e18e5fd5dbf08818afcab855fa10000000000000000000000000000000000000000000000000000000000000000",
"secnonce": "847d4625bd32503f81d016175829db4df8475660c77e28cf6dc7bc8b2f6e3fe6f67282b1cf81dcf2dabd867053461cf602f3e3345f42119066f4c493b85a0744ae7beb08",
"pubnonce": "028b80bb46028338d41101deacd7910e09ba148f75d2c01e9f8f767fa9cdcbbc7e02b0cec1ba331750a22bf3bb8d1b724bd2874f7f0c19c70227f64c463c8c6bcab2",
"aggregate_share": "5fe629d5f34fdb3ea2f6e545fc3d2cf1f5ce23a504b144e6ebe928793cc85cb4",
"public_share": "04d71784b58d8958141f8f405d56026f214a736e73f9c1f70776fc2e49f4e90fc0a9396bcc7471a83caf4076a18cb6ab4264aa37174ca19e142259aa5f6bb7fea3"
},
{
"secnonce": "847d4625f2128d893c4b8b62818bbc972d158b0aa96b08dcacba149dd7517b7fc7ddb89b70234941338c242dbca6e27ebf337ae458381ef83bea4ba2baab8df3d7f6b773",
"pubnonce": "030b4d942e88b7674819f3020c290db1162beda60bae05964bb344378166ec61a20221e3e76bfa50c22b98832d451d51e3b7cdef420a2b06e9a29373449aa77aa46c",
"aggregate_share": "71181e5b46742333f12672d85d0e1472770a082f0a62d3d204c9e191fb45ef91",
"public_share": "04ec0fb2b4c1ac2d9b761f32cb2972e6d6fb74ed4195d872aeaaf4306bb64eb465580d03102849363ec49c3d1eecdd239337d0a66cdfc4d74c29c824c0f941832a"
},
{
"secnonce": "847d4625ee1f0f41a485d2399b024b05d9a9b7cbb846cf107dcbfa125a136448cbd20441a5bbea0b8908781249bbd7a5f6c429e1678338d8a2f5a9095a85fd541cb49884",
"pubnonce": "034a67a3dbe320486110fac55f4e7ef4f5c5216766b8e4d635c6f1119a5c5e75a3026ada48e6491d2c9890f65fd3f9d675f644df9224b5beb5fc41b2934bc28d9bcc",
"aggregate_share": "15bc5e3eeb4ec318a718b3015b78e8496cc5ede81c05727936ade625532dce55",
"public_share": "04b3ad3909e919f1a27faff7a3aec8f04a9ca54a065d9774ae37c3b9903ad4a19b71f11e148b549ef168465d065279f773175b254f64573e7ce30f4aba0954be19"
}
]
}

View File

@ -1,5 +0,0 @@
{
"aggregate_public_key": "bd5561ef6dbff52d3f73b8cb0c065328988b71d3386d23890744a0dd6ad27c15",
"tweak_cache": "40252e41157cd26adda0440789236d38d3718b982853060ccbb8733f2df5bf6def6155bdeefcc6b83fe690d621b793b08f5b1562a7bef628c4db33f2d0b3f3bf2f541aaa000000000000000000000000000000000000000000000000000000000000000000",
"ec_tweak_add": "0444536a3cc348adb38660f06de15a6168c06d11a0ad00ca6ea34ff58a8300e2cc086d50550ee52f1d0334e31dfc67fa55201541078d89dcf79bf0c46a53e3af9b"
}

View File

@ -1,26 +0,0 @@
{
"aggregate_public_key": "bd5561ef6dbff52d3f73b8cb0c065328988b71d3386d23890744a0dd6ad27c15",
"tweak_cache": "40252e41157cd26adda0440789236d38d3718b982853060ccbb8733f2df5bf6def6155bdeefcc6b83fe690d621b793b08f5b1562a7bef628c4db33f2d0b3f3bf2f541aaa000000000000000000000000000000000000000000000000000000000000000000",
"expected": [
{
"aggregate_share": "1cfa28492e84e945343f1167401cdce061202a59e47e050c0c2f7f0c56e8e148",
"public_share": "0493effba7e50d3885bb0c4665149abd4fd13622047412f1da4c0e3754ecb1a9183aaadfdf0f2f82e24641e6ed7a0f7ee22a4a8a47c6d2df66daad37a4880fffe2"
},
{
"aggregate_share": "dd82fcc1806f1a968228c794a7001c18d209871fb3441bae80fd8229f6a9b0dd",
"public_share": "048482e27b533879d4f3d68bdb2038bf9480d4ce4cc614d7133238e55179c65a175c684afb7f983e60139542b80f0f12815f3194082f07c93e1f87f3cd1b1c0d8b"
},
{
"aggregate_share": "5fe629d5f34fdb3ea2f6e545fc3d2cf1f5ce23a504b144e6ebe928793cc85cb4",
"public_share": "04d71784b58d8958141f8f405d56026f214a736e73f9c1f70776fc2e49f4e90fc0a9396bcc7471a83caf4076a18cb6ab4264aa37174ca19e142259aa5f6bb7fea3"
},
{
"aggregate_share": "71181e5b46742333f12672d85d0e1472770a082f0a62d3d204c9e191fb45ef91",
"public_share": "04ec0fb2b4c1ac2d9b761f32cb2972e6d6fb74ed4195d872aeaaf4306bb64eb465580d03102849363ec49c3d1eecdd239337d0a66cdfc4d74c29c824c0f941832a"
},
{
"aggregate_share": "15bc5e3eeb4ec318a718b3015b78e8496cc5ede81c05727936ade625532dce55",
"public_share": "04b3ad3909e919f1a27faff7a3aec8f04a9ca54a065d9774ae37c3b9903ad4a19b71f11e148b549ef168465d065279f773175b254f64573e7ce30f4aba0954be19"
}
]
}

View File

@ -1,105 +0,0 @@
{
"pubkeys": [
"023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66",
"02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
"03935f972da013f80ae011890fa89b67a27b7be6ccb24d3274d18b2d4067f261a9",
"03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
],
"valid_signers_share_gen_test_case": {
"key_indices": [0, 1, 2, 3, 4],
"threshold": 3,
"signers": [
{
"seed": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"expected": {
"frost_share": [
"9a04e37bc40df0e1c3e05b82e7a7af6b7cdaadf337ec3eaa2b50bb9ffdb2da99",
"8d7b24fa2421a9157ce01b7a900fe4b06dbb922def5d0f8a6b6f420e94310d27",
"8cda987d0a9817ecb544f1ffcd7912006c6406bb95c9a9a45b70d641f6a0300c",
"bf0eaac669eac6ac43d094bb2e07e4fa7fd4b1d317188c690aad7ea211b49bdb",
"00c266074c34720f6d9a8511e4ec82bed44e104f93f20d9bbfbff8e2edf44400"
],
"vss_commitment": [
"04bc2f60d5a7494d506e6517c49db2104b05e087536ccb1cb2730282f469782bb93e2c0029d733beeea75120e831ed71255adde4ddbd0be049419572502d7b73b9",
"04ced2029d64827253175b5382cb327123fd2cdcdb5b2092e66020e9b6ece639f675029e36604347735eef9bf64137474b14d92d2996e67f5721705ee574c916a1",
"044f34156e0a6d49c96298a845fd07a122490dee82b80f090b3214162ed2b030c88a2cec6cac580d63d770f1d2933f21e58ec8d4b94ca1939e590add1616f6ea38"
],
"pok64": "8f63ac15582fd77b4c50eef4ca7f5810e9d31ff86a5e7c0c14483edc076290bcfdb481e5c41b24419c65ac7525560a8d3bbbcc303c8c232d63650252e7aff576"
}
},
{
"seed": "F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0",
"expected": {
"frost_share": [
"7adaa6dbd9df3f0944a516d13b6d7a826a956d05412a9f656b26b36e6507ff0c",
"69cedd07db8a28a4cfd70fcd672b573b842675a9a0222239173c2dcc2f300dd1",
"ca1df886e57b1ede4501a09b2c8e433f4503bdf9627784369650fd76a7ada20c",
"f53e5fb436fae00581f5077cb4736af110d5fe1ceb9c4047852adfea6fa2e8b0",
"8addd4121d313a0458b3649466f27dafadc5cb6dba441f7e1e5c4f2fb62139c0"
],
"vss_commitment": [
"0498fce8a40dd9fc23fd87cc4fecd572bd87a6d961576ba7eef3e140f61655c9ee276942ef761cf0cd2845d1c6f149c73e411c2869ac8e4faeedcc6235d0954460",
"04581308e2c7b7367f5f711a95c6f72a99b53f138cf8eafd6c0119ea038e18b51ec8e1bab854e22c42f56e0ddf6922bcac4064254700194a5a9abcdb619e96e701",
"04ebb8b0c4fcecb70d91b9e65ea897ed8930f6d7b41cdcd150718ebbcef40b50f7fb9817fd425b598e09f3133a9100d513e9ba97bcb26b8ef371ada9669a6ed11f"
],
"pok64": "b2d68140014812cfa83116100deba8a6152a20a243ea73e3e4edf7f25031805f25f3dd78e6d921de1bcebc0d9a57069af905d3f1d613896f6ef69820c601e651"
}
},
{
"seed": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"expected": {
"frost_share": [
"7bde839ba686bdadce0dd854f1f71455c2dd9257b2bfd1995a92e7601dc8ebb8",
"15b62e7af6dfbc1035197fa1439b0b93639798e52a52efb964ff8eb118f0218e",
"6a4179474660abdda21b9cb0c7f27f487b18dd96b7a78f24b2b3b2e7d53504f6",
"97c53a07bd8b7a27a1ba83e3be7b913d55d3112a9ece472c0e3ab2f3e18e34df",
"f5bb1c36ec28f1a2845dcfb10af91772609f401d5ab4357a2a23e4c4d4e1be0d"
],
"vss_commitment": [
"046b17a9bca13b4cec2ae589973ab0471915fd77c93374d7d5dfcfacac9da7132bf2d07f55f98caf84927c97fe2946174d73609afe7d9aba0364b0abe2d174d544",
"04abaab1242f669205aa0eb26a876a89cbe80c6c7364b1fba0d095d8181767b96d3d44e5014c2d5a82cc9cf7b9e5ae269e5bc22ffe0212ef1a0b7a2379778be500",
"0415d97c46268a919acbfadcdc0a23fb463d94473787365a6bdd8f5bce95d6d83156448f2582bddcbdbb9bfc58cde1db47e5fb6350530bfb1102b7d0d44a70b5c0"
],
"pok64": "5cca5711ca05df39c82fe518e4afcd2871654bbfe6fed31b97101bfc27e1d0083d1cbc2cb3d3fa7a972bd84c7c025f71042a3a0aec27311732ae8b679bdebda3"
}
},
{
"seed": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"expected": {
"frost_share": [
"28b9d1730cd16190c0e9903144bc93f1ea5febf0046a290e00887540c7f726cf",
"d423aafb512cb983b00bdca3e8b398d2f197db8339889b4d0934852f3a0be1cb",
"3d71eab243f39cb2a83ccf78cb611f24544d01dc53a14616cfcf7044f4cfd8c3",
"78b75d47b540a9f29937e7a9187abc31286e7fcafb112c25829ddec7ae73f2d8",
"0a917790664eb85b54b2527449fe445c93a9c384b31ccc8b9ade6638ff55dd96"
],
"vss_commitment": [
"04919b0ecff3c10aebec20d55f6eff6736df2cb9b0e549d633ea1711eea4def643c00c89025bd567b79e7fd808c821bca9e6516542ebd689a06f97f52bb8937b7c",
"041583c3a25ebab0fbddbc9c9c491f889ff1df5991b0963c3c6363c6040eb505f2a9e279987f70d30415ddae2bcace1ccf2f96f1d220016c526a363aebed6c02a8",
"04e53ec77bf28672cb36a527fd5ac2ec90b88ab99dfd54c734b4837ea84759332ff7259e9044af6a9847c9432f023a5c7039f95313d564572a326b0e34b3e2299d"
],
"pok64": "ff6bf4b90faf7a66dc54bfa2cd047be44e1c42622410d6e3a23697d30212d3bde11fc7fb4e61b2bc1e4310ad86089721ba912191d7ca561b9c5dcbcb604116fc"
}
},
{
"seed": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB",
"expected": {
"frost_share": [
"638248e2dd3f9a1b9cc2368ce6540aa841d04ae712ce6ccc9a417076aeda779e",
"fc5f214938b6d348504c400783763bc40055c4ad1e7a9f5c0fc2bb8880b9150e",
"613a34d878e85be35e57e6816ee23942ea5e394a5fb88247f748eead74e22f65",
"ac4e7c9132c25867f06e6b13a39c7714982a5dfd7ba8748323900cf05a8f0712",
"89cf905e2f716d0707baa735baa28c096bc6c8561e8f83d11334102e7b4d3774"
],
"vss_commitment": [
"04f63b636f91fda4fa756c64cd34c186e4595a5b4a6ed6351ba0e99961f2a189001f6f708312ed0ac8c1e291d6064e236e265f2b1b561925a29a39c2d3ad9f866b",
"040269fedd5bdb0d7bed5dd50f9fec28415d51694441dc6c5b0b3e2fd577429c40832c6263e9d218f6ae8cc6a87f72d69d3dc0096a8779d7530fec4983ed6c95b1",
"04fa8cf2f9a25d2d5b52872dd27afd89dfff5bad33f3335726fc56b49989f64c041e765acd4719bad295620393b00ec3f1b5420f02bad4794998d5f26c435deee5"
],
"pok64": "6888a2ffd199e69806e2c067befc41bd58de0605407542163031e8d75dd1cf80b7fae9e12c8e618081cf79a84d2a3a293dd260234383e5ab587f4459c5963d15"
}
}
]
}
}

View File

@ -1,182 +0,0 @@
{
"pubkeys": [
"023590a94e768f8e1815c2f24b4d80a8e3149316c3518ce7b7ad338368d038ca66",
"02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
"03935f972da013f80ae011890fa89b67a27b7be6ccb24d3274d18b2d4067f261a9",
"03dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"04f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
],
"valid_share_gen_test_cases": [
{
"seed": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"key_indices": [0, 1, 2, 3, 4],
"threshold": 3,
"expected": {
"frost_share": [
"9a04e37bc40df0e1c3e05b82e7a7af6b7cdaadf337ec3eaa2b50bb9ffdb2da99",
"8d7b24fa2421a9157ce01b7a900fe4b06dbb922def5d0f8a6b6f420e94310d27",
"8cda987d0a9817ecb544f1ffcd7912006c6406bb95c9a9a45b70d641f6a0300c",
"bf0eaac669eac6ac43d094bb2e07e4fa7fd4b1d317188c690aad7ea211b49bdb",
"00c266074c34720f6d9a8511e4ec82bed44e104f93f20d9bbfbff8e2edf44400"
],
"vss_commitment": [
"04bc2f60d5a7494d506e6517c49db2104b05e087536ccb1cb2730282f469782bb93e2c0029d733beeea75120e831ed71255adde4ddbd0be049419572502d7b73b9",
"04ced2029d64827253175b5382cb327123fd2cdcdb5b2092e66020e9b6ece639f675029e36604347735eef9bf64137474b14d92d2996e67f5721705ee574c916a1",
"044f34156e0a6d49c96298a845fd07a122490dee82b80f090b3214162ed2b030c88a2cec6cac580d63d770f1d2933f21e58ec8d4b94ca1939e590add1616f6ea38"
],
"pok64": "8f63ac15582fd77b4c50eef4ca7f5810e9d31ff86a5e7c0c14483edc076290bcfdb481e5c41b24419c65ac7525560a8d3bbbcc303c8c232d63650252e7aff576"
}
},
{
"seed": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"key_indices": [4, 3, 2, 1, 0],
"threshold": 3,
"expected": {
"frost_share": [
"7f3d4b3c13fe574ce2b24412aff67f19cc3d198b40e6bb57865df4f519ef65fa",
"9a46587ac615fc8afd0bc3a234075fb190f20a951df57ce21c7c7f6331117ab3",
"9188f7516271f4e4d9c71837db17f3a4e9ee1f979ca0245405ed13f88d8d54f2",
"afea9adf149439f663bb787190f7503984e632823607433e7c0d86884ba3ac58",
"11492bef4d7f35e038a1270a3a1a0a55c8e72a313d6086fec4bbdbe0522b9575"
],
"vss_commitment": [
"04ba2dd42da281b300358ed004da8755cc5b93c2ea56c8b79d63492501f2a8e69e4beb0f7a7adb01f2edec1ec84e338db286319f5be9faa82c2ac9ccd976e459eb",
"04d0be30dbc1da6ed9c84e33bf976671b7fadd38ee2243ec1c86780456092b4b5504416548cfae342746b8cde08a3aa3323c338a125031fbb31693e4fa94d4c3d9",
"04146aa11a44a2d2e1c53eb91f274c2145ff9d69d7af27ec85d34fb0df318bc4c9ab75f2d473b03fb133b0d8c1166dd6116c934fa7ff1498df2e6a51650a32940a"
],
"pok64": "c5fc820d0d64d447eec6c5f5eade3c08f2a0dac2fa244be7558247989c71a14dd51bfe784983a84fefc76fd4d049434d88df02b5474c14304150b9dce304e842"
}
},
{
"seed": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"key_indices": [0, 0, 0],
"threshold": 2,
"expected": {
"frost_share": [
"6a2ca571b1b83fbe65bb589949538f2d4c6cada3aa914232b96160584642262b",
"6a2ca571b1b83fbe65bb589949538f2d4c6cada3aa914232b96160584642262b",
"6a2ca571b1b83fbe65bb589949538f2d4c6cada3aa914232b96160584642262b"
],
"vss_commitment": [
"04a17464b9a1c4e7fb1f22221ba552640d891fdaca04b6a4c9570be4bfb8fdc0c0ca13853d1ea1eccf8e8f81d79d1500dac44a53282f58d17468e358ff78bc7e14",
"04af3c0779299cd72cf5f5dbe919068f4d9d3b525a48665363fa8b1ca26d6908cf477c156e3603f6cfe3d6f394d2a9e9f55e86d7b90a2ad2f0ea62f6ac4bd1f4b2"
],
"pok64": "a006b9b996888d552ce756bf7f217228e487fe510473c08b025ff145ccae6c015265e4f3ca39d488068f385afe0766b636fcbd3a5ac4cdd96f90e704dcce58d7"
}
},
{
"seed": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"key_indices": [0, 0, 1, 1],
"threshold": 2,
"expected": {
"frost_share": [
"9b84df35de7d30724c5ef2593c40e349e086e43bcbc4534034e0312510e55e6d",
"9b84df35de7d30724c5ef2593c40e349e086e43bcbc4534034e0312510e55e6d",
"ade61b5f4aec435ac0c4ff7ef61280a9f00150f88068cd391be3ea2f50128199",
"ade61b5f4aec435ac0c4ff7ef61280a9f00150f88068cd391be3ea2f50128199"
],
"vss_commitment": [
"04840febfcb959d1425df55ae74b78dbd1f9641438fbc6d5e5fc8a315dfdca30106b8846c149a27b2896a312e598cd4b447b375a46b5787f01ecd0a8b642bf5017",
"04935008aeb03eef3dfee5b9c42d1575dd53c8ab8078cabf38d8bd1768e1ee1f92ffe6bb6997b085104a048cac6ba7d7fa6a57c41c639ebfde516ce6d9292dc84b"
],
"pok64": "9d3c1f08ff8215d65e25ea8678ac923d089f10d7be7a77d2fa2f802174094d62d0f713dd874570ad4bdcbfc17cf02464523387893d85c134f201fee7cdbe3b14"
}
}
],
"valid_signers_share_gen_test_case": {
"key_indices": [0, 1, 2, 3, 4],
"threshold": 3,
"signers": [
{
"seed": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"expected": {
"frost_share": [
"9a04e37bc40df0e1c3e05b82e7a7af6b7cdaadf337ec3eaa2b50bb9ffdb2da99",
"8d7b24fa2421a9157ce01b7a900fe4b06dbb922def5d0f8a6b6f420e94310d27",
"8cda987d0a9817ecb544f1ffcd7912006c6406bb95c9a9a45b70d641f6a0300c",
"bf0eaac669eac6ac43d094bb2e07e4fa7fd4b1d317188c690aad7ea211b49bdb",
"00c266074c34720f6d9a8511e4ec82bed44e104f93f20d9bbfbff8e2edf44400"
],
"vss_commitment": [
"04bc2f60d5a7494d506e6517c49db2104b05e087536ccb1cb2730282f469782bb93e2c0029d733beeea75120e831ed71255adde4ddbd0be049419572502d7b73b9",
"04ced2029d64827253175b5382cb327123fd2cdcdb5b2092e66020e9b6ece639f675029e36604347735eef9bf64137474b14d92d2996e67f5721705ee574c916a1",
"044f34156e0a6d49c96298a845fd07a122490dee82b80f090b3214162ed2b030c88a2cec6cac580d63d770f1d2933f21e58ec8d4b94ca1939e590add1616f6ea38"
],
"pok64": "8f63ac15582fd77b4c50eef4ca7f5810e9d31ff86a5e7c0c14483edc076290bcfdb481e5c41b24419c65ac7525560a8d3bbbcc303c8c232d63650252e7aff576"
}
},
{
"seed": "F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0",
"expected": {
"frost_share": [
"7adaa6dbd9df3f0944a516d13b6d7a826a956d05412a9f656b26b36e6507ff0c",
"69cedd07db8a28a4cfd70fcd672b573b842675a9a0222239173c2dcc2f300dd1",
"ca1df886e57b1ede4501a09b2c8e433f4503bdf9627784369650fd76a7ada20c",
"f53e5fb436fae00581f5077cb4736af110d5fe1ceb9c4047852adfea6fa2e8b0",
"8addd4121d313a0458b3649466f27dafadc5cb6dba441f7e1e5c4f2fb62139c0"
],
"vss_commitment": [
"0498fce8a40dd9fc23fd87cc4fecd572bd87a6d961576ba7eef3e140f61655c9ee276942ef761cf0cd2845d1c6f149c73e411c2869ac8e4faeedcc6235d0954460",
"04581308e2c7b7367f5f711a95c6f72a99b53f138cf8eafd6c0119ea038e18b51ec8e1bab854e22c42f56e0ddf6922bcac4064254700194a5a9abcdb619e96e701",
"04ebb8b0c4fcecb70d91b9e65ea897ed8930f6d7b41cdcd150718ebbcef40b50f7fb9817fd425b598e09f3133a9100d513e9ba97bcb26b8ef371ada9669a6ed11f"
],
"pok64": "b2d68140014812cfa83116100deba8a6152a20a243ea73e3e4edf7f25031805f25f3dd78e6d921de1bcebc0d9a57069af905d3f1d613896f6ef69820c601e651"
}
},
{
"seed": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"expected": {
"frost_share": [
"7bde839ba686bdadce0dd854f1f71455c2dd9257b2bfd1995a92e7601dc8ebb8",
"15b62e7af6dfbc1035197fa1439b0b93639798e52a52efb964ff8eb118f0218e",
"6a4179474660abdda21b9cb0c7f27f487b18dd96b7a78f24b2b3b2e7d53504f6",
"97c53a07bd8b7a27a1ba83e3be7b913d55d3112a9ece472c0e3ab2f3e18e34df",
"f5bb1c36ec28f1a2845dcfb10af91772609f401d5ab4357a2a23e4c4d4e1be0d"
],
"vss_commitment": [
"046b17a9bca13b4cec2ae589973ab0471915fd77c93374d7d5dfcfacac9da7132bf2d07f55f98caf84927c97fe2946174d73609afe7d9aba0364b0abe2d174d544",
"04abaab1242f669205aa0eb26a876a89cbe80c6c7364b1fba0d095d8181767b96d3d44e5014c2d5a82cc9cf7b9e5ae269e5bc22ffe0212ef1a0b7a2379778be500",
"0415d97c46268a919acbfadcdc0a23fb463d94473787365a6bdd8f5bce95d6d83156448f2582bddcbdbb9bfc58cde1db47e5fb6350530bfb1102b7d0d44a70b5c0"
],
"pok64": "5cca5711ca05df39c82fe518e4afcd2871654bbfe6fed31b97101bfc27e1d0083d1cbc2cb3d3fa7a972bd84c7c025f71042a3a0aec27311732ae8b679bdebda3"
}
},
{
"seed": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"expected": {
"frost_share": [
"28b9d1730cd16190c0e9903144bc93f1ea5febf0046a290e00887540c7f726cf",
"d423aafb512cb983b00bdca3e8b398d2f197db8339889b4d0934852f3a0be1cb",
"3d71eab243f39cb2a83ccf78cb611f24544d01dc53a14616cfcf7044f4cfd8c3",
"78b75d47b540a9f29937e7a9187abc31286e7fcafb112c25829ddec7ae73f2d8",
"0a917790664eb85b54b2527449fe445c93a9c384b31ccc8b9ade6638ff55dd96"
],
"vss_commitment": [
"04919b0ecff3c10aebec20d55f6eff6736df2cb9b0e549d633ea1711eea4def643c00c89025bd567b79e7fd808c821bca9e6516542ebd689a06f97f52bb8937b7c",
"041583c3a25ebab0fbddbc9c9c491f889ff1df5991b0963c3c6363c6040eb505f2a9e279987f70d30415ddae2bcace1ccf2f96f1d220016c526a363aebed6c02a8",
"04e53ec77bf28672cb36a527fd5ac2ec90b88ab99dfd54c734b4837ea84759332ff7259e9044af6a9847c9432f023a5c7039f95313d564572a326b0e34b3e2299d"
],
"pok64": "ff6bf4b90faf7a66dc54bfa2cd047be44e1c42622410d6e3a23697d30212d3bde11fc7fb4e61b2bc1e4310ad86089721ba912191d7ca561b9c5dcbcb604116fc"
}
},
{
"seed": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB",
"expected": {
"frost_share": [
"638248e2dd3f9a1b9cc2368ce6540aa841d04ae712ce6ccc9a417076aeda779e",
"fc5f214938b6d348504c400783763bc40055c4ad1e7a9f5c0fc2bb8880b9150e",
"613a34d878e85be35e57e6816ee23942ea5e394a5fb88247f748eead74e22f65",
"ac4e7c9132c25867f06e6b13a39c7714982a5dfd7ba8748323900cf05a8f0712",
"89cf905e2f716d0707baa735baa28c096bc6c8561e8f83d11334102e7b4d3774"
],
"vss_commitment": [
"04f63b636f91fda4fa756c64cd34c186e4595a5b4a6ed6351ba0e99961f2a189001f6f708312ed0ac8c1e291d6064e236e265f2b1b561925a29a39c2d3ad9f866b",
"040269fedd5bdb0d7bed5dd50f9fec28415d51694441dc6c5b0b3e2fd577429c40832c6263e9d218f6ae8cc6a87f72d69d3dc0096a8779d7530fec4983ed6c95b1",
"04fa8cf2f9a25d2d5b52872dd27afd89dfff5bad33f3335726fc56b49989f64c041e765acd4719bad295620393b00ec3f1b5420f02bad4794998d5f26c435deee5"
],
"pok64": "6888a2ffd199e69806e2c067befc41bd58de0605407542163031e8d75dd1cf80b7fae9e12c8e618081cf79a84d2a3a293dd260234383e5ab587f4459c5963d15"
}
}
]
}
}

View File

@ -1,144 +0,0 @@
{
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"020000000000000000000000000000000000000000000000000000000000000007"
],
"msgs": [
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"2626262626262626262626262626262626262626262626262626262626262626262626262626"
],
"valid_test_cases": [
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"key_indices": [0, 1, 2],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 0,
"expected": [
"03D96275257C2FCCBB6EEB77BDDF51D3C88C26EE1626C6CDA8999B9D34F4BA13A60309BE2BF883C6ABE907FA822D9CA166D51A3DCC28910C57528F6983FC378B7843",
"41EA65093F71D084785B20DC26A887CD941C9597860A21660CBDB9CC2113CAD3"
]
},
{
"rand": null,
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"key_indices": [1, 0, 2],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 1,
"expected": [
"028FBCCF5BB73A7B61B270BAD15C0F9475D577DD85C2157C9D38BEF1EC922B48770253BE3638C87369BC287E446B7F2C8CA5BEB9FFBD1EA082C62913982A65FC214D",
"AEAA31262637BFA88D5606679018A0FEEEC341F3107D1199857F6C81DE61B8DD"
]
},
{
"rand": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"aggothernonce": "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"key_indices": [1, 2, 0],
"tweaks": [],
"is_xonly": [],
"msg_index": 1,
"signer_index": 2,
"expected": [
"024FA8D774F0C8743FAA77AFB4D08EE5A013C2E8EEAD8A6F08A77DDD2D28266DB803050905E8C994477F3F2981861A2E3791EF558626E645FBF5AA131C5D6447C2C2",
"FEE28A56B8556B7632E42A84122C51A4861B1F2DEC7E81B632195E56A52E3E13"
],
"comment": "Message longer than 32 bytes"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046",
"key_indices": [0, 1, 2],
"tweaks": ["E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB"],
"is_xonly": [true],
"msg_index": 0,
"signer_index": 0,
"expected": [
"031E07C0D11A0134E55DB1FC16095ADCBD564236194374AA882BFB3C78273BF673039D0336E8CA6288C00BFC1F8B594563529C98661172B9BC1BE85C23A4CE1F616B",
"7B1246C5889E59CB0375FA395CC86AC42D5D7D59FD8EAB4FDF1DCAB2B2F006EA"
],
"comment": "Tweaked public key"
}
],
"error_test_cases": [
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"key_indices": [1, 0, 3],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 1,
"error": {
"type": "invalid_contribution",
"signer": 2,
"contrib": "pubkey"
},
"comment": "Signer 2 provided an invalid public key"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"key_indices": [1, 2],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 1,
"error": {
"type": "value",
"message": "The signer's pubkey must be included in the list of pubkeys."
},
"comment": "The signers pubkey is not in the list of pubkeys"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0437C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"key_indices": [1, 2, 0],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 2,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggothernonce"
},
"comment": "aggothernonce is invalid due wrong tag, 0x04, in the first half"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0000000000000000000000000000000000000000000000000000000000000000000287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"key_indices": [1, 2, 0],
"tweaks": [],
"is_xonly": [],
"msg_index": 0,
"signer_index": 2,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggothernonce"
},
"comment": "aggothernonce is invalid because first half corresponds to point at infinity"
},
{
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"key_indices": [1, 2, 0],
"tweaks": ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"],
"is_xonly": [false],
"msg_index": 0,
"signer_index": 2,
"error": {
"type": "value",
"message": "The tweak must be less than n."
},
"comment": "Tweak is invalid because it exceeds group size"
}
]
}

View File

@ -1,88 +0,0 @@
{
"pubkeys": [
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"020000000000000000000000000000000000000000000000000000000000000005",
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
"04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
],
"tweaks": [
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B"
],
"valid_test_cases": [
{
"key_indices": [0, 1, 2],
"expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C"
},
{
"key_indices": [2, 1, 0],
"expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B"
},
{
"key_indices": [0, 0, 0],
"expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935"
},
{
"key_indices": [0, 0, 1, 1],
"expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E"
}
],
"error_test_cases": [
{
"key_indices": [0, 3],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubkey"
},
"comment": "Invalid public key"
},
{
"key_indices": [0, 4],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubkey"
},
"comment": "Public key exceeds field size"
},
{
"key_indices": [5, 0],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubkey"
},
"comment": "First byte of public key is not 2 or 3"
},
{
"key_indices": [0, 1],
"tweak_indices": [0],
"is_xonly": [true],
"error": {
"type": "value",
"message": "The tweak must be less than n."
},
"comment": "Tweak is out of range"
},
{
"key_indices": [6],
"tweak_indices": [1],
"is_xonly": [false],
"error": {
"type": "value",
"message": "The result of tweaking cannot be infinity."
},
"comment": "Intermediate tweaking result is point at infinity"
}
]
}

View File

@ -1,18 +0,0 @@
{
"pubkeys": [
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8"
],
"sorted_pubkeys": [
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
]
}

View File

@ -1,51 +0,0 @@
{
"pnonces": [
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
],
"valid_test_cases": [
{
"pnonce_indices": [0, 1],
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8"
},
{
"pnonce_indices": [2, 3],
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000",
"comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes"
}
],
"error_test_cases": [
{
"pnonce_indices": [0, 4],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half"
},
{
"pnonce_indices": [5, 1],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate"
},
{
"pnonce_indices": [6, 1],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 0 is invalid because second half exceeds field size"
}
]
}

View File

@ -1,44 +0,0 @@
{
"test_cases": [
{
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "0101010101010101010101010101010101010101010101010101010101010101",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected_secnonce": "B114E502BEAA4E301DD08A50264172C84E41650E6CB726B410C0694D59EFFB6495B5CAF28D045B973D63E3C99A44B807BDE375FD6CB39E46DC4A511708D0E9D2024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"expected_pubnonce": "02F7BE7089E8376EB355272368766B17E88E7DB72047D05E56AA881EA52B3B35DF02C29C8046FDD0DED4C7E55869137200FBDBFE2EB654267B6D7013602CAED3115A"
},
{
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected_secnonce": "E862B068500320088138468D47E0E6F147E01B6024244AE45EAC40ACE5929B9F0789E051170B9E705D0B9EB49049A323BBBBB206D8E05C19F46C6228742AA7A9024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"expected_pubnonce": "023034FA5E2679F01EE66E12225882A7A48CC66719B1B9D3B6C4DBD743EFEDA2C503F3FD6F01EB3A8E9CB315D73F1F3D287CAFBB44AB321153C6287F407600205109"
},
{
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected_secnonce": "3221975ACBDEA6820EABF02A02B7F27D3A8EF68EE42787B88CBEFD9AA06AF3632EE85B1A61D8EF31126D4663A00DD96E9D1D4959E72D70FE5EBB6E7696EBA66F024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"expected_pubnonce": "02E5BBC21C69270F59BD634FCBFA281BE9D76601295345112C58954625BF23793A021307511C79F95D38ACACFF1B4DA98228B77E65AA216AD075E9673286EFB4EAF3"
},
{
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
"sk": null,
"pk": "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"aggpk": null,
"msg": null,
"extra_in": null,
"expected_secnonce": "89BDD787D0284E5E4D5FC572E49E316BAB7E21E3B1830DE37DFE80156FA41A6D0B17AE8D024C53679699A6FD7944D9C4A366B514BAF43088E0708B1023DD289702F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"expected_pubnonce": "02C96E7CB1E8AA5DAC64D872947914198F607D90ECDE5200DE52978AD5DED63C000299EC5117C2D29EDEE8A2092587C3909BE694D5CFF0667D6C02EA4059F7CD9786"
}
]
}

View File

@ -1,151 +0,0 @@
{
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05",
"03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C",
"02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581"
],
"pnonces": [
"036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E",
"03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00",
"02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6",
"031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9",
"023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A",
"02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00"
],
"tweaks": [
"B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C",
"A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC",
"75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8"
],
"psigs": [
"B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB",
"6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64",
"9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505",
"66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15",
"4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE",
"DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4",
"97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC",
"53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869",
"valid_test_cases": [
{
"aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B",
"nonce_indices": [
0,
1
],
"key_indices": [
0,
1
],
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [
0,
1
],
"expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E"
},
{
"aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20",
"nonce_indices": [
0,
2
],
"key_indices": [
0,
2
],
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [
2,
3
],
"expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9"
},
{
"aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D",
"nonce_indices": [
0,
3
],
"key_indices": [
0,
2
],
"tweak_indices": [
0
],
"is_xonly": [
false
],
"psig_indices": [
4,
5
],
"expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC"
},
{
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
"nonce_indices": [
0,
4
],
"key_indices": [
0,
3
],
"tweak_indices": [
0,
1,
2
],
"is_xonly": [
true,
false,
true
],
"psig_indices": [
6,
7
],
"expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E"
}
],
"error_test_cases": [
{
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
"nonce_indices": [
0,
4
],
"key_indices": [
0,
3
],
"tweak_indices": [
0,
1,
2
],
"is_xonly": [
true,
false,
true
],
"psig_indices": [
7,
8
],
"error": {
"type": "invalid_contribution",
"signer": 1
},
"comment": "Partial signature is invalid because it exceeds group size"
}
]
}

View File

@ -1,212 +0,0 @@
{
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661",
"020000000000000000000000000000000000000000000000000000000000000007"
],
"secnonces": [
"508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
],
"pnonces": [
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046",
"0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480"
],
"aggnonces": [
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009",
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
],
"msgs": [
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"",
"2626262626262626262626262626262626262626262626262626262626262626262626262626"
],
"valid_test_cases": [
{
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"expected": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB"
},
{
"key_indices": [1, 0, 2],
"nonce_indices": [1, 0, 2],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 1,
"expected": "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52"
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 2,
"expected": "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900"
},
{
"key_indices": [0, 1],
"nonce_indices": [0, 3],
"aggnonce_index": 1,
"msg_index": 0,
"signer_index": 0,
"expected": "AE386064B26105404798F75DE2EB9AF5EDA5387B064B83D049CB7C5E08879531",
"comment": "Both halves of aggregate nonce correspond to point at infinity"
},
{
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"aggnonce_index": 0,
"msg_index": 1,
"signer_index": 0,
"expected": "D7D63FFD644CCDA4E62BC2BC0B1D02DD32A1DC3030E155195810231D1037D82D",
"comment": "Empty message"
},
{
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"aggnonce_index": 0,
"msg_index": 2,
"signer_index": 0,
"expected": "E184351828DA5094A97C79CABDAAA0BFB87608C32E8829A4DF5340A6F243B78C",
"comment": "38-byte message"
}
],
"sign_error_test_cases": [
{
"key_indices": [1, 2],
"aggnonce_index": 0,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "value",
"message": "The signer's pubkey must be included in the list of pubkeys."
},
"comment": "The signers pubkey is not in the list of pubkeys. This test case is optional: it can be skipped by implementations that do not check that the signer's pubkey is included in the list of pubkeys."
},
{
"key_indices": [1, 0, 3],
"aggnonce_index": 0,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 2,
"contrib": "pubkey"
},
"comment": "Signer 2 provided an invalid public key"
},
{
"key_indices": [1, 2, 0],
"aggnonce_index": 2,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half"
},
{
"key_indices": [1, 2, 0],
"aggnonce_index": 3,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate"
},
{
"key_indices": [1, 2, 0],
"aggnonce_index": 4,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid because second half exceeds field size"
},
{
"key_indices": [0, 1, 2],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 1,
"error": {
"type": "value",
"message": "first secnonce value is out of range."
},
"comment": "Secnonce is invalid which may indicate nonce reuse"
}
],
"verify_fail_test_cases": [
{
"sig": "97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406",
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"msg_index": 0,
"signer_index": 0,
"comment": "Wrong signature (which is equal to the negation of valid signature)"
},
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"msg_index": 0,
"signer_index": 1,
"comment": "Wrong signer"
},
{
"sig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"msg_index": 0,
"signer_index": 0,
"comment": "Signature exceeds group size"
}
],
"verify_error_test_cases": [
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [0, 1, 2],
"nonce_indices": [4, 1, 2],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Invalid pubnonce"
},
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [3, 1, 2],
"nonce_indices": [0, 1, 2],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubkey"
},
"comment": "Invalid pubkey"
}
]
}

View File

@ -1,84 +0,0 @@
{
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
],
"secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"pnonces": [
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046"
],
"aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"tweaks": [
"E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB",
"AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455",
"F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0",
"1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"valid_test_cases": [
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0],
"is_xonly": [true],
"signer_index": 2,
"expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91",
"comment": "A single x-only tweak"
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0],
"is_xonly": [false],
"signer_index": 2,
"expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D",
"comment": "A single plain tweak"
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0, 1],
"is_xonly": [false, true],
"signer_index": 2,
"expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408",
"comment": "A plain tweak followed by an x-only tweak"
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0, 1, 2, 3],
"is_xonly": [false, false, true, true],
"signer_index": 2,
"expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435",
"comment": "Four tweaks: plain, plain, x-only, x-only."
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0, 1, 2, 3],
"is_xonly": [true, false, true, false],
"signer_index": 2,
"expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239",
"comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error."
}
],
"error_test_cases": [
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [4],
"is_xonly": [false],
"signer_index": 2,
"error": {
"type": "value",
"message": "The tweak must be less than n."
},
"comment": "Tweak is invalid because it exceeds group size"
}
]
}