diff --git a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c index d4c4823..2e323a0 100644 --- a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c +++ b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c @@ -1359,8 +1359,8 @@ JNIEXPORT jobjectArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp2 { secp256k1_context *ctx = (secp256k1_context *)jctx; - secp256k1_frost_share *shares; - secp256k1_pubkey* vss_commitment; + secp256k1_frost_share **shares; + secp256k1_pubkey **vss_commitment; jbyte* pok64; size_t size; @@ -1420,11 +1420,37 @@ JNIEXPORT jobjectArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp2 jobjectArray output = (*penv)->NewObjectArray(penv, 2, jobjectArray, NULL); - output[0] = (*penv)->NewObjectArray(penv, jn_participants, jbyteArray, NULL); + jobjectArray jshares = (*penv)->NewObjectArray(penv, jn_participants, jbyteArray, NULL); + // Copy shares into jshares + unsigned char out32[32]; + for (i = 0; i < jn_participants; i++) + { + result = secp256k1_frost_share_serialize(ctx, out32, shares[i]); + CHECKRESULT(!result, "secp256k1_frost_share_serialize failed"); - output[1] = (*penv)->NewObjectArray(penv, jthreshold, jbyteArray, NULL); + jbyteArray jshare = (*penv)->NewByteArray(penv, 32); + copy_bytes_to_java(penv, jshare, 32, out32); + + jshares[i] = jshare; + } + output[0] = jshares; + + jobjectArray jvss_commitment = (*penv)->NewObjectArray(penv, jthreshold, jbyteArray, NULL); + + // Copy vss_commitment into jvss_commitment + for (i = 0; i < jn_participants; i++) + { + // need share object... + result = secp256k1_xonly_pubkey_serialize(ctx, out32, vss_commitment[i]); + CHECKRESULT(!result, "secp256k1_xonly_pubkey_serialize failed"); + + jbyteArray jpubkey = (*penv)->NewByteArray(penv, 32); + copy_bytes_to_java(penv, jpubkey, 32, out32); + + jvss_commitment[i] = jpubkey; + } + output[1] = jvss_commitment; - // TODO: Copy over the required data... return output; } diff --git a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt index d56483d..a054b11 100644 --- a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt +++ b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt @@ -489,11 +489,11 @@ public object Secp256k1Native : Secp256k1 { totalSigners: Int, ids33: Array ): Triple, Array, ByteArray> { - require(seed32.size == 32) - require(threshold > 0) - require(threshold <= totalSigners) - require(ids33.size == totalSigners) - ids33.forEach { require(it.size == 33) } + require(seed32.size == 32, { "seed size should be 32" }) + require(threshold > 1, { "threshold cannot be less then 1" }) + require(threshold <= totalSigners, { "threshold($threshold) cannot be greater then totalSigners ($totalSigners)" }) + require(ids33.size == totalSigners, { "ids33 size need to be the same as totalSigners size" }) + ids33.forEach { require(it.size == 33, { "id33 size must be 33" }) } memScoped { val nShares = allocArray(ids33.size) @@ -543,7 +543,7 @@ public object Secp256k1Native : Secp256k1 { require(publicKey.size == 33 || publicKey.size == 65) } } - require(threshold > 0) + require(threshold > 1) require(threshold <= totalShareCount) require(id33.size == 33) @@ -587,7 +587,7 @@ public object Secp256k1Native : Secp256k1 { share: ByteArray, vssCommitment: Array ): Int { - require(threshold > 0) + require(threshold > 1) require(id33.size == 33) require(share.size == Secp256k1.FROST_SHARE_SIZE) @@ -617,7 +617,7 @@ public object Secp256k1Native : Secp256k1 { vssCommitments: Array>, totalSignersCount: Int ): ByteArray { - require(threshold > 0) + require(threshold > 1) require(threshold <= totalSignersCount) require(id33.size == 33) diff --git a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/BaseTest.kt b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/BaseTest.kt new file mode 100644 index 0000000..01f0410 --- /dev/null +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/BaseTest.kt @@ -0,0 +1,24 @@ +package fr.acinq.secp256k1 + +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +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 + +abstract class BaseTest { + 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) + } +} \ No newline at end of file diff --git a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/FrostTest.kt b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/FrostTest.kt new file mode 100644 index 0000000..fb75a7c --- /dev/null +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/FrostTest.kt @@ -0,0 +1,115 @@ +package fr.acinq.secp256k1 + +import kotlinx.serialization.json.* + +import kotlin.test.* + +class FrostTest: BaseTest() { + + // Frost Share Generation + @Test + fun `frost share generation`() { + val tests = readData("frost/share_gen_vectors.json") + val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + + tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach { validTestCases -> + val keyIndices = validTestCases.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + + val seed32 = ByteArray(32) + val nParticipants = keyIndices.size + val threshold = validTestCases.jsonObject["threshold"]!!.jsonPrimitive.int + val ids33 = keyIndices.map { pubkeys[it] }.toTypedArray() + + val result = Secp256k1.frostSharesGen( + seed32, + threshold, + nParticipants, + ids33 + ) + + val expected = validTestCases.jsonObject["expected"]!!; + + val expectedShare = expected.jsonObject["frost_share"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val expectedVSSCommitment = expected.jsonObject["vss_commitment"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val expectedPoK64 = Hex.decode(expected.jsonObject["pok64"]!!.jsonPrimitive.content) + + result.first.forEachIndexed { index, share -> + assertEquals( + expected = Hex.encode(expectedShare[index]), + actual = Hex.encode(share), + "Unexpected $index:share for $keyIndices test case" + ) + } + result.second.forEachIndexed { index, vssCommitment -> + assertEquals( + expected = Hex.encode(expectedVSSCommitment[index]), + actual = Hex.encode(vssCommitment), + "Unexpected $index:vss_commitment for the $keyIndices test case" + ) + } + assertEquals( + expected = Hex.encode(expectedPoK64), + actual = Hex.encode(result.third), + message = "Unexpected pok64 for $keyIndices test case" + ) + } + } + + @Test + fun `frost share aggregation`() { + + } + + @Test + fun `frost share verify`() { + + } + + @Test + fun `frost compute pubshare`() { + + } + + // Frost Tweak + + @Test + fun `frost pubkey tweak`() { + + } + + @Test + fun `frost pubkey ec tweak add`() { + + } + + @Test + fun `frost pubkey xonly tweak add`() { + + } + + // Frost Sign functionality + @Test + fun `frost nonce gen`() { + + } + + @Test + fun `frost nonce process`() { + + } + + @Test + fun `frost partial sign `() { + + } + + @Test + fun `frost partial signature verify`() { + + } + + @Test + fun `frost partial signature aggregation`() { + + } +} \ No newline at end of file diff --git a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt index 7630df4..300d354 100644 --- a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt @@ -10,17 +10,7 @@ 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) - } +class Musig2Test: BaseTest() { @Test fun `aggregate public keys`() { diff --git a/tests/src/commonTest/resources/frost/share_gen_vectors.json b/tests/src/commonTest/resources/frost/share_gen_vectors.json new file mode 100644 index 0000000..942d7fc --- /dev/null +++ b/tests/src/commonTest/resources/frost/share_gen_vectors.json @@ -0,0 +1,84 @@ +{ + "pubkeys": [ + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + "04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "020000000000000000000000000000000000000000000000000000000000000005", + "02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ], + "valid_test_cases": [ + { + "key_indices": [0, 1, 2, 3, 4], + "threshold": 3, + "expected": { + "frost_share": [ + "0d9c85106ab6168b13e3b569695fb57bf91cbbe1e589a205b48bb7e15ff6908b", + "807eb5b22ef56d6ae3601f2774a1985485386f6317e6bc075b2134818fd44c5b", + "5e67fe075e91549bf28558a0d55e4b954bf5f9f229ddc0d6e9ab76a139367bbf", + "d248680a6aa524a75c9f59c2732e77ac2d334b98ac2b09defc9030da218d17ca", + "7bdf748e9fd56cd87e0430847e3212d0814528aeb83e1d83aeda279cbc20b481" + ], + "vss_commitment": [ + "044c865c8c3f29a10a55bee4deca73b135d768eb7727a7ccc0fd47db82d7c9f4763d0ebe8feabf6b2119098f1f1f52ba165ae373d75a73c053f4bff6bd2ddb361f", + "045500faa3f2c4279bb95e66bd4c0011da084806e260395c0ff02ae25bd80102f45a7b7fe4d90cc76e9b26a1f9a9febcd0c767aeee63ab463fcfe559ac3b3ad653", + "0499c4d8f4ed163d07a112a74c0b03e1f6851b86b499b916d66824cec9694e5bd866e108b3f82413bdf96c6abde9995a165684432467b82be13dd73e67e4cf29f9" + ], + "pok64": "e79e0d894639155ffebbe03e97030758e6060929a468010ce70627b1ce92b553597f1709f2d82a2ad884f868123344ec871eec4cbf1fa12c5794df6fe5d06e27" + } + }, + { + "key_indices": [4, 3, 2, 1, 0], + "threshold": 3, + "expected": { + "frost_share": [ + "bdf8c4896736812a5548610bd39b66dc91b266e1bc68db4e760bcee797a695e3", + "5afe66c979b666141b8bf46747293797e74580341527f5f6aa01277a468fd9f2", + "9efe2e26dcf8e6f4224fea1fec9a9167ec29cb3b727f841ef76e23d592d2512f", + "dd4fd050344dc625737d018c69298ed312bd7043d3343583625703ed45726ac6", + "eb486a1c2e3ff755473fc41b01138b6a0d3f99f3b99b420e57271689d40eb7e4" + ], + "vss_commitment": [ + "0496291d2a1ec0f0e38d622db473004c18bd6713be47642cfdd7ad9c458a12506da5eb9d0b0a99aab0c97f5ac6d94f2becd8075eef8675d06e4b6cf9e58ed4c30b", + "0473b3681ac295de098568695b02820d57cab4c09f953518bfe1d133b54d794564da8a4c508942789b32fd483719bd847ab0a6f87e0ef7bb7d40376e1c109c0bda", + "044cd2e10e73b39450f2ce02609985ea1f2d84891a0485fbf42ddf9164c0dd5222306a396efb13eff789837b894ca6bf3681d1f937b94e209a8e8333e1e5a7d6c1" + ], + "pok64": "5f4e653cd392b04042629b78bc5982c552a4027816321355e519912e1e46514b6425bd31ae684a1ce1941a98cfb0d2b9ad108a39e230b4303e0e0237d7136998" + } + }, + { + "key_indices": [0, 0, 0], + "threshold": 2, + "expected": { + "frost_share": [ + "2bc9a1e8064cc39759023561f07507ad72db7e7fa20b7a01ec467d40bed167ff", + "2bc9a1e8064cc39759023561f07507ad72db7e7fa20b7a01ec467d40bed167ff", + "2bc9a1e8064cc39759023561f07507ad72db7e7fa20b7a01ec467d40bed167ff" + ], + "vss_commitment": [ + "041857d4fce91165cba35a6a074ce182883c92eaf0e8c97d985bb73db2befcee02dbcd9ba5bb8e01878d9900738833bc5111c20996b9f5f14da50011a0a4e0767f", + "04702b5ed0a3f937ca894a976f7927f537e37e19eafcf7ed970087b860bfa80f7c64a060e1835fdfe8780abf7d0dbb11b4334471aa133f1fa159c0151faf16edee" + ], + "pok64": "2081623ec3b670010d6a8ce55073cf0ef9906798b18db58b1385211d6a7b206e2408e5fc9c399fd1a448686cd29aa71e6053229bf42c5710a5f10af0af1140b0" + } + }, + { + "key_indices": [0, 0, 1, 1], + "threshold": 2, + "expected": { + "frost_share": [ + "70656f19f741cb3e1a46d033321c1627e4464f08316f0d2faad738903b2b2df9", + "70656f19f741cb3e1a46d033321c1627e4464f08316f0d2faad738903b2b2df9", + "624956ad9497abe08d08e4e367519b0c263d79d14754c4a6fb7b2b67796c3636", + "624956ad9497abe08d08e4e367519b0c263d79d14754c4a6fb7b2b67796c3636" + ], + "vss_commitment": [ + "04267d0994c34e87b281506e325a64b83935acb172e5d51a451b5e5f21f51490489dd865654935008aa6afb48955259ace1f8a7eeeafb95c2b0d78dec8e5f25361", + "04c616024ec0c9317bd7574b4fb55a9202d2e6d53465bfb37d1f9edcffc775df537c7c521eac64316f1339943c219df37fa597a49d6be6a2487d776b8305c3880c" + ], + "pok64": "ea25763debd2b96d9e24a3cf3d87f792bce88a27787c0b84bc0b2fc5a1fec841afc6606bc970e477694b1f3c6ad25370c06a7b9705d52c3acfec13b036a94df8" + } + } + ] +} \ No newline at end of file