64 Commits

Author SHA1 Message Date
Fabrice Drouin
d30e2a1b59 Set version to 0.15.0 (#105) 2024-03-04 19:29:52 +01:00
Fabrice Drouin
bae4744bcb Add macos build targets (#104)
* Add macos build targets

* Update snapshot publishing script
2024-03-04 17:58:20 +01:00
Fabrice Drouin
b2eb68bf83 Post release cleanup (#103)
* Set version to 0.15.0-SNAPSHOT

* Update publishing scripts
2024-02-14 18:28:19 +01:00
Fabrice Drouin
c582396b96 Set version to 0.14.0 (#102) 2024-02-14 13:51:52 +01:00
Fabrice Drouin
202b0c94b6 Add support for musig2 (#93)
* Use Jonas Nick's musig2 branch

* Reformat c code (no functional changes)

* Implement musig2

* Add documentation to musig2 functions (#97)

Usage of the Musig2 functions isn't intuitive at all, especially with
the key aggregation cache and session data. It's important to provide
accurate documentation to help users understand how to correctly produce
musig2 signatures.

We also change argument names to match Kotlin best practices instead of
using the same argument names as C functions.

* Add musig2 reference tests (no functional changes)

---------

Co-authored-by: Bastien Teinturier <31281497+t-bast@users.noreply.github.com>
2024-02-14 13:28:22 +01:00
Fabrice Drouin
780f97e46d Set version to 0.14.0-SNAPSHOT (#99) 2024-01-23 22:27:31 +01:00
Fabrice Drouin
da2cf93fcf Update README.md 2024-01-23 21:55:59 +01:00
Fabrice Drouin
f5e1655ff0 Set version to 0.13.0 (#98) 2024-01-23 16:46:55 +01:00
Fabrice Drouin
8ba5d4652e Use kotlin 1.9 (#92)
Use kotlin 1.9
2024-01-23 15:44:06 +01:00
Fabrice Drouin
e94e41b896 Update secp256k1 to version 0.4.1 (#96)
Use secp256k1 0.4.1
2024-01-02 11:16:36 +01:00
Fabrice Drouin
1a4c8b37cb Release 0.12.0 (#95) 2023-12-13 16:40:32 +01:00
Fabrice Drouin
f242b4ffe8 Check arguments passed to secp256k1 methods (#94)
* Check arguments passed to secp256k1 methods

Illegal arguments will trigger an internal callback that prints to stderr and calls abort.
We already check arguments in our JNI and kotlin native code but had missed 2 checks (recid in ecdsaRecover, empty arrays in pubkeyCombine).

* Implement the same "tweak" checks in the native code and JNI code

The native code was missing checks on the "tweak" size (which must be 32 bytes)
2023-12-13 13:42:14 +01:00
Fabrice Drouin
161da89ee1 Set version to 0.11.0 (#86) 2023-09-28 09:50:24 +02:00
Fabrice Drouin
3706a546a2 Use secp256k1 0.4.0 (#85) 2023-09-18 14:05:36 +02:00
Fabrice Drouin
ffcaaf1b64 Set version to 0.10.1 (#84) 2023-06-28 13:03:19 +02:00
Fabrice Drouin
6ef94df247 Use secp256k1 0.3.2 (#83) 2023-06-28 10:43:05 +02:00
Fabrice Drouin
5169073a92 Update README.md 2023-05-15 11:15:30 +02:00
Fabrice Drouin
317e086cba Set version to 0.10.0 (#82) 2023-05-11 18:29:50 +02:00
Fabrice Drouin
7c7aabba80 Upgrade to Kotlin 1.8 (#81)
* Upgrade to Kotlin 1.8

* Update snapshot deployment script

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

* Set version to 0.8.0-SNAPSHOT
2023-03-09 11:10:41 +01:00
gandlafbtc
840de25c5f remove kotlin from dependencies in readme example (#71)
The dependencies wouldn't resolve unless I removed kotlin(...)
2023-02-01 17:49:51 +01:00
Fabrice Drouin
cec3fb385f Set version to 0.7.1 (#70) 2023-01-04 15:33:55 +01:00
Fabrice Drouin
d59def1c79 Use secp256k1 0.2.0 (#67) 2022-12-13 19:33:28 +01:00
Fabrice Drouin
52d73951e6 Set version to 0.7.1-SNAPSHOT (#68) 2022-12-13 19:33:06 +01:00
Fabrice Drouin
08669500b6 Update README.md
Upgrade to a kotlin 1.6.21 badge
2022-09-22 10:29:21 +02:00
thunderbiscuit
68e77c70be Fix artifact names in README (#65) 2022-09-21 17:17:16 +02:00
Fabrice Drouin
5e59132e2a Set version to 0.7.0 (#64) 2022-09-21 16:25:50 +02:00
Fabrice Drouin
d4eba9fb96 Update to kotlin 1.6 (#63)
Use kotlin 1.6 (and gradle 7.5.1)
2022-09-21 16:00:19 +02:00
Fabrice Drouin
d01a067159 Update secp256k1 sources (#60)
* Set version to 0.6.5-SNAPSHOT

* Update secp256k1 sources

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

* Don't set up Android CI env on windows

It's useless as we just run Android CI checks on Android.
2021-11-07 20:37:50 +01:00
Bastien Teinturier
2ae6abcf93 Clarify public key encoding and enrich tests (#37)
Don't throw in `seckey_verify`: it's inconsistent to have this function throw
for some invalid inputs and return false for other invalid inputs.

Document public key compression and add tests.
2021-11-05 10:45:49 +01:00
Bastien Teinturier
f695e7453d Clean up and enrich tests (#35)
* Harmonize parameter names
* Document methods
* Replace pubKeyAdd with pubKeyCombine
* Clean-up tests
2021-10-26 17:16:36 +02:00
Fabrice Drouin
3389795a52 Update build instructions (#34) 2021-10-26 16:28:04 +02:00
Fabrice Drouin
6955c7416a Add option to skip building and testing Android libraries (#33)
* Add otpion to skip building and testing Android libraries

Add `skip.android=true` to the local.properties files at the project's root to skip Android builds.
2021-10-25 14:27:02 +02:00
sstone
e378bb04b1 Set version to 0.6.1-SNAPSHOT 2021-09-23 13:37:05 +02:00
Fabrice Drouin
32ef659f82 Update README 2021-09-22 17:48:15 +02:00
Fabrice Drouin
60801787f9 Release 0.6.0 (#30) 2021-09-22 15:51:58 +02:00
Fabrice Drouin
e7644b7ddb Upgrade to kotlin 1.5 (#27)
* Upgrade to kotlin 1.5.31

* Upgrade dokka gradle plugins to 1.5.30
2021-09-22 15:22:05 +02:00
Fabrice Drouin
3a394cdddc Update secp256k1 sources to be8d9c262f (#29) 2021-09-22 15:03:20 +02:00
Fabrice Drouin
4aa4dc2a50 Set version to 0.5.3-SNASPHOT (#25)
* Set version to 0.5.3-SNAPSHOT

* Use Android API level 27
2021-08-10 09:48:31 +02:00
Fabrice Drouin
7bc3aafd08 Set version to 0.5.2 (#22) 2021-06-28 11:30:05 +02:00
Fabrice Drouin
9c1afc715a Fix Android build (#23)
* Upgrade Android NDK

* Use Android emulator 30.7.3 on macos
2021-06-28 10:31:57 +02:00
Fabrice Drouin
b544b24cc3 Fix memory leak in pubkeyCreate() (#20) 2021-06-22 16:16:08 +02:00
Fabrice Drouin
2b0254affb Update secp256k1 sources (#19)
We're now at efad350, the same commit that bitcoin core is using
2021-06-17 11:42:50 +02:00
Fabrice Drouin
b23733d5d4 Use kotlin 1.4.32, set version to 0.5.2-SNAPSHOT, remove bintray settings. (#18) 2021-04-08 10:12:36 +02:00
Fabrice Drouin
0751d7c103 Update README.md 2021-03-26 14:35:15 +01:00
49 changed files with 4037 additions and 1196 deletions

View File

@@ -29,11 +29,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Android environment
if: matrix.os != 'windows-latest'
shell: bash
run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.3.6528147" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
- name: Cached Android NDK
if: matrix.os != 'windows-latest'
uses: actions/cache@v2
with:
path: ${{ format('{0}/ndk/{1}', env.ANDROID_HOME, env.ANDROID_NDK_VERSION) }}
@@ -43,34 +45,44 @@ jobs:
run: |
echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
rm.exe "C:/WINDOWS/system32/bash.EXE"
- name: Set up JDK 8
uses: actions/setup-java@v1
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Install Automake (windows)
if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
java-version: 8
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
- name: Setup Android
if: matrix.os != 'windows-latest'
shell: bash
run: |
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: bash
shell: msys2 {0}
run: |
$ANDROID_HOME\\tools\\bin\\sdkmanager.bat "ndk;$ANDROID_NDK_VERSION"
echo "skip.android=true" > local.properties
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Check JVM
shell: bash
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'
shell: bash
run: ./gradlew linuxTest
run: ./gradlew linuxX64Test
- name: Check iOS
if: matrix.os == 'macOS-latest'
shell: bash
@@ -79,33 +91,22 @@ jobs:
if: matrix.os == 'macOS-latest'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
api-level: 27
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404
cmake: 3.22.1
script: ./gradlew connectedCheck
- name: Publish Linux
if: matrix.os == 'ubuntu-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew publishLinuxPublicationToBintrayRepository :jni:jvm:linux:publishJvmPublicationToBintrayRepository
run: ./gradlew publishLinuxPublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
run: ./gradlew publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
- name: Publish Windows
if: matrix.os == 'windows-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew :jni:jvm:mingw:publishJvmPublicationToBintrayRepository
shell: msys2 {0}
run: ./gradlew :jni:jvm:mingw:publishToMavenLocal
- name: Publish MacOS
if: matrix.os == 'macOS-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew publish
run: ./gradlew publishToMavenLocal
- name: Copy artifact files
run: |
@@ -118,9 +119,3 @@ jobs:
path: |
maven-local
!maven-local/**/maven-metadata-local.xml
# - name: Discard
# if: ${{ failure() || cancelled() }}
# env:
# BINTRAY_USER: ${{ secrets.bintray_user }}
# BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
# run: ./gradlew postBintrayDiscard

View File

@@ -38,11 +38,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Android environment
if: matrix.os != 'windows-latest'
shell: bash
run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.3.6528147" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=25.2.9519653" >> $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) }}
@@ -52,34 +54,44 @@ jobs:
run: |
echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
rm.exe "C:/WINDOWS/system32/bash.EXE"
- name: Set up JDK 8
uses: actions/setup-java@v1
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Install Automake (windows)
if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
java-version: 8
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
- name: Setup Android
if: matrix.os != 'windows-latest'
shell: bash
run: |
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: bash
shell: msys2 {0}
run: |
$ANDROID_HOME\\tools\\bin\\sdkmanager.bat "ndk;$ANDROID_NDK_VERSION"
echo "skip.android=true" > local.properties
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Check JVM
shell: bash
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'
shell: bash
run: ./gradlew linuxTest
run: ./gradlew linuxX64Test
- name: Check iOS
if: matrix.os == 'macOS-latest'
shell: bash
@@ -88,33 +100,22 @@ jobs:
if: matrix.os == 'macOS-latest'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
api-level: 27
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404
cmake: 3.22.1
script: ./gradlew connectedCheck
- name: Publish Linux
if: matrix.os == 'ubuntu-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew publishLinuxPublicationToBintrayRepository :jni:jvm:linux:publishJvmPublicationToBintrayRepository -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
run: ./gradlew publishLinuxPublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
run: ./gradlew publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
- name: Publish Windows
if: matrix.os == 'windows-latest'
env:
BINTRAY_USER: ${{ secrets.bintray_user }}
BINTRAY_APIKEY: ${{ secrets.bintray_apikey }}
shell: bash
# ./gradlew :jni:jvm:mingw:publishJvmPublicationToBintrayRepository -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
shell: msys2 {0}
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: |
@@ -127,34 +128,4 @@ 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,11 +44,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Android environment
if: matrix.os != 'windows-latest'
shell: bash
run: |
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=21.3.6528147" >> $GITHUB_ENV
echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
- name: Cached Android NDK
if: matrix.os != 'windows-latest'
uses: actions/cache@v2
with:
path: ${{ format('{0}/ndk/{1}', env.ANDROID_HOME, env.ANDROID_NDK_VERSION) }}
@@ -58,34 +60,44 @@ jobs:
run: |
echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
rm.exe "C:/WINDOWS/system32/bash.EXE"
- name: Set up JDK 8
uses: actions/setup-java@v1
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Install Automake (windows)
if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
java-version: 8
path-type: minimal
update: true
install: >-
base-devel
autotools
mingw-w64-x86_64-gcc
- name: Setup Android
if: matrix.os != 'windows-latest'
shell: bash
run: |
$ANDROID_HOME/tools/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
- name: Setup Android
if: matrix.os == 'windows-latest'
shell: bash
shell: msys2 {0}
run: |
$ANDROID_HOME\\tools\\bin\\sdkmanager.bat "ndk;$ANDROID_NDK_VERSION"
echo "skip.android=true" > local.properties
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Install Automake
if: matrix.os == 'macOS-latest'
run: brew install automake
- name: Check JVM
shell: bash
if: matrix.os != 'windows-latest'
run: ./gradlew jvmTest
- name: Check JVM (Windows)
if: matrix.os == 'windows-latest'
shell: msys2 {0}
run: ./gradlew jvmTest
- name: Check Linux
if: matrix.os == 'ubuntu-latest'
shell: bash
run: ./gradlew linuxTest
run: ./gradlew linuxX64Test
- name: Check iOS
if: matrix.os == 'macOS-latest'
shell: bash
@@ -94,7 +106,8 @@ jobs:
if: matrix.os == 'macOS-latest'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
api-level: 27
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ndk: ${{ env.ANDROID_NDK_VERSION }}
cmake: 3.10.2.4988404
cmake: 3.22.1
script: ./gradlew connectedCheck

2
.gitmodules vendored
View File

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

View File

@@ -1,19 +1,15 @@
[![Kotlin](https://img.shields.io/badge/Kotlin-1.9.22-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)
# Secp256k1 for Kotlin/Multiplatform
Kotlin/Multiplatform wrapper for Bitcoin Core's secp256k1 library. Targets: JVM, Android, iOS & Linux.
## Installation
[ ![Download](https://api.bintray.com/packages/acinq/libs/secp256k1/images/download.svg) ](https://bintray.com/acinq/libs/secp256k1-kmp/)
First, you need to add the ACINQ libraries repository:
```kotlin
// build.gradle.kts
repositories {
maven(url = "https://dl.bintray.com/acinq/libs")
}
```
secp256k1-kmp is available on [maven central](https://search.maven.org/search?q=g:fr.acinq.secp256k1%20a:secp256k1-kmp*)
Then, the actual dependency depends on your targeted platform(s):
@@ -34,19 +30,19 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
implementation(kotlin("fr.acinq.secp256k1:secp256k1:$secp256k1_version"))
implementation("fr.acinq.secp256k1:secp256k1-kmp:$secp256k1_version")
}
}
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib"))
implementation(kotlin("fr.acinq.secp256k1:secp256k1-jni-jvm:$secp256k1_version"))
implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:$secp256k1_version")
}
}
val androidMain by getting {
dependencies {
implementation(kotlin("stdlib"))
implementation(kotlin("fr.acinq.secp256k1:secp256k1-jni-android:$secp256k1_version"))
implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-android:$secp256k1_version")
}
}
}
@@ -62,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
- Windows 64 bits
- Macos 64 bits
- Linux 64 bits (x86_64 and arm64)
- Windows 64 bits (x86_64)
- Macos 64 bits (x86_64 and arm64)
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-jni-jvm` dependency which imports all supported platforms.
* Either the `fr.acinq.secp256k1:secp256k1-kmp-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-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
* `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
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-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-kmp-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.
@@ -88,3 +84,58 @@ 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

@@ -1,16 +1,10 @@
import org.apache.http.impl.client.HttpClients
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.auth.BasicScheme
import org.apache.http.auth.UsernamePasswordCredentials
import org.gradle.internal.os.OperatingSystem
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.dokka.Platform
plugins {
kotlin("multiplatform") version "1.4.31"
id("org.jetbrains.dokka") version "1.4.20"
kotlin("multiplatform") version "1.9.22"
id("org.jetbrains.dokka") version "1.9.10"
`maven-publish`
}
@@ -18,21 +12,19 @@ buildscript {
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:4.0.2")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.4.20")
classpath("com.android.tools.build:gradle:7.3.1")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
}
}
allprojects {
group = "fr.acinq.secp256k1"
version = "0.5.1"
version = "0.15.0"
repositories {
jcenter()
google()
mavenCentral()
}
@@ -60,26 +52,35 @@ kotlin {
}
}
val nativeMain by sourceSets.creating { dependsOn(commonMain) }
val nativeMain by sourceSets.creating
linuxX64("linux") {
linuxX64 {
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")
}
ios {
macosX64 {
secp256k1CInterop("host")
}
macosArm64 {
secp256k1CInterop("host")
}
iosX64 {
secp256k1CInterop("ios")
}
iosArm64 {
secp256k1CInterop("ios")
}
iosSimulatorArm64 {
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.useExperimentalAnnotation("kotlin.RequiresOptIn")
languageSettings.optIn("kotlin.RequiresOptIn")
}
}
// Disable cross compilation
@@ -89,9 +90,9 @@ allprojects {
val currentOs = OperatingSystem.current()
val targets = when {
currentOs.isLinux -> listOf()
currentOs.isMacOsX -> listOf("linux")
currentOs.isWindows -> listOf("linux")
else -> listOf("linux")
currentOs.isMacOsX -> listOf("linuxX64")
currentOs.isWindows -> listOf("linuxX64")
else -> listOf("linuxX64")
}.mapNotNull { kotlin.targets.findByName(it) as? KotlinNativeTarget }
configure(targets) {
@@ -112,19 +113,6 @@ allprojects {
}
}
// Publication
val bintrayRepo = if (project.version.toString().contains("SNAPSHOT")) "snapshots" else "libs"
val bintrayUsername: String? = (properties["bintrayUsername"] as String?) ?: System.getenv("BINTRAY_USER")
val bintrayApiKey: String? = (properties["bintrayApiKey"] as String?) ?: System.getenv("BINTRAY_APIKEY")
val hasBintray = bintrayUsername != null && bintrayApiKey != null
if (!hasBintray) logger.warn("Skipping bintray configuration as bintrayUsername or bintrayApiKey is not defined")
val sonatypeUsername: String? = (properties["sonatypeUsername"] as String?) ?: System.getenv("SONATYPE_USERNAME")
val sonatypePassword: String? = (properties["sonatypePassword"] as String?) ?: System.getenv("SONATYPE_PASSWORD")
val hasSonatype = sonatypeUsername != null && sonatypePassword != null
if (!hasSonatype) logger.warn("Skipping sonatype snapshot configuration as sonatypeUsername or sonatypePassword is not defined")
allprojects {
val javadocJar = tasks.create<Jar>("javadocJar") {
archiveClassifier.set("javadoc")
@@ -134,30 +122,6 @@ allprojects {
// Publication
plugins.withId("maven-publish") {
publishing {
repositories {
if (hasBintray) {
maven {
name = "bintray"
setUrl("https://api.bintray.com/maven/acinq/$bintrayRepo/${rootProject.name}/;publish=0")
credentials {
username = bintrayUsername
password = bintrayApiKey
}
}
}
if (hasSonatype) {
maven {
name = "snapshot"
setUrl("https://oss.sonatype.org/content/repositories/snapshots")
credentials {
username = sonatypeUsername
password = sonatypePassword
}
}
}
}
publications.withType<MavenPublication>().configureEach {
version = project.version.toString()
artifact(javadocJar)
@@ -203,6 +167,7 @@ allprojects {
Platform.js -> "js"
Platform.native -> "native"
Platform.common -> "common"
Platform.wasm -> "wasm"
}
displayName.set(platformName)
@@ -224,32 +189,6 @@ allprojects {
}
}
if (hasBintray) {
val postBintrayPublish by tasks.creating {
doLast {
HttpClients.createDefault().use { client ->
val post = HttpPost("https://api.bintray.com/content/acinq/$bintrayRepo/${rootProject.name}/${project.version.toString()}/publish").apply {
entity = StringEntity("{}", ContentType.APPLICATION_JSON)
addHeader(BasicScheme().authenticate(UsernamePasswordCredentials(bintrayUsername, bintrayApiKey), this, null))
}
client.execute(post)
}
}
}
val postBintrayDiscard by tasks.creating {
doLast {
HttpClients.createDefault().use { client ->
val post = HttpPost("https://api.bintray.com/content/acinq/$bintrayRepo/${rootProject.name}/${project.version.toString()}/publish").apply {
entity = StringEntity("{ \"discard\": true }", ContentType.APPLICATION_JSON)
addHeader(BasicScheme().authenticate(UsernamePasswordCredentials(bintrayUsername, bintrayApiKey), this, null))
}
client.execute(post)
}
}
}
}
allprojects {
afterEvaluate {
tasks.withType<AbstractTestTask>() {

View File

@@ -4,14 +4,9 @@ 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
# https://github.com/gradle/gradle/issues/11412
systemProp.org.gradle.internal.publish.checksums.insecure = true
kotlin.mpp.enableCInteropCommonization=true
kotlin.native.cacheKind.macosArm64=none
# Android
android.useAndroidX = true

Binary file not shown.

View File

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

2
gradlew vendored
View File

@@ -82,6 +82,7 @@ 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
@@ -129,6 +130,7 @@ 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,103 +1,89 @@
@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
@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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,34 @@
#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
/*
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
* Method: secp256k1_context_create
@@ -119,14 +147,6 @@ 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
@@ -159,6 +179,94 @@ 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);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -154,7 +154,7 @@ internal object OSInfo {
if (systemOsArch.startsWith("arm")) {
resolveArmArchType()
} else {
val lc = systemOsArch.toLowerCase(Locale.US)
val lc = systemOsArch.lowercase(Locale.US)
if (archMapping.containsKey(lc)) return archMapping[lc]
systemOsArch
}

View File

@@ -5,36 +5,56 @@ 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 int SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0);
public static int SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1);
public static final int SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0);
public static final int SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1);
/**
* The higher bits contain the actual data. Do not use directly.
*/
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);
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);
/**
* Flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and
* secp256k1_context_preallocated_create.
*/
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);
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);
/**
* Flag to pass to secp256k1_ec_pubkey_serialize.
*/
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 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 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);
@@ -57,8 +77,6 @@ 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);
@@ -66,4 +84,26 @@ 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);
}

View File

@@ -14,15 +14,16 @@
* 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, data: ByteArray, pub: ByteArray): Boolean {
return Secp256k1CFunctions.secp256k1_ecdsa_verify(Secp256k1Context.getContext(), signature, data, pub) == 1
override fun verify(signature: ByteArray, message: ByteArray, pubkey: ByteArray): Boolean {
return Secp256k1CFunctions.secp256k1_ecdsa_verify(Secp256k1Context.getContext(), signature, message, pubkey) == 1
}
override fun sign(data: ByteArray, sec: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ecdsa_sign(Secp256k1Context.getContext(), data, sec)
override fun sign(message: ByteArray, privkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ecdsa_sign(Secp256k1Context.getContext(), message, privkey)
}
override fun signatureNormalize(sig: ByteArray): Pair<ByteArray, Boolean> {
@@ -31,35 +32,30 @@ public object NativeSecp256k1 : Secp256k1 {
return Pair(sigout, result == 1)
}
override fun secKeyVerify(seckey: ByteArray): Boolean {
val result = Secp256k1CFunctions.secp256k1_ec_seckey_verify(Secp256k1Context.getContext(), seckey);
return result == 1;
override fun secKeyVerify(privkey: ByteArray): Boolean {
return Secp256k1CFunctions.secp256k1_ec_seckey_verify(Secp256k1Context.getContext(), privkey) == 1
}
override fun pubkeyCreate(seckey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_create(Secp256k1Context.getContext(), seckey)
override fun pubkeyCreate(privkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_create(Secp256k1Context.getContext(), privkey)
}
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 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 privKeyTweakMul(privkey: ByteArray, tweak: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_privkey_tweak_mul(Secp256k1Context.getContext(), privkey, tweak)
}
override fun pubKeyNegate(pubkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_negate(Secp256k1Context.getContext(), pubkey)
}
@@ -72,12 +68,12 @@ public object NativeSecp256k1 : Secp256k1 {
return Secp256k1CFunctions.secp256k1_ec_pubkey_tweak_mul(Secp256k1Context.getContext(), pubkey, tweak)
}
override fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_add(Secp256k1Context.getContext(), pubkey1, pubkey2)
override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray {
return Secp256k1CFunctions.secp256k1_ec_pubkey_combine(Secp256k1Context.getContext(), pubkeys)
}
override fun ecdh(seckey: ByteArray, pubkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ecdh(Secp256k1Context.getContext(), seckey, pubkey)
override fun ecdh(privkey: ByteArray, pubkey: ByteArray): ByteArray {
return Secp256k1CFunctions.secp256k1_ecdh(Secp256k1Context.getContext(), privkey, pubkey)
}
override fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray {
@@ -87,4 +83,52 @@ 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 {
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 cleanup() {
return Secp256k1CFunctions.secp256k1_context_destroy(Secp256k1Context.getContext())
}
}

View File

@@ -19,23 +19,21 @@ 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/$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
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
cd secp256k1
./autogen.sh
./configure CFLAGS=-fpic --host=$TARGET --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
./configure CFLAGS=-fpic --host=$TARGET --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
make clean
make

View File

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

View File

@@ -1,9 +1,13 @@
import org.gradle.internal.os.OperatingSystem
evaluationDependsOn(":jni:android")
val includeAndroid = System.getProperty("includeAndroid")?.toBoolean() ?: true
if (includeAndroid) {
evaluationDependsOn(":jni:android")
}
val currentOs = OperatingSystem.current()
val bash = if (currentOs.isWindows) "bash.exe" else "bash"
val bash = "bash"
val buildSecp256k1 by tasks.creating { group = "build" }
@@ -23,7 +27,7 @@ val buildSecp256k1Host by tasks.creating(Exec::class) {
workingDir = projectDir
environment("TARGET", target)
commandLine(bash, "build.sh")
commandLine(bash, "-l", "build.sh")
}
val buildSecp256k1Ios by tasks.creating(Exec::class) {
@@ -39,34 +43,39 @@ val buildSecp256k1Ios by tasks.creating(Exec::class) {
commandLine(bash, "build-ios.sh")
}
val buildSecp256k1Android by tasks.creating {
group = "build"
buildSecp256k1.dependsOn(this)
}
fun creatingBuildSecp256k1Android(arch: String) = tasks.creating(Exec::class) {
group = "build"
buildSecp256k1Android.dependsOn(this)
if (includeAndroid) {
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")
val buildSecp256k1Android by tasks.creating {
group = "build"
buildSecp256k1.dependsOn(this)
}
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")
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 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

@@ -16,14 +16,18 @@ if [ "$TARGET" == "mingw" ]; then
elif [ "$TARGET" == "linux" ]; then
CONF_OPTS="CFLAGS=-fPIC"
elif [ "$TARGET" == "darwin" ]; then
CONF_OPTS="--host=x86_64-w64-darwin"
CONF_OPTS=""
else
echo "Unknown TARGET=$TARGET"
exit 1
fi
./autogen.sh
./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
if [ "$TARGET" == "darwin" ]; then
CFLAGS="-arch arm64 -arch x86_64" ./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
else
./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
fi
make clean
make

View File

@@ -69,9 +69,22 @@ 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}" &> /dev/null
lipo -create -output "${UNIVERSAL}/${LIB_NAME}" "${PLATFORMS}/${PLATFORM_ARM}/lib/${LIB_NAME}" "${PLATFORMS}/${PLATFORM_ISIM}/lib/${LIB_NAME}"
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}"

57
publishing/PUBLISHING.md Normal file
View File

@@ -0,0 +1,57 @@
# 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

@@ -0,0 +1,22 @@
#!/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

@@ -0,0 +1,22 @@
#!/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

@@ -0,0 +1,72 @@
#!/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

@@ -0,0 +1,41 @@
#!/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,15 +2,30 @@ pluginManagement {
repositories {
google()
gradlePluginPortal()
jcenter()
}
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == "com.android" || requested.id.name == "kotlin-android-extensions") {
useModule("com.android.tools.build:gradle:7.3.1")
}
}
}
}
rootProject.name = "secp256k1-kmp"
// 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",
@@ -18,3 +33,10 @@ include(
":jni:jvm:all",
":tests"
)
if (!skipAndroid) {
print("building android library")
include(":jni:android")
} else {
print("skipping android build")
}

View File

@@ -20,60 +20,258 @@ import kotlin.jvm.JvmStatic
public interface Secp256k1 {
public fun verify(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean
/**
* 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 sign(data: ByteArray, sec: ByteArray): ByteArray
/**
* Create a normalized ECDSA signature.
*
* @param message message to sign.
* @param privkey signer's private key.
*/
public fun sign(message: ByteArray, privkey: 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>
public fun secKeyVerify(seckey: ByteArray): Boolean
/**
* Verify the validity of a private key.
*/
public fun secKeyVerify(privkey: ByteArray): Boolean
public fun pubkeyCreate(seckey: ByteArray): ByteArray
/**
* Get the public key corresponding to the given private key.
* Returns the uncompressed public key (65 bytes).
*/
public fun pubkeyCreate(privkey: ByteArray): ByteArray
/**
* Parse a serialized public key.
* Returns the uncompressed public key (65 bytes).
*/
public fun pubkeyParse(pubkey: ByteArray): ByteArray
public fun cleanup()
/**
* Negate the given private key.
*/
public fun privKeyNegate(privkey: ByteArray): ByteArray
public fun privKeyTweakMul(privkey: ByteArray, tweak: 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 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
public fun pubKeyAdd(pubkey1: ByteArray, pubkey2: 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 ecdh(seckey: ByteArray, pubkey: ByteArray): ByteArray
/**
* Compute an elliptic curve Diffie-Hellman secret.
*/
public fun ecdh(privkey: 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
public fun pubKeyCompress(pubkey: ByteArray) : ByteArray {
/**
* Serialize a public key to compact form (33 bytes).
*/
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 pub1 = pubkey.copyOf(33)
pub1[0] = if (pubkey.last() % 2 == 0) 2.toByte() else 3.toByte()
pub1
val compressed = pubkey.copyOf(33)
compressed[0] = if (pubkey.last() % 2 == 0) 2.toByte() else 3.toByte()
compressed
}
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
/**
* 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
/**
* Delete the secp256k1 context from dynamic memory.
*/
public fun cleanup()
public companion object : Secp256k1 by getSecpk256k1() {
@JvmStatic public fun get(): Secp256k1 = this
@JvmStatic
public fun get(): Secp256k1 = this
// @formatter:off
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
// @formatter:on
}
}
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

@@ -20,12 +20,12 @@ import java.util.*
private fun tryLoad(platform: String): Secp256k1? {
try {
val cls = Class.forName("fr.acinq.secp256k1.jni.NativeSecp256k1${platform.capitalize(Locale.ROOT)}Loader")
return try {
val cls = Class.forName("fr.acinq.secp256k1.jni.NativeSecp256k1${platform.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() }}Loader")
val load = cls.getMethod("load")
return load.invoke(null) as Secp256k1
load.invoke(null) as Secp256k1
} catch (ex: ClassNotFoundException) {
return null
null
}
}

View File

@@ -1,10 +1,22 @@
package = secp256k1
headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h
headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1.h
headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1_musig.h
headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1_musig.h secp256k1.h
libraryPaths.linux = c/secp256k1/build/linux/
staticLibraries.linux = libsecp256k1.a
libraryPaths.linux = c/secp256k1/build/linux/ native/build/linux/ native/build/darwin/
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/local/lib
libraryPaths.ios = c/secp256k1/build/ios/ /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/
linkerOpts.ios = -framework Security -framework Foundation

View File

@@ -1,15 +1,16 @@
package fr.acinq.secp256k1
import kotlinx.cinterop.*
import platform.posix.memcpy
import platform.posix.size_tVar
import secp256k1.*
@OptIn(ExperimentalUnsignedTypes::class)
@OptIn(ExperimentalUnsignedTypes::class, ExperimentalForeignApi::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 segp256k1 context")
?: error("Could not create secp256k1 context")
}
private fun Int.requireSuccess(message: String): Int = if (this != 1) throw Secp256k1Exception(message) else this
@@ -40,6 +41,20 @@ public object Secp256k1Native : Secp256k1 {
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>()
@@ -48,6 +63,24 @@ 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> {
val ubytes = bytes.asUByteArray()
val pinned = ubytes.pin()
@@ -55,25 +88,25 @@ public object Secp256k1Native : Secp256k1 {
return pinned.addressOf(0)
}
public override fun verify(signature: ByteArray, data: ByteArray, pub: ByteArray): Boolean {
require(data.size == 32)
require(pub.size == 33 || pub.size == 65)
public override fun verify(signature: ByteArray, message: ByteArray, pubkey: ByteArray): Boolean {
require(message.size == 32)
require(pubkey.size == 33 || pubkey.size == 65)
memScoped {
val nPubkey = allocPublicKey(pub)
val nData = toNat(data)
val nPubkey = allocPublicKey(pubkey)
val nMessage = toNat(message)
val nSig = allocSignature(signature)
return secp256k1_ecdsa_verify(ctx, nSig.ptr, nData, nPubkey.ptr) == 1
return secp256k1_ecdsa_verify(ctx, nSig.ptr, nMessage, nPubkey.ptr) == 1
}
}
public override fun sign(data: ByteArray, sec: ByteArray): ByteArray {
require(sec.size == 32)
require(data.size == 32)
public override fun sign(message: ByteArray, privkey: ByteArray): ByteArray {
require(privkey.size == 32)
require(message.size == 32)
memScoped {
val nSec = toNat(sec)
val nData = toNat(data)
val nPrivkey = toNat(privkey)
val nMessage = toNat(message)
val nSig = alloc<secp256k1_ecdsa_signature>()
secp256k1_ecdsa_sign(ctx, nSig.ptr, nData, nSec, null, null).requireSuccess("secp256k1_ecdsa_sign() failed")
secp256k1_ecdsa_sign(ctx, nSig.ptr, nMessage, nPrivkey, null, null).requireSuccess("secp256k1_ecdsa_sign() failed")
return serializeSignature(nSig)
}
}
@@ -87,20 +120,20 @@ public object Secp256k1Native : Secp256k1 {
}
}
public override fun secKeyVerify(seckey: ByteArray): Boolean {
require(seckey.size == 32)
public override fun secKeyVerify(privkey: ByteArray): Boolean {
if (privkey.size != 32) return false
memScoped {
val nSec = toNat(seckey)
return secp256k1_ec_seckey_verify(ctx, nSec) == 1
val nPrivkey = toNat(privkey)
return secp256k1_ec_seckey_verify(ctx, nPrivkey) == 1
}
}
public override fun pubkeyCreate(seckey: ByteArray): ByteArray {
require(seckey.size == 32)
public override fun pubkeyCreate(privkey: ByteArray): ByteArray {
require(privkey.size == 32)
memScoped {
val nSec = toNat(seckey)
val nPrivkey = toNat(privkey)
val nPubkey = alloc<secp256k1_pubkey>()
secp256k1_ec_pubkey_create(ctx, nPubkey.ptr, nSec).requireSuccess("secp256k1_ec_pubkey_create() failed")
secp256k1_ec_pubkey_create(ctx, nPubkey.ptr, nPrivkey).requireSuccess("secp256k1_ec_pubkey_create() failed")
return serializePubkey(nPubkey)
}
}
@@ -113,22 +146,31 @@ 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_privkey_negate(ctx, negPriv).requireSuccess("secp256k1_ec_privkey_negate() failed")
secp256k1_ec_seckey_negate(ctx, negPriv).requireSuccess("secp256k1_ec_seckey_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)
@@ -138,17 +180,6 @@ 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 {
@@ -160,6 +191,7 @@ 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)
@@ -170,6 +202,7 @@ 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)
@@ -178,26 +211,25 @@ public object Secp256k1Native : Secp256k1 {
}
}
public override fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray {
require(pubkey1.size == 33 || pubkey1.size == 65)
require(pubkey2.size == 33 || pubkey2.size == 65)
public override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray {
require(pubkeys.isNotEmpty())
pubkeys.forEach { require(it.size == 33 || it.size == 65) }
memScoped {
val nPubkey1 = allocPublicKey(pubkey1)
val nPubkey2 = allocPublicKey(pubkey2)
val nPubkeys = pubkeys.map { allocPublicKey(it).ptr }
val combined = alloc<secp256k1_pubkey>()
secp256k1_ec_pubkey_combine(ctx, combined.ptr, cValuesOf(nPubkey1.ptr, nPubkey2.ptr), 2.convert()).requireSuccess("secp256k1_ec_pubkey_combine() failed")
secp256k1_ec_pubkey_combine(ctx, combined.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess("secp256k1_ec_pubkey_combine() failed")
return serializePubkey(combined)
}
}
public override fun ecdh(seckey: ByteArray, pubkey: ByteArray): ByteArray {
require(seckey.size == 32)
public override fun ecdh(privkey: ByteArray, pubkey: ByteArray): ByteArray {
require(privkey.size == 32)
require(pubkey.size == 33 || pubkey.size == 65)
memScoped {
val nPubkey = allocPublicKey(pubkey)
val nSeckey = toNat(seckey)
val nPrivkey = toNat(privkey)
val output = allocArray<UByteVar>(32)
secp256k1_ecdh(ctx, output, nPubkey.ptr, nSeckey, null, null).requireSuccess("secp256k1_ecdh() failed")
secp256k1_ecdh(ctx, output, nPubkey.ptr, nPrivkey, null, null).requireSuccess("secp256k1_ecdh() failed")
return output.readBytes(32)
}
}
@@ -205,6 +237,7 @@ 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>()
@@ -227,6 +260,193 @@ 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, 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).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)
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
}
}
public override fun cleanup() {
secp256k1_context_destroy(ctx)
}
}
internal actual fun getSecpk256k1(): Secp256k1 = Secp256k1Native

View File

@@ -1,11 +1,19 @@
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")
id("com.android.library")
if (System.getProperty("includeAndroid")?.toBoolean() == true) {
id("com.android.library")
}
}
kotlin {
explicitApi()
val includeAndroid = System.getProperty("includeAndroid")?.toBoolean() ?: true
val commonMain by sourceSets.getting {
dependencies {
implementation(rootProject)
@@ -13,7 +21,10 @@ 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")
}
}
@@ -29,42 +40,72 @@ kotlin {
}
}
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")
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")
}
}
}
linuxX64("linux")
ios()
linuxX64()
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
}
android {
defaultConfig {
compileSdkVersion(30)
minSdkVersion(21)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
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"
}
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

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

View File

@@ -0,0 +1,298 @@
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 {
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)
}
@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,272 +3,527 @@ 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
@Test
fun testVerifyPos() {
var result: Boolean
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing"
val sig: ByteArray = Hex.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase())
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase())
result = Secp256k1.verify(sig, data, pub)
assertTrue(result, "testVerifyPos")
val sigCompact: ByteArray = Hex.decode("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase())
result = Secp256k1.verify(sigCompact, data, pub)
assertTrue(result, "testVerifyPos")
fun verifyValidPrivateKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
assertTrue(Secp256k1.secKeyVerify(priv))
}
@Test
fun testVerifyNeg() {
var result: Boolean
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91".toLowerCase()) //sha256hash of "testing"
val sig: ByteArray = Hex.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase())
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase())
result = Secp256k1.verify(sig, data, pub)
assertFalse(result, "testVerifyNeg")
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))
}
@Test
fun testSecKeyVerifyPos() {
var result: Boolean
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
result = Secp256k1.secKeyVerify(sec)
assertTrue(result, "testSecKeyVerifyPos")
}
@Test
fun testSecKeyVerifyNeg() {
var result: Boolean
val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase())
result = Secp256k1.secKeyVerify(sec)
assertFalse(result, "testSecKeyVerifyNeg")
}
@Test
fun testPubKeyCreatePos() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
val resultArr: ByteArray = Secp256k1.pubkeyCreate(sec)
val pubkeyString: String = Hex.encode(resultArr).toUpperCase()
fun createValidPublicKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub = Secp256k1.pubkeyCreate(priv)
assertEquals(
"04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6",
pubkeyString,
"testPubKeyCreatePos"
Hex.encode(pub).uppercase(),
)
}
@Test
fun testPubKeyCreateNeg() {
val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase())
assertFailsWith<Secp256k1Exception> {
Secp256k1.pubkeyCreate(sec)
}
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())) }
}
@Test
fun testPubkeyCompress() {
fun compressPublicKey() {
val pub = Hex.decode("04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6")
val compressed = Secp256k1.pubKeyCompress(pub)
assertEquals("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D", Hex.encode(compressed).toUpperCase())
assertEquals("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D", Hex.encode(compressed).uppercase())
}
@Test
fun testPubKeyNegatePos() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
val pubkey: ByteArray = Secp256k1.pubkeyCreate(sec)
val pubkeyString: String = Hex.encode(pubkey).toUpperCase()
fun negatePublicKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub = Secp256k1.pubkeyCreate(priv)
assertEquals(
"04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6",
pubkeyString,
"testPubKeyCreatePos"
Hex.encode(pub).uppercase(),
)
val pubkey1: ByteArray = Secp256k1.pubKeyNegate(pubkey)
val pubkeyString1: String = Hex.encode(pubkey1).toUpperCase()
assertEquals(
"02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D",
Hex.encode(Secp256k1.pubKeyCompress(pub)).uppercase()
)
val npub = Secp256k1.pubKeyNegate(pub)
assertEquals(
"04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2DDEFC12B6B8E73968536514302E69ED1DDB24B999EFEE79C12D03AB17E79E1989",
pubkeyString1,
"testPubKeyNegatePos"
Hex.encode(npub).uppercase(),
)
assertEquals(
"03C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D",
Hex.encode(Secp256k1.pubKeyCompress(npub)).uppercase()
)
val nnpub = Secp256k1.pubKeyNegate(npub)
assertContentEquals(pub, nnpub)
}
@Test
fun testPubKeyParse() {
val pub: ByteArray = Hex.decode("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D".toLowerCase())
val resultArr: ByteArray = Secp256k1.pubkeyParse(pub)
val pubkeyString: String = Hex.encode(resultArr).toUpperCase()
fun parsePublicKey() {
val pub = Hex.decode("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D".lowercase())
val parsed1 = Secp256k1.pubkeyParse(pub)
assertEquals(
"04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6",
pubkeyString,
"testPubKeyAdd"
Hex.encode(parsed1).uppercase(),
)
val parsed2 = Secp256k1.pubkeyParse(parsed1)
assertContentEquals(parsed1, parsed2)
}
@Test
fun testPubKeyAdd() {
val pub1: ByteArray = Hex.decode("041b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f70beaf8f588b541507fed6a642c5ab42dfdf8120a7f639de5122d47a69a8e8d1".toLowerCase())
val pub2: ByteArray = Hex.decode("044d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d07662a3eada2d0fe208b6d257ceb0f064284662e857f57b66b54c198bd310ded36d0".toLowerCase())
val pub3: ByteArray = Secp256k1.pubKeyAdd(pub1, pub2)
val pubkeyString: String = Hex.encode(pub3).toUpperCase()
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(
"04531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE3379E92C265E71E481BA82A84675A47AC705A200FCD524E92D93B0E7386F26A5458",
pubkeyString,
"testPubKeyAdd"
"042C0B7CF95324A07D05398B240174DC0C2BE444D96B159AA6C7F7B1E668680991AE31A9C671A36543F46CEA8FCE6984608AA316AA0472A7EED08847440218CB2F",
Hex.encode(pub4).uppercase(),
)
}
@Test
fun testSignPos() {
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing"
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
val resultArr: ByteArray = Secp256k1.sign(data, sec)
val sigString: String = Hex.encode(resultArr).toUpperCase()
fun createEcdsaSignature() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val sig = Secp256k1.sign(message, priv)
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
sigString,
"testSignPos"
Hex.encode(sig).uppercase(),
)
}
@Test
fun testSignatureNormalize() {
val data: ByteArray = Hex.decode("30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".toLowerCase())
val (resultArr, isHighS) = Secp256k1.signatureNormalize(data)
val sigString: String = Hex.encode(resultArr).toUpperCase()
fun normalizeEcdsaSignature() {
val normalizedDerSig = Hex.decode("30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".lowercase())
val (normalizedCompactSig1, wasNotNormalized1) = Secp256k1.signatureNormalize(normalizedDerSig)
assertFalse(wasNotNormalized1)
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
sigString,
"testSignPos"
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(),
)
assertFalse(isHighS, "isHighS")
}
@Test
fun testSignNeg() {
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing"
val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase())
fun failToCreateEcdsaSignature() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val priv = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".lowercase())
assertFailsWith<Secp256k1Exception> {
Secp256k1.sign(data, sec)
Secp256k1.sign(message, priv)
}
}
@Test
fun testSignCompactPos() {
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing"
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
val resultArr: ByteArray = Secp256k1.sign(data, sec)
val sigString: String = Hex.encode(resultArr).toUpperCase()
fun createCompactEcdsaSignature() {
val message = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".lowercase()) //sha256hash of "testing"
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val sig = Secp256k1.sign(message, priv)
assertEquals(
"182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
sigString,
"testSignCompactPos"
Hex.encode(sig).uppercase(),
)
}
@Test
fun testPrivKeyTweakNegate() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
val sec1: ByteArray = Secp256k1.privKeyNegate(sec)
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)
assertEquals(
"981A9A7DD677A622518DA068D66D5F824E5F22F084B8A0E2F195B5662F300C11",
Hex.encode(sec1).toUpperCase(),
"testPrivKeyNegate"
Hex.encode(npriv).uppercase(),
)
val sec2: ByteArray = Secp256k1.privKeyNegate(sec1)
assertTrue(sec.contentEquals(sec2))
val nnpriv: ByteArray = Secp256k1.privKeyNegate(npriv)
assertContentEquals(priv, nnpriv)
}
@Test
fun testPrivKeyTweakAdd_1() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()) //sha256hash of "tweak"
val resultArr: ByteArray = Secp256k1.privKeyTweakAdd(sec, data)
val sigString: String = Hex.encode(resultArr).toUpperCase()
fun addTweakToPrivateKey() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val tweak = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase())
val tweakedPriv = Secp256k1.privKeyTweakAdd(priv, tweak)
assertEquals(
"A168571E189E6F9A7E2D657A4B53AE99B909F7E712D1C23CED28093CD57C88F3",
sigString,
"testPrivKeyAdd_1"
Hex.encode(tweakedPriv).uppercase(),
)
}
@Test
fun testPrivKeyTweakMul_1() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()) //sha256hash of "tweak"
val resultArr: ByteArray = Secp256k1.privKeyTweakMul(sec, data)
val sigString: String = Hex.encode(resultArr).toUpperCase()
fun multiplyPrivateKeyWithTweak() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val tweak = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase())
val tweakedPriv = Secp256k1.privKeyTweakMul(priv, tweak)
assertEquals(
"97F8184235F101550F3C71C927507651BD3F1CDB4A5A33B8986ACF0DEE20FFFC",
sigString,
"testPrivKeyMul_1"
Hex.encode(tweakedPriv).uppercase(),
)
}
@Test
fun testPrivKeyTweakAdd_2() {
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase())
val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()) //sha256hash of "tweak"
val resultArr: ByteArray = Secp256k1.pubKeyTweakAdd(pub, data)
val sigString: String = Hex.encode(resultArr).toUpperCase()
fun addTweakToPublicKey() {
val pub = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val tweak = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase())
val tweakedPub = Secp256k1.pubKeyTweakAdd(pub, tweak)
assertEquals(
"0411C6790F4B663CCE607BAAE08C43557EDC1A4D11D88DFCB3D841D0C6A941AF525A268E2A863C148555C48FB5FBA368E88718A46E205FABC3DBA2CCFFAB0796EF",
sigString,
"testPrivKeyAdd_2"
Hex.encode(tweakedPub).uppercase(),
)
}
@Test
fun testPrivKeyTweakMul_2() {
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase())
val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()) //sha256hash of "tweak"
val resultArr: ByteArray = Secp256k1.pubKeyTweakMul(pub, data)
val sigString: String = Hex.encode(resultArr).toUpperCase()
fun multiplyPublicKeyWithTweak() {
val pub = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val tweak = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".lowercase())
val tweakedPub = Secp256k1.pubKeyTweakMul(pub, tweak)
assertEquals(
"04E0FE6FE55EBCA626B98A807F6CAF654139E14E5E3698F01A9A658E21DC1D2791EC060D4F412A794D5370F672BC94B722640B5F76914151CFCA6E712CA48CC589",
sigString,
"testPrivKeyMul_2"
Hex.encode(tweakedPub).uppercase(),
)
}
@Test
fun testCreateECDHSecret() {
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase())
val resultArr: ByteArray = Secp256k1.ecdh(sec, pub)
val ecdhString: String = Hex.encode(resultArr).toUpperCase()
fun createEcdhSecret() {
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
val pub = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".lowercase())
val secret = Secp256k1.ecdh(priv, pub)
assertEquals(
"2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043",
ecdhString,
"testCreateECDHSecret"
Hex.encode(secret).uppercase(),
)
}
@Test
fun testEcdsaRecover() {
val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing"
val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase())
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")
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)
}
@Test
fun testCompactToDER() {
val sig: ByteArray = Hex.decode("182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".toLowerCase()) //sha256hash of "testing"
val der: ByteArray = Secp256k1.compact2der(sig)
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)
assertEquals(
"30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9",
Hex.encode(der).toUpperCase(),
Hex.encode(der).uppercase(),
)
}
@Test
fun testFormatConversion() {
fun testSchnorrSignature() {
val seckey = Hex.decode("0000000000000000000000000000000000000000000000000000000000000003")
val msg = Hex.decode("0000000000000000000000000000000000000000000000000000000000000000")
val auxrand32 = Hex.decode("0000000000000000000000000000000000000000000000000000000000000000")
val sig = Secp256k1.signSchnorr(msg, seckey, auxrand32)
assertEquals(
"E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0",
Hex.encode(sig).uppercase(),
)
val pubkey = Secp256k1.pubkeyCreate(seckey).drop(1).take(32).toByteArray()
assertEquals(
"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
Hex.encode(pubkey).uppercase()
)
assertTrue(Secp256k1.verifySchnorr(sig, msg, pubkey))
}
@Test
fun testSchnorrTestVectors() {
// BIP340 test vectors copied from https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
val bip340TestVectors = """index,secret key,public key,aux_rand,message,signature,verification result,comment
0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE,
1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE,
2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE,
3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n
4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE,
5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve
6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false
7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message
8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value
9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0
10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1
11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve
12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size
13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order
14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size"""
bip340TestVectors.split('\n').drop(1).forEach {
val testData = it.split(',')
val index = testData[0]
val seckey = Hex.decode(testData[1])
val pubkey = Hex.decode(testData[2])
val auxrand = if (testData[3].isEmpty()) null else Hex.decode(testData[3])
val msg = Hex.decode(testData[4])
val sig = testData[5]
val expected = when (testData[6]) {
"FALSE" -> false
else -> true
}
val comment = testData[7]
if (seckey.isNotEmpty()) {
val ourSig = Secp256k1.signSchnorr(msg, seckey, auxrand)
assertEquals(Hex.encode(ourSig).uppercase(), sig)
}
val result = try {
Secp256k1.verifySchnorr(Hex.decode(sig), msg, pubkey)
} catch (t: Throwable) {
false
}
assertEquals(expected, result, "test [$index, $comment] failed")
}
}
@Test
fun 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(
"0101010101010101010101010101010101010101010101010101010101010101",
"0202020202020202020202020202020202020202020202020202020202020202",
).map { Hex.decode(it) }.toTypedArray()
val pubkeys = privkeys.map { Secp256k1.pubkeyCreate(it) }
val sessionId = Hex.decode("0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F")
val nonces = pubkeys.map { Secp256k1.musigNonceGen(sessionId, null, it, null, null, null) }
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]))
assertContentEquals(keyaggCaches[0], keyaggCaches[1])
val msg32 = Hex.decode("0303030303030303030303030303030303030303030303030303030303030303")
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]))
assertEquals(0, Secp256k1.musigPartialSigVerify(Random.nextBytes(32), pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it]))
psig
}
val sig = Secp256k1.musigPartialSigAgg(sessions[0], psigs.toTypedArray())
assertContentEquals(sig, Secp256k1.musigPartialSigAgg(sessions[1], psigs.toTypedArray()))
assertTrue(Secp256k1.verifySchnorr(sig, msg32, aggpubkey))
val invalidSig1 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(psigs[0], psigs[0]))
assertFalse(Secp256k1.verifySchnorr(invalidSig1, msg32, aggpubkey))
val invalidSig2 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(Random.nextBytes(32), Random.nextBytes(32)))
assertFalse(Secp256k1.verifySchnorr(invalidSig2, msg32, aggpubkey))
}
@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 fuzzEcdsaSignVerify() {
val random = Random.Default
fun randomBytes(length: Int): ByteArray {
@@ -279,11 +534,13 @@ class Secp256k1Test {
repeat(200) {
val priv = randomBytes(32)
assertTrue(Secp256k1.secKeyVerify(priv))
val pub = Secp256k1.pubkeyCreate(priv)
val data = randomBytes(32)
val sig = Secp256k1.sign(data, priv)
val message = randomBytes(32)
val sig = Secp256k1.sign(message, priv)
assertTrue(Secp256k1.verify(sig, message, pub))
val der = Secp256k1.compact2der(sig)
Secp256k1.verify(der, data, pub)
assertTrue(Secp256k1.verify(der, message, pub))
}
}
}

View File

@@ -0,0 +1,144 @@
{
"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

@@ -0,0 +1,88 @@
{
"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

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

View File

@@ -0,0 +1,51 @@
{
"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

@@ -0,0 +1,44 @@
{
"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

@@ -0,0 +1,151 @@
{
"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

@@ -0,0 +1,212 @@
{
"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

@@ -0,0 +1,84 @@
{
"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"
}
]
}