Compare commits

...

7 Commits

Author SHA1 Message Date
sstone
62afbc87ac
CI: build and test android library on ubuntu instead of macos 2024-06-06 10:03:24 +02:00
sstone
e77e42e139
CI: install libtool on macos-latest 2024-06-06 09:44:19 +02:00
sstone
6fce4aa625
Update secp256k1 submodule to 77489bf 2024-06-06 08:52:08 +02:00
sstone
2a3badd6b0
Update secp256k1 submodule to ae7ca3d 2024-04-22 18:25:49 +02:00
sstone
988b461975
Update secp256k1 submodule to 3b6c90a 2024-04-22 18:25:48 +02:00
sstone
6fc2f0086a
Implement secp256k1_musig_nonce_gen_counter() 2024-04-22 18:25:45 +02:00
sstone
10e079ffeb
Update secp256k1 submodule
We're now at 461970682f56a8e15fc71ecab18d4537e50441fc.
2024-04-22 18:15:46 +02:00
11 changed files with 160 additions and 22 deletions

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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 {

@ -1 +1 @@
Subproject commit dd4932b67b573b2366e729e869918b17964f5f83
Subproject commit 77489bf422e3714b5a5f6427b97ea4ef91bd34dc

View File

@ -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.

View File

@ -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)
@ -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,7 +302,7 @@ 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())
@ -311,7 +312,7 @@ public object Secp256k1Native : Secp256k1 {
ctx,
secnonce.ptr,
pubnonce.ptr,
toNat(sessionId32),
toNat(sessionRandom32),
privkey?.let { toNat(it) },
nPubkey.ptr,
msg32?.let { toNat(it) },
@ -324,6 +325,29 @@ public object Secp256k1Native : Secp256k1 {
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)
}
return nonce
}
override fun musigNonceAgg(pubnonces: Array<ByteArray>): ByteArray {
require(pubnonces.isNotEmpty())
pubnonces.forEach { require(it.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) }

View File

@ -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")