Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c582396b96 | ||
|
|
202b0c94b6 | ||
|
|
780f97e46d | ||
|
|
da2cf93fcf | ||
|
|
f5e1655ff0 | ||
|
|
8ba5d4652e | ||
|
|
e94e41b896 | ||
|
|
1a4c8b37cb | ||
|
|
f242b4ffe8 | ||
|
|
161da89ee1 | ||
|
|
3706a546a2 | ||
|
|
ffcaaf1b64 | ||
|
|
6ef94df247 | ||
|
|
5169073a92 | ||
|
|
317e086cba | ||
|
|
7c7aabba80 | ||
|
|
b6823cbda6 |
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
|
||||
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV
|
||||
echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
|
||||
- name: Cached Android NDK
|
||||
if: matrix.os != 'windows-latest'
|
||||
uses: actions/cache@v2
|
||||
@@ -58,15 +58,11 @@ jobs:
|
||||
base-devel
|
||||
autotools
|
||||
mingw-w64-x86_64-gcc
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- 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: msys2 {0}
|
||||
@@ -86,7 +82,7 @@ jobs:
|
||||
- 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
|
||||
@@ -96,15 +92,14 @@ jobs:
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 27
|
||||
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
ndk: ${{ env.ANDROID_NDK_VERSION }}
|
||||
cmake: 3.10.2.4988404
|
||||
cmake: 3.22.1
|
||||
script: ./gradlew connectedCheck
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
run: ./gradlew publishLinuxPublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
|
||||
run: ./gradlew publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
|
||||
- name: Publish Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: msys2 {0}
|
||||
|
||||
15
.github/workflows/snapshot.yml
vendored
15
.github/workflows/snapshot.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
|
||||
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV
|
||||
echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
|
||||
- name: Cached Android NDK
|
||||
if: matrix.os != 'windows-latest'
|
||||
uses: actions/cache@v2
|
||||
@@ -67,15 +67,11 @@ jobs:
|
||||
base-devel
|
||||
autotools
|
||||
mingw-w64-x86_64-gcc
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- 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: msys2 {0}
|
||||
@@ -95,7 +91,7 @@ jobs:
|
||||
- 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
|
||||
@@ -105,15 +101,14 @@ jobs:
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 27
|
||||
-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
ndk: ${{ env.ANDROID_NDK_VERSION }}
|
||||
cmake: 3.10.2.4988404
|
||||
cmake: 3.22.1
|
||||
script: ./gradlew connectedCheck
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
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'
|
||||
shell: msys2 {0}
|
||||
|
||||
13
.github/workflows/test.yml
vendored
13
.github/workflows/test.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
|
||||
echo "ANDROID_NDK_VERSION=21.4.7075529" >> $GITHUB_ENV
|
||||
echo "ANDROID_NDK_VERSION=25.2.9519653" >> $GITHUB_ENV
|
||||
- name: Cached Android NDK
|
||||
if: matrix.os != 'windows-latest'
|
||||
uses: actions/cache@v2
|
||||
@@ -73,15 +73,11 @@ jobs:
|
||||
base-devel
|
||||
autotools
|
||||
mingw-w64-x86_64-gcc
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- 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: msys2 {0}
|
||||
@@ -101,7 +97,7 @@ jobs:
|
||||
- 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
|
||||
@@ -111,8 +107,7 @@ jobs:
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 27
|
||||
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
ndk: ${{ env.ANDROID_NDK_VERSION }}
|
||||
cmake: 3.10.2.4988404
|
||||
cmake: 3.22.1
|
||||
script: ./gradlew connectedCheck
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](http://kotlinlang.org)
|
||||
[](http://kotlinlang.org)
|
||||
[](https://search.maven.org/search?q=g:fr.acinq.secp256k1%20a:secp256k1-kmp*)
|
||||

|
||||
[](https://github.com/ACINQ/secp256k1-kmp/blob/master/LICENSE)
|
||||
@@ -58,9 +58,9 @@ Native targets include libsecp256k1, called through KMP's c-interop, simply add
|
||||
The JVM library uses JNI bindings for libsecp256k1, which is much faster than BouncyCastle. It will extract and load native bindings for your operating system in a temporary directory.
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
import org.jetbrains.dokka.Platform
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.6.21"
|
||||
id("org.jetbrains.dokka") version "1.6.21"
|
||||
kotlin("multiplatform") version "1.9.22"
|
||||
id("org.jetbrains.dokka") version "1.9.10"
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ buildscript {
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.3.1")
|
||||
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.6.21")
|
||||
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = "fr.acinq.secp256k1"
|
||||
version = "0.9.0"
|
||||
version = "0.14.0"
|
||||
|
||||
repositories {
|
||||
google()
|
||||
@@ -52,20 +52,22 @@ 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 {
|
||||
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 {
|
||||
@@ -80,9 +82,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) {
|
||||
@@ -157,6 +159,7 @@ allprojects {
|
||||
Platform.js -> "js"
|
||||
Platform.native -> "native"
|
||||
Platform.common -> "common"
|
||||
Platform.wasm -> "wasm"
|
||||
}
|
||||
displayName.set(platformName)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -167,6 +195,78 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
|
||||
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
@@ -29,12 +29,32 @@ public class Secp256k1CFunctions {
|
||||
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);
|
||||
@@ -68,4 +88,22 @@ public class Secp256k1CFunctions {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -92,6 +92,42 @@ public object NativeSecp256k1 : Secp256k1 {
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ 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-module-schnorrsig --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
|
||||
|
||||
|
||||
@@ -6,10 +6,13 @@ cp xconfigure.sh secp256k1
|
||||
cd secp256k1
|
||||
|
||||
./autogen.sh
|
||||
sh xconfigure.sh --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
|
||||
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
|
||||
|
||||
@@ -23,7 +23,7 @@ else
|
||||
fi
|
||||
|
||||
./autogen.sh
|
||||
./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
|
||||
./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
|
||||
make clean
|
||||
make
|
||||
|
||||
|
||||
Submodule native/secp256k1 updated: 346a053d4c...dd4932b67b
@@ -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}"
|
||||
@@ -2,52 +2,64 @@
|
||||
|
||||
GROUP_ID=fr.acinq.secp256k1
|
||||
ARTIFACT_ID_BASE=secp256k1-kmp
|
||||
VERSION=0.9.0-SNAPSHOT
|
||||
|
||||
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
|
||||
-DpomFile=$ARTIFACT_ID_BASE-$VERSION.pom \
|
||||
-Dfile=$ARTIFACT_ID_BASE-$VERSION.jar \
|
||||
-Dfiles=$ARTIFACT_ID_BASE-$VERSION.module,$ARTIFACT_ID_BASE-$VERSION-kotlin-tooling-metadata.json \
|
||||
-Dtypes=module,json \
|
||||
-Dclassifiers=,kotlin-tooling-metadata \
|
||||
-Dsources=$ARTIFACT_ID_BASE-$VERSION-sources.jar \
|
||||
-Djavadoc=$ARTIFACT_ID_BASE-$VERSION-javadoc.jar
|
||||
popd
|
||||
pushd .
|
||||
for i in iosarm64 iosx64 jni-android jni-common jni-jvm-darwin jni-jvm-extract jni-jvm-linux jni-jvm-mingw jni-jvm jvm linux
|
||||
do
|
||||
cd fr/acinq/secp256k1/secp256k1-kmp-$i/$VERSION
|
||||
if [ $i == iosarm64 ] || [ $i == iosx64 ] || [ $i == linux ]; then
|
||||
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
|
||||
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
|
||||
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \
|
||||
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
|
||||
-Dtypes=module,klib \
|
||||
-Dclassifiers=,cinterop-libsecp256k1 \
|
||||
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
|
||||
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
|
||||
elif [ $i == jni-android ]; then
|
||||
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
|
||||
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
|
||||
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.aar \
|
||||
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
|
||||
-Dtypes=module \
|
||||
-Dclassifiers= \
|
||||
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
|
||||
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
|
||||
else
|
||||
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
|
||||
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
|
||||
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.jar \
|
||||
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
|
||||
-Dtypes=module \
|
||||
-Dclassifiers= \
|
||||
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
|
||||
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
|
||||
fi
|
||||
popd
|
||||
pushd .
|
||||
for i in iosarm64 iossimulatorarm64 iosx64 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
|
||||
if [ $i == iosarm64 ] || [ $i == iossimulatorarm64 ] || [ $i == iosx64 ]; then
|
||||
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
|
||||
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
|
||||
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \
|
||||
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION-metadata.jar,$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
|
||||
-Dtypes=jar,module,klib \
|
||||
-Dclassifiers=metadata,,cinterop-libsecp256k1 \
|
||||
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
|
||||
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
|
||||
elif [ $i == linuxx64 ]; then
|
||||
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
|
||||
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
|
||||
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.klib \
|
||||
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module,$ARTIFACT_ID_BASE-$i-$VERSION-cinterop-libsecp256k1.klib \
|
||||
-Dtypes=module,klib \
|
||||
-Dclassifiers=,cinterop-libsecp256k1 \
|
||||
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
|
||||
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
|
||||
elif [ $i == jni-android ]; then
|
||||
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
|
||||
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
|
||||
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.aar \
|
||||
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
|
||||
-Dtypes=module \
|
||||
-Dclassifiers= \
|
||||
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
|
||||
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
|
||||
else
|
||||
mvn deploy:deploy-file -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/content/repositories/snapshots/ \
|
||||
-DpomFile=$ARTIFACT_ID_BASE-$i-$VERSION.pom \
|
||||
-Dfile=$ARTIFACT_ID_BASE-$i-$VERSION.jar \
|
||||
-Dfiles=$ARTIFACT_ID_BASE-$i-$VERSION.module \
|
||||
-Dtypes=module \
|
||||
-Dclassifiers= \
|
||||
-Dsources=$ARTIFACT_ID_BASE-$i-$VERSION-sources.jar \
|
||||
-Djavadoc=$ARTIFACT_ID_BASE-$i-$VERSION-javadoc.jar
|
||||
fi
|
||||
popd
|
||||
pushd .
|
||||
done
|
||||
|
||||
@@ -55,7 +55,7 @@ public interface Secp256k1 {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -149,10 +149,108 @@ public interface Secp256k1 {
|
||||
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.
|
||||
*/
|
||||
@@ -161,6 +259,13 @@ public interface Secp256k1 {
|
||||
public companion object : Secp256k1 by getSecpk256k1() {
|
||||
@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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package = secp256k1
|
||||
|
||||
headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h
|
||||
headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1.h
|
||||
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.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
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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 {
|
||||
@@ -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()
|
||||
@@ -125,6 +158,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
|
||||
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)
|
||||
@@ -136,6 +170,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
|
||||
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)
|
||||
@@ -156,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)
|
||||
@@ -166,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)
|
||||
@@ -175,6 +212,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
}
|
||||
|
||||
public override fun pubKeyCombine(pubkeys: Array<ByteArray>): ByteArray {
|
||||
require(pubkeys.isNotEmpty())
|
||||
pubkeys.forEach { require(it.size == 33 || it.size == 65) }
|
||||
memScoped {
|
||||
val nPubkeys = pubkeys.map { allocPublicKey(it).ptr }
|
||||
@@ -199,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>()
|
||||
@@ -232,7 +271,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
secp256k1_xonly_pubkey_parse(ctx, pubkey.ptr, nPub).requireSuccess("secp256k1_xonly_pubkey_parse() failed")
|
||||
val nData = toNat(data)
|
||||
val nSig = toNat(signature)
|
||||
return secp256k1_schnorrsig_verify(ctx, nSig, nData, 32, pubkey.ptr) == 1
|
||||
return secp256k1_schnorrsig_verify(ctx, nSig, nData, 32u, pubkey.ptr) == 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,12 +290,163 @@ public object Secp256k1Native : Secp256k1 {
|
||||
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
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
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")
|
||||
@@ -20,6 +23,8 @@ kotlin {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +41,14 @@ kotlin {
|
||||
}
|
||||
|
||||
if (includeAndroid) {
|
||||
android {
|
||||
androidTarget {
|
||||
compilations.all {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
sourceSets["androidMain"].dependencies {
|
||||
implementation(project(":jni:android"))
|
||||
}
|
||||
sourceSets["androidTest"].dependencies {
|
||||
sourceSets["androidUnitTest"].dependencies {
|
||||
implementation(kotlin("test-junit"))
|
||||
implementation("androidx.test.ext:junit:1.1.2")
|
||||
implementation("androidx.test.espresso:espresso-core:3.3.0")
|
||||
@@ -51,17 +56,18 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
linuxX64("linux")
|
||||
|
||||
ios()
|
||||
linuxX64()
|
||||
iosX64()
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
}
|
||||
|
||||
val includeAndroid = System.getProperty("includeAndroid")?.toBoolean() ?: true
|
||||
if (includeAndroid) {
|
||||
extensions.configure<com.android.build.gradle.LibraryExtension>("android") {
|
||||
defaultConfig {
|
||||
compileSdkVersion(30)
|
||||
minSdkVersion(21)
|
||||
compileSdk = 30
|
||||
minSdk = 21
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -78,4 +84,26 @@ if (includeAndroid) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
298
tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt
Normal file
298
tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,6 +352,176 @@ class Secp256k1Test {
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
144
tests/src/commonTest/resources/musig2/det_sign_vectors.json
Normal file
144
tests/src/commonTest/resources/musig2/det_sign_vectors.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
88
tests/src/commonTest/resources/musig2/key_agg_vectors.json
Normal file
88
tests/src/commonTest/resources/musig2/key_agg_vectors.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
tests/src/commonTest/resources/musig2/key_sort_vectors.json
Normal file
18
tests/src/commonTest/resources/musig2/key_sort_vectors.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"pubkeys": [
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8"
|
||||
],
|
||||
"sorted_pubkeys": [
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
|
||||
]
|
||||
}
|
||||
51
tests/src/commonTest/resources/musig2/nonce_agg_vectors.json
Normal file
51
tests/src/commonTest/resources/musig2/nonce_agg_vectors.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
44
tests/src/commonTest/resources/musig2/nonce_gen_vectors.json
Normal file
44
tests/src/commonTest/resources/musig2/nonce_gen_vectors.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
151
tests/src/commonTest/resources/musig2/sig_agg_vectors.json
Normal file
151
tests/src/commonTest/resources/musig2/sig_agg_vectors.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
212
tests/src/commonTest/resources/musig2/sign_verify_vectors.json
Normal file
212
tests/src/commonTest/resources/musig2/sign_verify_vectors.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
84
tests/src/commonTest/resources/musig2/tweak_vectors.json
Normal file
84
tests/src/commonTest/resources/musig2/tweak_vectors.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user