From 4bdc836556e0d21c84cb53c778f23b18b0a4c05e Mon Sep 17 00:00:00 2001 From: Salomon BRYS Date: Mon, 29 Jun 2020 17:10:58 +0200 Subject: [PATCH] Explicit Signature & PubKey formats + bug fixes --- build.gradle.kts | 2 +- native/jni/src/org_bitcoin_NativeSecp256k1.c | 80 ++++++++----- native/jni/src/org_bitcoin_NativeSecp256k1.h | 24 ++-- .../kotlin/fr/acinq/secp256k1/Secp256k1.kt | 14 ++- .../fr/acinq/secp256k1/Secp256k1Test.kt | 88 ++++++++------ .../kotlin/fr/acinq/secp256k1/Secp256k1.kt | 10 +- .../kotlin/org/bitcoin/NativeSecp256k1.kt | 74 +++++------- .../kotlin/fr/acinq/secp256k1/Secp256k1.kt | 112 +++++++++--------- 8 files changed, 217 insertions(+), 187 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5bd8226..c9d4d99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { `maven-publish` } group = "fr.acinq.secp256k1" -version = "0.1.0-1.4-M2" +version = "0.2.0-1.4-M2" repositories { jcenter() diff --git a/native/jni/src/org_bitcoin_NativeSecp256k1.c b/native/jni/src/org_bitcoin_NativeSecp256k1.c index 92d8f5d..ebaf366 100644 --- a/native/jni/src/org_bitcoin_NativeSecp256k1.c +++ b/native/jni/src/org_bitcoin_NativeSecp256k1.c @@ -76,7 +76,7 @@ SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1ve } SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jboolean compact, jlong ctx_l) { secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); @@ -91,10 +91,14 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e int ret = secp256k1_ecdsa_sign(ctx, &sig, data, secKey, NULL, NULL); unsigned char outputSer[72]; - size_t outputLen = 72; + size_t outputLen = compact ? 64 : 72; if( ret ) { - int ret2 = secp256k1_ecdsa_signature_serialize_der(ctx,outputSer, &outputLen, &sig ); (void)ret2; + if (compact) { + int ret2 = secp256k1_ecdsa_signature_serialize_compact(ctx, outputSer, &sig ); (void)ret2; + } else { + int ret2 = secp256k1_ecdsa_signature_serialize_der(ctx, outputSer, &outputLen, &sig ); (void)ret2; + } } intsarray[0] = outputLen; @@ -117,30 +121,44 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e return retArray; } -JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign_1compact -(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1normalize + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jint siglen, jboolean compact, jlong ctx_l) { secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; - unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); - unsigned char* secKey = (unsigned char*) (data + 32); + unsigned char* sigdata = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); jobjectArray retArray; jbyteArray sigArray, intsByteArray; - unsigned char intsarray[2]; + unsigned char intsarray[3]; - secp256k1_ecdsa_signature sig[72]; + secp256k1_ecdsa_signature sig; - int ret = secp256k1_ecdsa_sign(ctx, sig, data, secKey, NULL, NULL); + int ret = 0; + if (siglen == 64) { + ret = secp256k1_ecdsa_signature_parse_compact(ctx, &sig, sigdata); + } else { + ret = secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigdata, siglen); + } - unsigned char outputSer[64]; - size_t outputLen = 64; + int ret2 = 0; + if (ret) { + ret2 = secp256k1_ecdsa_signature_normalize(ctx, &sig, &sig); + } + + unsigned char outputSer[72]; + size_t outputLen = compact ? 64 : 72; if( ret ) { - int ret2 = secp256k1_ecdsa_signature_serialize_compact(ctx,outputSer, sig ); (void)ret2; + if (compact) { + int ret3 = secp256k1_ecdsa_signature_serialize_compact(ctx, outputSer, &sig ); (void)ret3; + } else { + int ret3 = secp256k1_ecdsa_signature_serialize_der(ctx, outputSer, &outputLen, &sig ); (void)ret3; + } } intsarray[0] = outputLen; intsarray[1] = ret; + intsarray[2] = ret2; retArray = (*env)->NewObjectArray(env, 2, (*env)->FindClass(env, "[B"), @@ -150,15 +168,15 @@ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa (*env)->SetByteArrayRegion(env, sigArray, 0, outputLen, (jbyte*)outputSer); (*env)->SetObjectArrayElement(env, retArray, 0, sigArray); - intsByteArray = (*env)->NewByteArray(env, 2); - (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); + intsByteArray = (*env)->NewByteArray(env, 3); + (*env)->SetByteArrayRegion(env, intsByteArray, 0, 3, (jbyte*)intsarray); (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); (void)classObject; return retArray; -} +} SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1seckey_1verify (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) @@ -172,7 +190,7 @@ SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1secke } SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1create - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jboolean compressed, jlong ctx_l) { secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; const unsigned char* secKey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); @@ -186,10 +204,10 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e int ret = secp256k1_ec_pubkey_create(ctx, &pubkey, secKey); unsigned char outputSer[65]; - size_t outputLen = 65; + size_t outputLen = compressed ? 33 : 65; if( ret ) { - int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; + int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey, compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED );(void)ret2; } intsarray[0] = outputLen; @@ -213,7 +231,7 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e } SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1parse - (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint inputlen) + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint inputlen, jboolean compressed) { secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; const unsigned char* pubkeydata = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); @@ -227,10 +245,10 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeydata, inputlen); unsigned char outputSer[65]; - size_t outputLen = 65; + size_t outputLen = compressed ? 33 : 65; if( ret ) { - int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey, SECP256K1_EC_UNCOMPRESSED );(void)ret2; + int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey, compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED );(void)ret2; } intsarray[0] = outputLen; @@ -412,7 +430,7 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1p jbyteArray pubArray, intsByteArray; unsigned char intsarray[2]; unsigned char outputSer[65]; - size_t outputLen = 65; + size_t outputLen = publen; secp256k1_pubkey pubkey; int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen); @@ -422,7 +440,7 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1p } if( ret ) { - int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; + int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey, publen == 33 ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);(void)ret2; } intsarray[0] = outputLen; @@ -456,7 +474,7 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1p jbyteArray pubArray, intsByteArray; unsigned char intsarray[2]; unsigned char outputSer[65]; - size_t outputLen = 65; + size_t outputLen = publen; secp256k1_pubkey pubkey; int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen); @@ -466,7 +484,7 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1p } if( ret ) { - int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; + int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey, publen == 33 ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);(void)ret2; } intsarray[0] = outputLen; @@ -501,7 +519,7 @@ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1p secp256k1_pubkey pubkey1, pubkey2; const secp256k1_pubkey *pubkeys[2]; unsigned char outputSer[65]; - size_t outputLen = 65; + size_t outputLen = publen1; int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey1, pubdata1, publen1); if (ret) { @@ -514,7 +532,7 @@ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1p ret = secp256k1_ec_pubkey_combine(ctx, &result, pubkeys, 2); } if (ret) { - ret = secp256k1_ec_pubkey_serialize(ctx, outputSer, &outputLen, &result, SECP256K1_EC_UNCOMPRESSED ); + ret = secp256k1_ec_pubkey_serialize(ctx, outputSer, &outputLen, &result, publen1 == 33 ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED ); } intsarray[0] = outputLen; intsarray[1] = ret; @@ -588,7 +606,7 @@ SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1e * Signature: (Ljava/nio/ByteBuffer;JI)[[B */ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1recover - (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint recid) + (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint recid, jboolean compressed) { secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; const unsigned char* sigdata = (*env)->GetDirectBufferAddress(env, byteBufferObject); @@ -596,7 +614,7 @@ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa secp256k1_ecdsa_recoverable_signature sig; secp256k1_pubkey pub; unsigned char outputSer[65]; - size_t outputLen = 65; + size_t outputLen = compressed ? 33 : 65; jobjectArray retArray; jbyteArray pubArray, intsByteArray; unsigned char intsarray[1]; @@ -605,7 +623,7 @@ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa if (ret) { ret = secp256k1_ecdsa_recover(ctx, &pub, &sig, msgdata); if (ret) { - ret = secp256k1_ec_pubkey_serialize(ctx, outputSer, &outputLen, &pub, SECP256K1_EC_UNCOMPRESSED ); + ret = secp256k1_ec_pubkey_serialize(ctx, outputSer, &outputLen, &pub, compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED ); } } diff --git a/native/jni/src/org_bitcoin_NativeSecp256k1.h b/native/jni/src/org_bitcoin_NativeSecp256k1.h index 2e739a5..8e1c2e0 100644 --- a/native/jni/src/org_bitcoin_NativeSecp256k1.h +++ b/native/jni/src/org_bitcoin_NativeSecp256k1.h @@ -90,18 +90,18 @@ JNIEXPORT jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1verify /* * Class: org_bitcoin_NativeSecp256k1 * Method: secp256k1_ecdsa_sign - * Signature: (Ljava/nio/ByteBuffer;J)[[B + * Signature: (Ljava/nio/ByteBuffer;ZJ)[[B */ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign - (JNIEnv *, jclass, jobject, jlong); + (JNIEnv *, jclass, jobject, jboolean, jlong); /* * Class: org_bitcoin_NativeSecp256k1 - * Method: secp256k1_ecdsa_sign_compact - * Signature: (Ljava/nio/ByteBuffer;J)[[B + * Method: secp256k1_ecdsa_normalize + * Signature: (Ljava/nio/ByteBuffer;I114:1ZJ)[[B */ -JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign_1compact - (JNIEnv *, jclass, jobject, jlong); +JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1normalize + (JNIEnv *, jclass, jobject, jint, jboolean, jlong); /* * Class: org_bitcoin_NativeSecp256k1 @@ -114,18 +114,18 @@ JNIEXPORT jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1seckey_1v /* * Class: org_bitcoin_NativeSecp256k1 * Method: secp256k1_ec_pubkey_create - * Signature: (Ljava/nio/ByteBuffer;J)[[B + * Signature: (Ljava/nio/ByteBuffer;ZJ)[[B */ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1create - (JNIEnv *, jclass, jobject, jlong); + (JNIEnv *, jclass, jobject, jboolean, jlong); /* * Class: org_bitcoin_NativeSecp256k1 * Method: secp256k1_ec_pubkey_parse - * Signature: (Ljava/nio/ByteBuffer;JI)[[B + * Signature: (Ljava/nio/ByteBuffer;JIZ)[[B */ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1parse - (JNIEnv *, jclass, jobject, jlong, jint); + (JNIEnv *, jclass, jobject, jlong, jint, jboolean); /* * Class: org_bitcoin_NativeSecp256k1 @@ -146,10 +146,10 @@ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdh /* * Class: org_bitcoin_NativeSecp256k1 * Method: secp256k1_ecdsa_recover - * Signature: (Ljava/nio/ByteBuffer;JI)[[B + * Signature: (Ljava/nio/ByteBuffer;JIZ)[[B */ JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1recover - (JNIEnv *, jclass, jobject, jlong, jint); + (JNIEnv *, jclass, jobject, jlong, jint, jboolean); #ifdef __cplusplus } diff --git a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt index e327f81..a1be21d 100644 --- a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt +++ b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt @@ -19,19 +19,23 @@ package fr.acinq.secp256k1 import kotlin.jvm.JvmStatic import kotlin.jvm.Synchronized +public enum class SigFormat(internal val size: Int) { COMPACT(64), DER(72) } + +public enum class PubKeyFormat(internal val size: Int) { COMPRESSED(33), UNCOMPRESSED(65) } + public expect object Secp256k1 { public fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean - public fun sign(data: ByteArray, sec: ByteArray): ByteArray + public fun sign(data: ByteArray, sec: ByteArray, format: SigFormat): ByteArray - public fun signCompact(data: ByteArray, sec: ByteArray): ByteArray + public fun signatureNormalize(sig: ByteArray, format: SigFormat): Pair public fun secKeyVerify(seckey: ByteArray): Boolean - public fun computePubkey(seckey: ByteArray): ByteArray + public fun computePubkey(seckey: ByteArray, format: PubKeyFormat): ByteArray - public fun parsePubkey(pubkey: ByteArray): ByteArray + public fun parsePubkey(pubkey: ByteArray, format: PubKeyFormat): ByteArray public fun cleanup() @@ -51,7 +55,7 @@ public expect object Secp256k1 { public fun createECDHSecret(seckey: ByteArray, pubkey: ByteArray): ByteArray - public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray + public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int, format: PubKeyFormat): ByteArray public fun randomize(seed: ByteArray): Boolean } \ No newline at end of file diff --git a/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt b/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt index 18f61fe..d8f09d1 100644 --- a/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt +++ b/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt @@ -2,6 +2,7 @@ package fr.acinq.secp256k1 import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -20,10 +21,10 @@ class Secp256k1Test { val sig: ByteArray = Hex.decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()) val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()) result = Secp256k1.verify(data, sig, pub) - assertEquals(result, true, "testVerifyPos") + assertTrue(result, "testVerifyPos") val sigCompact: ByteArray = Hex.decode("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()) result = Secp256k1.verify(data, sigCompact, pub) - assertEquals(result, true, "testVerifyPos") + assertTrue(result, "testVerifyPos") } /** @@ -37,7 +38,7 @@ class Secp256k1Test { val pub: ByteArray = Hex.decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()) result = Secp256k1.verify(data, sig, pub) //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); - assertEquals(result, false, "testVerifyNeg") + assertFalse(result, "testVerifyNeg") } /** @@ -49,7 +50,7 @@ class Secp256k1Test { val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()) result = Secp256k1.secKeyVerify(sec) //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); - assertEquals(result, true, "testSecKeyVerifyPos") + assertTrue(result, "testSecKeyVerifyPos") } /** @@ -61,7 +62,7 @@ class Secp256k1Test { val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()) result = Secp256k1.secKeyVerify(sec) //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); - assertEquals(result, false, "testSecKeyVerifyNeg") + assertFalse(result, "testSecKeyVerifyNeg") } /** @@ -70,11 +71,11 @@ class Secp256k1Test { @Test fun testPubKeyCreatePos() { val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()) - val resultArr: ByteArray = Secp256k1.computePubkey(sec) + val resultArr: ByteArray = Secp256k1.computePubkey(sec, PubKeyFormat.UNCOMPRESSED) val pubkeyString: String = Hex.encode(resultArr).toUpperCase() assertEquals( - pubkeyString, "04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6", + pubkeyString, "testPubKeyCreatePos" ) } @@ -85,26 +86,26 @@ class Secp256k1Test { @Test fun testPubKeyCreateNeg() { val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()) - val resultArr: ByteArray = Secp256k1.computePubkey(sec) + val resultArr: ByteArray = Secp256k1.computePubkey(sec, PubKeyFormat.UNCOMPRESSED) val pubkeyString: String = Hex.encode(resultArr).toUpperCase() - assertEquals(pubkeyString, "", "testPubKeyCreateNeg") + assertEquals("", pubkeyString, "testPubKeyCreateNeg") } @Test fun testPubKeyNegatePos() { val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()) - val pubkey: ByteArray = Secp256k1.computePubkey(sec) + val pubkey: ByteArray = Secp256k1.computePubkey(sec, PubKeyFormat.UNCOMPRESSED) val pubkeyString: String = Hex.encode(pubkey).toUpperCase() assertEquals( - pubkeyString, "04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6", + pubkeyString, "testPubKeyCreatePos" ) val pubkey1: ByteArray = Secp256k1.pubKeyNegate(pubkey) val pubkeyString1: String = Hex.encode(pubkey1).toUpperCase() assertEquals( - pubkeyString1, "04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2DDEFC12B6B8E73968536514302E69ED1DDB24B999EFEE79C12D03AB17E79E1989", + pubkeyString1, "testPubKeyNegatePos" ) } @@ -115,11 +116,11 @@ class Secp256k1Test { @Test fun testPubKeyParse() { val pub: ByteArray = Hex.decode("02C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D".toLowerCase()) - val resultArr: ByteArray = Secp256k1.parsePubkey(pub) + val resultArr: ByteArray = Secp256k1.parsePubkey(pub, PubKeyFormat.UNCOMPRESSED) val pubkeyString: String = Hex.encode(resultArr).toUpperCase() assertEquals( - pubkeyString, "04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6", + pubkeyString, "testPubKeyAdd" ) } @@ -131,8 +132,8 @@ class Secp256k1Test { val pub3: ByteArray = Secp256k1.pubKeyAdd(pub1, pub2) val pubkeyString: String = Hex.encode(pub3).toUpperCase() assertEquals( - pubkeyString, "04531FE6068134503D2723133227C867AC8FA6C83C537E9A44C3C5BDBDCB1FE3379E92C265E71E481BA82A84675A47AC705A200FCD524E92D93B0E7386F26A5458", + pubkeyString, "testPubKeyAdd" ) } @@ -144,15 +145,28 @@ class Secp256k1Test { fun testSignPos() { val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing" val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()) - val resultArr: ByteArray = Secp256k1.sign(data, sec) + val resultArr: ByteArray = Secp256k1.sign(data, sec, SigFormat.DER) val sigString: String = Hex.encode(resultArr).toUpperCase() assertEquals( - sigString, "30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9", + sigString, "testSignPos" ) } + @Test + fun testSignatureNormalize() { + val data: ByteArray = Hex.decode("30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9".toLowerCase()) + val (resultArr, isHighS) = Secp256k1.signatureNormalize(data, SigFormat.COMPACT) + val sigString: String = Hex.encode(resultArr).toUpperCase() + assertEquals( + "182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9", + sigString, + "testSignPos" + ) + assertFalse(isHighS, "isHighS") + } + /** * This tests sign() for a invalid secretkey */ @@ -160,20 +174,20 @@ class Secp256k1Test { fun testSignNeg() { val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing" val sec: ByteArray = Hex.decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()) - val resultArr: ByteArray = Secp256k1.sign(data, sec) + val resultArr: ByteArray = Secp256k1.sign(data, sec, SigFormat.DER) val sigString: String = Hex.encode(resultArr) - assertEquals(sigString, "", "testSignNeg") + assertEquals("", sigString, "testSignNeg") } @Test fun testSignCompactPos() { val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing" val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()) - val resultArr: ByteArray = Secp256k1.signCompact(data, sec) + val resultArr: ByteArray = Secp256k1.sign(data, sec, SigFormat.COMPACT) val sigString: String = Hex.encode(resultArr).toUpperCase() assertEquals( - sigString, "182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A21C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9", + sigString, "testSignCompactPos" ) //assertEquals( sigString, "30 44 02 20 182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A2 02 20 1C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9" , "testSignPos"); @@ -184,8 +198,8 @@ class Secp256k1Test { val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()) val sec1: ByteArray = Secp256k1.privKeyNegate(sec) assertEquals( - Hex.encode(sec1).toUpperCase(), "981A9A7DD677A622518DA068D66D5F824E5F22F084B8A0E2F195B5662F300C11", + Hex.encode(sec1).toUpperCase(), "testPrivKeyNegate" ) val sec2: ByteArray = Secp256k1.privKeyNegate(sec1) @@ -201,7 +215,11 @@ class Secp256k1Test { val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()) //sha256hash of "tweak" val resultArr: ByteArray = Secp256k1.privKeyTweakAdd(sec, data) val sigString: String = Hex.encode(resultArr).toUpperCase() - assertEquals(sigString, "A168571E189E6F9A7E2D657A4B53AE99B909F7E712D1C23CED28093CD57C88F3", "testPrivKeyAdd_1") + assertEquals( + "A168571E189E6F9A7E2D657A4B53AE99B909F7E712D1C23CED28093CD57C88F3", + sigString, + "testPrivKeyAdd_1" + ) } /** @@ -213,7 +231,11 @@ class Secp256k1Test { val data: ByteArray = Hex.decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()) //sha256hash of "tweak" val resultArr: ByteArray = Secp256k1.privKeyTweakMul(sec, data) val sigString: String = Hex.encode(resultArr).toUpperCase() - assertEquals(sigString, "97F8184235F101550F3C71C927507651BD3F1CDB4A5A33B8986ACF0DEE20FFFC", "testPrivKeyMul_1") + assertEquals( + "97F8184235F101550F3C71C927507651BD3F1CDB4A5A33B8986ACF0DEE20FFFC", + sigString, + "testPrivKeyMul_1" + ) } /** @@ -226,8 +248,8 @@ class Secp256k1Test { val resultArr: ByteArray = Secp256k1.pubKeyTweakAdd(pub, data) val sigString: String = Hex.encode(resultArr).toUpperCase() assertEquals( - sigString, "0411C6790F4B663CCE607BAAE08C43557EDC1A4D11D88DFCB3D841D0C6A941AF525A268E2A863C148555C48FB5FBA368E88718A46E205FABC3DBA2CCFFAB0796EF", + sigString, "testPrivKeyAdd_2" ) } @@ -242,8 +264,8 @@ class Secp256k1Test { val resultArr: ByteArray = Secp256k1.pubKeyTweakMul(pub, data) val sigString: String = Hex.encode(resultArr).toUpperCase() assertEquals( - sigString, "04E0FE6FE55EBCA626B98A807F6CAF654139E14E5E3698F01A9A658E21DC1D2791EC060D4F412A794D5370F672BC94B722640B5F76914151CFCA6E712CA48CC589", + sigString, "testPrivKeyMul_2" ) } @@ -255,7 +277,7 @@ class Secp256k1Test { fun testRandomize() { val seed: ByteArray = Hex.decode("A441B15FE9A3CF56661190A0B93B9DEC7D04127288CC87250967CF3B52894D11".toLowerCase()) //sha256hash of "random" val result: Boolean = Secp256k1.randomize(seed) - assertEquals(result, true, "testRandomize") + assertTrue(result, "testRandomize") } @Test @@ -265,8 +287,8 @@ class Secp256k1Test { val resultArr: ByteArray = Secp256k1.createECDHSecret(sec, pub) val ecdhString: String = Hex.encode(resultArr).toUpperCase() assertEquals( - ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043", + ecdhString, "testCreateECDHSecret" ) } @@ -275,10 +297,10 @@ class Secp256k1Test { fun testEcdsaRecover() { val data: ByteArray = Hex.decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()) //sha256hash of "testing" val sec: ByteArray = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()) - val pub: ByteArray = Secp256k1.computePubkey(sec) - val sig: ByteArray = Secp256k1.signCompact(data, sec) - val pub0: ByteArray = Secp256k1.ecdsaRecover(sig, data, 0) - val pub1: ByteArray = Secp256k1.ecdsaRecover(sig, data, 1) + val pub: ByteArray = Secp256k1.computePubkey(sec, PubKeyFormat.UNCOMPRESSED) + val sig: ByteArray = Secp256k1.sign(data, sec, SigFormat.COMPACT) + val pub0: ByteArray = Secp256k1.ecdsaRecover(sig, data, 0, PubKeyFormat.UNCOMPRESSED) + val pub1: ByteArray = Secp256k1.ecdsaRecover(sig, data, 1, PubKeyFormat.UNCOMPRESSED) assertTrue(pub.contentEquals(pub0) || pub.contentEquals(pub1), "testEcdsaRecover") } -} \ No newline at end of file +} diff --git a/src/jvmAndAndroidMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt b/src/jvmAndAndroidMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt index 39ab393..aa19c98 100644 --- a/src/jvmAndAndroidMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt +++ b/src/jvmAndAndroidMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt @@ -30,15 +30,15 @@ public actual object Secp256k1 { public actual fun verify(data: ByteArray, signature: ByteArray, pub: ByteArray): Boolean = NativeSecp256k1.verify(data, signature, pub) - public actual fun sign(data: ByteArray, sec: ByteArray): ByteArray = NativeSecp256k1.sign(data, sec) + public actual fun sign(data: ByteArray, sec: ByteArray, format: SigFormat): ByteArray = NativeSecp256k1.sign(data, sec, format == SigFormat.COMPACT) - public actual fun signCompact(data: ByteArray, sec: ByteArray): ByteArray = NativeSecp256k1.signCompact(data, sec) + public actual fun signatureNormalize(sig: ByteArray, format: SigFormat): Pair = NativeSecp256k1.signatureNormalize(sig, format == SigFormat.COMPACT) public actual fun secKeyVerify(seckey: ByteArray): Boolean = NativeSecp256k1.secKeyVerify(seckey) - public actual fun computePubkey(seckey: ByteArray): ByteArray = NativeSecp256k1.computePubkey(seckey) + public actual fun computePubkey(seckey: ByteArray, format: PubKeyFormat): ByteArray = NativeSecp256k1.computePubkey(seckey, format == PubKeyFormat.COMPRESSED) - public actual fun parsePubkey(pubkey: ByteArray): ByteArray = NativeSecp256k1.parsePubkey(pubkey) + public actual fun parsePubkey(pubkey: ByteArray, format: PubKeyFormat): ByteArray = NativeSecp256k1.parsePubkey(pubkey, format == PubKeyFormat.COMPRESSED) public actual fun cleanup(): Unit = NativeSecp256k1.cleanup() @@ -58,7 +58,7 @@ public actual object Secp256k1 { public actual fun createECDHSecret(seckey: ByteArray, pubkey: ByteArray): ByteArray = NativeSecp256k1.createECDHSecret(seckey, pubkey) - public actual fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray = NativeSecp256k1.ecdsaRecover(sig, message, recid) + public actual fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int, format: PubKeyFormat): ByteArray = NativeSecp256k1.ecdsaRecover(sig, message, recid, format == PubKeyFormat.COMPRESSED) public actual fun randomize(seed: ByteArray): Boolean = NativeSecp256k1.randomize(seed) diff --git a/src/jvmAndAndroidMain/kotlin/org/bitcoin/NativeSecp256k1.kt b/src/jvmAndAndroidMain/kotlin/org/bitcoin/NativeSecp256k1.kt index b1be0f0..300dc12 100644 --- a/src/jvmAndAndroidMain/kotlin/org/bitcoin/NativeSecp256k1.kt +++ b/src/jvmAndAndroidMain/kotlin/org/bitcoin/NativeSecp256k1.kt @@ -92,14 +92,15 @@ public object NativeSecp256k1 { /** * libsecp256k1 Create an ECDSA signature. * - * @param data Message hash, 32 bytes - * @param sec Secret key, 32 bytes + * @param data Message hash, 32 bytes + * @param sec Secret key, 32 bytes + * @param compact True for compact signature, false for DER * @return a signature, or an empty array is signing failed * @throws AssertFailException in case of failure */ @JvmStatic @Throws(AssertFailException::class) - public fun sign(data: ByteArray, sec: ByteArray): ByteArray { + public fun sign(data: ByteArray, sec: ByteArray, compact: Boolean): ByteArray { require(data.size == 32 && sec.size <= 32) val byteBuff = pack(data, sec) val retByteArray: Array @@ -107,6 +108,7 @@ public object NativeSecp256k1 { retByteArray = try { secp256k1_ecdsa_sign( byteBuff, + compact, Secp256k1Context.getContext() ) } finally { @@ -123,27 +125,18 @@ public object NativeSecp256k1 { return if (retVal == 0) ByteArray(0) else sigArr } - /** - * libsecp256k1 Create an ECDSA signature. - * - * @param data Message hash, 32 bytes - * @param sec Secret key, 32 bytes - * - * - * Return values - * @return a signature, or an empty array is signing failed - * @throws AssertFailException in case of failure - */ @JvmStatic @Throws(AssertFailException::class) - public fun signCompact(data: ByteArray, sec: ByteArray): ByteArray { - require(data.size == 32 && sec.size <= 32) - val byteBuff = pack(data, sec) + public fun signatureNormalize(sig: ByteArray, compact: Boolean): Pair { + require(sig.size == 64 || sig.size in 70..73) + val byteBuff = pack(sig) val retByteArray: Array r.lock() retByteArray = try { - secp256k1_ecdsa_sign_compact( + secp256k1_ecdsa_normalize( byteBuff, + sig.size, + compact, Secp256k1Context.getContext() ) } finally { @@ -152,12 +145,13 @@ public object NativeSecp256k1 { val sigArr = retByteArray[0] val sigLen = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt() + val retBool = BigInteger(byteArrayOf(retByteArray[1][2])).toInt() NativeSecp256k1Util.assertEquals( sigArr.size, sigLen, "Got bad signature length." ) - return if (retVal == 0) ByteArray(0) else sigArr + return (if (retVal == 0) ByteArray(0) else sigArr) to (retBool == 1) } /** @@ -191,7 +185,7 @@ public object NativeSecp256k1 { //TODO add a 'compressed' arg @JvmStatic @Throws(AssertFailException::class) - public fun computePubkey(seckey: ByteArray): ByteArray { + public fun computePubkey(seckey: ByteArray, compressed: Boolean): ByteArray { require(seckey.size == 32) val byteBuff = pack(seckey) val retByteArray: Array @@ -199,6 +193,7 @@ public object NativeSecp256k1 { retByteArray = try { secp256k1_ec_pubkey_create( byteBuff, + compressed, Secp256k1Context.getContext() ) } finally { @@ -218,7 +213,7 @@ public object NativeSecp256k1 { */ @JvmStatic @Throws(AssertFailException::class) - public fun parsePubkey(pubkey: ByteArray): ByteArray { + public fun parsePubkey(pubkey: ByteArray, compressed: Boolean): ByteArray { require(pubkey.size == 33 || pubkey.size == 65) val byteBuff = pack(pubkey) val retByteArray: Array @@ -227,7 +222,8 @@ public object NativeSecp256k1 { secp256k1_ec_pubkey_parse( byteBuff, Secp256k1Context.getContext(), - pubkey.size + pubkey.size, + compressed ) } finally { r.unlock() @@ -235,7 +231,7 @@ public object NativeSecp256k1 { val pubArr = retByteArray[0] BigInteger(byteArrayOf(retByteArray[1][0])).toInt() val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt() - NativeSecp256k1Util.assertEquals(pubArr.size, 65, "Got bad pubkey length.") + NativeSecp256k1Util.assertEquals(pubArr.size, if (compressed) 33 else 65, "Got bad pubkey length.") return if (retVal == 0) ByteArray(0) else pubArr } @@ -456,7 +452,7 @@ public object NativeSecp256k1 { val pubArr = retByteArray[0] val pubLen: Int = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() and 0xFF val retVal = BigInteger(byteArrayOf(retByteArray[1][1])).toInt() - NativeSecp256k1Util.assertEquals(65, pubLen, "Got bad pubkey length.") + NativeSecp256k1Util.assertEquals(pubkey1.size, pubLen, "Got bad pubkey length.") NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.") return pubArr } @@ -494,7 +490,7 @@ public object NativeSecp256k1 { @JvmStatic @Throws(AssertFailException::class) - public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray { + public fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int, compressed: Boolean): ByteArray { require(sig.size == 64) require(message.size == 32) val byteBuff = pack(sig, message) @@ -504,14 +500,15 @@ public object NativeSecp256k1 { secp256k1_ecdsa_recover( byteBuff, Secp256k1Context.getContext(), - recid + recid, + compressed ) } finally { r.unlock() } val resArr = retByteArray[0] val retVal = BigInteger(byteArrayOf(retByteArray[1][0])).toInt() - NativeSecp256k1Util.assertEquals(resArr.size, 65, "Got bad result length.") + NativeSecp256k1Util.assertEquals(resArr.size, if (compressed) 33 else 65, "Got bad result length.") NativeSecp256k1Util.assertEquals(retVal, 1, "Failed return value check.") return resArr } @@ -549,23 +546,12 @@ public object NativeSecp256k1 { @JvmStatic private external fun secp256k1_pubkey_tweak_mul(byteBuff: ByteBuffer, context: Long, pubLen: Int): Array @JvmStatic private external fun secp256k1_destroy_context(context: Long) @JvmStatic private external fun secp256k1_ecdsa_verify(byteBuff: ByteBuffer, context: Long, sigLen: Int, pubLen: Int): Int - @JvmStatic private external fun secp256k1_ecdsa_sign(byteBuff: ByteBuffer, context: Long): Array - @JvmStatic private external fun secp256k1_ecdsa_sign_compact(byteBuff: ByteBuffer, context: Long): Array + @JvmStatic private external fun secp256k1_ecdsa_sign(byteBuff: ByteBuffer, compact: Boolean, context: Long): Array + @JvmStatic private external fun secp256k1_ecdsa_normalize(byteBuff: ByteBuffer, sigLen: Int, compact: Boolean, context: Long): Array @JvmStatic private external fun secp256k1_ec_seckey_verify(byteBuff: ByteBuffer, context: Long): Int - @JvmStatic private external fun secp256k1_ec_pubkey_create(byteBuff: ByteBuffer, context: Long): Array - @JvmStatic private external fun secp256k1_ec_pubkey_parse( - byteBuff: ByteBuffer, - context: Long, - inputLen: Int - ): Array - - @JvmStatic private external fun secp256k1_ec_pubkey_add( - byteBuff: ByteBuffer, - context: Long, - lent1: Int, - len2: Int - ): Array - + @JvmStatic private external fun secp256k1_ec_pubkey_create(byteBuff: ByteBuffer, compressed: Boolean, context: Long): Array + @JvmStatic private external fun secp256k1_ec_pubkey_parse(byteBuff: ByteBuffer, context: Long, inputLen: Int, compressed: Boolean): Array + @JvmStatic private external fun secp256k1_ec_pubkey_add(byteBuff: ByteBuffer, context: Long, lent1: Int, len2: Int): Array @JvmStatic private external fun secp256k1_ecdh(byteBuff: ByteBuffer, context: Long, inputLen: Int): Array - @JvmStatic private external fun secp256k1_ecdsa_recover(byteBuff: ByteBuffer, context: Long, recid: Int): Array + @JvmStatic private external fun secp256k1_ecdsa_recover(byteBuff: ByteBuffer, context: Long, recid: Int, compressed: Boolean): Array } \ No newline at end of file diff --git a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt index cd3d3cd..a63af12 100644 --- a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt +++ b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt @@ -7,10 +7,6 @@ import secp256k1.* @OptIn(ExperimentalUnsignedTypes::class) public actual object Secp256k1 { - private const val SIG_FORMAT_UNKNOWN = 0 - private const val SIG_FORMAT_COMPACT = 1 - private const val SIG_FORMAT_DER = 2 - private val ctx: CPointer by lazy { secp256k1_context_create((SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_SIGN or SECP256K1_FLAGS_BIT_CONTEXT_VERIFY).toUInt()) ?: error("Could not create segp256k1 context") @@ -19,22 +15,34 @@ public actual object Secp256k1 { private fun Int.requireSuccess() = require(this == 1) { "secp256k1 native function call failed" } private fun MemScope.allocSignature(input: ByteArray): secp256k1_ecdsa_signature { - val sigFormat = when (input.size) { - 64 -> SIG_FORMAT_COMPACT - 70, 71, 72, 73 -> SIG_FORMAT_DER - else -> SIG_FORMAT_UNKNOWN - } val sig = alloc() val nativeBytes = toNat(input) - val result = when (sigFormat) { - SIG_FORMAT_COMPACT -> secp256k1_ecdsa_signature_parse_compact(ctx, sig.ptr, nativeBytes) - SIG_FORMAT_DER -> secp256k1_ecdsa_signature_parse_der(ctx, sig.ptr, nativeBytes, input.size.toULong()) - else -> 0 + + val result = when (input.size) { + 64 -> secp256k1_ecdsa_signature_parse_compact(ctx, sig.ptr, nativeBytes) + in 70..73 -> secp256k1_ecdsa_signature_parse_der(ctx, sig.ptr, nativeBytes, input.size.convert()) + else -> error("Unknown signature format") } - require(result == 1) { "cannot parse signature (size = ${input.size}, format = $sigFormat sig = ${Hex.encode(input)}" } + require(result == 1) { "cannot parse signature (size = ${input.size} sig = ${Hex.encode(input)}" } return sig } + private fun MemScope.serializeSignature(signature: secp256k1_ecdsa_signature, format: SigFormat): ByteArray { + val natOutput = allocArray(format.size) + when (format) { + SigFormat.DER -> { + val outputLen = alloc() + outputLen.value = 72.convert() + secp256k1_ecdsa_signature_serialize_der(ctx, natOutput, outputLen.ptr, signature.ptr).requireSuccess() + return natOutput.readBytes(outputLen.value.toInt()) + } + SigFormat.COMPACT -> { + secp256k1_ecdsa_signature_serialize_compact(ctx, natOutput, signature.ptr).requireSuccess() + return natOutput.readBytes(64) + } + } + } + private fun MemScope.allocPublicKey(pubkey: ByteArray): secp256k1_pubkey { val natPub = toNat(pubkey) val pub = alloc() @@ -61,68 +69,59 @@ public actual object Secp256k1 { require(data.size == 32) require(pub.size == 33 || pub.size == 65) memScoped { - val pubkey = allocPublicKey(pub) - val natData = toNat(data) - val parsedSig = allocSignature(signature) - return secp256k1_ecdsa_verify(ctx, parsedSig.ptr, natData, pubkey.ptr) == 1 + val nPubkey = allocPublicKey(pub) + val nData = toNat(data) + val nSig = allocSignature(signature) + return secp256k1_ecdsa_verify(ctx, nSig.ptr, nData, nPubkey.ptr) == 1 } } - public actual fun sign(data: ByteArray, sec: ByteArray): ByteArray { + public actual fun sign(data: ByteArray, sec: ByteArray, format: SigFormat): ByteArray { require(sec.size == 32) require(data.size == 32) memScoped { - val natSec = toNat(sec) - val natData = toNat(data) - val natSig = alloc() - val result = secp256k1_ecdsa_sign(ctx, natSig.ptr, natData, natSec, null, null) + val nSec = toNat(sec) + val nData = toNat(data) + val nSig = alloc() + val result = secp256k1_ecdsa_sign(ctx, nSig.ptr, nData, nSec, null, null) if (result == 0) return ByteArray(0) - val natOutput = allocArray(72) - val outputLen = alloc() - outputLen.value = 72.convert() - secp256k1_ecdsa_signature_serialize_der(ctx, natOutput, outputLen.ptr, natSig.ptr).requireSuccess() - return natOutput.readBytes(outputLen.value.toInt()) + return serializeSignature(nSig, format) } } - public actual fun signCompact(data: ByteArray, sec: ByteArray): ByteArray { - require(data.size == 32) - require(sec.size <= 32) + public actual fun signatureNormalize(sig: ByteArray, format: SigFormat): Pair { + require(sig.size == 64 || sig.size in 70..73) memScoped { - val natSec = toNat(sec) - val natData = toNat(data) - val natSig = alloc() - secp256k1_ecdsa_sign(ctx, natSig.ptr, natData, natSec, null, null).requireSuccess() - val natCompact = allocArray(64) - secp256k1_ecdsa_signature_serialize_compact(ctx, natCompact, natSig.ptr).requireSuccess() - return natCompact.readBytes(64) + val nSig = allocSignature(sig) + val isHighS = secp256k1_ecdsa_signature_normalize(ctx, nSig.ptr, nSig.ptr) + return Pair(serializeSignature(nSig, format), isHighS == 1) } } public actual fun secKeyVerify(seckey: ByteArray): Boolean { require(seckey.size == 32) memScoped { - val natSec = toNat(seckey) - return secp256k1_ec_seckey_verify(ctx, natSec) == 1 + val nSec = toNat(seckey) + return secp256k1_ec_seckey_verify(ctx, nSec) == 1 } } - public actual fun computePubkey(seckey: ByteArray): ByteArray { + public actual fun computePubkey(seckey: ByteArray, format: PubKeyFormat): ByteArray { require(seckey.size == 32) memScoped { - val natSec = toNat(seckey) - val pubkey = alloc() - val result = secp256k1_ec_pubkey_create(ctx, pubkey.ptr, natSec) + val nSec = toNat(seckey) + val nPubkey = alloc() + val result = secp256k1_ec_pubkey_create(ctx, nPubkey.ptr, nSec) if (result == 0) return ByteArray(0) - return serializePubkey(pubkey, 65) + return serializePubkey(nPubkey, format.size) } } - public actual fun parsePubkey(pubkey: ByteArray): ByteArray { + public actual fun parsePubkey(pubkey: ByteArray, format: PubKeyFormat): ByteArray { require(pubkey.size == 33 || pubkey.size == 65) memScoped { val nPubkey = allocPublicKey(pubkey) - return serializePubkey(nPubkey, 65) + return serializePubkey(nPubkey, format.size) } } @@ -177,7 +176,7 @@ public actual object Secp256k1 { val nPubkey = allocPublicKey(pubkey) val nTweak = toNat(tweak) secp256k1_ec_pubkey_tweak_add(ctx, nPubkey.ptr, nTweak).requireSuccess() - return serializePubkey(nPubkey, 65) + return serializePubkey(nPubkey, pubkey.size) } } @@ -187,18 +186,19 @@ public actual object Secp256k1 { val nPubkey = allocPublicKey(pubkey) val nTweak = toNat(tweak) secp256k1_ec_pubkey_tweak_mul(ctx, nPubkey.ptr, nTweak).requireSuccess() - return serializePubkey(nPubkey, 65) + return serializePubkey(nPubkey, pubkey.size) } } public actual fun pubKeyAdd(pubkey1: ByteArray, pubkey2: ByteArray): ByteArray { - require(pubkey1.size == 33 || pubkey2.size == 65) + require(pubkey1.size == 33 || pubkey1.size == 65) + require(pubkey2.size == 33 || pubkey2.size == 65) memScoped { val nPubkey1 = allocPublicKey(pubkey1) val nPubkey2 = allocPublicKey(pubkey2) - val combined = nativeHeap.alloc() + val combined = alloc() secp256k1_ec_pubkey_combine(ctx, combined.ptr, cValuesOf(nPubkey1.ptr, nPubkey2.ptr), 2.convert()).requireSuccess() - return serializePubkey(combined, 65) + return serializePubkey(combined, pubkey1.size) } } @@ -214,17 +214,17 @@ public actual object Secp256k1 { } } - public actual fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int): ByteArray { + public actual fun ecdsaRecover(sig: ByteArray, message: ByteArray, recid: Int, format: PubKeyFormat): ByteArray { require(sig.size == 64) require(message.size == 32) memScoped { val nSig = toNat(sig) - val rSig = nativeHeap.alloc() + val rSig = alloc() secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, rSig.ptr, nSig, recid).requireSuccess() val nMessage = toNat(message) - val pubkey = nativeHeap.alloc() + val pubkey = alloc() secp256k1_ecdsa_recover(ctx, pubkey.ptr, rSig.ptr, nMessage).requireSuccess() - return serializePubkey(pubkey, 65) + return serializePubkey(pubkey, format.size) } }