Add support for musig2 (#93)
* Use Jonas Nick's musig2 branch * Reformat c code (no functional changes) * Implement musig2 * Add documentation to musig2 functions (#97) Usage of the Musig2 functions isn't intuitive at all, especially with the key aggregation cache and session data. It's important to provide accurate documentation to help users understand how to correctly produce musig2 signatures. We also change argument names to match Kotlin best practices instead of using the same argument names as C functions. * Add musig2 reference tests (no functional changes) --------- Co-authored-by: Bastien Teinturier <31281497+t-bast@users.noreply.github.com>
This commit is contained in:
298
tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt
Normal file
298
tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt
Normal file
@@ -0,0 +1,298 @@
|
||||
package fr.acinq.secp256k1
|
||||
|
||||
import kotlinx.serialization.json.*
|
||||
import org.kodein.memory.file.FileSystem
|
||||
import org.kodein.memory.file.Path
|
||||
import org.kodein.memory.file.openReadableFile
|
||||
import org.kodein.memory.file.resolve
|
||||
import org.kodein.memory.system.Environment
|
||||
import org.kodein.memory.text.readString
|
||||
import org.kodein.memory.use
|
||||
import kotlin.test.*
|
||||
|
||||
class Musig2Test {
|
||||
fun resourcesDir() =
|
||||
Environment.findVariable("TEST_RESOURCES_PATH")?.let { Path(it) }
|
||||
?: FileSystem.workingDir().resolve("src/commonTest/resources")
|
||||
|
||||
fun readData(filename: String): JsonElement {
|
||||
val file = resourcesDir().resolve(filename)
|
||||
val raw = file.openReadableFile().use { it.readString() }
|
||||
val format = Json { ignoreUnknownKeys = true }
|
||||
return format.parseToJsonElement(raw)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `aggregate public keys`() {
|
||||
val tests = readData("musig2/key_agg_vectors.json")
|
||||
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
|
||||
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
|
||||
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
|
||||
val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
val aggkey = Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache)
|
||||
assertContentEquals(expected, aggkey)
|
||||
}
|
||||
tests.jsonObject["error_test_cases"]!!.jsonArray.forEach {
|
||||
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val tweakIndex = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }.firstOrNull()
|
||||
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
|
||||
when (tweakIndex) {
|
||||
null -> {
|
||||
// One of the public keys is invalid, so key aggregation will fail.
|
||||
// Callers must verify that public keys are valid before aggregating them.
|
||||
assertFails {
|
||||
val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// The tweak cannot be applied, it would result in an invalid public key.
|
||||
assertFails {
|
||||
val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache)
|
||||
if (isXonly[0])
|
||||
Secp256k1.musigPubkeyXonlyTweakAdd(keyAggCache, tweaks[tweakIndex])
|
||||
else
|
||||
Secp256k1.musigPubkeyTweakAdd(keyAggCache, tweaks[tweakIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Secret nonces in test vectors use a custom encoding. */
|
||||
private fun deserializeSecretNonce(hex: String): ByteArray {
|
||||
val serialized = Hex.decode(hex)
|
||||
require(serialized.size == 97) { "secret nonce from test vector should be serialized using 97 bytes" }
|
||||
// In test vectors, secret nonces are serialized as: <scalar_1> <scalar_2> <compressed_public_key>
|
||||
val compressedPublicKey = serialized.takeLast(33).toByteArray()
|
||||
// We expect secret nonces serialized as: <magic> <scalar_1> <scalar_2> <public_key_x> <public_key_y>
|
||||
// Where we use a different endianness for the public key coordinates than the test vectors.
|
||||
val uncompressedPublicKey = Secp256k1.pubkeyParse(compressedPublicKey)
|
||||
val publicKeyX = uncompressedPublicKey.drop(1).take(32).reversed().toByteArray()
|
||||
val publicKeyY = uncompressedPublicKey.takeLast(32).reversed().toByteArray()
|
||||
val magic = Hex.decode("220EDCF1")
|
||||
return magic + serialized.take(64) + publicKeyX + publicKeyY
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate secret nonce`() {
|
||||
val tests = readData("musig2/nonce_gen_vectors.json")
|
||||
tests.jsonObject["test_cases"]!!.jsonArray.forEach {
|
||||
val randprime = Hex.decode(it.jsonObject["rand_"]!!.jsonPrimitive.content)
|
||||
val sk = it.jsonObject["sk"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) }
|
||||
val pk = Hex.decode(it.jsonObject["pk"]!!.jsonPrimitive.content)
|
||||
val keyagg = it.jsonObject["aggpk"]?.jsonPrimitive?.contentOrNull?.let {
|
||||
// The test vectors directly provide an aggregated public key: we must manually create the corresponding
|
||||
// key aggregation cache to correctly test.
|
||||
val agg = ByteArray(1) { 2.toByte() } + Hex.decode(it)
|
||||
val magic = Hex.decode("f4adbbdf")
|
||||
magic + Secp256k1.pubkeyParse(agg).drop(1) + ByteArray(129) { 0x00 }
|
||||
}
|
||||
val msg = it.jsonObject["msg"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) }
|
||||
val extraInput = it.jsonObject["extra_in"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) }
|
||||
val expectedSecnonce = deserializeSecretNonce(it.jsonObject["expected_secnonce"]!!.jsonPrimitive.content)
|
||||
val expectedPubnonce = Hex.decode(it.jsonObject["expected_pubnonce"]!!.jsonPrimitive.content)
|
||||
// secp256k1 only supports signing 32-byte messages (when provided), which excludes some of the test vectors.
|
||||
if (msg == null || msg.size == 32) {
|
||||
val nonce = Secp256k1.musigNonceGen(randprime, sk, pk, msg, keyagg, extraInput)
|
||||
val secnonce = nonce.copyOfRange(0, Secp256k1.MUSIG2_SECRET_NONCE_SIZE)
|
||||
val pubnonce = nonce.copyOfRange(Secp256k1.MUSIG2_SECRET_NONCE_SIZE, Secp256k1.MUSIG2_SECRET_NONCE_SIZE + Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE)
|
||||
assertContentEquals(expectedPubnonce, pubnonce)
|
||||
assertContentEquals(expectedSecnonce, secnonce)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `aggregate nonces`() {
|
||||
val tests = readData("musig2/nonce_agg_vectors.json")
|
||||
val nonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
|
||||
val nonceIndices = it.jsonObject["pnonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
|
||||
val agg = Secp256k1.musigNonceAgg(nonceIndices.map { nonces[it] }.toTypedArray())
|
||||
assertNotNull(agg)
|
||||
assertContentEquals(expected, agg)
|
||||
}
|
||||
tests.jsonObject["error_test_cases"]!!.jsonArray.forEach {
|
||||
val nonceIndices = it.jsonObject["pnonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
assertFails {
|
||||
Secp256k1.musigNonceAgg(nonceIndices.map { nonces[it] }.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sign() {
|
||||
val tests = readData("musig2/sign_verify_vectors.json")
|
||||
val sk = Hex.decode(tests.jsonObject["sk"]!!.jsonPrimitive.content)
|
||||
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val secnonces = tests.jsonObject["secnonces"]!!.jsonArray.map { deserializeSecretNonce(it.jsonPrimitive.content) }
|
||||
val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val aggnonces = tests.jsonObject["aggnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val msgs = tests.jsonObject["msgs"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
|
||||
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
|
||||
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
|
||||
val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int
|
||||
val messageIndex = it.jsonObject["msg_index"]!!.jsonPrimitive.int
|
||||
val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())
|
||||
assertNotNull(aggnonce)
|
||||
assertContentEquals(aggnonces[it.jsonObject["aggnonce_index"]!!.jsonPrimitive.int], aggnonce)
|
||||
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
|
||||
// We only support signing 32-byte messages.
|
||||
if (msgs[messageIndex].size == 32) {
|
||||
val session = Secp256k1.musigNonceProcess(aggnonce, msgs[messageIndex], keyagg)
|
||||
assertNotNull(session)
|
||||
val psig = Secp256k1.musigPartialSign(secnonces[keyIndices[signerIndex]], sk, keyagg, session)
|
||||
assertContentEquals(expected, psig)
|
||||
assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session))
|
||||
}
|
||||
}
|
||||
tests.jsonObject["verify_fail_test_cases"]!!.jsonArray.forEach {
|
||||
val psig = Hex.decode(it.jsonObject["sig"]!!.jsonPrimitive.content)
|
||||
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int
|
||||
val messageIndex = it.jsonObject["msg_index"]!!.jsonPrimitive.int
|
||||
if (msgs[messageIndex].size == 32) {
|
||||
val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())
|
||||
assertNotNull(aggnonce)
|
||||
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
|
||||
val session = Secp256k1.musigNonceProcess(aggnonce, msgs[messageIndex], keyagg)
|
||||
assertNotNull(session)
|
||||
assertFails {
|
||||
require(Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session) == 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `aggregate signatures`() {
|
||||
val tests = readData("musig2/sig_agg_vectors.json")
|
||||
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val psigs = tests.jsonObject["psigs"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val msg = Hex.decode(tests.jsonObject["msg"]!!.jsonPrimitive.content)
|
||||
|
||||
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
|
||||
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val psigIndices = it.jsonObject["psig_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
|
||||
val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())
|
||||
assertNotNull(aggnonce)
|
||||
val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
|
||||
assertContentEquals(Hex.decode(it.jsonObject["aggnonce"]!!.jsonPrimitive.content), aggnonce)
|
||||
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
|
||||
tweakIndices
|
||||
.zip(isXonly)
|
||||
.map { tweaks[it.first] to it.second }
|
||||
.forEach {
|
||||
if (it.second)
|
||||
Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first)
|
||||
else
|
||||
Secp256k1.musigPubkeyTweakAdd(keyagg, it.first)
|
||||
}
|
||||
val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg)
|
||||
val aggsig = Secp256k1.musigPartialSigAgg(session, psigIndices.map { psigs[it] }.toTypedArray())
|
||||
assertContentEquals(expected, aggsig)
|
||||
}
|
||||
tests.jsonObject["error_test_cases"]!!.jsonArray.forEach {
|
||||
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val psigIndices = it.jsonObject["psig_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())
|
||||
assertNotNull(aggnonce)
|
||||
val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
|
||||
assertContentEquals(Hex.decode(it.jsonObject["aggnonce"]!!.jsonPrimitive.content), aggnonce)
|
||||
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
|
||||
tweakIndices
|
||||
.zip(isXonly)
|
||||
.map { tweaks[it.first] to it.second }
|
||||
.forEach {
|
||||
if (it.second)
|
||||
Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first)
|
||||
else
|
||||
Secp256k1.musigPubkeyTweakAdd(keyagg, it.first)
|
||||
}
|
||||
val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg)
|
||||
assertFails {
|
||||
Secp256k1.musigPartialSigAgg(session, psigIndices.map { psigs[it] }.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tweak tests`() {
|
||||
val tests = readData("musig2/tweak_vectors.json")
|
||||
val sk = Hex.decode(tests.jsonObject["sk"]!!.jsonPrimitive.content)
|
||||
val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) }
|
||||
val msg = Hex.decode(tests.jsonObject["msg"]!!.jsonPrimitive.content)
|
||||
|
||||
val secnonce = deserializeSecretNonce(tests.jsonObject["secnonce"]!!.jsonPrimitive.content)
|
||||
val aggnonce = Hex.decode(tests.jsonObject["aggnonce"]!!.jsonPrimitive.content)
|
||||
|
||||
assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(arrayOf(pnonces[0], pnonces[1], pnonces[2])))
|
||||
|
||||
tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach {
|
||||
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content)
|
||||
assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray()))
|
||||
val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }
|
||||
val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int
|
||||
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
|
||||
tweakIndices
|
||||
.zip(isXonly)
|
||||
.map { tweaks[it.first] to it.second }
|
||||
.forEach {
|
||||
if (it.second)
|
||||
Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first)
|
||||
else
|
||||
Secp256k1.musigPubkeyTweakAdd(keyagg, it.first)
|
||||
}
|
||||
val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg)
|
||||
assertNotNull(session)
|
||||
val psig = Secp256k1.musigPartialSign(secnonce, sk, keyagg, session)
|
||||
assertContentEquals(expected, psig)
|
||||
assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session))
|
||||
}
|
||||
tests.jsonObject["error_test_cases"]!!.jsonArray.forEach {
|
||||
val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray()))
|
||||
val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }
|
||||
assertEquals(1, tweakIndices.size)
|
||||
val tweak = tweaks[tweakIndices.first()]
|
||||
val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }.first()
|
||||
val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg)
|
||||
assertFails {
|
||||
if (isXonly)
|
||||
Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, tweak)
|
||||
else
|
||||
Secp256k1.musigPubkeyTweakAdd(keyagg, tweak)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,6 +352,144 @@ class Secp256k1Test {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMusig2GenerateNonce() {
|
||||
val privkey = Hex.decode("0000000000000000000000000000000000000000000000000000000000000003")
|
||||
val pubkey = Hex.decode("02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9")
|
||||
val sessionId = Hex.decode("0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F")
|
||||
val nonce = Secp256k1.musigNonceGen(sessionId, null, pubkey, null, null, null)
|
||||
val pubnonce = Hex.encode(nonce.copyOfRange(132, 132 + 66)).uppercase()
|
||||
assertEquals("02C96E7CB1E8AA5DAC64D872947914198F607D90ECDE5200DE52978AD5DED63C000299EC5117C2D29EDEE8A2092587C3909BE694D5CFF0667D6C02EA4059F7CD9786", pubnonce)
|
||||
assertNotEquals(nonce, Secp256k1.musigNonceGen(sessionId, privkey, pubkey, null, null, null))
|
||||
assertNotEquals(nonce, Secp256k1.musigNonceGen(sessionId, null, pubkey, sessionId, null, null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMusig2AggregateNonce() {
|
||||
val nonces = listOf(
|
||||
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
|
||||
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
// The following nonces are invalid.
|
||||
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
|
||||
).map { Hex.decode(it) }
|
||||
val agg1 = Secp256k1.musigNonceAgg(arrayOf(nonces[0], nonces[1]))
|
||||
assertEquals("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8", Hex.encode(agg1).uppercase())
|
||||
|
||||
val agg2 = Secp256k1.musigNonceAgg(arrayOf(nonces[2], nonces[3]))
|
||||
assertEquals("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000", Hex.encode(agg2).uppercase())
|
||||
|
||||
assertFails {
|
||||
Secp256k1.musigNonceAgg(arrayOf(nonces[0], nonces[4]))
|
||||
}
|
||||
assertFails {
|
||||
Secp256k1.musigNonceAgg(arrayOf(nonces[5], nonces[1]))
|
||||
}
|
||||
assertFails {
|
||||
Secp256k1.musigNonceAgg(arrayOf(nonces[6], nonces[1]))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMusig2AggregatePubkey() {
|
||||
val pubkeys = listOf(
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"020000000000000000000000000000000000000000000000000000000000000005",
|
||||
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
|
||||
"04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
|
||||
).map { Hex.decode(it) }
|
||||
|
||||
val agg1 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), null)
|
||||
assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg1).uppercase())
|
||||
|
||||
// We provide an empty cache, which will be filled when aggregating public keys.
|
||||
val keyaggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
val agg2 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), keyaggCache)
|
||||
assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg2).uppercase())
|
||||
assertTrue(keyaggCache.count { it.toInt() != 0 } > 100) // the cache has been filled with key aggregation data
|
||||
|
||||
// We can reuse the key aggregation cache to speed up computation.
|
||||
val agg3 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), keyaggCache)
|
||||
assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg3).uppercase())
|
||||
|
||||
val agg4 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[2], pubkeys[1], pubkeys[0]), null)
|
||||
assertEquals("6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B", Hex.encode(agg4).uppercase())
|
||||
|
||||
val agg5 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[0]), null)
|
||||
assertEquals("B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935", Hex.encode(agg5).uppercase())
|
||||
|
||||
val agg6 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), null)
|
||||
assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg6).uppercase())
|
||||
|
||||
// If we provide the key aggregation cache for a different session, it is ignored and overwritten.
|
||||
val agg7 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), keyaggCache)
|
||||
assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg7).uppercase())
|
||||
|
||||
// If we provide random data in the key aggregation cache, it is ignored and overwritten.
|
||||
val agg8 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), Random.nextBytes(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE))
|
||||
assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg8).uppercase())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMusig2TweakPubkeys() {
|
||||
val pubkeys = listOf(
|
||||
"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
|
||||
"024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
|
||||
"02531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337"
|
||||
).map { Hex.decode(it) }.toTypedArray()
|
||||
val cache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)
|
||||
val agg1 = Secp256k1.musigPubkeyAgg(pubkeys, cache)
|
||||
assertEquals("b6d830642403fc82511aca5ff98a5e76fcef0f89bffc1aadbe78ee74cd5a5716", Hex.encode(agg1))
|
||||
val agg2 = Secp256k1.musigPubkeyTweakAdd(cache, Hex.decode("7468697320636f756c64206265206120424950333220747765616b2e2e2e2e00"))
|
||||
assertEquals("04791e4f22a21f19bd9798eceab92ad2ccc18f2d6660e91ae4c0709aaebf1aa9023701f468b0eddf8973495a5327f2169d9c6a50eb6a0f87c0fbee90a4067eb230", Hex.encode(agg2))
|
||||
val agg3 = Secp256k1.musigPubkeyXonlyTweakAdd(cache, Hex.decode("7468697320636f756c64206265206120746170726f6f7420747765616b2e2e00"))
|
||||
assertEquals("04537a081a8d32ff700ca86aaa77a423e9b8d1480938076b645c68ee39d263c93948026928799b2d942cb5851db397015b26b1759de1b9ab2c691ced64a2eef836", Hex.encode(agg3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMusig2SigningSession() {
|
||||
val privkeys = listOf(
|
||||
"0101010101010101010101010101010101010101010101010101010101010101",
|
||||
"0202020202020202020202020202020202020202020202020202020202020202",
|
||||
).map { Hex.decode(it) }.toTypedArray()
|
||||
val pubkeys = privkeys.map { Secp256k1.pubkeyCreate(it) }
|
||||
|
||||
val sessionId = Hex.decode("0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F")
|
||||
val nonces = pubkeys.map { Secp256k1.musigNonceGen(sessionId, null, it, null, null, null) }
|
||||
val secnonces = nonces.map { it.copyOfRange(0, 132) }
|
||||
val pubnonces = nonces.map { it.copyOfRange(132, 132 + 66) }
|
||||
val aggnonce = Secp256k1.musigNonceAgg(pubnonces.toTypedArray())
|
||||
|
||||
val keyaggCaches = (0 until 2).map { ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) }
|
||||
val aggpubkey = Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[0])
|
||||
assertContentEquals(aggpubkey, Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[1]))
|
||||
assertContentEquals(keyaggCaches[0], keyaggCaches[1])
|
||||
|
||||
val msg32 = Hex.decode("0303030303030303030303030303030303030303030303030303030303030303")
|
||||
val sessions = (0 until 2).map { Secp256k1.musigNonceProcess(aggnonce, msg32, keyaggCaches[it]) }
|
||||
val psigs = (0 until 2).map {
|
||||
val psig = Secp256k1.musigPartialSign(secnonces[it], privkeys[it], keyaggCaches[it], sessions[it])
|
||||
assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it]))
|
||||
assertEquals(0, Secp256k1.musigPartialSigVerify(Random.nextBytes(32), pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it]))
|
||||
psig
|
||||
}
|
||||
|
||||
val sig = Secp256k1.musigPartialSigAgg(sessions[0], psigs.toTypedArray())
|
||||
assertContentEquals(sig, Secp256k1.musigPartialSigAgg(sessions[1], psigs.toTypedArray()))
|
||||
assertTrue(Secp256k1.verifySchnorr(sig, msg32, aggpubkey))
|
||||
|
||||
val invalidSig1 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(psigs[0], psigs[0]))
|
||||
assertFalse(Secp256k1.verifySchnorr(invalidSig1, msg32, aggpubkey))
|
||||
val invalidSig2 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(Random.nextBytes(32), Random.nextBytes(32)))
|
||||
assertFalse(Secp256k1.verifySchnorr(invalidSig2, msg32, aggpubkey))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInvalidArguments() {
|
||||
assertFails {
|
||||
|
||||
144
tests/src/commonTest/resources/musig2/det_sign_vectors.json
Normal file
144
tests/src/commonTest/resources/musig2/det_sign_vectors.json
Normal file
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
|
||||
"pubkeys": [
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"020000000000000000000000000000000000000000000000000000000000000007"
|
||||
],
|
||||
"msgs": [
|
||||
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
|
||||
"2626262626262626262626262626262626262626262626262626262626262626262626262626"
|
||||
],
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"key_indices": [0, 1, 2],
|
||||
"tweaks": [],
|
||||
"is_xonly": [],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"expected": [
|
||||
"03D96275257C2FCCBB6EEB77BDDF51D3C88C26EE1626C6CDA8999B9D34F4BA13A60309BE2BF883C6ABE907FA822D9CA166D51A3DCC28910C57528F6983FC378B7843",
|
||||
"41EA65093F71D084785B20DC26A887CD941C9597860A21660CBDB9CC2113CAD3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rand": null,
|
||||
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"key_indices": [1, 0, 2],
|
||||
"tweaks": [],
|
||||
"is_xonly": [],
|
||||
"msg_index": 0,
|
||||
"signer_index": 1,
|
||||
"expected": [
|
||||
"028FBCCF5BB73A7B61B270BAD15C0F9475D577DD85C2157C9D38BEF1EC922B48770253BE3638C87369BC287E446B7F2C8CA5BEB9FFBD1EA082C62913982A65FC214D",
|
||||
"AEAA31262637BFA88D5606679018A0FEEEC341F3107D1199857F6C81DE61B8DD"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rand": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
||||
"aggothernonce": "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"key_indices": [1, 2, 0],
|
||||
"tweaks": [],
|
||||
"is_xonly": [],
|
||||
"msg_index": 1,
|
||||
"signer_index": 2,
|
||||
"expected": [
|
||||
"024FA8D774F0C8743FAA77AFB4D08EE5A013C2E8EEAD8A6F08A77DDD2D28266DB803050905E8C994477F3F2981861A2E3791EF558626E645FBF5AA131C5D6447C2C2",
|
||||
"FEE28A56B8556B7632E42A84122C51A4861B1F2DEC7E81B632195E56A52E3E13"
|
||||
],
|
||||
"comment": "Message longer than 32 bytes"
|
||||
},
|
||||
{
|
||||
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"aggothernonce": "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046",
|
||||
"key_indices": [0, 1, 2],
|
||||
"tweaks": ["E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB"],
|
||||
"is_xonly": [true],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"expected": [
|
||||
"031E07C0D11A0134E55DB1FC16095ADCBD564236194374AA882BFB3C78273BF673039D0336E8CA6288C00BFC1F8B594563529C98661172B9BC1BE85C23A4CE1F616B",
|
||||
"7B1246C5889E59CB0375FA395CC86AC42D5D7D59FD8EAB4FDF1DCAB2B2F006EA"
|
||||
],
|
||||
"comment": "Tweaked public key"
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"key_indices": [1, 0, 3],
|
||||
"tweaks": [],
|
||||
"is_xonly": [],
|
||||
"msg_index": 0,
|
||||
"signer_index": 1,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 2,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Signer 2 provided an invalid public key"
|
||||
},
|
||||
{
|
||||
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"key_indices": [1, 2],
|
||||
"tweaks": [],
|
||||
"is_xonly": [],
|
||||
"msg_index": 0,
|
||||
"signer_index": 1,
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The signer's pubkey must be included in the list of pubkeys."
|
||||
},
|
||||
"comment": "The signers pubkey is not in the list of pubkeys"
|
||||
},
|
||||
{
|
||||
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"aggothernonce": "0437C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"key_indices": [1, 2, 0],
|
||||
"tweaks": [],
|
||||
"is_xonly": [],
|
||||
"msg_index": 0,
|
||||
"signer_index": 2,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": null,
|
||||
"contrib": "aggothernonce"
|
||||
},
|
||||
"comment": "aggothernonce is invalid due wrong tag, 0x04, in the first half"
|
||||
},
|
||||
{
|
||||
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"aggothernonce": "0000000000000000000000000000000000000000000000000000000000000000000287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"key_indices": [1, 2, 0],
|
||||
"tweaks": [],
|
||||
"is_xonly": [],
|
||||
"msg_index": 0,
|
||||
"signer_index": 2,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": null,
|
||||
"contrib": "aggothernonce"
|
||||
},
|
||||
"comment": "aggothernonce is invalid because first half corresponds to point at infinity"
|
||||
},
|
||||
{
|
||||
"rand": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"key_indices": [1, 2, 0],
|
||||
"tweaks": ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"],
|
||||
"is_xonly": [false],
|
||||
"msg_index": 0,
|
||||
"signer_index": 2,
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The tweak must be less than n."
|
||||
},
|
||||
"comment": "Tweak is invalid because it exceeds group size"
|
||||
}
|
||||
]
|
||||
}
|
||||
88
tests/src/commonTest/resources/musig2/key_agg_vectors.json
Normal file
88
tests/src/commonTest/resources/musig2/key_agg_vectors.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"pubkeys": [
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"020000000000000000000000000000000000000000000000000000000000000005",
|
||||
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
|
||||
"04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
|
||||
],
|
||||
"tweaks": [
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
|
||||
"252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B"
|
||||
],
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"key_indices": [0, 1, 2],
|
||||
"expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C"
|
||||
},
|
||||
{
|
||||
"key_indices": [2, 1, 0],
|
||||
"expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B"
|
||||
},
|
||||
{
|
||||
"key_indices": [0, 0, 0],
|
||||
"expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935"
|
||||
},
|
||||
{
|
||||
"key_indices": [0, 0, 1, 1],
|
||||
"expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E"
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"key_indices": [0, 3],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 1,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Invalid public key"
|
||||
},
|
||||
{
|
||||
"key_indices": [0, 4],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 1,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Public key exceeds field size"
|
||||
},
|
||||
{
|
||||
"key_indices": [5, 0],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "First byte of public key is not 2 or 3"
|
||||
},
|
||||
{
|
||||
"key_indices": [0, 1],
|
||||
"tweak_indices": [0],
|
||||
"is_xonly": [true],
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The tweak must be less than n."
|
||||
},
|
||||
"comment": "Tweak is out of range"
|
||||
},
|
||||
{
|
||||
"key_indices": [6],
|
||||
"tweak_indices": [1],
|
||||
"is_xonly": [false],
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The result of tweaking cannot be infinity."
|
||||
},
|
||||
"comment": "Intermediate tweaking result is point at infinity"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
tests/src/commonTest/resources/musig2/key_sort_vectors.json
Normal file
18
tests/src/commonTest/resources/musig2/key_sort_vectors.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"pubkeys": [
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8"
|
||||
],
|
||||
"sorted_pubkeys": [
|
||||
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
|
||||
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
|
||||
]
|
||||
}
|
||||
51
tests/src/commonTest/resources/musig2/nonce_agg_vectors.json
Normal file
51
tests/src/commonTest/resources/musig2/nonce_agg_vectors.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"pnonces": [
|
||||
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
|
||||
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831",
|
||||
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
|
||||
],
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"pnonce_indices": [0, 1],
|
||||
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8"
|
||||
},
|
||||
{
|
||||
"pnonce_indices": [2, 3],
|
||||
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes"
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"pnonce_indices": [0, 4],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 1,
|
||||
"contrib": "pubnonce"
|
||||
},
|
||||
"comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half"
|
||||
},
|
||||
{
|
||||
"pnonce_indices": [5, 1],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubnonce"
|
||||
},
|
||||
"comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate"
|
||||
},
|
||||
{
|
||||
"pnonce_indices": [6, 1],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubnonce"
|
||||
},
|
||||
"comment": "Public nonce from signer 0 is invalid because second half exceeds field size"
|
||||
}
|
||||
]
|
||||
}
|
||||
44
tests/src/commonTest/resources/musig2/nonce_gen_vectors.json
Normal file
44
tests/src/commonTest/resources/musig2/nonce_gen_vectors.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"test_cases": [
|
||||
{
|
||||
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
|
||||
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
|
||||
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
|
||||
"msg": "0101010101010101010101010101010101010101010101010101010101010101",
|
||||
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
|
||||
"expected_secnonce": "B114E502BEAA4E301DD08A50264172C84E41650E6CB726B410C0694D59EFFB6495B5CAF28D045B973D63E3C99A44B807BDE375FD6CB39E46DC4A511708D0E9D2024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"expected_pubnonce": "02F7BE7089E8376EB355272368766B17E88E7DB72047D05E56AA881EA52B3B35DF02C29C8046FDD0DED4C7E55869137200FBDBFE2EB654267B6D7013602CAED3115A"
|
||||
},
|
||||
{
|
||||
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
|
||||
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
|
||||
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
|
||||
"msg": "",
|
||||
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
|
||||
"expected_secnonce": "E862B068500320088138468D47E0E6F147E01B6024244AE45EAC40ACE5929B9F0789E051170B9E705D0B9EB49049A323BBBBB206D8E05C19F46C6228742AA7A9024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"expected_pubnonce": "023034FA5E2679F01EE66E12225882A7A48CC66719B1B9D3B6C4DBD743EFEDA2C503F3FD6F01EB3A8E9CB315D73F1F3D287CAFBB44AB321153C6287F407600205109"
|
||||
},
|
||||
{
|
||||
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
|
||||
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
|
||||
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
|
||||
"msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626",
|
||||
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
|
||||
"expected_secnonce": "3221975ACBDEA6820EABF02A02B7F27D3A8EF68EE42787B88CBEFD9AA06AF3632EE85B1A61D8EF31126D4663A00DD96E9D1D4959E72D70FE5EBB6E7696EBA66F024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
|
||||
"expected_pubnonce": "02E5BBC21C69270F59BD634FCBFA281BE9D76601295345112C58954625BF23793A021307511C79F95D38ACACFF1B4DA98228B77E65AA216AD075E9673286EFB4EAF3"
|
||||
},
|
||||
{
|
||||
"rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
|
||||
"sk": null,
|
||||
"pk": "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"aggpk": null,
|
||||
"msg": null,
|
||||
"extra_in": null,
|
||||
"expected_secnonce": "89BDD787D0284E5E4D5FC572E49E316BAB7E21E3B1830DE37DFE80156FA41A6D0B17AE8D024C53679699A6FD7944D9C4A366B514BAF43088E0708B1023DD289702F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"expected_pubnonce": "02C96E7CB1E8AA5DAC64D872947914198F607D90ECDE5200DE52978AD5DED63C000299EC5117C2D29EDEE8A2092587C3909BE694D5CFF0667D6C02EA4059F7CD9786"
|
||||
}
|
||||
]
|
||||
}
|
||||
151
tests/src/commonTest/resources/musig2/sig_agg_vectors.json
Normal file
151
tests/src/commonTest/resources/musig2/sig_agg_vectors.json
Normal file
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"pubkeys": [
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05",
|
||||
"03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C",
|
||||
"02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581"
|
||||
],
|
||||
"pnonces": [
|
||||
"036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E",
|
||||
"03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00",
|
||||
"02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6",
|
||||
"031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9",
|
||||
"023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A",
|
||||
"02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00"
|
||||
],
|
||||
"tweaks": [
|
||||
"B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C",
|
||||
"A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC",
|
||||
"75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8"
|
||||
],
|
||||
"psigs": [
|
||||
"B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB",
|
||||
"6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64",
|
||||
"9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505",
|
||||
"66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15",
|
||||
"4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE",
|
||||
"DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4",
|
||||
"97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC",
|
||||
"53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971",
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
||||
],
|
||||
"msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869",
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"psig_indices": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E"
|
||||
},
|
||||
{
|
||||
"aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
2
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
2
|
||||
],
|
||||
"tweak_indices": [],
|
||||
"is_xonly": [],
|
||||
"psig_indices": [
|
||||
2,
|
||||
3
|
||||
],
|
||||
"expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9"
|
||||
},
|
||||
{
|
||||
"aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
3
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
2
|
||||
],
|
||||
"tweak_indices": [
|
||||
0
|
||||
],
|
||||
"is_xonly": [
|
||||
false
|
||||
],
|
||||
"psig_indices": [
|
||||
4,
|
||||
5
|
||||
],
|
||||
"expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC"
|
||||
},
|
||||
{
|
||||
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
4
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
3
|
||||
],
|
||||
"tweak_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"is_xonly": [
|
||||
true,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"psig_indices": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E"
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
|
||||
"nonce_indices": [
|
||||
0,
|
||||
4
|
||||
],
|
||||
"key_indices": [
|
||||
0,
|
||||
3
|
||||
],
|
||||
"tweak_indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"is_xonly": [
|
||||
true,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"psig_indices": [
|
||||
7,
|
||||
8
|
||||
],
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 1
|
||||
},
|
||||
"comment": "Partial signature is invalid because it exceeds group size"
|
||||
}
|
||||
]
|
||||
}
|
||||
212
tests/src/commonTest/resources/musig2/sign_verify_vectors.json
Normal file
212
tests/src/commonTest/resources/musig2/sign_verify_vectors.json
Normal file
@@ -0,0 +1,212 @@
|
||||
{
|
||||
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
|
||||
"pubkeys": [
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661",
|
||||
"020000000000000000000000000000000000000000000000000000000000000007"
|
||||
],
|
||||
"secnonces": [
|
||||
"508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
|
||||
],
|
||||
"pnonces": [
|
||||
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046",
|
||||
"0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480"
|
||||
],
|
||||
"aggnonces": [
|
||||
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
|
||||
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009",
|
||||
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
|
||||
],
|
||||
"msgs": [
|
||||
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
|
||||
"",
|
||||
"2626262626262626262626262626262626262626262626262626262626262626262626262626"
|
||||
],
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"key_indices": [0, 1, 2],
|
||||
"nonce_indices": [0, 1, 2],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"expected": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB"
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 0, 2],
|
||||
"nonce_indices": [1, 0, 2],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"signer_index": 1,
|
||||
"expected": "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52"
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"nonce_indices": [1, 2, 0],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"signer_index": 2,
|
||||
"expected": "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900"
|
||||
},
|
||||
{
|
||||
"key_indices": [0, 1],
|
||||
"nonce_indices": [0, 3],
|
||||
"aggnonce_index": 1,
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"expected": "AE386064B26105404798F75DE2EB9AF5EDA5387B064B83D049CB7C5E08879531",
|
||||
"comment": "Both halves of aggregate nonce correspond to point at infinity"
|
||||
},
|
||||
{
|
||||
"key_indices": [0, 1, 2],
|
||||
"nonce_indices": [0, 1, 2],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 1,
|
||||
"signer_index": 0,
|
||||
"expected": "D7D63FFD644CCDA4E62BC2BC0B1D02DD32A1DC3030E155195810231D1037D82D",
|
||||
"comment": "Empty message"
|
||||
},
|
||||
{
|
||||
"key_indices": [0, 1, 2],
|
||||
"nonce_indices": [0, 1, 2],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 2,
|
||||
"signer_index": 0,
|
||||
"expected": "E184351828DA5094A97C79CABDAAA0BFB87608C32E8829A4DF5340A6F243B78C",
|
||||
"comment": "38-byte message"
|
||||
}
|
||||
],
|
||||
"sign_error_test_cases": [
|
||||
{
|
||||
"key_indices": [1, 2],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The signer's pubkey must be included in the list of pubkeys."
|
||||
},
|
||||
"comment": "The signers pubkey is not in the list of pubkeys. This test case is optional: it can be skipped by implementations that do not check that the signer's pubkey is included in the list of pubkeys."
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 0, 3],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 2,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Signer 2 provided an invalid public key"
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"aggnonce_index": 2,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": null,
|
||||
"contrib": "aggnonce"
|
||||
},
|
||||
"comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half"
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"aggnonce_index": 3,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": null,
|
||||
"contrib": "aggnonce"
|
||||
},
|
||||
"comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate"
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"aggnonce_index": 4,
|
||||
"msg_index": 0,
|
||||
"secnonce_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": null,
|
||||
"contrib": "aggnonce"
|
||||
},
|
||||
"comment": "Aggregate nonce is invalid because second half exceeds field size"
|
||||
},
|
||||
{
|
||||
"key_indices": [0, 1, 2],
|
||||
"aggnonce_index": 0,
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"secnonce_index": 1,
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "first secnonce value is out of range."
|
||||
},
|
||||
"comment": "Secnonce is invalid which may indicate nonce reuse"
|
||||
}
|
||||
],
|
||||
"verify_fail_test_cases": [
|
||||
{
|
||||
"sig": "97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406",
|
||||
"key_indices": [0, 1, 2],
|
||||
"nonce_indices": [0, 1, 2],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"comment": "Wrong signature (which is equal to the negation of valid signature)"
|
||||
},
|
||||
{
|
||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
||||
"key_indices": [0, 1, 2],
|
||||
"nonce_indices": [0, 1, 2],
|
||||
"msg_index": 0,
|
||||
"signer_index": 1,
|
||||
"comment": "Wrong signer"
|
||||
},
|
||||
{
|
||||
"sig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
|
||||
"key_indices": [0, 1, 2],
|
||||
"nonce_indices": [0, 1, 2],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"comment": "Signature exceeds group size"
|
||||
}
|
||||
],
|
||||
"verify_error_test_cases": [
|
||||
{
|
||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
||||
"key_indices": [0, 1, 2],
|
||||
"nonce_indices": [4, 1, 2],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubnonce"
|
||||
},
|
||||
"comment": "Invalid pubnonce"
|
||||
},
|
||||
{
|
||||
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
|
||||
"key_indices": [3, 1, 2],
|
||||
"nonce_indices": [0, 1, 2],
|
||||
"msg_index": 0,
|
||||
"signer_index": 0,
|
||||
"error": {
|
||||
"type": "invalid_contribution",
|
||||
"signer": 0,
|
||||
"contrib": "pubkey"
|
||||
},
|
||||
"comment": "Invalid pubkey"
|
||||
}
|
||||
]
|
||||
}
|
||||
84
tests/src/commonTest/resources/musig2/tweak_vectors.json
Normal file
84
tests/src/commonTest/resources/musig2/tweak_vectors.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
|
||||
"pubkeys": [
|
||||
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
|
||||
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
|
||||
],
|
||||
"secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
|
||||
"pnonces": [
|
||||
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
|
||||
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
|
||||
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046"
|
||||
],
|
||||
"aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
|
||||
"tweaks": [
|
||||
"E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB",
|
||||
"AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455",
|
||||
"F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0",
|
||||
"1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D",
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
||||
],
|
||||
"msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
|
||||
"valid_test_cases": [
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"nonce_indices": [1, 2, 0],
|
||||
"tweak_indices": [0],
|
||||
"is_xonly": [true],
|
||||
"signer_index": 2,
|
||||
"expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91",
|
||||
"comment": "A single x-only tweak"
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"nonce_indices": [1, 2, 0],
|
||||
"tweak_indices": [0],
|
||||
"is_xonly": [false],
|
||||
"signer_index": 2,
|
||||
"expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D",
|
||||
"comment": "A single plain tweak"
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"nonce_indices": [1, 2, 0],
|
||||
"tweak_indices": [0, 1],
|
||||
"is_xonly": [false, true],
|
||||
"signer_index": 2,
|
||||
"expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408",
|
||||
"comment": "A plain tweak followed by an x-only tweak"
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"nonce_indices": [1, 2, 0],
|
||||
"tweak_indices": [0, 1, 2, 3],
|
||||
"is_xonly": [false, false, true, true],
|
||||
"signer_index": 2,
|
||||
"expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435",
|
||||
"comment": "Four tweaks: plain, plain, x-only, x-only."
|
||||
},
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"nonce_indices": [1, 2, 0],
|
||||
"tweak_indices": [0, 1, 2, 3],
|
||||
"is_xonly": [true, false, true, false],
|
||||
"signer_index": 2,
|
||||
"expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239",
|
||||
"comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error."
|
||||
}
|
||||
],
|
||||
"error_test_cases": [
|
||||
{
|
||||
"key_indices": [1, 2, 0],
|
||||
"nonce_indices": [1, 2, 0],
|
||||
"tweak_indices": [4],
|
||||
"is_xonly": [false],
|
||||
"signer_index": 2,
|
||||
"error": {
|
||||
"type": "value",
|
||||
"message": "The tweak must be less than n."
|
||||
},
|
||||
"comment": "Tweak is invalid because it exceeds group size"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user