Compare commits
9 Commits
v0.15.0
...
snapshot/u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62afbc87ac | ||
|
|
e77e42e139 | ||
|
|
6fce4aa625 | ||
|
|
2a3badd6b0 | ||
|
|
988b461975 | ||
|
|
6fc2f0086a | ||
|
|
10e079ffeb | ||
|
|
567f411e12 | ||
|
|
eb92fccbd6 |
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
rm.exe "C:/WINDOWS/system32/bash.EXE"
|
||||
- name: Install Automake
|
||||
if: matrix.os == 'macOS-latest'
|
||||
run: brew install automake
|
||||
run: brew install automake libtool
|
||||
- name: Install Automake (windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: msys2/setup-msys2@v2
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
shell: bash
|
||||
run: ./gradlew iosX64Test
|
||||
- name: Check Android
|
||||
if: matrix.os == 'macOS-latest'
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 27
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
run: ./gradlew publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
|
||||
run: ./gradlew publishAndroidPublicationToMavenLocal publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal
|
||||
- name: Publish Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: msys2 {0}
|
||||
|
||||
6
.github/workflows/snapshot.yml
vendored
6
.github/workflows/snapshot.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
rm.exe "C:/WINDOWS/system32/bash.EXE"
|
||||
- name: Install Automake
|
||||
if: matrix.os == 'macOS-latest'
|
||||
run: brew install automake
|
||||
run: brew install automake libtool
|
||||
- name: Install Automake (windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: msys2/setup-msys2@v2
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
shell: bash
|
||||
run: ./gradlew iosX64Test
|
||||
- name: Check Android
|
||||
if: matrix.os == 'macOS-latest'
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 27
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
- name: Publish Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
run: ./gradlew publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
|
||||
run: ./gradlew publishAndroidPublicationToMavenLocal publishLinuxX64PublicationToMavenLocal :jni:jvm:linux:publishJvmPublicationToMavenLocal -PsnapshotNumber=${{ github.run_number }} -PgitRef=${{ github.ref }}
|
||||
- name: Publish Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: msys2 {0}
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
rm.exe "C:/WINDOWS/system32/bash.EXE"
|
||||
- name: Install Automake
|
||||
if: matrix.os == 'macOS-latest'
|
||||
run: brew install automake
|
||||
run: brew install automake libtool
|
||||
- name: Install Automake (windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: msys2/setup-msys2@v2
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
shell: bash
|
||||
run: ./gradlew iosX64Test
|
||||
- name: Check Android
|
||||
if: matrix.os == 'macOS-latest'
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 27
|
||||
|
||||
@@ -22,7 +22,7 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
group = "fr.acinq.secp256k1"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
google()
|
||||
|
||||
@@ -203,6 +203,14 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1sc
|
||||
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_gen_counter
|
||||
* Signature: (JJ[B[B[B[B[B)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen_1counter
|
||||
(JNIEnv *, jclass, jlong, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray);
|
||||
|
||||
/*
|
||||
* Class: fr_acinq_secp256k1_Secp256k1CFunctions
|
||||
* Method: secp256k1_musig_nonce_agg
|
||||
|
||||
@@ -549,9 +549,9 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
|
||||
if (jpubkeys == NULL)
|
||||
return NULL;
|
||||
|
||||
count = (*penv)->GetArrayLength(penv, jpubkeys);
|
||||
CHECKRESULT(count < 1, "pubkey array cannot be empty")
|
||||
pubkeys = calloc(count, sizeof(secp256k1_pubkey *));
|
||||
count = (*penv)->GetArrayLength(penv, jpubkeys);
|
||||
CHECKRESULT(count < 1, "pubkey array cannot be empty")
|
||||
pubkeys = calloc(count, sizeof(secp256k1_pubkey *));
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
@@ -907,6 +907,80 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
|
||||
return jnonce;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen_1counter(JNIEnv *penv, jclass clazz, jlong jctx, jlong jcounter, jbyteArray jseckey, jbyteArray jpubkey, jbyteArray jmsg32, jbyteArray jkeyaggcache, jbyteArray jextra_input32)
|
||||
{
|
||||
secp256k1_context *ctx = (secp256k1_context *)jctx;
|
||||
int result = 0;
|
||||
size_t size;
|
||||
secp256k1_musig_pubnonce pubnonce;
|
||||
secp256k1_musig_secnonce secnonce;
|
||||
jbyte *pubkey_ptr;
|
||||
secp256k1_pubkey pubkey;
|
||||
unsigned char seckey[32];
|
||||
unsigned char msg32[32];
|
||||
secp256k1_musig_keyagg_cache keyaggcache;
|
||||
unsigned char extra_input32[32];
|
||||
jbyteArray jnonce;
|
||||
jbyte *nonce_ptr = NULL;
|
||||
unsigned char nonce[fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE + fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE];
|
||||
|
||||
if (jctx == 0)
|
||||
return NULL;
|
||||
|
||||
if (jseckey == NULL)
|
||||
return NULL;
|
||||
|
||||
size = (*penv)->GetArrayLength(penv, jseckey);
|
||||
CHECKRESULT(size != 32, "invalid private key size");
|
||||
copy_bytes_from_java(penv, jseckey, size, seckey);
|
||||
|
||||
if (jpubkey == NULL)
|
||||
return NULL;
|
||||
|
||||
size = (*penv)->GetArrayLength(penv, jpubkey);
|
||||
CHECKRESULT((size != 33) && (size != 65), "invalid public key size");
|
||||
pubkey_ptr = (*penv)->GetByteArrayElements(penv, jpubkey, 0);
|
||||
result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char *)pubkey_ptr, size);
|
||||
(*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkey_ptr, 0);
|
||||
CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed");
|
||||
|
||||
if (jmsg32 != NULL)
|
||||
{
|
||||
size = (*penv)->GetArrayLength(penv, jmsg32);
|
||||
CHECKRESULT(size != 32, "invalid message size");
|
||||
copy_bytes_from_java(penv, jmsg32, size, msg32);
|
||||
}
|
||||
|
||||
if (jkeyaggcache != NULL)
|
||||
{
|
||||
size = (*penv)->GetArrayLength(penv, jkeyaggcache);
|
||||
CHECKRESULT(size != sizeof(secp256k1_musig_keyagg_cache), "invalid keyagg cache size");
|
||||
copy_bytes_from_java(penv, jkeyaggcache, size, keyaggcache.data);
|
||||
}
|
||||
|
||||
if (jextra_input32 != NULL)
|
||||
{
|
||||
size = (*penv)->GetArrayLength(penv, jextra_input32);
|
||||
CHECKRESULT(size != 32, "invalid extra input size");
|
||||
copy_bytes_from_java(penv, jextra_input32, size, extra_input32);
|
||||
}
|
||||
|
||||
result = secp256k1_musig_nonce_gen_counter(ctx, &secnonce, &pubnonce, jcounter,
|
||||
seckey, &pubkey,
|
||||
jmsg32 == NULL ? NULL : msg32, jkeyaggcache == NULL ? NULL : &keyaggcache, jextra_input32 == NULL ? NULL : extra_input32);
|
||||
CHECKRESULT(!result, "secp256k1_musig_nonce_gen failed");
|
||||
|
||||
memcpy(nonce, secnonce.data, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE);
|
||||
result = secp256k1_musig_pubnonce_serialize(ctx, nonce + fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE, &pubnonce);
|
||||
CHECKRESULT(!result, "secp256k1_musig_pubnonce_serialize failed");
|
||||
|
||||
jnonce = (*penv)->NewByteArray(penv, sizeof(nonce));
|
||||
nonce_ptr = (*penv)->GetByteArrayElements(penv, jnonce, 0);
|
||||
memcpy(nonce_ptr, nonce, sizeof(nonce));
|
||||
(*penv)->ReleaseByteArrayElements(penv, jnonce, nonce_ptr, 0);
|
||||
return jnonce;
|
||||
}
|
||||
|
||||
void free_nonces(secp256k1_musig_pubnonce **nonces, size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
@@ -17,6 +17,7 @@ if [ "$TARGET" == "linux" ]; then
|
||||
CC_OPTS="-fPIC"
|
||||
elif [ "$TARGET" == "darwin" ]; then
|
||||
OUTFILE=libsecp256k1-jni.dylib
|
||||
CC_OPTS="-arch arm64 -arch x86_64"
|
||||
elif [ "$TARGET" == "mingw" ]; then
|
||||
OUTFILE=secp256k1-jni.dll
|
||||
CC=x86_64-w64-mingw32-gcc
|
||||
|
||||
@@ -12,12 +12,8 @@ dependencies {
|
||||
val copyJni by tasks.creating(Sync::class) {
|
||||
onlyIf { org.gradle.internal.os.OperatingSystem.current().isMacOsX }
|
||||
dependsOn(":jni:jvm:buildNativeHost")
|
||||
val arch = when (System.getProperty("os.arch")) {
|
||||
"aarch64" -> "aarch64"
|
||||
else -> "x86_64"
|
||||
}
|
||||
from(rootDir.resolve("jni/jvm/build/darwin/libsecp256k1-jni.dylib"))
|
||||
into(buildDir.resolve("jniResources/fr/acinq/secp256k1/jni/native/darwin-$arch"))
|
||||
into(buildDir.resolve("jniResources/fr/acinq/secp256k1/jni/native/darwin"))
|
||||
}
|
||||
|
||||
(tasks["processResources"] as ProcessResources).apply {
|
||||
|
||||
@@ -19,7 +19,8 @@ internal object OSInfo {
|
||||
private const val PPC = "ppc"
|
||||
private const val PPC64 = "ppc64"
|
||||
|
||||
@JvmStatic val nativeSuffix: String get() = "$os-$arch"
|
||||
// on macos we build a universal library that contains arm64 and x64 binaries
|
||||
@JvmStatic val nativeSuffix: String get() = if (os == "darwin") os else "$os-$arch"
|
||||
|
||||
@JvmStatic val os: String get() = translateOSName(System.getProperty("os.name"))
|
||||
|
||||
|
||||
@@ -89,7 +89,9 @@ public class Secp256k1CFunctions {
|
||||
|
||||
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_gen(long ctx, byte[] session_rand32, byte[] seckey, byte[] pubkey, byte[] msg32, byte[] keyagg_cache, byte[] extra_input32);
|
||||
|
||||
public static native byte[] secp256k1_musig_nonce_gen_counter(long ctx, long nonrepeating_cnt, byte[] seckey, byte[] pubkey, byte[] msg32, byte[] keyagg_cache, byte[] extra_input32);
|
||||
|
||||
public static native byte[] secp256k1_musig_nonce_agg(long ctx, byte[][] nonces);
|
||||
|
||||
|
||||
@@ -92,8 +92,12 @@ 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 musigNonceGen(sessionRandom32: ByteArray, privkey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray {
|
||||
return Secp256k1CFunctions.secp256k1_musig_nonce_gen(Secp256k1Context.getContext(), sessionRandom32, privkey, pubkey, msg32, keyaggCache, extraInput32)
|
||||
}
|
||||
|
||||
override fun musigNonceGenCounter(nonRepeatingCounter: ULong, privkey: ByteArray, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray {
|
||||
return Secp256k1CFunctions.secp256k1_musig_nonce_gen_counter(Secp256k1Context.getContext(), nonRepeatingCounter.toLong(), privkey, pubkey, msg32, keyaggCache, extraInput32)
|
||||
}
|
||||
|
||||
override fun musigNonceAgg(pubnonces: Array<ByteArray>): ByteArray {
|
||||
@@ -117,6 +121,7 @@ public object NativeSecp256k1 : Secp256k1 {
|
||||
}
|
||||
|
||||
override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray {
|
||||
require(musigNonceValidate(secnonce, pubkeyCreate(privkey)))
|
||||
return Secp256k1CFunctions.secp256k1_musig_partial_sign(Secp256k1Context.getContext(), secnonce, privkey, keyaggCache, session)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,22 +12,20 @@ cd "$(dirname "$0")"
|
||||
cd secp256k1
|
||||
|
||||
if [ "$TARGET" == "mingw" ]; then
|
||||
CONF_OPTS="CFLAGS=-fPIC --host=x86_64-w64-mingw32"
|
||||
CFLAGS="-fPIC"
|
||||
CONF_OPTS=" --host=x86_64-w64-mingw32"
|
||||
elif [ "$TARGET" == "linux" ]; then
|
||||
CONF_OPTS="CFLAGS=-fPIC"
|
||||
CFLAGS="-fPIC"
|
||||
elif [ "$TARGET" == "darwin" ]; then
|
||||
CONF_OPTS=""
|
||||
CFLAGS="-arch arm64 -arch x86_64"
|
||||
LDFLAGS="-arch arm64 -arch x86_64"
|
||||
else
|
||||
echo "Unknown TARGET=$TARGET"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./autogen.sh
|
||||
if [ "$TARGET" == "darwin" ]; then
|
||||
CFLAGS="-arch arm64 -arch x86_64" ./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
|
||||
else
|
||||
./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no
|
||||
fi
|
||||
CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" ./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: dd4932b67b...77489bf422
@@ -159,15 +159,30 @@ public interface Secp256k1 {
|
||||
* 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 sessionRandom32 unique 32-byte random data that must not be reused to generate other nonces
|
||||
* @param privkey (optional) signer's private key.
|
||||
* @param aggpubkey aggregated public key of all participants in the signing session.
|
||||
* @param pubkey signer's public key
|
||||
* @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
|
||||
public fun musigNonceGen(sessionRandom32: ByteArray, privkey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray
|
||||
|
||||
/**
|
||||
* Alternative counter-based method for generating nonce.
|
||||
* 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 nonRepeatingCounter non-repeating counter that must never be reused with the same private key
|
||||
* @param privkey signer's private key.
|
||||
* @param pubkey signer's public key
|
||||
* @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 musigNonceGenCounter(nonRepeatingCounter: ULong, privkey: ByteArray, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray
|
||||
|
||||
/**
|
||||
* Aggregate public nonces from all participants of a signing session.
|
||||
@@ -217,6 +232,26 @@ public interface Secp256k1 {
|
||||
*/
|
||||
public fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* Check that a secret nonce was generated with a public key that matches the private key used for signing.
|
||||
* @param secretnonce secret nonce.
|
||||
* @param pubkey public key for the private key that will be used, with the secret nonce, to generate a partial signature.
|
||||
* @return false if the secret nonce does not match the public key.
|
||||
*/
|
||||
public fun musigNonceValidate(secretnonce: ByteArray, pubkey: ByteArray): Boolean {
|
||||
if (secretnonce.size != MUSIG2_SECRET_NONCE_SIZE) return false
|
||||
if (pubkey.size != 33 && pubkey.size != 65) return false
|
||||
val pk = Secp256k1.pubkeyParse(pubkey)
|
||||
// this is a bit hackish but the secp256k1 library does not export methods to do this cleanly
|
||||
val x = secretnonce.copyOfRange(68, 68 + 32)
|
||||
x.reverse()
|
||||
val y = secretnonce.copyOfRange(68 + 32, 68 + 32 + 32)
|
||||
y.reverse()
|
||||
val pkx = pk.copyOfRange(1, 1 + 32)
|
||||
val pky = pk.copyOfRange(33, 33 + 32)
|
||||
return x.contentEquals(pkx) && y.contentEquals(pky)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a partial signature.
|
||||
*
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.acinq.secp256k1
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.memcpy
|
||||
import platform.posix.size_tVar
|
||||
import platform.posix.uint64_t
|
||||
import secp256k1.*
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class, ExperimentalForeignApi::class)
|
||||
@@ -81,7 +82,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
return serialized.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
|
||||
}
|
||||
|
||||
private fun DeferScope.toNat(bytes: ByteArray): CPointer<UByteVar> {
|
||||
private fun DeferScope.toNat(bytes: ByteArray): CPointer<UByteVar> {
|
||||
val ubytes = bytes.asUByteArray()
|
||||
val pinned = ubytes.pin()
|
||||
this.defer { pinned.unpin() }
|
||||
@@ -112,7 +113,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
}
|
||||
|
||||
public override fun signatureNormalize(sig: ByteArray): Pair<ByteArray, Boolean> {
|
||||
require(sig.size >= 64){ "invalid signature ${Hex.encode(sig)}" }
|
||||
require(sig.size >= 64) { "invalid signature ${Hex.encode(sig)}" }
|
||||
memScoped {
|
||||
val nSig = allocSignature(sig)
|
||||
val isHighS = secp256k1_ecdsa_signature_normalize(ctx, nSig.ptr, nSig.ptr)
|
||||
@@ -291,8 +292,8 @@ public object Secp256k1Native : Secp256k1 {
|
||||
}
|
||||
}
|
||||
|
||||
override fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray {
|
||||
require(sessionId32.size == 32)
|
||||
override fun musigNonceGen(sessionRandom32: ByteArray, privkey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray {
|
||||
require(sessionRandom32.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) }
|
||||
@@ -301,13 +302,45 @@ public object Secp256k1Native : Secp256k1 {
|
||||
val nonce = memScoped {
|
||||
val secnonce = alloc<secp256k1_musig_secnonce>()
|
||||
val pubnonce = alloc<secp256k1_musig_pubnonce>()
|
||||
val nPubkey = allocPublicKey(aggpubkey)
|
||||
val nPubkey = allocPublicKey(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_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")
|
||||
secp256k1_musig_nonce_gen(
|
||||
ctx,
|
||||
secnonce.ptr,
|
||||
pubnonce.ptr,
|
||||
toNat(sessionRandom32),
|
||||
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 musigNonceGenCounter(nonRepeatingCounter: ULong, privkey: ByteArray, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray {
|
||||
require(privkey.size ==32)
|
||||
require(pubkey.size == 33 || pubkey.size == 65)
|
||||
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(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_nonce_gen_counter(ctx, secnonce.ptr, pubnonce.ptr, nonRepeatingCounter, toNat(privkey), nPubkey.ptr, msg32?.let { toNat(it) },nKeyAggCache?.ptr, extraInput32?.let { toNat(it) }).requireSuccess("secp256k1_musig_nonce_gen_counter() 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)
|
||||
@@ -339,7 +372,7 @@ public object Secp256k1Native : Secp256k1 {
|
||||
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)
|
||||
val agg = serializeXonlyPubkey(combined)
|
||||
keyaggCache?.let { blob -> nKeyAggCache?.let { memcpy(toNat(blob), it.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) } }
|
||||
return agg
|
||||
}
|
||||
@@ -386,13 +419,14 @@ public object Secp256k1Native : Secp256k1 {
|
||||
memcpy(toNat(session), nSession.ptr, Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong())
|
||||
return session
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray {
|
||||
require(secnonce.size == Secp256k1.MUSIG2_SECRET_NONCE_SIZE)
|
||||
require(privkey.size == 32)
|
||||
require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
require(session.size == Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE)
|
||||
require(musigNonceValidate(secnonce, pubkeyCreate(privkey)))
|
||||
|
||||
memScoped {
|
||||
val nSecnonce = alloc<secp256k1_musig_secnonce>()
|
||||
|
||||
@@ -108,6 +108,17 @@ class Musig2Test {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate secret nonce from counter`() {
|
||||
val sk = Hex.decode("EEC1CB7D1B7254C5CAB0D9C61AB02E643D464A59FE6C96A7EFE871F07C5AEF54")
|
||||
val pk = Secp256k1.pubkeyCreate(sk)
|
||||
val nonce = Secp256k1.musigNonceGenCounter(0UL, sk, pk, null, null, null)
|
||||
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(secnonce.copyOfRange(4, 4 + 64), Hex.decode("842F1380CD17A198FC3DAD3B7DA7492941F46976F2702FF7C66F24F472036AF1DA3F952DDE4A2DA6B6325707CE87A4E3616D06FC5F81A9C99386D20A99CECF99"))
|
||||
assertContentEquals(pubnonce, Hex.decode("03A5B9B6907942EACDDA49A366016EC2E62404A1BF4AB6D4DB82067BC3ADF086D7033205DB9EB34D5C7CE02848CAC68A83ED73E3883477F563F23CE9A11A7721EC64"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `aggregate nonces`() {
|
||||
val tests = readData("musig2/nonce_agg_vectors.json")
|
||||
|
||||
@@ -5,6 +5,14 @@ import kotlin.test.*
|
||||
|
||||
class Secp256k1Test {
|
||||
|
||||
val random = Random.Default
|
||||
|
||||
fun randomBytes(length: Int): ByteArray {
|
||||
val buffer = ByteArray(length)
|
||||
random.nextBytes(buffer)
|
||||
return buffer
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyValidPrivateKey() {
|
||||
val priv = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase())
|
||||
@@ -454,40 +462,55 @@ class Secp256k1Test {
|
||||
|
||||
@Test
|
||||
fun testMusig2SigningSession() {
|
||||
val privkeys = listOf(
|
||||
"0101010101010101010101010101010101010101010101010101010101010101",
|
||||
"0202020202020202020202020202020202020202020202020202020202020202",
|
||||
).map { Hex.decode(it) }.toTypedArray()
|
||||
val privkeys = listOf(randomBytes(32), randomBytes(32))
|
||||
val sessionId = randomBytes(32)
|
||||
val msg32 = randomBytes(32)
|
||||
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 testData = run {
|
||||
val builder = StringBuilder()
|
||||
builder.append("private keys\n")
|
||||
privkeys.forEach { builder.append(Hex.encode(it)).append("\n") }
|
||||
builder.append("sessionId ${Hex.encode(sessionId)}\n")
|
||||
builder.append("msg32 ${Hex.encode(msg32)}\n")
|
||||
builder.append("nonces\n")
|
||||
nonces.forEach { builder.append(Hex.encode(it)).append("\n") }
|
||||
builder.toString()
|
||||
}
|
||||
val secnonces = nonces.map { it.copyOfRange(0, 132) }
|
||||
val pubnonces = nonces.map { it.copyOfRange(132, 132 + 66) }
|
||||
val aggnonce = Secp256k1.musigNonceAgg(pubnonces.toTypedArray())
|
||||
|
||||
val keyaggCaches = (0 until 2).map { ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) }
|
||||
val aggpubkey = Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[0])
|
||||
assertContentEquals(aggpubkey, Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[1]))
|
||||
assertContentEquals(keyaggCaches[0], keyaggCaches[1])
|
||||
assertContentEquals(aggpubkey, Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[1]), testData)
|
||||
assertContentEquals(keyaggCaches[0], keyaggCaches[1], testData)
|
||||
|
||||
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]))
|
||||
assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it]), testData)
|
||||
assertEquals(0, Secp256k1.musigPartialSigVerify(Random.nextBytes(32), pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it]), testData)
|
||||
psig
|
||||
}
|
||||
|
||||
// signing fails if the secret nonce does not match the private key's public key
|
||||
assertFails(testData) {
|
||||
Secp256k1.musigPartialSign(secnonces[1], privkeys[0], keyaggCaches[0], sessions[0])
|
||||
}
|
||||
|
||||
assertFails(testData) {
|
||||
Secp256k1.musigPartialSign(secnonces[0], privkeys[1], keyaggCaches[1], sessions[1])
|
||||
}
|
||||
|
||||
val sig = Secp256k1.musigPartialSigAgg(sessions[0], psigs.toTypedArray())
|
||||
assertContentEquals(sig, Secp256k1.musigPartialSigAgg(sessions[1], psigs.toTypedArray()))
|
||||
assertTrue(Secp256k1.verifySchnorr(sig, msg32, aggpubkey))
|
||||
assertContentEquals(sig, Secp256k1.musigPartialSigAgg(sessions[1], psigs.toTypedArray()), testData)
|
||||
assertTrue(Secp256k1.verifySchnorr(sig, msg32, aggpubkey), testData)
|
||||
|
||||
val invalidSig1 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(psigs[0], psigs[0]))
|
||||
assertFalse(Secp256k1.verifySchnorr(invalidSig1, msg32, aggpubkey))
|
||||
assertFalse(Secp256k1.verifySchnorr(invalidSig1, msg32, aggpubkey), testData)
|
||||
val invalidSig2 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(Random.nextBytes(32), Random.nextBytes(32)))
|
||||
assertFalse(Secp256k1.verifySchnorr(invalidSig2, msg32, aggpubkey))
|
||||
assertFalse(Secp256k1.verifySchnorr(invalidSig2, msg32, aggpubkey), testData)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -523,15 +546,14 @@ class Secp256k1Test {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fuzzEcdsaSignVerify() {
|
||||
val random = Random.Default
|
||||
|
||||
fun randomBytes(length: Int): ByteArray {
|
||||
val buffer = ByteArray(length)
|
||||
random.nextBytes(buffer)
|
||||
return buffer
|
||||
fun fuzzMusig2SigningSession() {
|
||||
repeat(1000) {
|
||||
testMusig2SigningSession()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fuzzEcdsaSignVerify() {
|
||||
repeat(200) {
|
||||
val priv = randomBytes(32)
|
||||
assertTrue(Secp256k1.secKeyVerify(priv))
|
||||
|
||||
Reference in New Issue
Block a user