diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml new file mode 100644 index 0000000..c82be8a --- /dev/null +++ b/.github/workflows/ci_cd.yml @@ -0,0 +1,62 @@ +name: CI +on: + workflow_dispatch: + + push: + branches: + - master + pull_request: + branches: + - master + - feature/keygen_aux + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + + steps: + # Step 1: Checkout the repository + - name: Checkout code + uses: actions/checkout@v3 + + # Step 2: Set up JDK for Kotlin + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 11 + + # Step 3: Cache Gradle dependencies + - name: Cache Gradle dependencies + uses: actions/cache@v3 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # Step 4: Build the project + - name: Build project + run: ./gradlew build + + # Step 5: Run tests with code coverage verification + - name: Run tests and verify coverage + run: ./gradlew test + + # Step 6: Generate code coverage report + - name: Generate code coverage report + run: ./gradlew jacocoTestReport + + # Step 7: Upload code coverage report as artifact + - name: Generate Coverage Badge + uses: cicirello/jacoco-badge-generator@v2 + with: + jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv + badges-directory: build/reports/jacoco/test/html/badges + + - name: Publish coverage report to GitHub Pages + # if: ${{ github.ref == 'refs/heads/master' }} + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: build/reports/jacoco/test/html \ No newline at end of file diff --git a/README.md b/README.md index 533907d..c905656 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # Threshold ECDSA Signature +[![Test Coverage](https://github.com/perun-network/ecdsa-threshold/blob/gh-pages/badges/jacoco.svg?raw=true)](https://perun-network.github.io/ecdsa-threshold/) +[![CI](https://github.com/perun-network/ecdsa-threshold/actions/workflows/ci_cd.yml/badge.svg?branch=keygen)](https://github.com/perun-network/ecdsa-threshold/actions/workflows/ci_cd.yml) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + This project implements the threshold ECDSA protocol by [Canetti et al.](https://eprint.iacr.org/2021/060) (2021) that achieves non-interactive signing using 3 preprocessing rounds. It further provides malicious security and identifiable aborts. + We provide an implementation of the protocol in Kotlin using the secp256k1 elliptic curve. The report on threshold ECDSA signatures for Atala PRISM and the project timeline can be found in the [Wiki](https://github.com/perun-network/atala-prism-threshold/wiki/Threshold-ECDSA-Signatures-for-Atala-PRISM-Report). @@ -13,6 +18,7 @@ The report on threshold ECDSA signatures for Atala PRISM and the project timelin - [Requirements](#requirements) - [Installation](#installation) - [Usage](#usage) +- [Test](#test) - [Code Structure](#code-structure) - [Limitations](#limitations) - [Copyright](#copyright) @@ -21,7 +27,7 @@ The report on threshold ECDSA signatures for Atala PRISM and the project timelin - Threshold ECDSA signing with preprocessing, where subsets of t signers can create signatures in one round. - Implementation of the Paillier encryption scheme and Pedersen commitments. -- Zero-knowledge proofs for Paillier encryptions, which are during the signing process. +- Zero-knowledge proofs to prove the validity of computations along the execution of the protocol. - The protocol can be integrated to [Apollo](https://github.com/hyperledger/identus-apollo) to be used in crypto services and the threshold Signature can be translated to Apollo's ECDSA Secp256k1 implementation. ## Architecture @@ -29,30 +35,32 @@ The report on threshold ECDSA signatures for Atala PRISM and the project timelin The project is structured into several packages: - **`ecdsa`**: Core ECDSA functionalities and mathematical operations. -- **`keygen`**: Key generation and precomputation. +- **`precomp`**: Centralized key generation and precomputation. - **`math`**: Mathematical operations and utilities used throughout the signing process. - **`paillier`**: Implementation of the Paillier cryptosystem for encryption. - **`pedersen`**: Pedersen commitment scheme with parameter generation. -- **`presign`**: Management of the presigning process, including rounds of communication and computations between signers. - **`sign`**: Signing process management and partial signature combination. + - **`keygen`**: 3-round Key generation protocol. + - **`aux`**: 3-round key refresh/auxiliary-info protocol. + - **`presign`**: 3-round presigning protocol. - **`zero_knowledge`**: Zero-knowledge proof implementations. ## Requirements - Kotlin 1.5 or higher -- Java Development Kit (JDK) 8 or higher +- Java Development Kit (JDK) 11 or higher - Dependencies for cryptographic operations (included in the project) ## Installation 1. **Clone the Repository**: ```bash - git clone https://github.com/yourusername/ecdsa-threshold-signing.git + git clone https://github.com/perun-network/atala-prism-threshold.git ``` 2. **Navigate to the Project Directory**: ```bash - cd ecdsa-threshold-signing + cd ecdsa-threshold ``` 3. **Build the Project**: @@ -73,29 +81,75 @@ The main entry point for the threshold signing process is located in the `main` The application will output the execution time and confirm if the ECDSA signature was generated and verified successfully. +## Test +This section describes the testing strategy and tools used to maintain code quality and reliability. + +### Testing Frameworks and Tools +- **Framework**: The project uses [JUnit 5](https://junit.org/junit5/) for unit and integration testing. +- **Build Tool Integration**: Tests are executed using Gradle's test task. + +### Unit Tests and Integration Tests. + - Test individual components (e.g., classes, functions) in isolation. + - Validate interactions between components. + - Located in `src/test/kotlin`. + +### Running Tests +To execute tests locally: + +- **Run all tests**: + ```bash + ./gradlew test + ``` +- **Run a specific test class: + ```bash + ./gradlew test --tests + ``` +### Test Coverage Report +The project uses [JaCoCo](https://www.eclemma.org/jacoco/) to measure test coverage. + +1. Generate Coverage Report: Run the following command to generate the coverage report: + ```bash + ./gradlew jacocoTestReport + ``` +2. View the Report: The HTML report is available at: + ```bash + build/reports/jacoco/test/html/index.html + ``` + or online at +[Test Report](https://perun-network.github.io/ecdsa-threshold/) + +3. Coverage Standards: + - Instruction coverage: 90% or higher. + - Branches coverage: 80% or higher + - Critical areas must be thoroughly covered. + ## Code Structure - **`src`**: Contains all source code. - **`main`**: Contains main functionality. - **`ecdsa`**: Core functionalities. - - **`keygen`**: Key generation and precomputation. + - **`precomp`**: Centralized key generation and precomputation. - **`math`**: Mathematical operations and utilities. - **`paillier`**: Paillier cryptosystem implementation. - **`pedersen`**: Pedersen commitment management. - - **`presign`**: Presigning process management. - **`sign`**: Signing process management. - - **`zkproof`**: Zero-knowledge proof implementations. + - **`keygen`**: Keygen process management. + - **`aux`**: Aux-Info process management. + - **`presign`**: Presigning process management. + - **`zero_knowledge`**: Zero-knowledge proof implementations. - **`test`**: Contains functionality test. - **`ecdsa`**: Contains unit test for the Secp256k1 ECDSA signatures. + - **`math`**: Contains unit test for the `math` classes. + - **`paillier`**: Contains unit test for the Paillier encryption scheme. + - **`precomp`**: Contains unit test for the `precomputation` classes. - **`sign`**: Contains unit test for the signing of Threshold ECDSA. - **`zk`**: Contains unit test for zero-knowledge implementations. ## Limitations The current implementation is currently lacking some intended features: -- Distributed key generation protocol by [Canetti et al.](https://eprint.iacr.org/2021/060) have not been fully implemented. (Currently centralized) -- Missing key refresh and adversary identification protocols. - Main currently using precomputed secret primes to generate precomputations. This is to speed up the process. It is expected to have an accelerated prime generator incorporated in the precomputation phase. +- Parallelization of Broadcast communication. --- ## Copyright diff --git a/build.gradle.kts b/build.gradle.kts index c07c232..f1daaac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ plugins { kotlin("jvm") version "2.0.0" id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20-RC2" + id("jacoco") } group = "perun_network.ecdsa_threshold" @@ -11,9 +12,11 @@ repositories { maven("https://jitpack.io") } + dependencies { -// define the BOM and its version + // define the BOM and its version implementation(platform("org.kotlincrypto.hash:bom:0.5.3")) + implementation("org.kotlincrypto.hash:sha2") testImplementation(kotlin("test")) implementation(kotlin("stdlib")) implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.1") @@ -22,8 +25,12 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.5.0") implementation("fr.acinq.secp256k1:secp256k1-kmp:0.15.0") implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:0.15.0") - implementation("org.kotlincrypto.hash:sha2") + implementation("com.ionspin.kotlin:bignum:0.3.8") + // Add Kotlin Logging + implementation("io.github.microutils:kotlin-logging:3.0.5") + implementation("ch.qos.logback:logback-classic:1.4.11") + } tasks.test { @@ -31,4 +38,25 @@ tasks.test { } kotlin { jvmToolchain(11) -} \ No newline at end of file +} + + +jacoco { + toolVersion = "0.8.10" // Adjust to the latest JaCoCo version +} + +tasks.jacocoTestReport { + reports { + csv.required.set(true) + xml.required.set(true) // Generate XML report + html.required.set(true) // Generate HTML report + } + classDirectories.setFrom(files(classDirectories.files.map { + fileTree(it) { + setExcludes(listOf( + "**/MainKt.class", + "perun_network/ecdsa_threshold/tuple/*", + )) + } + })) +} diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 5a1a9bf..b38d827 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,242 +1,294 @@ package perun_network.ecdsa_threshold +import mu.KotlinLogging import org.kotlincrypto.hash.sha2.SHA256 import perun_network.ecdsa_threshold.ecdsa.PartialSignature import perun_network.ecdsa_threshold.ecdsa.Point -import perun_network.ecdsa_threshold.ecdsa.Scalar -import perun_network.ecdsa_threshold.keygen.getSamplePrecomputations -import perun_network.ecdsa_threshold.keygen.publicKeyFromShares -import perun_network.ecdsa_threshold.keygen.scalePrecomputations -import perun_network.ecdsa_threshold.paillier.PaillierCipherText -import perun_network.ecdsa_threshold.presign.* -import perun_network.ecdsa_threshold.sign.SignParty +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.precomp.generateSessionId +import perun_network.ecdsa_threshold.precomp.publicKeyFromShares +import perun_network.ecdsa_threshold.sign.Signer +import perun_network.ecdsa_threshold.sign.aux.AuxRound1Broadcast +import perun_network.ecdsa_threshold.sign.aux.AuxRound2Broadcast +import perun_network.ecdsa_threshold.sign.aux.AuxRound3Broadcast import perun_network.ecdsa_threshold.sign.combinePartialSignatures -import perun_network.ecdsa_threshold.sign.processPresignOutput -import perun_network.ecdsa_threshold.zero_knowledge.ZeroKnowledgeException -import java.math.BigInteger +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound1Broadcast +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound2Broadcast +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound3Broadcast +import perun_network.ecdsa_threshold.sign.presign.PresignRound1Broadcast +import perun_network.ecdsa_threshold.sign.presign.PresignRound2Broadcast +import perun_network.ecdsa_threshold.sign.presign.PresignRound3Broadcast + +private val logger = KotlinLogging.logger {} /** * Main function to demonstrate the threshold ECDSA signing process. */ @OptIn(ExperimentalStdlibApi::class) fun main() { - val n = 5 - val t = 3 + val n = 5 // Number of total parties. + val t = 3 // Threshold of minimum required signers. val startTime = System.currentTimeMillis() // capture the start time - // Generate Precomputations (Assuming the secret primes are precomputed). - val (ids, secretPrecomps, publicPrecomps) = getSamplePrecomputations(n, t, n) // Use generatePrecomputation instead to generate new safe primes. - println("Precomputation finished for $n signerIds with threshold $t") + val ssid = generateSessionId() + val parties = mutableMapOf() + for (i in 1..n) { + parties[i] = Signer( + id = i, + ssid = ssid, + threshold = t, + ) + } + // KEY GENERATION + logger.info {"Key generation started for $n signers with threshold $t "} + keyGen(parties) + logger.info {"Key generation finished for $n signers with threshold $t "} - // Message - val message = "hello" - val hash = SHA256().digest(message.toByteArray()) + // AUXILIARX INFO + logger.info { "Begin auxiliary info protocol for parties: ${parties.keys} "} + val publicPrecomps = auxInfo(parties) + logger.info { "Finished auxiliary info protocol for parties: ${parties.keys} "} + + // Calculate the public key. + val publicKey = publicKeyFromShares(parties.keys.toList(), publicPrecomps) // Determine signerIds - val signerIds = randomSigners(ids, t) - println("signerIds: $signerIds") - val publicKey = publicKeyFromShares(signerIds, publicPrecomps) - val (scaledPrecomps, scaledPublics, publicPoint) = scalePrecomputations(signerIds, secretPrecomps, publicPrecomps) + val signers = randomSigners(parties, t) + val signerIds = signers.keys.toList() + logger.info {"Randomly chosen signers: $signerIds" } + + // Scale Secret/Public Precomputations + val (publicPoint, _) = scalePrecomputation(signers) if (publicKey != publicPoint.toPublicKey()) { throw IllegalStateException("Inconsistent public Key") } - println("Scaled precomputations finished.\n") + logger.info {"Scaled precomputations finished."} - // Prepare the signers - val signers = mutableMapOf() - for (i in signerIds) { - signers[i] = ThresholdSigner( - id = i, - private = scaledPrecomps[i]!!, - publics = scaledPublics - ) + // **PRESIGN** + logger.info { "Begin Presigning protocol for signers $signerIds." } + val bigR = presign(signers) + logger.info { "Finished presigning protocol for signers $signerIds." } + + // Message + val message = "hello" + val hash = SHA256().digest(message.toByteArray()) + + // ** PARTIAL SIGNING ** + logger.info {"Partial signing the message: \"$message\"" } + val partialSignatures = partialSignMessage(signers, hash) + logger.info {"Finish ECDSA Partial Signing."} + + // ** ECDSA SIGNING ** + val ecdsaSignature= combinePartialSignatures(bigR, partialSignatures, publicPoint, hash) + logger.info {"Finish Combining ECDSA Signature: ${ecdsaSignature.toSecp256k1Signature().toHexString().uppercase()}."} + + // ** ECDSA VERIFICATION ** // + + if (ecdsaSignature.verifySecp256k1(hash, publicKey)) { + logger.info {"ECDSA signature verified successfully."} + } else { + logger.info {"failed to verify ecdsa signature."} } - // **PRESIGN** - // PRESIGN ROUND 1 - val presignRound1Inputs = mutableMapOf() - val presignRound1Outputs = mutableMapOf>() - val KShares = mutableMapOf() // K_i of every party - val GShares = mutableMapOf() // G_i of every party + val endTime = System.currentTimeMillis() // End time in milliseconds + val elapsedTime = (endTime - startTime) / 1000.0 // Convert milliseconds to seconds + logger.info {"Execution time: $elapsedTime seconds" } +} - for (i in signerIds) { - presignRound1Inputs[i] = PresignRound1Input( - ssid = scaledPrecomps[i]!!.ssid, - id = scaledPrecomps[i]!!.id, - publics = scaledPublics - ) +/** + * Chooses a random subset of t signerIds and their corresponding ThresholdSigners from the given map of parties. + * + * @param parties A map where the keys are signer IDs and the values are ThresholdSigners. + * @param t The number of signerIds to randomly select. + * @return A map containing t randomly selected signer IDs and their corresponding ThresholdSigners. + */ +fun randomSigners(parties: Map, t: Int): Map { + require(t <= parties.size) { "t must be less than or equal to the number of parties." } + require(t > 0) { "t must be greater than 0." } + + val partyIds = parties.keys.toList() + + // Shuffle the list and take the first t elements + val signerIds = partyIds.shuffled().take(t) - // Produce Presign Round1 output - val (output, gammaShare, kShare, gNonce, kNonce, K, G) = presignRound1Inputs[i]!!.producePresignRound1Output(signerIds) - presignRound1Outputs[i] = output - signers[i]!!.gammaShare = gammaShare - signers[i]!!.kShare = kShare - signers[i]!!.gNonce = gNonce - signers[i]!!.kNonce = kNonce - KShares[i] = K - GShares[i] = G + // Filter the map to include only the randomly selected signer IDs + return parties.filterKeys { it in signerIds } +} + +private fun scalePrecomputation(signers : Map) : Pair> { + val publicPoints = mutableMapOf() + val publicAllPrecomps = mutableMapOf>() + for (i in signers.keys.toList()) { + val (publicPrecomp, publicPoint) = signers[i]!!.scalePrecomputations(signers.keys.toList()) + publicPoints[i] = publicPoint + publicAllPrecomps[i] = publicPrecomp } - println("Finish Presign Round 1") - // PRESIGN ROUND 2 - val bigGammaShares = mutableMapOf() - val presignRound2Inputs = mutableMapOf() - val presignRound2Outputs = mutableMapOf>() - for (i in signerIds) { - // Prepare Presign Round 2 Inputs - presignRound2Inputs[i] = PresignRound2Input( - ssid = scaledPrecomps[i]!!.ssid, - id = scaledPrecomps[i]!!.id, - gammaShare = signers[i]!!.gammaShare!!, - secretECDSA = scaledPrecomps[i]!!.ecdsaShare, - secretPaillier = scaledPrecomps[i]!!.paillierSecret , - gNonce = signers[i]!!.gNonce!!, - publics = scaledPublics - ) + // Check output consistency + val referencePoint = publicPoints[signers.keys.first()]!! + val referencePrecomp = publicAllPrecomps[signers.keys.first()]!! + for (i in signers.keys) { + if (publicPoints[i] != referencePoint) throw IllegalStateException("Inconsistent public Key") + } - // Verify Presign Round 1 Outputs - for ((j, presign1output) in presignRound1Outputs) { - if (j != i) { - if (!presignRound2Inputs[i]!!.verifyPresignRound1Output(j, presign1output[i]!!)) { - throw ZeroKnowledgeException("failed to validate enc proof for K from $j to $i") - } - println("Validated presign round 1 output from $j to $i ") - } - } + return referencePoint to referencePrecomp +} - // Produce Presign Round2 output - val (presign2output, bigGammaShare) = presignRound2Inputs[i]!!.producePresignRound2Output( - signerIds, - KShares, - GShares) - presignRound2Outputs[i] = presign2output - bigGammaShares[i] = bigGammaShare +private fun keyGen(parties : Map) { + val startTime = System.currentTimeMillis() // capture the start time + val partyIds = parties.keys.toList() + // KEYGEN ROUND 1 + logger.info {"KEYGEN ROUND 1 started." } + val keygenRound1AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + keygenRound1AllBroadcasts[i] = parties[i]!!.keygenRound1(partyIds) } - println("Finish Presign Round 2.\n") + logger.info {"KEYGEN ROUND 1 finished." } - // PRESIGN ROUND 3 - val presignRound3Inputs = mutableMapOf() - val presignRound3Outputs = mutableMapOf>() - val deltaShares = mutableMapOf() - val bigDeltaShares = mutableMapOf() - val bigGammas = mutableMapOf() - for (i in signerIds) { - // Prepare Presign Round 3 Inputs - presignRound3Inputs[i] = PresignRound3Input( - ssid = scaledPrecomps[i]!!.ssid, - id = scaledPrecomps[i]!!.id, - gammaShare = signers[i]!!.gammaShare!!.value, - secretPaillier = scaledPrecomps[i]!!.paillierSecret, - kShare = signers[i]!!.kShare!!, - K = KShares[i]!!, - kNonce = signers[i]!!.kNonce!!, - secretECDSA = scaledPrecomps[i]!!.ecdsaShare.value, - publics = scaledPublics - ) + // KEYGEN ROUND 2 + logger.info {"KEYGEN ROUND 2 started." } + val keygenRound2AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + keygenRound2AllBroadcasts[i] = parties[i]!!.keygenRound2(partyIds) + } + logger.info {"KEYGEN ROUND 2 finished." } - // Verify Presign Round 2 Outputs - for ((j, presign2output) in presignRound2Outputs) { - if (j != i) { - if (!presignRound3Inputs[i]!!.verifyPresignRound2Output( - j, - presign2output[i]!!, - KShares[i]!!, - GShares[j]!!, - scaledPublics[j]!!.publicEcdsa - ) - ) { - throw ZeroKnowledgeException("failed to validate presign round 2 output from $j to $i") - } - println("Validated presign round 2 output from $j to $i ") - } - } + // KEYGEN ROUND 3 + logger.info {"KEYGEN ROUND 3 started." } + val keygenRound3AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + keygenRound3AllBroadcasts[i] = parties[i]!!.keygenRound3(partyIds, keygenRound1AllBroadcasts, keygenRound2AllBroadcasts) + } + logger.info {"KEYGEN ROUND 3 finished."} - // Produce Presign Round 3 output - val (presign3output, chiShare, deltaShare, bigDeltaShare, bigGamma) = presignRound3Inputs[i]!!.producePresignRound3Output( - signerIds, - bigGammaShares, - presignRound2Outputs) + // KEYGEN OUTPUT + logger.info {"PROCESS KEYGEN OUTPUT." } + val publicPoints = mutableMapOf() + for (i in partyIds) { + publicPoints[i] = parties[i]!!.keygenOutput(partyIds, keygenRound2AllBroadcasts, keygenRound3AllBroadcasts) + } + + val endTime = System.currentTimeMillis() // End time in milliseconds + val elapsedTime = (endTime - startTime) / 1000.0 // Convert milliseconds to seconds + logger.info {"KEYGEN FINISHED after $elapsedTime seconds."} - presignRound3Outputs[i] = presign3output - signers[i]!!.chiShare = Scalar(chiShare) - deltaShares[i] = deltaShare - bigDeltaShares[i] = bigDeltaShare - bigGammas[i] = bigGamma + // Check all public Points + val publicPoint = publicPoints[partyIds[0]]!! + for (i in partyIds) { + if (publicPoints[i] != publicPoint) throw IllegalStateException("Inconsistent public Key") } +} - println("Finish Presign Round 3.\n") +private fun auxInfo(parties: Map) : Map { + val startTime = System.currentTimeMillis() // capture the start time + val partyIds = parties.keys.toList() - // ** PARTIAL SIGNING ** + // AUX ROUND 1 + logger.info {"AUX ROUND 1 started." } + val auxRound1AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + auxRound1AllBroadcasts[i] = parties[i]!!.auxRound1(partyIds) + } + logger.info {"AUX ROUND 1 finished."} - // process Presign output - val bigR = processPresignOutput( - signers= signerIds, - deltaShares = deltaShares, - bigDeltaShares = bigDeltaShares, - gamma= bigGammas[signerIds[0]]!! - ) + // AUX ROUND 2 + logger.info {"AUX ROUND 2 started." } + val auxRound2AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + auxRound2AllBroadcasts[i] = parties[i]!!.auxRound2(partyIds) + } + logger.info {"AUX ROUND 2 finished."} - val partialsignerIds = mutableMapOf() - val partialSignatures = mutableListOf() - println("Partial signing the message: \"$message\"") - for (i in signerIds) { - partialsignerIds[i] = SignParty( - ssid = scaledPrecomps[i]!!.ssid, - id = scaledPrecomps[i]!!.id, - publics = scaledPublics, - hash = hash, - ) + // AUX ROUND 3 + logger.info {"AUX ROUND 3 started." } + val auxRound3AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + auxRound3AllBroadcasts[i] = parties[i]!!.auxRound3(partyIds, auxRound1AllBroadcasts, auxRound2AllBroadcasts) + } + logger.info {"AUX ROUND 3 finished."} - // Verify Presign outputs - for (j in signerIds) { - if (j != i) { - if (!partialsignerIds[i]!!.verifyPresignRound3Output(j, presignRound3Outputs[j]!![i]!!, KShares[j]!!)) { - throw ZeroKnowledgeException("failed to validate presign round 3 output from $j to $i") - } - println("Validated presign round 3 output from $j to $i ") + // AUX OUTPUT + logger.info {"PROCESS AUX OUTPUT." } + val publicPrecomps = mutableMapOf>() + for (i in partyIds) { + publicPrecomps[i] = parties[i]!!.auxOutput(partyIds, auxRound2AllBroadcasts, auxRound3AllBroadcasts) + } + + val endTime = System.currentTimeMillis() // End time in milliseconds + val elapsedTime = (endTime - startTime) / 1000.0 // Convert milliseconds to seconds + logger.info {"AUX FINISHED after $elapsedTime seconds."} + + // Check all public Points + val publicPrecomp = publicPrecomps[partyIds[0]]!! + for (i in partyIds) { + for (j in partyIds) { + if (publicPrecomps[i]!![j]!! != publicPrecomp[j]) { + throw IllegalStateException("Inconsistent public precomputations of index $j from party $i.") } } - - // Produce partial signature - partialSignatures.add(partialsignerIds[i]!!.createPartialSignature( - kShare = signers[i]!!.kShare!!, - chiShare = signers[i]!!.chiShare!!, - bigR= bigR - )) } - println("Finish ECDSA Partial Signing.\n") + return publicPrecomp +} - // ** ECDSA SIGNING ** - val ecdsaSignature= combinePartialSignatures(bigR, partialSignatures, publicPoint, hash) - println("Finish Combining ECDSA Signature: ${ecdsaSignature.toSecp256k1Signature().toHexString().uppercase()}.\n") +private fun presign(signers: Map) : Point { + val startTime = System.currentTimeMillis() // capture the start time + val signerIds = signers.keys.toList() + // PRESIGN ROUND 1 + logger.info {"PRESIGN ROUND1 started." } + val presignRound1AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + presignRound1AllBroadcasts[i] = signers[i]!!.presignRound1(signerIds) + } + logger.info {"PRESIGN ROUND 1 finished."} - if (ecdsaSignature.verifySecp256k1(hash, publicKey)) { - println("ECDSA signature verified successfully.\n") - } else { - println("failed to convert and verified ecdsa signature.\n") + // PRESIGN ROUND 2 + logger.info {"PRESIGN ROUND 2 started." } + val presignRound2AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, presignRound1AllBroadcasts) + } + logger.info {"PRESIGN ROUND 2 finished."} + + // PRESIGN ROUND 3 + logger.info {"PRESIGN ROUND 3 started." } + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, presignRound2AllBroadcasts) + } + logger.info {"PRESIGN ROUND 3 finished."} + + // PRESIGN OUTPUT + logger.info {"Process PRESIGN output." } + val bigRs = mutableMapOf() + for (i in signerIds) { + bigRs[i] = signers[i]!!.presignOutput(signerIds, presignRound3AllBroadcasts) } + // VERIFY OUTPUT CONSISTENCY + val referenceBigR = bigRs[signerIds[0]]!! + for (i in signerIds) { + if (referenceBigR != bigRs[i]) throw IllegalStateException("Inconsistent public Key") + } val endTime = System.currentTimeMillis() // End time in milliseconds val elapsedTime = (endTime - startTime) / 1000.0 // Convert milliseconds to seconds + logger.info {"PRESIGN finished after $elapsedTime seconds."} + return referenceBigR +} + +private fun partialSignMessage(signers: Map, hash: ByteArray) : List { + val partialSignatures = mutableListOf() + + for (i in signers.keys.toList()) { + partialSignatures.add(signers[i]!!.partialSignMessage(hash)) + } - println("Execution time: $elapsedTime seconds") + return partialSignatures } -/** - * Chooses a random list of t signerIds from the given list of ids. - * - * @param ids A list of signer IDs. - * @param t The number of signerIds to randomly select. - * @return A list containing t randomly selected signer IDs. - */ -fun randomSigners(ids: List, t : Int) : List { - require(t <= ids.size) { "t must be less than or equal to n" } - // Shuffle the list and take the first t elements - return ids.shuffled().take(t) -} \ No newline at end of file + diff --git a/src/main/kotlin/ecdsa/PrivateKey.kt b/src/main/kotlin/ecdsa/PrivateKey.kt index d5bbad7..f0a1ee7 100644 --- a/src/main/kotlin/ecdsa/PrivateKey.kt +++ b/src/main/kotlin/ecdsa/PrivateKey.kt @@ -39,15 +39,6 @@ class PrivateKey ( } return PrivateKey(data) } - - /** - * Returns a private key that is initialized to zero. - * - * @return A zeroed private key. - */ - fun zeroPrivateKey(): PrivateKey { - return Scalar.zero().toPrivateKey() - } } /** @@ -115,4 +106,11 @@ class PrivateKey ( fun toByteArray() : ByteArray { return value } + + override fun equals(other: Any?): Boolean { + if (other !is PrivateKey) { + return false + } + return this.value.contentEquals(other.value) + } } \ No newline at end of file diff --git a/src/main/kotlin/ecdsa/PublicKey.kt b/src/main/kotlin/ecdsa/PublicKey.kt index 9d161ff..d7564cc 100644 --- a/src/main/kotlin/ecdsa/PublicKey.kt +++ b/src/main/kotlin/ecdsa/PublicKey.kt @@ -27,16 +27,6 @@ class PublicKey( } - /** - * Adds another [PublicKey] to this public key using elliptic curve point addition. - * - * @param other The public key to add. - * @return A new [PublicKey] representing the result of the addition. - */ - fun add(other: PublicKey): PublicKey { - return PublicKey(Secp256k1.pubKeyTweakAdd(value, other.value)) - } - /** * Checks equality between this [PublicKey] and another object. * diff --git a/src/main/kotlin/ecdsa/Secp256k1Curve.kt b/src/main/kotlin/ecdsa/Secp256k1Curve.kt index 63b239e..5dc7bf9 100644 --- a/src/main/kotlin/ecdsa/Secp256k1Curve.kt +++ b/src/main/kotlin/ecdsa/Secp256k1Curve.kt @@ -56,7 +56,6 @@ data class Point( require(y >= BigInteger.ZERO && y < P) { "y-coordinate must be in range" } } - /** * Returns the x-coordinate of this point as a Scalar. * @@ -95,6 +94,12 @@ data class Point( return PublicKey.newPublicKey(data) } + fun toByteArray() : ByteArray { + val xBytes = bigIntegerToByteArray(x) + val yBytes = bigIntegerToByteArray(y) + return xBytes + yBytes + } + /** * Adds this point to another point on the curve. * @@ -160,6 +165,13 @@ data class Point( return this.x == BigInteger.ZERO || this.y == BigInteger.ZERO } + + /** + * Checks equality between this [Point] and another object. + * + * @param other The object to compare with. + * @return `true` if the other object is a [Point] with the same coordinates, otherwise `false`. + */ override fun equals(other: Any?): Boolean { return (other is Point) && (x == other.x && y == other.y) } @@ -184,19 +196,6 @@ data class Point( } -/** - * Converts a byte array into a Point on the secp256k1 curve. - * - * @param bytes The byte array to convert. - * @return The resulting Point. - */ -fun byteArrayToPoint(bytes: ByteArray): Point { - require(bytes.size == 65) - val x = BigInteger(bytes.copyOfRange(1, 33)).mod(P) - val y = BigInteger(bytes.copyOfRange(33, bytes.size)).mod(P) - return Point(x, y) -} - /** * Creates a new base point (G) on the secp256k1 curve. * @@ -224,7 +223,7 @@ fun newPoint() : Point { * @param bi The BigInteger to convert. * @return The resulting byte array. */ -fun bigIntegerToByteArray(bi: BigInteger): ByteArray { +internal fun bigIntegerToByteArray(bi: BigInteger): ByteArray { val bytes = bi.toByteArray() return when { @@ -233,8 +232,7 @@ fun bigIntegerToByteArray(bi: BigInteger): ByteArray { // If it's smaller, pad with leading zeros bytes.size < 32 -> ByteArray(32) { i -> if (i < 32 - bytes.size) 0 else bytes[i - (32 - bytes.size)] } // If it's larger, truncate it to the first 32 bytes - bytes.size > 32 -> bytes.copyOfRange(bytes.size - 32, bytes.size) // Handle cases where sign bit causes extra byte - else -> bytes + else -> bytes.copyOfRange(bytes.size - 32, bytes.size) // Handle cases where sign bit causes extra byte } } @@ -287,6 +285,16 @@ data class Scalar ( return Scalar(BigInteger.ZERO) } + /** + * Creates a scalar from a BigInteger value. + * + * @param value The integer value. + * @return The corresponding scalar. + */ + fun scalarFromBigInteger(value: BigInteger): Scalar { + return Scalar(value.mod(N)) + } + /** * Creates a scalar from an integer value. * @@ -297,7 +305,6 @@ data class Scalar ( return Scalar(value.toBigInteger().mod(N)) } - /** * Creates a scalar from a byte array. * @@ -323,26 +330,6 @@ data class Scalar ( return value == BigInteger.ZERO } - /** - * Checks if the scalar is high (greater than the curve order divided by 2). - * - * @return True if the scalar is high, otherwise false. - */ - fun isHigh(): Boolean { - return value > N.divide(BigInteger.valueOf(2)) - } - - /** - * Normalizes the scalar to ensure it's below the midpoint of the curve order. - * - * @return The normalized scalar. - */ - fun normalize() : Scalar { - if (isHigh()) { - return Scalar(N-value) - } - return this - } /** * Converts the scalar to a private key. @@ -354,7 +341,6 @@ data class Scalar ( return PrivateKey.newPrivateKey(scalarBytes) } - /** * Converts the scalar to a byte array. * diff --git a/src/main/kotlin/ecdsa/Signature.kt b/src/main/kotlin/ecdsa/Signature.kt index 4eaf795..217f8d4 100644 --- a/src/main/kotlin/ecdsa/Signature.kt +++ b/src/main/kotlin/ecdsa/Signature.kt @@ -60,19 +60,6 @@ class Signature ( return sig } - /** - * Normalizes the `S` value of the signature if it is greater than the curve order divided by 2. - * This ensures uniqueness by forcing `S` to be low. - * - * @return A normalized `Signature` with a potentially updated `S` value. - */ - fun normalize(): Signature { - var s = Scalar.scalarFromByteArray(S) - if (s.isHigh()) { - s = s.normalize() - } - return Signature(R, s.toByteArray()) - } /** * Verifies the signature using the secp256k1 elliptic curve with the provided message hash and public key. @@ -87,7 +74,6 @@ class Signature ( return Secp256k1.verify(secpSignature, hash, secpPublic) } - /** * Verifies the signature using elliptic curve arithmetic based on the provided message hash and public point. * diff --git a/src/main/kotlin/keygen/KeyGen.kt b/src/main/kotlin/keygen/KeyGen.kt deleted file mode 100644 index ca0c2bb..0000000 --- a/src/main/kotlin/keygen/KeyGen.kt +++ /dev/null @@ -1,278 +0,0 @@ -package perun_network.ecdsa_threshold.keygen - -import perun_network.ecdsa_threshold.ecdsa.Point -import perun_network.ecdsa_threshold.ecdsa.PublicKey -import perun_network.ecdsa_threshold.ecdsa.Scalar -import perun_network.ecdsa_threshold.ecdsa.newPoint -import perun_network.ecdsa_threshold.math.shamir.lagrange -import perun_network.ecdsa_threshold.math.shamir.sampleEcdsaShare -import perun_network.ecdsa_threshold.paillier.* -import perun_network.ecdsa_threshold.pedersen.PedersenParameters -import java.math.BigInteger -import java.security.MessageDigest -import java.security.SecureRandom - -/** - * Class representing secret precomputations for a party in a threshold ECDSA protocol. - * - * @property id The ID of the party. - * @property ssid The session identifier (SSID) as a byte array. - * @property threshold The threshold for the protocol. - * @property ecdsaShare The secret ECDSA share for the party. - * @property paillierSecret The Paillier secret key for the party. - */ -class SecretPrecomputation( - val id : Int, - val ssid: ByteArray, - val threshold: Int, - val ecdsaShare: Scalar, - val paillierSecret: PaillierSecret, -) - -/** - * Class representing public precomputations for a party in a threshold ECDSA protocol. - * - * @property id The ID of the party. - * @property ssid The session identifier (SSID) as a byte array. - * @property publicEcdsa The public ECDSA point corresponding to the party's share. - * @property paillierPublic The Paillier public key for the party. - * @property aux Pedersen parameters for the party. - */ -class PublicPrecomputation ( - val id : Int, - val ssid: ByteArray, - val publicEcdsa: Point, - val paillierPublic : PaillierPublic, - val aux: PedersenParameters -) - -/** - * Generates a random session identifier (SSID) of a given byte size, hashed with SHA-256. - * - * @param byteSize The size of the session ID in bytes (default is 16 bytes). - * @return A byte array representing the session ID. - */ -fun generateSessionId(byteSize: Int = 16): ByteArray { - val randomBytes = ByteArray(byteSize) - val secureRandom = SecureRandom() - secureRandom.nextBytes(randomBytes) - - val sha256Digest = MessageDigest.getInstance("SHA-256") - val hashedBytes = sha256Digest.digest(randomBytes) - - return hashedBytes.copyOf(byteSize) -} - -/** - * Generates secret and public precomputations for a group of parties. - * - * @param n The number of parties. - * @param t The threshold for the protocol. - * @param idRange The range of IDs to choose from for the parties. - * @return A Triple containing the list of party IDs, a map of secret precomputations, and a map of public precomputations. - * @throws IllegalArgumentException if `idRange` is less than `n`. - */ -fun generatePrecomputations(n: Int, t: Int, idRange: Int) : Triple, Map, Map> { - if (idRange < n ) throw IllegalArgumentException("id must be higher than n") - val ids = generatePartyIds(n, idRange) - val ssid = generateSessionId() - val precomps = mutableMapOf() - val publics = mutableMapOf() - - // generate threshold precomputations - val (secretShares, publicShares) = sampleEcdsaShare(t, ids) - for (i in ids) { - val (paillierPublic, paillierSecret) = paillierKeyGen() - val (aux, _) = paillierSecret.generatePedersen() - - val secretPrecomputation = SecretPrecomputation( - id = i, - ssid = ssid, - threshold = t, - ecdsaShare = secretShares[i]!!, - paillierSecret = paillierSecret - ) - - val publicPrecomp = PublicPrecomputation ( - id = i, - ssid = ssid, - publicEcdsa = publicShares[i]!!, - paillierPublic = paillierPublic, - aux = aux - ) - publics[i] = publicPrecomp - precomps[i] = secretPrecomputation - } - - return Triple(ids , precomps , publics) -} - -/** - * Samples secret and public precomputations for a group of parties from precomputed safe primes. - * - * @param n The number of parties. - * @param t The threshold for the protocol. - * @param idRange The range of IDs to choose from for the parties. - * @return A Triple containing the list of party IDs, a map of secret precomputations, and a map of public precomputations. - * @throws IllegalArgumentException if `idRange` is less than `n`. - */ -fun getSamplePrecomputations(n: Int, t: Int, idRange: Int) : Triple, Map, Map> { - // Precomputed safe Blum prime pairs - val precomputedPrimes: List> = listOf( - Pair( - BigInteger("00e8dc0a9a7723c9ba02f5b2375c5922d092e042a5a194c324a7068c3365e982d5f08875dfb85ee7a8eba68e967e0c0040793d26f5ed4e1f4a3ef2244706609b48ecdc00d81dbf11e7ed20eb1da86a626508f22a0bc1b49cf4795e2b19dccc9e6cd7c1bda0e41d71e5531c30f748373757f1ed8966b964286f8fa236b5e1a7b3df", 16), // p1 - BigInteger("00e4e03ba61889000a30da54b29b801e69bd7a4ded27389f4fab57b5a0d0acb00ff45f0187c625425b70cedf0910c14be1d269cd77db016795f4691efe5857d46aa3e43921829e4ce807e765177113b04d7860d5033821ae409a269d086f1a3447bd00103bab2d61a139cf949e0341e2fa38a28fbee4fd2046b95a79884b0bfadf", 16) // q1 - ), - Pair( - BigInteger("0080249672e660f63696820621f012f53cb097a40dc36ba61516face3401d74be0848349b74a496b164339eb56eeae4b9c6b10c9d92c279affe9db02e2928c6b0e0548746f1adb2afd9dbd0faa9c1c580822ae6710ce8692e822a097c7ef1452d44ec628060c044f54cd3f10b9084da4ceccb1c165a7e76517bb2f6809d5f3fe0b", 16), // p2 - BigInteger("00b864ec793381f8ec0230accb20585c0a9398e13796e06924fae416d8c65159c9d19965de84a27ce86d276d77d6ff28e3588e7f19086ebee6697978c57afd6cb9bdb202ff5afe9e3e4c17163f3e5fdceb3e6522cc312e0374bfc8f66818429fc99338626c72aa273f06897c2fb13bd74f652fcb1e08c98f53c271a377457b70bf", 16) // q2 - ), - Pair( - BigInteger("00d6d817faa9ea143ad3b4ad3b067221765673ba63eb0cfbf1bccc2b6502b6d09c787111e03695b038d4bdae051a534116fed9e24458bc9e76368450afd6f4cc9b45124c3de3cb960bdd98c4be22e8eaf02899b8e7c7e644286745f2855821fec0cea484c2ce092f6ac234f6dd0ab6b526687ec2a406d8afd93b8838e7223f2f1f", 16), // p3 - BigInteger("00f0a3a43f62fbd4da1a762df7c407f9fa99d8dfbf5d2e1f12854292ff8ed1c658a2fc1c1719d1f48b5fc3bb420266fd263441cfd354c414fe8abee12cb79f9b45dbfc1cc310ef55cd23793b1e11854288e37763fa21a523a767d0b09d04d6b1cd1eb5f33690798bdb3daaed584b75e72a0a0fc549693faf0e6253f5309c24e407", 16) // q3 - ), - Pair( - BigInteger("00d6a59f9bb8a268e7d09980ac46f4d896a167e53d1c4b64f631ce332a50d24549f2833098d264de70f8515f135097042f8ea986ee22667f5e1ee2ca09ab5b514955a361de6ec101226c712621d114d9ff108ac05ab403dbbe0a9dd8ab3ec641e5d01af2123bfe0c351197c0f70c7762bb9488c3197445177467f470c0d526af23", 16), // p4 - BigInteger("00d30084a00ca2d9666dc2b0a914aed59078f90926dd7ca7f409f32b14e49ca466a9f96d7c4faf85b01b07cb29e268dc37f3dc317f7c82279437794f2d274acdd2541540f62e95bbb1fca2eab3d43a17cda6c099fcd20a97da251df4e0caf03c55c445b8b4d58324b5426d72e8a4a0e2d5d3c25b4cdeb90c597f1ffbca695b737b", 16) // q4 - ), - Pair( - BigInteger("00e35cf40ac7cee41245db61490ad6a93a5a518a888f637fa204bedec976c74e3b61b541fe85f9200951c53eca7d0e0cb54911f5b2a19e48baae4cc9cf39927d574c242d6b0fdfe05aa766d7f45c1a6c76aa6b1b2310a8c9fac0bbae69b120f7c41842f563a93b18b721534423ad8cc90133a511a521933904c7ad7541be8277c3", 16), // p5 - BigInteger("00d9e276b93cb25c891c5e2944c01930f7e30c23dac0c26b2b3745655a3fb1dac760d2f489cccae0920e976aa043d710624951165b078478d4353f8c01e3d15ea3c7d39a4a89f632d67e27128c98d312d20755c7d1aadcc7326cd6954358b0bd6972f26649f7556220880a6a59e1a9347bf7ba235907f23280a7ffd2b19b7148cb", 16) // q5 - ), - Pair( - BigInteger("00e97f56625eb209d583152c843f6905c25e6bea4495df684d8a3eb49d10ebe72a6dd0ce0f22aa0fa58f05b9c013dc0c9bd6d7cfffba38f03d8ec3e86dbecbe76548be186bba9d2f470aa0a497c6ad51d0dee24e42215a2afd793211cb987d7f90d3c1c15716dc70f442db7ea76ef97a94b3e417482f4fef81cd6702981f1202f7", 16), // p6 - BigInteger("00bd6dbc14107371fe387c4634e24e3722a9b7f7b8c971913c71a35f8a7d2f44295dd3541836687191a8d74050745aeb41a4e53ad0d7be2441bd0a4fc213ac21e0a2fc0d65e9d9e1d1e68fd96c5524e818fda6c1fd15eff45ebbc695266a5ef4abc145c02fc4867609afe949026f4f2c841a2e0e5e0d5d635d4c93f4230f7d5b7f", 16) // q6 - ), - Pair( - BigInteger("00e851ddcfe4832be7424cebbb2446586177807ef64aa0f19e3c1dd82ab3d299eebb826c8bc0879fa7abc638e2d7e97520729254cb9e264136288a26e7c98fa74b9a3654db07736a9a7b6a5189d26cd1d81a7bfe5f81a153eb3c9456d2b713d3cb7a2e4c0afd0bc6e1892b35357afd144e80e14201417f550950d17a660842f517", 16), // p7 - BigInteger("00804d0dc1f69cfd8754e718340caf9f7a6ee6472b669ff05de26cb701e7dc9b8df7a2abf1bc1f32256c3d7c81297ccd81edef9c533b3f40afd9ad2fe7af35cb28003912eb2845ba0310b01b275dbcbcaae7d844c9f326243af45acbfaa34562d6784fb6f274c09c65f50e713a5656dddf5171506b60c0b12f37972d3fabc10c6b", 16) // q7 - ), - Pair( - BigInteger("00df72feb91e073a7713792e45883bb9115b097513fd62b336fc04d66ee2426e9d0dfbe2a6b6a078e5bd420caba6a6d9f5fd63b92e2b97b5e68bf8fe35afa4acff2fb1ab1c3bed582a310c99c847c8bd68113f96faca91d0ca294d48808ea41c87f2a131cf7870d31f413c90428fb6f9020570094a347b5b24be2a515f7cedc9a3", 16), // p8 - BigInteger("00bb96cb3bf965bba6b22ebb97eb5cb363ca61934774bcd30a66bb1785c64b9a8ec00427756f6114bed12f4b3314c8c2d4422ac07c094681da7f84d330715ae3cd245c78ed5e1d4d374d5984f304436e8684bf0b0043a0cdfabbe9832901b73c5c78f8594f2987e2d5d48cf481092130d1bcadebb6042a86d3692e3ac121723bb3", 16) // q8 - ), - Pair( - BigInteger("00d5dc4e14b1581867b703155345899b9ac57b5b8dbac181d12f09dd4c4b70e6600a164bc6dbbbafeca0a1b3e93f31a49bf174996b4d1ae8f44a824f1db00627e654aa2b44ba5d843d73a3b3905e0db590d306dc0ab58639aa369ec418785032cc1e5acc2b85abdc3edd00942a22ef9c0c3bf13e888440a04be04185815f21f4cb", 16), // p9 - BigInteger("00c088b648fb8b56ef810f081d62e8426e446e758a817aae463ab2e804e74101d66c8e8a7b2a53c968f717faf7a3b77598a85c7acacab7c3f7428e6769b94130c03cc878b5f3d045f453c836ff4a7986ba9371d2c8d98383a717a004640561f1ae00d66d161da15f8b72ff67be6322b3845bf57a2ae6991181ffac8dc429621f1b", 16) // q9 - ), - Pair( - BigInteger("00e5db3b97eac7e38b2fb674957e9fa377b3e63419c216d392e577763f86048bbe76b3f1f9ec82928052ce16f7ac744856391bcdd339f4d2ebf9925cb116747f3709c8382e14496376ba0bebf7574be7f0af023b042d15e7569e35090d6936efc1b39c6c1c8df033396829bae2c8a617c328f801627f2ffbbac7738e5028fa03e7", 16), // p10 - BigInteger("0097c0909bfdc4e5df81b5533021768a330e41359953a72b39a8219fd68f7e28de383250017b9f1a1c7f45e0ab0ea941f939587d8f659c5aac9ba83d1490b9554828294304446966608b5e6b2e9e108dbbb5d86a4cc2a3d1b07fca79054db382f096021dbd090af25e0046baf583ad31b500f34acaf2c9fb521525cc6f33a9239f", 16) // q10 - ) - ) - - if (idRange < n ) throw IllegalArgumentException("id must be higher than n") - if (n > precomputedPrimes.size) throw IllegalArgumentException("not enough precomputed primes") - val ids = generatePartyIds(n, idRange) - println("Parties: $ids") - val ssid = generateSessionId() - val precomps = mutableMapOf() - val publics = mutableMapOf() - - // generate threshold precomputations - val (secretShares, publicShares) = sampleEcdsaShare(t, ids) - for (i in ids) { - val paillierSecret = newPaillierSecretFromPrimes(precomputedPrimes[i-1].first, precomputedPrimes[i-1].second) - val paillierPublic = paillierSecret.publicKey - val (aux, _) = paillierSecret.generatePedersen() - - val secretPrecomputation = SecretPrecomputation( - id = i, - ssid = ssid, - threshold = t, - ecdsaShare = secretShares[i]!!, - paillierSecret = paillierSecret - ) - - val publicPrecomp = PublicPrecomputation ( - id = i, - ssid = ssid, - publicEcdsa = publicShares[i]!!, - paillierPublic = paillierPublic, - aux = aux - ) - publics[i] = publicPrecomp - precomps[i] = secretPrecomputation - println("Finished precomputation for $i") - } - - return Triple(ids , precomps , publics) -} - -/** - * Computes a public key from the shares of the signers using Lagrange interpolation. - * - * @param signers The list of party IDs that are participating. - * @param publicShares The map of public precomputations for the parties. - * @return The resulting public key. - */ -fun publicKeyFromShares(signers : List, publicShares : Map) : PublicKey { - var sum = newPoint() - val lagrangeCoeffs = lagrange(signers) - for (i in signers) { - sum = sum.add(lagrangeCoeffs[i]!!.act(publicShares[i]!!.publicEcdsa)) - } - return sum.toPublicKey() -} - -/** - * Scales the precomputations for signers using Lagrange's coefficients and computes the combined public point. - * - * @param signers The list of party IDs that are participating. - * @param precomps The map of secret precomputations for the parties. - * @param publics The map of public precomputations for the parties. - * @return A Triple containing scaled secret precomputations, scaled public precomputations, and the combined public point. - */ -fun scalePrecomputations(signers : List, precomps : Map, publics : Map) -: Triple, MutableMap, Point> { - val lagrangeCoefficients = lagrange(signers) - - - // Initialize a map to hold the scaled precomputations - val scaledPrecomps = mutableMapOf() - val scaledPublics = mutableMapOf() - - // Scale secret and public ECDSA Shares - for (id in signers) { - val scaledEcdsaShare = lagrangeCoefficients[id]!!.multiply(precomps[id]!!.ecdsaShare) - - val scaledPublicShare = lagrangeCoefficients[id]!!.act(publics[id]!!.publicEcdsa) - - scaledPublics[id] = PublicPrecomputation( - id = id, - ssid = precomps[id]!!.ssid, - publicEcdsa = scaledPublicShare, - paillierPublic = publics[id]!!.paillierPublic, - aux = publics[id]!!.aux - ) - - // Create a new SecretPrecomputation with the scaled private and public shares - scaledPrecomps[id] = SecretPrecomputation( - id = id, - ssid = precomps[id]!!.ssid, - threshold = precomps[id]!!.threshold, - ecdsaShare = scaledEcdsaShare, - paillierSecret = precomps[id]!!.paillierSecret, - ) - } - - var public = newPoint() - for (j in signers) { - public = public.add(scaledPublics[j]!!.publicEcdsa) - } - - return Triple(scaledPrecomps, scaledPublics, public) -} - -/** - * Generates a list of distinct party IDs from a given range. - * - * @param n The number of party IDs to generate. - * @param idRange The range within which to generate party IDs. - * @return A list of unique party IDs. - * @throws IllegalArgumentException if `n` is greater than `idRange`. - */ -fun generatePartyIds(n: Int, idRange: Int): List { - if (n > idRange) throw IllegalArgumentException("Cannot generate $n distinct numbers in the range [1, 100]") - return (1.. idRange).shuffled().take(n) -} \ No newline at end of file diff --git a/src/main/kotlin/math/Checks.kt b/src/main/kotlin/math/Checks.kt index f249e35..55b153a 100644 --- a/src/main/kotlin/math/Checks.kt +++ b/src/main/kotlin/math/Checks.kt @@ -9,8 +9,7 @@ import java.math.BigInteger * @param ints The integers to check. * @return `true` if all integers are in the valid range and co-prime to N; `false` otherwise. */ - -fun isValidModN(N: BigInteger, vararg ints: BigInteger?): Boolean { +internal fun isValidModN(N: BigInteger, vararg ints: BigInteger?): Boolean { val one = BigInteger.ONE for (i in ints) { if (i == null) { @@ -49,3 +48,14 @@ fun isInIntervalLEps(n: BigInteger): Boolean { fun isInIntervalLPrimeEps(n: BigInteger): Boolean { return n.bitLength() <= LPrimePlusEpsilon } + +/** + * Checks if n ∈ [-2^(1+L+E+√N), ..., 2^(1+L+E+√N)], for a Paillier modulus N. + * + * @param n The integer to check. + * @return True if n is within the interval, false otherwise. + */ +fun isInIntervalLEpsPlus1RootN(n: BigInteger?): Boolean { + if (n == null) return false + return n.bitLength() <= 1 + LPlusEpsilon + (BITS_INT_MOD_N / 2) +} diff --git a/src/main/kotlin/math/Helper.kt b/src/main/kotlin/math/Helper.kt new file mode 100644 index 0000000..9c0be97 --- /dev/null +++ b/src/main/kotlin/math/Helper.kt @@ -0,0 +1,66 @@ +package perun_network.ecdsa_threshold.math + +import java.math.BigInteger + +/** + * Computes the Jacobi symbol (x/y), which can be +1, -1, or 0. + * @param x The numerator. + * @param y The denominator (must be an odd integer). + * @return The Jacobi symbol of (x/y). + */ +fun jacobi(x: BigInteger, y: BigInteger): Int { + require(y > BigInteger.ZERO && y.and(BigInteger.ONE) == BigInteger.ONE) { + "The second argument (y) must be an odd integer greater than zero." + } + + var a = x + var b = y + var j = 1 + + // Adjust sign of b + if (b < BigInteger.ZERO) { + if (a < BigInteger.ZERO) { + j = -1 + } + b = b.negate() + } + + while (true) { + if (b == BigInteger.ONE) { + return j + } + if (a == BigInteger.ZERO) { + return 0 + } + + // a = a mod b + a = a.mod(b) + if (a == BigInteger.ZERO) { + return 0 + } + + // Handle factors of 2 in 'a' + val s = a.lowestSetBit // Number of trailing zero bits in 'a' + if (s % 2 != 0) { + val bMod8 = b.and(BigInteger.valueOf(7)) // b % 8 + if (bMod8 == BigInteger.valueOf(3) || bMod8 == BigInteger.valueOf(5)) { + j = -j + } + } + + // Divide a by 2^s + a = a.shiftRight(s) + + // Swap numerator and denominator + if (b.and(BigInteger.valueOf(3)) == BigInteger.valueOf(3) && + a.and(BigInteger.valueOf(3)) == BigInteger.valueOf(3) + ) { + j = -j + } + + // Swap a and b + val temp = a + a = b + b = temp + } +} \ No newline at end of file diff --git a/src/main/kotlin/math/Prime.kt b/src/main/kotlin/math/Prime.kt index 4faa5d6..775f61d 100644 --- a/src/main/kotlin/math/Prime.kt +++ b/src/main/kotlin/math/Prime.kt @@ -9,7 +9,7 @@ import java.security.SecureRandom * @param n The number to check for primality. * @return `true` if n is probably prime; `false` otherwise. */ -fun isPrime(n: BigInteger): Boolean { +private fun isPrime(n: BigInteger): Boolean { return n.isProbablePrime(100) // 100 is the certainty level for the primality test } @@ -39,7 +39,6 @@ fun generateSafeBlumPrime(bits: Int): BigInteger { return prime } - /** * Generates the necessary integers for a Paillier key pair. * Returns a pair of safe Blum primes (p, q) such that both p and q are: diff --git a/src/main/kotlin/math/Random.kt b/src/main/kotlin/math/Random.kt index deb3272..2b3b335 100644 --- a/src/main/kotlin/math/Random.kt +++ b/src/main/kotlin/math/Random.kt @@ -6,6 +6,18 @@ import java.io.InputStream import java.math.BigInteger import java.security.SecureRandom +// Security parameter definition +const val SecParam = 256 +const val SEC_BYTES = SecParam / 8 +const val L = 1 * SecParam // = 256 +const val LPrime = 5 * SecParam // = 1280 +const val Epsilon = 2 * SecParam // = 512 +const val LPlusEpsilon = L + Epsilon // = 768 +const val LPrimePlusEpsilon = LPrime + Epsilon // 1792 +const val BITS_INT_MOD_N = 8 * SecParam // = 2048 +const val BitsBlumPrime = 4 * SecParam // = 1024 +const val BitsPaillier = 2 * BitsBlumPrime // = 2048 + /** * Maximum number of iterations for random sampling. */ @@ -14,7 +26,7 @@ const val MAX_ITERATIONS = 255 /** * Secure random input stream for generating random bytes. */ -val random = SecureRandomInputStream(SecureRandom.getInstanceStrong()) +private val random = SecureRandomInputStream(SecureRandom.getInstanceStrong()) /** * Exception thrown when the maximum number of iterations is reached without a successful sample. @@ -42,7 +54,20 @@ fun mustReadBits(inputStream: InputStream , buffer: ByteArray) { } /** - * Samples a random element from the group of integers modulo `n` that is co-prime to `n`. + * Generates a random identifier (RID) as a secure random byte array. + * + * The RID is a 256-bit (32-byte) cryptographically secure random value. + * + * @return A 32-byte array of secure random values. + */ +fun sampleRID() : ByteArray { + val byteArray = ByteArray(SEC_BYTES) // Create a 32-byte array + random.read(byteArray) // Fill the array with random bytes + return byteArray +} + +/** + * Samples a random element from the group of integers modulo `n` that is co-prime to `n` (u ∈ ℤₙˣ). * * This function will attempt to generate a valid candidate up to [MAX_ITERATIONS] times. * @@ -50,9 +75,9 @@ fun mustReadBits(inputStream: InputStream , buffer: ByteArray) { * @return A random BigInteger in ℤₙ that is co-prime to `n`. * @throws IllegalStateException if the maximum number of iterations is reached without finding a valid candidate. */ -fun sampleUnitModN(n: BigInteger): BigInteger { +fun sampleModNStar(n: BigInteger): BigInteger { val bitLength = n.bitLength() - val buf = ByteArray((bitLength + 7) / 8) + val buf = ByteArray((bitLength + 7) / 8) // guarantees the correct buffer size in bytes. repeat(MAX_ITERATIONS) { random.read(buf) val candidate = BigInteger(buf) @@ -62,7 +87,7 @@ fun sampleUnitModN(n: BigInteger): BigInteger { } /** - * Samples a random element from the integers modulo `n`. + * Samples a random element from the integers modulo `n` (u ∈ ℤₙ). * * This function will attempt to generate a valid candidate up to [MAX_ITERATIONS] times. * @@ -70,35 +95,67 @@ fun sampleUnitModN(n: BigInteger): BigInteger { * @return A random BigInteger in ℤₙ. * @throws IllegalStateException if the maximum number of iterations is reached without finding a valid candidate. */ -fun modN(n: BigInteger): BigInteger { +fun sampleModN(n: BigInteger): BigInteger { val bitLength = n.bitLength() - val buf = ByteArray((bitLength + 7) / 8) + val buf = ByteArray((bitLength + 7) / 8) // guarantees the correct buffer size in bytes. repeat(MAX_ITERATIONS) { random.read(buf) - val candidate = BigInteger(buf) + val candidate = BigInteger(1, buf) if (candidate < n) return candidate } throw ERR_MAX_ITERATIONS } +/** + * Samples a quadratic non-residue modulo `n`. + * + * This function generates a random integer modulo `n` and checks if it is a + * quadratic non-residue (QNR) using the Jacobi symbol. It repeats the process + * up to [MAX_ITERATIONS] times until a valid QNR is found. + * + * A quadratic non-residue `x` modulo `n` satisfies the condition that the + * Jacobi symbol `(x/n)` is -1, indicating that `x` is not a square in the + * group of integers modulo `n`. + * + * @param n The modulus for which a quadratic non-residue is sampled. + * It is expected to be a positive integer. + * @return A randomly sampled quadratic non-residue modulo `n`. + * @throws IllegalStateException if no quadratic non-residue is found + * within [MAX_ITERATIONS]. + */ +fun sampleQuadraticNonResidue(n: BigInteger): BigInteger { + val buffer = ByteArray(BITS_INT_MOD_N / 8) + repeat(MAX_ITERATIONS) { + // Generate a random number modulo n + random.read(buffer) + val candidate = BigInteger(1, buffer).mod(n) + + // Check if it's a quadratic non-residue + if (jacobi(candidate, n) == -1) { + return candidate + } + } + throw IllegalStateException("Exceeded maximum iterations to find a QNR") +} + /** * Generates the parameters for the Pedersen commitment. * * This function samples values for `s`, `t`, and `λ` such that: - * - `s = tˡ` where `t = τ² mod N` + * - `s = t^λ` where `t = τ² mod N` * * @param phi The value used in the computation of `s`. * @param n The modulus used for sampling. * @return A Triple containing the values `(s, t, λ)`. */ fun samplePedersen(phi: BigInteger, n : BigInteger) : Triple { - val lambda = modN(phi) - val tau = sampleUnitModN(n) + val lambda = sampleModN(phi) + val tau = sampleModNStar(n) // t = τ² mod N val t = tau.mod(n).multiply(tau.mod(n)).mod(n) - // s = tˡ mod N + // s = t^λ mod N val s = t.modPow(lambda, n) return Triple(s, t, lambda) } @@ -128,12 +185,96 @@ fun sampleScalar(): Scalar { } } +/** + * Generates a random integer with the given number of bits, potentially negated. + * + * @param inputStream The input stream to read random bytes from. + * @param bits The number of bits for the random integer. + * @return A randomly generated BigInteger, which may be negative. + */ +fun sampleNeg(inputStream: InputStream, bits: Int): BigInteger { + val buf = ByteArray(bits / 8 + 1) + mustReadBits(inputStream, buf) + val neg = buf[0].toInt() and 1 + val out = BigInteger(1, buf.copyOfRange(1, buf.size)) + return if (neg == 1) -out else out +} + +/** + * Samples a random integer L in the range ±2^l. + * + * @return A randomly generated BigInteger within the specified range. + */ +fun sampleL() : BigInteger = sampleNeg(random, L) + +/** + * Samples a random integer in the range ±2^l'. + * + * @return A randomly generated BigInteger within the specified range. + */ +fun sampleLPrime(): BigInteger = sampleNeg(random,LPrime) + +/** + * Samples a random integer in the range ±2^(l+ε). + * + * @return A randomly generated BigInteger within the specified range. + */ +fun sampleLEps(): BigInteger = sampleNeg(random, LPlusEpsilon) + +/** + * Samples a random integer in the range ±2^(l'+ε). + * + * @return A randomly generated BigInteger within the specified range. + */ +fun sampleLPrimeEps(): BigInteger = sampleNeg(random, LPrimePlusEpsilon) + +/** + * Samples a random integer in the range ±2^l•N, where N is the size of a Paillier modulus. + * + * @return A randomly generated BigInteger within the specified range. + */ +fun sampleLN(): BigInteger = sampleNeg(random, L + BITS_INT_MOD_N) + +/** + * Samples a random integer in the range ±2^l•2N, where N is the size of a Paillier modulus. + * + * The sampled integer is uniformly distributed in the range [-2^l•2N, 2^l•2N]. + * + * @return A randomly generated BigInteger in the range ±2^l•2N. + */ +fun sampleLN2(): BigInteger = sampleNeg(random, L + (2* BITS_INT_MOD_N)) + +/** + * Samples a random integer in the range ±2^(l+ε)•N. + * + * @return A randomly generated BigInteger within the specified range. + */ +fun sampleLEpsN(): BigInteger = sampleNeg(random, LPlusEpsilon + BITS_INT_MOD_N) + +/** + * Samples a random integer in the range ±2^(l+ε)•2N, where N is the size of a Paillier modulus. + * + * The sampled integer is uniformly distributed in the range [-2^(l+ε)•2N, 2^(l+ε)•2N]. + * + * @return A randomly generated BigInteger in the range ±2^(l+ε)•2N. + */ +fun sampleLEpsN2(): BigInteger = sampleNeg(random, LPlusEpsilon + (2* BITS_INT_MOD_N)) + +/** + * Samples a random integer in the range ±2^(l+ε)•√N, where N is the size of a Paillier modulus. + * + * The sampled integer is uniformly distributed in the range [-2^(l+ε)•√N, 2^(l+ε)•√N]. + * + * @return A randomly generated BigInteger in the range ±2^(l+ε)•√N. + */ +fun sampleLEpsRootN() : BigInteger = sampleNeg(random, LPlusEpsilon + (BITS_INT_MOD_N/2)) + /** * A secure random input stream that reads bytes from a SecureRandom source. * * @param secureRandom The SecureRandom instance used for generating random bytes. */ -class SecureRandomInputStream(private val secureRandom: SecureRandom) : InputStream() { +internal class SecureRandomInputStream(private val secureRandom: SecureRandom) : InputStream() { /** * Reads a single byte from the input stream. diff --git a/src/main/kotlin/math/Sample.kt b/src/main/kotlin/math/Sample.kt deleted file mode 100644 index 882b066..0000000 --- a/src/main/kotlin/math/Sample.kt +++ /dev/null @@ -1,75 +0,0 @@ -package perun_network.ecdsa_threshold.math - -import java.io.InputStream -import java.math.BigInteger - -// Security parameter definition -const val SecParam = 256 -const val L = 1 * SecParam // = 256 -const val LPrime = 5 * SecParam // = 1280 -const val Epsilon = 2 * SecParam // = 512 -const val LPlusEpsilon = L + Epsilon // = 768 -const val LPrimePlusEpsilon = LPrime + Epsilon // 1792 - -const val BitsIntModN = 8 * SecParam // = 2048 - -const val BitsBlumPrime = 4 * SecParam // = 1024 -const val BitsPaillier = 2 * BitsBlumPrime // = 2048 - -/** - * Generates a random integer with the given number of bits, potentially negated. - * - * @param inputStream The input stream to read random bytes from. - * @param bits The number of bits for the random integer. - * @return A randomly generated BigInteger, which may be negative. - */ -fun sampleNeg(inputStream: InputStream, bits: Int): BigInteger { - val buf = ByteArray(bits / 8 + 1) - mustReadBits(inputStream, buf) - val neg = buf[0].toInt() and 1 - val out = BigInteger(1, buf.copyOfRange(1, buf.size)) - return if (neg == 1) -out else out -} - -/** - * Samples a random integer L in the range ±2^l. - * - * @return A randomly generated BigInteger within the specified range. - */ -fun sampleL() : BigInteger = sampleNeg(random, L) - -/** - * Samples a random integer in the range ±2^l'. - * - * @return A randomly generated BigInteger within the specified range. - */ -fun sampleLPrime(): BigInteger = sampleNeg(random,LPrime) - -/** - * Samples a random integer in the range ±2^(l+ε). - * - * @return A randomly generated BigInteger within the specified range. - */ -fun sampleLEps(): BigInteger = sampleNeg(random, LPlusEpsilon) - -/** - * Samples a random integer in the range ±2^(l'+ε). - * - * @return A randomly generated BigInteger within the specified range. - */ -fun sampleLPrimeEps(): BigInteger = sampleNeg(random, LPrimePlusEpsilon) - -/** - * Samples a random integer in the range ±2^l•N, where N is the size of a Paillier modulus. - * - * @return A randomly generated BigInteger within the specified range. - */ -fun sampleLN(): BigInteger = sampleNeg(random, L + BitsIntModN) - -/** - * Samples a random integer in the range ±2^(l+ε)•N. - * - * @return A randomly generated BigInteger within the specified range. - */ -fun sampleLEpsN(): BigInteger = sampleNeg(random, LPlusEpsilon + BitsIntModN) - diff --git a/src/main/kotlin/math/shamir/ExponentPolynomial.kt b/src/main/kotlin/math/shamir/ExponentPolynomial.kt new file mode 100644 index 0000000..a2e701a --- /dev/null +++ b/src/main/kotlin/math/shamir/ExponentPolynomial.kt @@ -0,0 +1,101 @@ +package perun_network.ecdsa_threshold.math.shamir + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.newPoint + +/** + * Represents a polynomial over points on an elliptic curve. + * The polynomial has the form: + * f(X) = P₀ + P₁⋅X + P₂⋅X² + ... + Pₜ⋅Xᵗ + * where Pᵢ are points on the elliptic curve, and X is a scalar. + * + * @property isConstant Indicates if the polynomial represents a constant value. + * @property coefficients List of elliptic curve points representing the coefficients. + */ +class ExponentPolynomial ( + val isConstant: Boolean, + val coefficients: List +) { + fun eval(x : Scalar) : Point { + var result = newPoint() + + // Iterate over coefficients in reverse order + for (i in coefficients.size - 1 downTo 0) { + result = x.act(result).add(coefficients[i]) + } + + if (isConstant) { + // If constant, multiply result by x + result = x.act(result) + } + + return result + } + + /** + * Provides a clone of this exponent polynomial. + * + * @return A cloned ExponentPolynomial based on this Exponent Polynomial. + */ + fun clone() : ExponentPolynomial { + return ExponentPolynomial(isConstant, coefficients.toMutableList()) + } + + /** + * Adds this polynomial to another polynomial. + * + * @param other The ExponentPolynomial to add. + * @return A new ExponentPolynomial representing the sum. + * @throws IllegalArgumentException If the two polynomials have different lengths + * or different `isConstant` flags. + */ + fun add(other: ExponentPolynomial) : ExponentPolynomial { + if (this.coefficients.size != other.coefficients.size) { + throw IllegalArgumentException("different length coefficients") + } + + if (isConstant != other.isConstant) throw IllegalArgumentException("different constant flag") + + val newCoeffs = mutableListOf() + + for (i in coefficients.indices) { + newCoeffs.add(this.coefficients[i].add(other.coefficients[i])) + } + + return ExponentPolynomial(isConstant = isConstant, coefficients = newCoeffs) + } + + /** + * Serializes the polynomial to a byte array. + * + * @return The serialized byte array representation. + */ + fun toByteArray(): ByteArray { + // Convert the `isConstant` boolean to a byte (1 for true, 0 for false) + val constantByte = if (isConstant) 1.toByte() else 0.toByte() + + // Convert each `Point` in `coefficients` to its byte array + val coefficientsBytes = coefficients.flatMap { it.toByteArray().asList() } + + // Combine the `isConstant` byte with the serialized coefficients + return byteArrayOf(constantByte) + coefficientsBytes.toByteArray() + } +} + +/** + * Computes the sum of a list of ExponentPolynomial objects. + * + * @param ePolynoms The list of ExponentPolynomial objects. + * @return The resulting sum as a single ExponentPolynomial. + * @throws IllegalArgumentException If the input list is empty. + */ +fun sum(ePolynoms: List) : ExponentPolynomial { + var sum = ePolynoms[0].clone() + + for (i in 1 until ePolynoms.size) { + sum = sum.add(ePolynoms[i]) + } + + return sum +} \ No newline at end of file diff --git a/src/main/kotlin/math/shamir/Lagrange.kt b/src/main/kotlin/math/shamir/Lagrange.kt index c2a8601..8d0db2b 100644 --- a/src/main/kotlin/math/shamir/Lagrange.kt +++ b/src/main/kotlin/math/shamir/Lagrange.kt @@ -24,7 +24,7 @@ fun lagrange(signers : List) : Map { * @param signers A list of signer IDs. * @return The Lagrange coefficient for signer `j`. */ -fun lagrangeOf(j : Int, signers: List) : Scalar { +private fun lagrangeOf(j : Int, signers: List) : Scalar { var result = Scalar(BigInteger.ONE) val x_j = Scalar.scalarFromInt(j) // denominator diff --git a/src/main/kotlin/math/shamir/Polynomial.kt b/src/main/kotlin/math/shamir/Polynomial.kt index 7c24054..fcd888c 100644 --- a/src/main/kotlin/math/shamir/Polynomial.kt +++ b/src/main/kotlin/math/shamir/Polynomial.kt @@ -2,13 +2,12 @@ package perun_network.ecdsa_threshold.math.shamir import perun_network.ecdsa_threshold.ecdsa.Point import perun_network.ecdsa_threshold.ecdsa.Scalar -import perun_network.ecdsa_threshold.math.shamir.Polynomial.Companion.newPolynomial import perun_network.ecdsa_threshold.math.sampleScalar -import java.math.BigInteger /** * Polynomial represents a function f(X) = a₀ + a₁⋅X + … + aₜ⋅Xᵗ. * This is used for secret sharing where coefficients represent secrets. + * The coefficients are sampled using SecureRandom, so it can be used for Key Generation. * * @property coefficients The list of coefficients representing the polynomial. */ @@ -22,11 +21,10 @@ class Polynomial ( * @param degree The degree of the polynomial. * @return A polynomial with randomly sampled coefficients. */ - fun newPolynomial(degree: Int) : Polynomial { + fun newPolynomial(degree: Int, constant : Scalar = sampleScalar()) : Polynomial { val coefficients = mutableListOf() // sample a0 - val constant = sampleScalar() coefficients.add(constant) for (i in 1..degree) { @@ -44,33 +42,32 @@ class Polynomial ( * @throws IllegalArgumentException if `x` is zero (could leak the secret). */ fun eval(x : Scalar) : Scalar { - if (x.isZero()) { - throw IllegalArgumentException("Attempting to leak secret") - } - var result = Scalar.zero() for (i in coefficients.size - 1 downTo 0) { result = result.multiply(x).add(coefficients[i]) } return result } -} -/** - * Generates secret ECDSA shares and their corresponding public points using Shamir's Secret Sharing scheme. - * - * @param threshold The threshold number of shares required to reconstruct the secret. - * @param ids The list of participant IDs. - * @return A pair containing the secret shares and their corresponding public points. - */ -fun sampleEcdsaShare(threshold: Int, ids: List) : Pair, Map> { - val secretShares = mutableMapOf() - val publicShares = mutableMapOf() - val polynomial = newPolynomial(threshold) - for (i in ids) { - secretShares[i] = (polynomial.eval(Scalar(BigInteger.valueOf(i.toLong())))) - publicShares[i] = (secretShares[i]!!.actOnBase()) + /** + * Converts this polynomial into an exponent polynomial. + * Each coefficient is treated as a scalar acting on the curve base point. + * + * @return The corresponding ExponentPolynomial. + */ + fun exponentPolynomial(): ExponentPolynomial { + val coefficients = mutableListOf() + val isConstant = this.coefficients[0].isZero() + + for (i in 0..> = listOf( + Pair( + BigInteger("00e8dc0a9a7723c9ba02f5b2375c5922d092e042a5a194c324a7068c3365e982d5f08875dfb85ee7a8eba68e967e0c0040793d26f5ed4e1f4a3ef2244706609b48ecdc00d81dbf11e7ed20eb1da86a626508f22a0bc1b49cf4795e2b19dccc9e6cd7c1bda0e41d71e5531c30f748373757f1ed8966b964286f8fa236b5e1a7b3df", 16), // p1 + BigInteger("00e4e03ba61889000a30da54b29b801e69bd7a4ded27389f4fab57b5a0d0acb00ff45f0187c625425b70cedf0910c14be1d269cd77db016795f4691efe5857d46aa3e43921829e4ce807e765177113b04d7860d5033821ae409a269d086f1a3447bd00103bab2d61a139cf949e0341e2fa38a28fbee4fd2046b95a79884b0bfadf", 16) // q1 + ), + Pair( + BigInteger("0080249672e660f63696820621f012f53cb097a40dc36ba61516face3401d74be0848349b74a496b164339eb56eeae4b9c6b10c9d92c279affe9db02e2928c6b0e0548746f1adb2afd9dbd0faa9c1c580822ae6710ce8692e822a097c7ef1452d44ec628060c044f54cd3f10b9084da4ceccb1c165a7e76517bb2f6809d5f3fe0b", 16), // p2 + BigInteger("00b864ec793381f8ec0230accb20585c0a9398e13796e06924fae416d8c65159c9d19965de84a27ce86d276d77d6ff28e3588e7f19086ebee6697978c57afd6cb9bdb202ff5afe9e3e4c17163f3e5fdceb3e6522cc312e0374bfc8f66818429fc99338626c72aa273f06897c2fb13bd74f652fcb1e08c98f53c271a377457b70bf", 16) // q2 + ), + Pair( + BigInteger("00d6d817faa9ea143ad3b4ad3b067221765673ba63eb0cfbf1bccc2b6502b6d09c787111e03695b038d4bdae051a534116fed9e24458bc9e76368450afd6f4cc9b45124c3de3cb960bdd98c4be22e8eaf02899b8e7c7e644286745f2855821fec0cea484c2ce092f6ac234f6dd0ab6b526687ec2a406d8afd93b8838e7223f2f1f", 16), // p3 + BigInteger("00f0a3a43f62fbd4da1a762df7c407f9fa99d8dfbf5d2e1f12854292ff8ed1c658a2fc1c1719d1f48b5fc3bb420266fd263441cfd354c414fe8abee12cb79f9b45dbfc1cc310ef55cd23793b1e11854288e37763fa21a523a767d0b09d04d6b1cd1eb5f33690798bdb3daaed584b75e72a0a0fc549693faf0e6253f5309c24e407", 16) // q3 + ), + Pair( + BigInteger("00d6a59f9bb8a268e7d09980ac46f4d896a167e53d1c4b64f631ce332a50d24549f2833098d264de70f8515f135097042f8ea986ee22667f5e1ee2ca09ab5b514955a361de6ec101226c712621d114d9ff108ac05ab403dbbe0a9dd8ab3ec641e5d01af2123bfe0c351197c0f70c7762bb9488c3197445177467f470c0d526af23", 16), // p4 + BigInteger("00d30084a00ca2d9666dc2b0a914aed59078f90926dd7ca7f409f32b14e49ca466a9f96d7c4faf85b01b07cb29e268dc37f3dc317f7c82279437794f2d274acdd2541540f62e95bbb1fca2eab3d43a17cda6c099fcd20a97da251df4e0caf03c55c445b8b4d58324b5426d72e8a4a0e2d5d3c25b4cdeb90c597f1ffbca695b737b", 16) // q4 + ), + Pair( + BigInteger("00e35cf40ac7cee41245db61490ad6a93a5a518a888f637fa204bedec976c74e3b61b541fe85f9200951c53eca7d0e0cb54911f5b2a19e48baae4cc9cf39927d574c242d6b0fdfe05aa766d7f45c1a6c76aa6b1b2310a8c9fac0bbae69b120f7c41842f563a93b18b721534423ad8cc90133a511a521933904c7ad7541be8277c3", 16), // p5 + BigInteger("00d9e276b93cb25c891c5e2944c01930f7e30c23dac0c26b2b3745655a3fb1dac760d2f489cccae0920e976aa043d710624951165b078478d4353f8c01e3d15ea3c7d39a4a89f632d67e27128c98d312d20755c7d1aadcc7326cd6954358b0bd6972f26649f7556220880a6a59e1a9347bf7ba235907f23280a7ffd2b19b7148cb", 16) // q5 + ), + Pair( + BigInteger("00e97f56625eb209d583152c843f6905c25e6bea4495df684d8a3eb49d10ebe72a6dd0ce0f22aa0fa58f05b9c013dc0c9bd6d7cfffba38f03d8ec3e86dbecbe76548be186bba9d2f470aa0a497c6ad51d0dee24e42215a2afd793211cb987d7f90d3c1c15716dc70f442db7ea76ef97a94b3e417482f4fef81cd6702981f1202f7", 16), // p6 + BigInteger("00bd6dbc14107371fe387c4634e24e3722a9b7f7b8c971913c71a35f8a7d2f44295dd3541836687191a8d74050745aeb41a4e53ad0d7be2441bd0a4fc213ac21e0a2fc0d65e9d9e1d1e68fd96c5524e818fda6c1fd15eff45ebbc695266a5ef4abc145c02fc4867609afe949026f4f2c841a2e0e5e0d5d635d4c93f4230f7d5b7f", 16) // q6 + ), + Pair( + BigInteger("00e851ddcfe4832be7424cebbb2446586177807ef64aa0f19e3c1dd82ab3d299eebb826c8bc0879fa7abc638e2d7e97520729254cb9e264136288a26e7c98fa74b9a3654db07736a9a7b6a5189d26cd1d81a7bfe5f81a153eb3c9456d2b713d3cb7a2e4c0afd0bc6e1892b35357afd144e80e14201417f550950d17a660842f517", 16), // p7 + BigInteger("00804d0dc1f69cfd8754e718340caf9f7a6ee6472b669ff05de26cb701e7dc9b8df7a2abf1bc1f32256c3d7c81297ccd81edef9c533b3f40afd9ad2fe7af35cb28003912eb2845ba0310b01b275dbcbcaae7d844c9f326243af45acbfaa34562d6784fb6f274c09c65f50e713a5656dddf5171506b60c0b12f37972d3fabc10c6b", 16) // q7 + ), + Pair( + BigInteger("00df72feb91e073a7713792e45883bb9115b097513fd62b336fc04d66ee2426e9d0dfbe2a6b6a078e5bd420caba6a6d9f5fd63b92e2b97b5e68bf8fe35afa4acff2fb1ab1c3bed582a310c99c847c8bd68113f96faca91d0ca294d48808ea41c87f2a131cf7870d31f413c90428fb6f9020570094a347b5b24be2a515f7cedc9a3", 16), // p8 + BigInteger("00bb96cb3bf965bba6b22ebb97eb5cb363ca61934774bcd30a66bb1785c64b9a8ec00427756f6114bed12f4b3314c8c2d4422ac07c094681da7f84d330715ae3cd245c78ed5e1d4d374d5984f304436e8684bf0b0043a0cdfabbe9832901b73c5c78f8594f2987e2d5d48cf481092130d1bcadebb6042a86d3692e3ac121723bb3", 16) // q8 + ), + Pair( + BigInteger("00d5dc4e14b1581867b703155345899b9ac57b5b8dbac181d12f09dd4c4b70e6600a164bc6dbbbafeca0a1b3e93f31a49bf174996b4d1ae8f44a824f1db00627e654aa2b44ba5d843d73a3b3905e0db590d306dc0ab58639aa369ec418785032cc1e5acc2b85abdc3edd00942a22ef9c0c3bf13e888440a04be04185815f21f4cb", 16), // p9 + BigInteger("00c088b648fb8b56ef810f081d62e8426e446e758a817aae463ab2e804e74101d66c8e8a7b2a53c968f717faf7a3b77598a85c7acacab7c3f7428e6769b94130c03cc878b5f3d045f453c836ff4a7986ba9371d2c8d98383a717a004640561f1ae00d66d161da15f8b72ff67be6322b3845bf57a2ae6991181ffac8dc429621f1b", 16) // q9 + ), + Pair( + BigInteger("00e5db3b97eac7e38b2fb674957e9fa377b3e63419c216d392e577763f86048bbe76b3f1f9ec82928052ce16f7ac744856391bcdd339f4d2ebf9925cb116747f3709c8382e14496376ba0bebf7574be7f0af023b042d15e7569e35090d6936efc1b39c6c1c8df033396829bae2c8a617c328f801627f2ffbbac7738e5028fa03e7", 16), // p10 + BigInteger("0097c0909bfdc4e5df81b5533021768a330e41359953a72b39a8219fd68f7e28de383250017b9f1a1c7f45e0ab0ea941f939587d8f659c5aac9ba83d1490b9554828294304446966608b5e6b2e9e108dbbb5d86a4cc2a3d1b07fca79054db382f096021dbd090af25e0046baf583ad31b500f34acaf2c9fb521525cc6f33a9239f", 16) // q10 + ) +) /** * Represents the public key in the Paillier cryptosystem. @@ -10,30 +55,12 @@ import java.math.BigInteger * @property n The modulus, calculated as n = p * q, where p and q are prime factors. * @property nSquared The square of the modulus, calculated as n². * @property nPlusOne The value of n + 1. - * - * @constructor Creates a new instance of [PaillierPublic] with the given parameters. - * - * @param n The modulus. - * @param nSquared The square of the modulus. - * @param nPlusOne The value of n + 1. */ class PaillierPublic ( val n: BigInteger, val nSquared: BigInteger, - private val nPlusOne: BigInteger + val nPlusOne: BigInteger ) { - companion object { - /** - * Creates a new instance of [PaillierPublic] using the specified modulus n. - * - * @param n The modulus to be used for the public key. - * @return A new instance of [PaillierPublic]. - */ - fun newPublicKey(n: BigInteger): PaillierPublic { - return PaillierPublic(n, n.multiply(n), n.add(BigInteger.ONE)) - } - } - /** * Encrypts a message using a randomly generated nonce. * @@ -44,7 +71,7 @@ class PaillierPublic ( * @return A pair consisting of the resulting [PaillierCipherText] and the used nonce. */ fun encryptRandom(m: BigInteger): Pair { - val nonce = sampleUnitModN(n) + val nonce = sampleModNStar(n) return Pair(encryptWithNonce(m, nonce), nonce) } @@ -82,7 +109,6 @@ class PaillierPublic ( return (other is PaillierPublic) && n.compareTo(other.n) == 0 } - /** * Validates that the provided ciphertexts are in the correct range and are coprime to N². * @@ -95,6 +121,26 @@ class PaillierPublic ( } return true } + + fun toByteArray(): ByteArray { + // Convert each BigInteger to its byte array representation + val nBytes = n.toByteArray() + val nSquaredBytes = nSquared.toByteArray() + val nPlusOneBytes = nPlusOne.toByteArray() + + // Helper function to convert an integer size to a 4-byte array + fun Int.toByteArray(): ByteArray = byteArrayOf( + (this shr 24).toByte(), + (this shr 16).toByte(), + (this shr 8).toByte(), + this.toByte() + ) + + // Combine the lengths and the actual byte arrays + return nBytes.size.toByteArray() + nBytes + + nSquaredBytes.size.toByteArray() + nSquaredBytes + + nPlusOneBytes.size.toByteArray() + nPlusOneBytes + } } /** @@ -107,9 +153,9 @@ class PaillierPublic ( * @param n The modulus to validate. * @return An [Exception] if validation fails; otherwise, returns null. */ -fun validateN(n: BigInteger): Exception? { +internal fun validateN(n: BigInteger): Exception? { if (n.signum() <= 0) return IllegalArgumentException("modulus N is nil") - if (n.bitLength() != BitsPaillier) { + if (n.bitLength() > BitsPaillier) { return IllegalArgumentException("Expected bit length: $BitsPaillier, found: ${n.bitLength()}") } if (!n.testBit(0)) return IllegalArgumentException("Modulus N is even") @@ -117,7 +163,6 @@ fun validateN(n: BigInteger): Exception? { return null } - /** Paillier's Secret Key **/ // Define errors for prime validation val ErrPrimeBadLength = IllegalArgumentException("Prime factor is not the right length") @@ -194,10 +239,11 @@ data class PaillierSecret( // x = C(N+1)⁻ᵐ (mod N) val n = publicKey.n - val x = publicKey.n.modPow(mNeg, n).multiply(ct.c).mod(n) + var x = publicKey.nPlusOne.modPow(mNeg, publicKey.n) + x = x.multiply(ct.c).mod(publicKey.n) // r = xⁿ⁻¹ (mod N) - val nInverse = phi.modInverse(n) + val nInverse = n.modInverse(phi) val r = x.modPow(nInverse, n) return m to r @@ -208,7 +254,7 @@ data class PaillierSecret( * * @return A pair consisting of [PedersenParameters] and a lambda value. */ - fun generatePedersen(): Pair { + fun generatePedersenParameters(): Pair { val n = publicKey.n val (s, t, lambda) = samplePedersen(phi, publicKey.n) val ped = PedersenParameters(n, s, t) @@ -226,6 +272,16 @@ fun paillierKeyGen(): Pair { return sk.publicKey to sk } +/** + * Generates a new PublicKey and its associated SecretKey from a precomputed primesPair for the Paillier cryptosystem. + * + * @return A pair consisting of a new [PaillierPublic] and its corresponding [PaillierSecret]. + */ +fun paillierKeyGenMock() : Pair { + val id = Random.nextInt(PRECOMPUTED_PRIMES.size) + val sk = newPaillierSecretFromPrimes(PRECOMPUTED_PRIMES[id].first, PRECOMPUTED_PRIMES[id].second) + return sk.publicKey to sk +} /** * Generates a new SecretKey for the Paillier cryptosystem by generating suitable primes p and q. @@ -238,7 +294,6 @@ fun newPaillierSecret(): PaillierSecret { return newPaillierSecretFromPrimes(p, q) } - /** * Generates a new SecretKey from given prime factors p and q. (N = p*q) * @@ -284,7 +339,7 @@ fun newPaillierSecretFromPrimes(p: BigInteger, q: BigInteger): PaillierSecret { * @return `true` if p is a suitable prime; `false` otherwise. * @throws IllegalArgumentException If the prime does not meet the validation criteria. */ -fun validatePrime(p: BigInteger): Boolean { +internal fun validatePrime(p: BigInteger): Boolean { val bitsWant = BitsBlumPrime // Check bit lengths diff --git a/src/main/kotlin/pedersen/Pedersen.kt b/src/main/kotlin/pedersen/Pedersen.kt index 2af5c8f..ee84065 100644 --- a/src/main/kotlin/pedersen/Pedersen.kt +++ b/src/main/kotlin/pedersen/Pedersen.kt @@ -9,12 +9,6 @@ import java.math.BigInteger * @property n The modulus used for calculations in the commitment scheme. * @property s The first base used for the commitment. * @property t The second base used for the commitment. - * - * @constructor Creates an instance of [PedersenParameters]. - * - * @param n The modulus. - * @param s The first base. - * @param t The second base. */ data class PedersenParameters( val n: BigInteger, // Modulus @@ -60,4 +54,28 @@ data class PedersenParameters( val rhs = te.multiply(S).mod(n) // rhs = S⋅Tᵉ (mod N) return lhs == rhs } + + /** + * Serializes the Pedersen parameters to a byte array. + * + * @return The serialized byte array representation. + */ + fun toByteArray(): ByteArray { + // Convert each BigInteger to its byte array representation + val nBytes = n.toByteArray() + val sBytes = s.toByteArray() + val tBytes = t.toByteArray() + + // Helper function to convert an integer size to a 4-byte array + fun Int.toByteArray(): ByteArray = byteArrayOf( + (this shr 24).toByte(), + (this shr 16).toByte(), + (this shr 8).toByte(), + this.toByte() + ) + + // Combine the lengths and the actual byte arrays + return nBytes.size.toByteArray() + nBytes + sBytes.size.toByteArray() + sBytes + + tBytes.size.toByteArray() + tBytes + } } \ No newline at end of file diff --git a/src/main/kotlin/precomp/Precomp.kt b/src/main/kotlin/precomp/Precomp.kt new file mode 100644 index 0000000..6881c91 --- /dev/null +++ b/src/main/kotlin/precomp/Precomp.kt @@ -0,0 +1,261 @@ +package perun_network.ecdsa_threshold.precomp + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.PublicKey +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.newPoint +import perun_network.ecdsa_threshold.math.shamir.Polynomial.Companion.newPolynomial +import perun_network.ecdsa_threshold.math.shamir.lagrange +import perun_network.ecdsa_threshold.paillier.* +import perun_network.ecdsa_threshold.pedersen.PedersenParameters +import java.math.BigInteger +import java.security.MessageDigest +import java.security.SecureRandom + +/** + * Class representing secret precomputations for a party in a threshold ECDSA protocol. + * + * @property id The ID of the party. + * @property ssid The session identifier (SSID) as a byte array. + * @property threshold The threshold for the protocol. + * @property ecdsaShare The secret ECDSA share for the party. + * @property paillierSecret The Paillier secret key for the party. + */ +class SecretPrecomputation( + val id : Int, + val ssid: ByteArray, + val threshold: Int, + val ecdsaShare: Scalar, + val paillierSecret: PaillierSecret, +) + +/** + * Class representing public precomputations for a party in a threshold ECDSA protocol. + * + * @property id The ID of the party. + * @property ssid The session identifier (SSID) as a byte array. + * @property publicEcdsa The public ECDSA point corresponding to the party's share. + * @property paillierPublic The Paillier public key for the party. + * @property aux Pedersen parameters for the party. + */ +class PublicPrecomputation ( + val id : Int, + val ssid: ByteArray, + val publicEcdsa: Point, + val paillierPublic : PaillierPublic, + val aux: PedersenParameters +) { + /** + * Checks equality between this [PublicPrecomputation] and another object. + * + * @param other The object to compare with. + * @return `true` if the other object is a [PublicPrecomputation] with the same byte array, otherwise `false`. + */ + override fun equals(other: Any?): Boolean { + if (other !is PublicPrecomputation) return false + return id == other.id + && ssid.contentEquals(other.ssid) + && publicEcdsa == other.publicEcdsa + && paillierPublic == other.paillierPublic + && aux == other.aux + } +} + +/** + * Generates a random session identifier (SSID) of a given byte size, hashed with SHA-256. + * + * @param byteSize The size of the session ID in bytes (default is 16 bytes). + * @return A byte array representing the session ID. + */ +fun generateSessionId(byteSize: Int = 16): ByteArray { + val randomBytes = ByteArray(byteSize) + val secureRandom = SecureRandom() + secureRandom.nextBytes(randomBytes) + + val sha256Digest = MessageDigest.getInstance("SHA-256") + val hashedBytes = sha256Digest.digest(randomBytes) + + return hashedBytes.copyOf(byteSize) +} + +/** + * Generates secret and public precomputations for a group of parties. + * + * @param n The number of parties. + * @param t The threshold for the protocol. + * @return A Triple containing the list of party IDs, a map of secret precomputations, and a map of public precomputations. + * @throws IllegalArgumentException if `idRange` is less than `n`. + */ +fun generatePrecomputations(n: Int, t: Int) : Triple, Map, Map> { + require(n >= t, { "threshold must be less than or equals total parties" }) + val ids = generatePartyIds(n) + val ssid = generateSessionId() + val precomps = mutableMapOf() + val publicPrecomps = mutableMapOf() + + // generate threshold precomputations + val (secretShares, publicShares) = sampleEcdsaShare(t, ids) + for (i in ids) { + val (paillierPublic, paillierSecret) = paillierKeyGen() + val (aux, _) = paillierSecret.generatePedersenParameters() + + val secretPrecomputation = SecretPrecomputation( + id = i, + ssid = ssid, + threshold = t, + ecdsaShare = secretShares[i]!!, + paillierSecret = paillierSecret + ) + + val publicPrecomp = PublicPrecomputation ( + id = i, + ssid = ssid, + publicEcdsa = publicShares[i]!!, + paillierPublic = paillierPublic, + aux = aux + ) + publicPrecomps[i] = publicPrecomp + precomps[i] = secretPrecomputation + } + + return Triple(ids , precomps , publicPrecomps) +} + +/** + * Samples secret and public precomputations for a group of parties from precomputed safe primes. + * + * @param n The number of parties. + * @param t The threshold for the protocol. + * @return A Triple containing the list of party IDs, a map of secret precomputations, and a map of public precomputations. + * @throws IllegalArgumentException if `idRange` is less than `n`. + */ +fun getSamplePrecomputations(n: Int, t: Int) : Triple, Map, Map> { + if (n > PRECOMPUTED_PRIMES.size) throw IllegalArgumentException("not enough precomputed primes") + val ids = generatePartyIds(n) + val ssid = generateSessionId() + val precomps = mutableMapOf() + val publicPrecomps = mutableMapOf() + + // generate threshold precomputations + val (secretShares, publicShares) = sampleEcdsaShare(t, ids) + for (i in ids) { + val paillierSecret = newPaillierSecretFromPrimes(PRECOMPUTED_PRIMES[i-1].first, PRECOMPUTED_PRIMES[i-1].second) + val paillierPublic = paillierSecret.publicKey + val (aux, _) = paillierSecret.generatePedersenParameters() + + val secretPrecomputation = SecretPrecomputation( + id = i, + ssid = ssid, + threshold = t, + ecdsaShare = secretShares[i]!!, + paillierSecret = paillierSecret + ) + + val publicPrecomp = PublicPrecomputation ( + id = i, + ssid = ssid, + publicEcdsa = publicShares[i]!!, + paillierPublic = paillierPublic, + aux = aux + ) + publicPrecomps[i] = publicPrecomp + precomps[i] = secretPrecomputation + } + + return Triple(ids , precomps , publicPrecomps) +} + +/** + * Computes a public key from the shares of the signers using Lagrange interpolation. + * + * @param signers The list of party IDs that are participating. + * @param publicShares The map of public precomputations for the parties. + * @return The resulting public key. + */ +fun publicKeyFromShares(signers : List, publicShares : Map) : PublicKey { + var sum = newPoint() + val lagrangeCoeffs = lagrange(signers) + for (i in signers) { + sum = sum.add(lagrangeCoeffs[i]!!.act(publicShares[i]!!.publicEcdsa)) + } + return sum.toPublicKey() +} + +/** + * Scales the precomputations for signers using Lagrange's coefficients and computes the combined public point. + * Scaling in threshold secret sharing ensures that each participant's share is correctly weighted when reconstructing the secret or combining values. + * + * @param signers The list of party IDs that are participating. + * @param precomps The map of secret precomputations for the parties. + * @param publicPrecomps The map of public precomputations for the parties. + * @return A Triple containing scaled secret precomputations, scaled public precomputations, and the combined public point. + */ +fun scalePrecomputations(signers : List, precomps : Map, publicPrecomps : Map) +: Triple, MutableMap, Point> { + val lagrangeCoefficients = lagrange(signers) + + // Initialize a map to hold the scaled precomputations + val scaledPrecomps = mutableMapOf() + val scaledPublics = mutableMapOf() + + // Scale secret and public ECDSA Shares + for (id in signers) { + val scaledEcdsaShare = lagrangeCoefficients[id]!!.multiply(precomps[id]!!.ecdsaShare) + + val scaledPublicShare = lagrangeCoefficients[id]!!.act(publicPrecomps[id]!!.publicEcdsa) + + scaledPublics[id] = PublicPrecomputation( + id = id, + ssid = precomps[id]!!.ssid, + publicEcdsa = scaledPublicShare, + paillierPublic = publicPrecomps[id]!!.paillierPublic, + aux = publicPrecomps[id]!!.aux + ) + + // Create a new SecretPrecomputation with the scaled private and public shares + scaledPrecomps[id] = SecretPrecomputation( + id = id, + ssid = precomps[id]!!.ssid, + threshold = precomps[id]!!.threshold, + ecdsaShare = scaledEcdsaShare, + paillierSecret = precomps[id]!!.paillierSecret, + ) + } + + var public = newPoint() + for (j in signers) { + public = public.add(scaledPublics[j]!!.publicEcdsa) + } + + return Triple(scaledPrecomps, scaledPublics, public) +} + +/** + * Generates a list of distinct party IDs from a given range. + * + * @param n The number of party IDs to generate. + * @return A list of unique party IDs. + * @throws IllegalArgumentException if `n` is greater than `idRange`. + */ +fun generatePartyIds(n: Int): List { + return (1.. n).shuffled().take(n) +} + +/** + * Generates secret ECDSA shares and their corresponding public points using Shamir's Secret Sharing scheme. + * + * @param threshold The threshold number of shares required to reconstruct the secret. + * @param ids The list of participant IDs. + * @return A pair containing the secret shares and their corresponding public points. + */ +fun sampleEcdsaShare(threshold: Int, ids: List) : Pair, Map> { + val secretShares = mutableMapOf() + val publicShares = mutableMapOf() + val polynomial = newPolynomial(threshold) + for (i in ids) { + secretShares[i] = (polynomial.eval(Scalar(BigInteger.valueOf(i.toLong())))) + publicShares[i] = (secretShares[i]!!.actOnBase()) + } + + return secretShares to publicShares +} \ No newline at end of file diff --git a/src/main/kotlin/presign/PresignRound1.kt b/src/main/kotlin/presign/PresignRound1.kt deleted file mode 100644 index f043322..0000000 --- a/src/main/kotlin/presign/PresignRound1.kt +++ /dev/null @@ -1,87 +0,0 @@ -package perun_network.ecdsa_threshold.presign - -import perun_network.ecdsa_threshold.ecdsa.Scalar -import perun_network.ecdsa_threshold.keygen.PublicPrecomputation -import perun_network.ecdsa_threshold.math.sampleScalar -import perun_network.ecdsa_threshold.paillier.PaillierCipherText -import perun_network.ecdsa_threshold.tuple.Septuple -import perun_network.ecdsa_threshold.zkproof.enc.EncPrivate -import perun_network.ecdsa_threshold.zkproof.enc.EncProof -import perun_network.ecdsa_threshold.zkproof.enc.EncPublic -import java.math.BigInteger - -/** - * Represents the output of the first round of the presigning process. - * - * @property ssid A unique identifier for the session. - * @property id The identifier of the signer. - * @property K The ciphertext representing Kᵢ. - * @property G The ciphertext representing Gᵢ. - * @property proof The cryptographic proof associated with the presigning. - * - */ -class PresignRound1Output ( - val ssid: ByteArray, - val id : Int, - val K : PaillierCipherText, // K = K_i - val G: PaillierCipherText, // G = G_i - val proof: EncProof, -) - -/** - * Represents the input for the first round of the presigning process. - * - * @property ssid A unique identifier for the session. - * @property id The identifier of the signer. - * @property publics A map of public precomputed values indexed by signer identifiers. - * - */ -class PresignRound1Input ( - val ssid: ByteArray, - val id: Int, - val publics : Map -) { - /** - * Produces the output for the first round of the presigning process. - * - * This method generates necessary ciphertexts and a proof for each signer. - * - * @param signers A list of signer identifiers participating in the presigning. - * @return A [Septuple] containing the results of the presigning, including the outputs for each signer, - * gamma share, k share, and nonces. - */ - fun producePresignRound1Output( - signers: List - ) : Septuple, Scalar, Scalar, BigInteger, BigInteger, PaillierCipherText, PaillierCipherText> { - val result = mutableMapOf() - // sample γi ← Fq - val gammaShare = sampleScalar() - // Gᵢ = Encᵢ(γᵢ;νᵢ) - val paillier = publics[id]!!.paillierPublic - val (G, gNonce) = paillier.encryptRandom(gammaShare.value) - - // kᵢ <- 𝔽, - val kShare = sampleScalar() - val (K, kNonce) = paillier.encryptRandom(kShare.value) - for (j in signers) { - if (id != j) { - // Compute ψ_0_j,i = M(prove, Πenc_j,(ssid, i),(Iε, Ki); (ki, ρi)) for every j 6= i. - val proof = EncProof.newProof( - id, - EncPublic(K, publics[id]!!.paillierPublic, publics[j]!!.aux), - EncPrivate(kShare.value, kNonce) - ) - - result[j] = PresignRound1Output( - ssid = ssid, - id = id, - K = K, - G = G, - proof = proof - ) - } - } - return Septuple(result , gammaShare, kShare , gNonce, kNonce, K, G) - } - -} \ No newline at end of file diff --git a/src/main/kotlin/presign/PresignRound2.kt b/src/main/kotlin/presign/PresignRound2.kt deleted file mode 100644 index a9d4ff0..0000000 --- a/src/main/kotlin/presign/PresignRound2.kt +++ /dev/null @@ -1,144 +0,0 @@ -package perun_network.ecdsa_threshold.presign - -import perun_network.ecdsa_threshold.ecdsa.Point -import perun_network.ecdsa_threshold.ecdsa.Scalar -import perun_network.ecdsa_threshold.ecdsa.newBasePoint -import perun_network.ecdsa_threshold.keygen.PublicPrecomputation -import perun_network.ecdsa_threshold.paillier.PaillierCipherText -import perun_network.ecdsa_threshold.paillier.PaillierSecret -import perun_network.ecdsa_threshold.zkproof.affg.AffgProof -import perun_network.ecdsa_threshold.zkproof.affg.produceAffGMaterials -import perun_network.ecdsa_threshold.zkproof.enc.EncPublic -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarPrivate -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarProof -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarPublic - -import java.math.BigInteger - -/** - * Represents the output of the second round of the presigning process. - * - * @property ssid A unique identifier for the session. - * @property id The identifier of the signer. - * @property bigGammaShare The computed big gamma share for the signer. - * @property deltaD The Paillier ciphertext representing Delta D. - * @property deltaF The Paillier ciphertext representing Delta F. - * @property deltaProof The proof associated with delta. - * @property chiD The Paillier ciphertext representing Chi D. - * @property chiF The Paillier ciphertext representing Chi F. - * @property chiProof The proof associated with chi. - * @property proofLog The log-star proof associated with the presigning process. - * @property chiBeta The beta value for chi. - * @property deltaBeta The beta value for delta. - */ -class PresignRound2Output ( - val ssid: ByteArray, - val id : Int, - val bigGammaShare : Point, - val deltaD: PaillierCipherText, - val deltaF: PaillierCipherText, - val deltaProof: AffgProof, - val chiD: PaillierCipherText, - val chiF: PaillierCipherText, - val chiProof: AffgProof, - val proofLog: LogStarProof, - val chiBeta: BigInteger, - val deltaBeta: BigInteger, -) - -/** - * Represents the input for the second round of the presigning process. - * - * @property ssid A unique identifier for the session. - * @property id The identifier of the signer. - * @property gammaShare The gamma share for the signer. - * @property secretECDSA The ECDSA secret key for the signer. - * @property secretPaillier The Paillier secret key for the signer. - * @property gNonce The nonce used for generating the proof. - * @property publics A map of public precomputed values indexed by signer identifiers. - */ -class PresignRound2Input ( - val ssid: ByteArray, - val id: Int, - val gammaShare: Scalar, - val secretECDSA: Scalar, - val secretPaillier : PaillierSecret, - val gNonce: BigInteger, - val publics: Map -) { - /** - * Produces the output for the second round of the presigning process. - * - * This method generates necessary ciphertexts and proofs for each signer. - * - * @param signers A list of signer identifiers participating in the presigning. - * @param ks A map of Paillier ciphertexts indexed by signer identifiers. - * @param gs A map of Paillier ciphertexts indexed by signer identifiers. - * @return A pair containing a map of the presign outputs for each signer and the computed big gamma share. - */ - fun producePresignRound2Output( - signers : List, - ks : Map, - gs : Map, - ): Pair, Point> { - val result = mutableMapOf() - // Γᵢ = [γᵢ]⋅G - val bigGammaShare = gammaShare.actOnBase() - - for (j in signers) { - if (j != id) { - // deltaBeta = βi,j - // compute DeltaD = Dᵢⱼ - // compute DeltaF = Fᵢⱼ - // compute deltaProof = ψj,i - val (deltaBeta, deltaD, deltaF, deltaProof) = produceAffGMaterials(id, gammaShare.value, bigGammaShare, ks[j]!!.clone(), secretPaillier, publics[j]!!.paillierPublic, publics[j]!!.aux) - // chiBeta = β^i,j - // compute chiD = D^ᵢⱼ - // compute chiF = F^ᵢⱼ - // compute chiProof = ψ^j,i - val (chiBeta, chiD, chiF, chiProof) = produceAffGMaterials(id, secretECDSA.value, publics[id]!!.publicEcdsa, ks[j]!!.clone(), secretPaillier, publics[j]!!.paillierPublic, publics[j]!!.aux) - - val proofLog = LogStarProof.newProof(id, - LogStarPublic(gs[id]!!, bigGammaShare, newBasePoint(), publics[id]!!.paillierPublic, publics[j]!!.aux), - LogStarPrivate(gammaShare.value, gNonce)) - - val presignOutput2 = PresignRound2Output( - ssid = ssid, - id = id, - bigGammaShare = bigGammaShare, - deltaD = deltaD, - deltaF = deltaF, - deltaProof = deltaProof, - chiD = chiD, - chiF = chiF, - chiProof = chiProof, - proofLog = proofLog, - deltaBeta = deltaBeta, - chiBeta = chiBeta, - ) - result[j] = presignOutput2 - } - } - - return result to bigGammaShare - } - - /** - * Verifies the output of the first round of the presigning process from a given signer. - * - * @param j The identifier of the signer whose output is being verified. - * @param presignRound1Output The output from the first round for the given signer. - * @return True if the verification is successful; otherwise, false. - */ - fun verifyPresignRound1Output( - j: Int, - presignRound1Output : PresignRound1Output, - ) : Boolean { - val public = EncPublic( - K = presignRound1Output.K, - n0 = publics[j]!!.paillierPublic, - aux = publics[id]!!.aux, - ) - return presignRound1Output.proof.verify(presignRound1Output.id, public) - } -} \ No newline at end of file diff --git a/src/main/kotlin/presign/PresignRound3.kt b/src/main/kotlin/presign/PresignRound3.kt deleted file mode 100644 index c588a29..0000000 --- a/src/main/kotlin/presign/PresignRound3.kt +++ /dev/null @@ -1,205 +0,0 @@ -package perun_network.ecdsa_threshold.presign - -import perun_network.ecdsa_threshold.ecdsa.* -import perun_network.ecdsa_threshold.keygen.PublicPrecomputation -import perun_network.ecdsa_threshold.paillier.PaillierCipherText -import perun_network.ecdsa_threshold.paillier.PaillierSecret -import perun_network.ecdsa_threshold.tuple.Quintuple -import perun_network.ecdsa_threshold.zkproof.affg.AffgPublic -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarPrivate -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarProof -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarPublic -import java.math.BigInteger - -/** - * Represents the output of the third round of the presigning process. - * - * @property ssid A unique identifier for the session. - * @property id The identifier of the signer. - * @property chiShare The computed chi share for the signer. - * @property deltaShare The computed delta share for the signer. - * @property bigDeltaShare The computed big delta share for the signer. - * @property gamma The computed gamma point for the signer. - * @property proofLog The log-star proof associated with the presigning process. - */ -data class PresignRound3Output ( - val ssid: ByteArray, - val id : Int, - val chiShare : BigInteger, - val deltaShare : BigInteger, - val bigDeltaShare : Point, - val gamma : Point, - val proofLog: LogStarProof -) - -/** - * Represents the input for the third round of the presigning process. - * - * @property ssid A unique identifier for the session. - * @property id The identifier of the signer. - * @property gammaShare The gamma share for the signer. - * @property secretPaillier The Paillier secret key for the signer. - * @property kShare The scalar value for k. - * @property K The Paillier ciphertext for K. - * @property kNonce The nonce used for generating the proof. - * @property secretECDSA The ECDSA secret key for the signer. - * @property publics A map of public precomputed values indexed by signer identifiers. - */ -class PresignRound3Input( - val ssid: ByteArray, - val id: Int, - val gammaShare : BigInteger, - val secretPaillier: PaillierSecret, - val kShare: Scalar, - val K : PaillierCipherText, - val kNonce: BigInteger, - val secretECDSA: BigInteger, - val publics: Map -) { - /** - * Produces the output for the third round of the presigning process. - * - * This method generates the necessary shares and proofs for each signer. - * - * @param signers A list of signer identifiers participating in the presigning. - * @param bigGammaShares A map of big gamma shares indexed by signer identifiers. - * @param presignRound2Outputs A map of outputs from the second round indexed by signer identifiers. - * @return A quintuple containing a map of the presign outputs for each signer, the computed chi share, - * the computed delta share, the computed big delta share, and the computed gamma point. - */ - fun producePresignRound3Output( - signers : List, - bigGammaShares : Map, - presignRound2Outputs: Map> - ) : Quintuple, BigInteger, BigInteger, Point, Point>{ - val result = mutableMapOf() - val deltaShareAlphas= mutableMapOf() // DeltaShareAlpha[j] = αᵢⱼ - val deltaShareBetas= mutableMapOf() // DeltaShareBeta[j] = βᵢⱼ - val chiShareAlphas= mutableMapOf() // ChiShareAlpha[j] = α̂ᵢⱼ - val chiShareBetas= mutableMapOf() // ChiShareBeta[j] = β̂^ᵢⱼ - for (j in signers) { - if (j != id) { - deltaShareBetas[j] = presignRound2Outputs[j]!![id]!!.deltaBeta - chiShareBetas[j] = presignRound2Outputs[j]!![id]!!.chiBeta - deltaShareAlphas[j] = secretPaillier.decrypt(presignRound2Outputs[j]!![id]!!.deltaD) - chiShareAlphas[j] = secretPaillier.decrypt(presignRound2Outputs[j]!![id]!!.chiD) - } - } - - - // Γ = ∑ⱼ Γⱼ - var bigGamma = newPoint() - for ((_, bigGammaShare) in bigGammaShares) { - bigGamma = bigGamma.add(bigGammaShare) - } - - // Δᵢ = [kᵢ]Γ - val bigDeltaShare = kShare.act(bigGamma) - - // δᵢ = γᵢ kᵢ - var deltaShare = gammaShare.multiply(kShare.value) - - // χᵢ = xᵢ kᵢ - var chiShare = secretECDSA.multiply(kShare.value) - - for (j in signers) { - if (j != this.id) { - //δᵢ += αᵢⱼ + βᵢⱼ - deltaShare = deltaShare.add(deltaShareAlphas[j]) - deltaShare = deltaShare.add(deltaShareBetas[j]) - - // χᵢ += α̂ᵢⱼ + ̂βᵢⱼ - chiShare = chiShare.add(chiShareAlphas[j]) - chiShare = chiShare.add(chiShareBetas[j]) - } - } - deltaShare = deltaShare.mod(secp256k1Order()) - chiShare = chiShare.mod(secp256k1Order()) - for (j in signers) { - if (j != id) { - val logstarPublic = LogStarPublic( - C = K.clone(), - X = bigDeltaShare, - g = bigGamma, - n0 = publics[id]!!.paillierPublic, - aux = publics[j]!!.aux, - ) - - val logStarPrivate = LogStarPrivate( - x= kShare.value, - rho= kNonce - ) - val proofLog = LogStarProof.newProof(id, logstarPublic, logStarPrivate) - result[j] = PresignRound3Output( - ssid = ssid, - id = id, - chiShare = chiShare, - deltaShare = deltaShare, - bigDeltaShare = bigDeltaShare, - gamma = bigGamma, - proofLog = proofLog - ) - } - } - - return Quintuple(result, chiShare, deltaShare, bigDeltaShare, bigGamma) - } - - /** - * Verifies the output of the second round of the presigning process for a given signer. - * - * @param j The identifier of the signer whose output is being verified. - * @param presignRound2Output The output from the second round for the given signer. - * @param k_i The Paillier ciphertext for K. - * @param g_j The Paillier ciphertext for G. - * @param ecdsa_j The ECDSA point for the signer. - * @return True if the verification is successful; otherwise, false. - */ - fun verifyPresignRound2Output( - j : Int, - presignRound2Output: PresignRound2Output, - k_i : PaillierCipherText, - g_j : PaillierCipherText, - ecdsa_j: Point, - ) : Boolean { - // Verify M(vrfy, Πaff-g_i ,(ssid, j),(Iε,Jε, Di,j , Ki, Fj,i, Γj ), ψi,j ) = 1. - val deltaPublic = AffgPublic( - C = k_i.clone(), - D = presignRound2Output.deltaD, - Y = presignRound2Output.deltaF, - X = presignRound2Output.bigGammaShare, - n1 = publics[j]!!.paillierPublic, - n0 = publics[id]!!.paillierPublic, - aux = publics[id]!!.aux - ) - if (!presignRound2Output.deltaProof.verify(presignRound2Output.id, deltaPublic)) { - return false - } - - // Verify M(vrfy, Πaff-g_i,(ssid, j),(Iε,Jε, Dˆk,j , Ki, Fˆj,i, Xj ), ψˆi,j ) = 1 - val chiPublic = AffgPublic( - C = k_i.clone(), - D = presignRound2Output.chiD, - Y = presignRound2Output.chiF, - X = ecdsa_j, - n1 = publics[j]!!.paillierPublic, - n0= publics[id]!!.paillierPublic, - aux = publics[id]!!.aux - ) - if (!presignRound2Output.chiProof.verify(presignRound2Output.id, chiPublic)) { - return false - } - - // Verify M(vrfy, Πlog∗_i,(ssid, j),(Iε, Gj , Γj , g), ψ0, i,j ) = 1 - val logPublic = LogStarPublic( - C = g_j.clone(), - X = presignRound2Output.bigGammaShare, - g = newBasePoint(), - n0 = publics[j]!!.paillierPublic, - aux = publics[id]!!.aux - ) - return presignRound2Output.proofLog.verify(presignRound2Output.id, logPublic) - } - -} - diff --git a/src/main/kotlin/presign/Signer.kt b/src/main/kotlin/presign/Signer.kt deleted file mode 100644 index 9da68c5..0000000 --- a/src/main/kotlin/presign/Signer.kt +++ /dev/null @@ -1,34 +0,0 @@ -package perun_network.ecdsa_threshold.presign - -import perun_network.ecdsa_threshold.ecdsa.Scalar -import perun_network.ecdsa_threshold.keygen.PublicPrecomputation -import perun_network.ecdsa_threshold.keygen.SecretPrecomputation -import java.math.BigInteger - -/** - * Represents the secrets of the signer during the process of threshold signing. - * - * @property id The identifier of the signer. - * @property private The signer's private precomputation, containing secret values. - * @property publics A map of public precomputations, indexed by the signer's IDs. - * - * @property kShare The signer's share of the secret value `kᵢ` in the presigning protocol (Round 1). - * @property gammaShare The signer's share of the secret value `gammaᵢ` in the presigning protocol (Round 1). - * @property kNonce The signer's nonce value used in the presigning protocol (Round 1). - * @property gNonce The group's nonce value used in the presigning protocol (Round 1). - * @property chiShare The signer's share of the secret value `Xᵢ` in the presigning protocol (Round 3). - */ -data class ThresholdSigner( - val id : Int, // i - val private : SecretPrecomputation, - val publics: Map, - - // PRESIGN ROUND 1 - var kShare : Scalar? = null, // k_i - var gammaShare: Scalar? = null, // gamma_i - var kNonce : BigInteger? = null, - var gNonce : BigInteger? = null, - - // PRESIGN ROUND 3 - var chiShare: Scalar? = null, // X_i -) \ No newline at end of file diff --git a/src/main/kotlin/sign/Broadcast.kt b/src/main/kotlin/sign/Broadcast.kt new file mode 100644 index 0000000..c10b9c2 --- /dev/null +++ b/src/main/kotlin/sign/Broadcast.kt @@ -0,0 +1,14 @@ +package perun_network.ecdsa_threshold.sign + +/** + * Represents a broadcast message with basic metadata. + * + * @param ssid A unique identifier for the broadcast (could represent session ID). + * @param from The identifier of the sender (typically a participant index or node). + * @param to The identifier of the recipient (typically a participant index or node). + */ +open class Broadcast ( + open val ssid: ByteArray, + open val from : Int, + open val to: Int, +) diff --git a/src/main/kotlin/sign/SignRound.kt b/src/main/kotlin/sign/SignRound.kt deleted file mode 100644 index 4937608..0000000 --- a/src/main/kotlin/sign/SignRound.kt +++ /dev/null @@ -1,127 +0,0 @@ -package perun_network.ecdsa_threshold.sign - -import perun_network.ecdsa_threshold.ecdsa.* -import perun_network.ecdsa_threshold.keygen.PublicPrecomputation -import perun_network.ecdsa_threshold.paillier.PaillierCipherText -import perun_network.ecdsa_threshold.presign.PresignRound3Output -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarPublic -import java.math.BigInteger - -/** - * Represents a signing party in the threshold ECDSA scheme. - * - * @property hash The hash of the message to be signed. - * @property ssid A unique session identifier for the signing process. - * @property id The unique identifier for the signing party. - * @property publics A map of public precomputed values indexed by signer identifiers. - */ -class SignParty( - val hash: ByteArray, - val ssid: ByteArray, - val id : Int, - val publics: Map -) { - /** - * Creates a partial signature for the signer. - * - * @param kShare The scalar value representing the share of the secret key. - * @param chiShare The share of the signature from the presigning process. - * @param bigR The point representing the commitment for the signature. - * @return A [PartialSignature] instance containing the session ID, signer's ID, and the computed signature share. - */ - fun createPartialSignature(kShare: Scalar, chiShare: Scalar, bigR: Point ): PartialSignature { - val rX = bigR.xScalar() - val sigmaShare = rX.multiply(chiShare).add(Scalar.scalarFromByteArray(hash).multiply(kShare)) - return PartialSignature( - ssid = ssid, - id = id, - sigmaShare = sigmaShare - ) - } - - /** - * Verifies the output of the third round of the presigning process for a given signer. - * - * @param j The identifier of the signer whose output is being verified. - * @param presignRound3Output The output from the third round for the given signer. - * @param k_j The Paillier ciphertext for K corresponding to the signer. - * @return True if the verification is successful; otherwise, false. - */ - fun verifyPresignRound3Output( - j : Int, - presignRound3Output: PresignRound3Output, - k_j : PaillierCipherText - ) : Boolean { - val logStarPublic = LogStarPublic( - C = k_j, - X = presignRound3Output.bigDeltaShare, - g = presignRound3Output.gamma, - n0 = publics[j]!!.paillierPublic, - aux = publics[id]!!.aux - ) - return presignRound3Output.proofLog.verify(presignRound3Output.id, logStarPublic) - } -} - -/** - * Combines partial signatures into a final signature. - * - * @param bigR The point representing the commitment for the signature. - * @param partialSignatures A list of partial signatures from each signing party. - * @param publicPoint The public point used for verifying the signature. - * @param hash The hash of the message that was signed. - * @return A [Signature] instance representing the final signature. - * @throws IllegalStateException If the final signature is invalid. - */ -fun combinePartialSignatures(bigR: Point, partialSignatures : List, publicPoint: Point, hash : ByteArray) : Signature { - val r = bigR.xScalar() - var sigma = Scalar.zero() - for (partial in partialSignatures) { - sigma = sigma.add(partial.sigmaShare) - } - - val signature = Signature.newSignature(r, sigma) - - if (!signature.verifyWithPoint(hash, publicPoint)) { - throw IllegalStateException("invalid signature") - } - - return signature -} - -/** - * Processes the presigning outputs from multiple signers to compute the final commitment point. - * - * @param signers A list of identifiers for the signers. - * @param deltaShares A map of delta shares indexed by signer identifiers. - * @param bigDeltaShares A map of big delta shares indexed by signer identifiers. - * @param gamma The gamma point from the presigning process. - * @return The computed point representing the commitment. - * @throws Exception If the computed point is inconsistent with the expected value. - */ -fun processPresignOutput( - signers : List, - deltaShares: Map, - bigDeltaShares: Map, - gamma: Point) : Point { - // δ = ∑ⱼ δⱼ - // Δ = ∑ⱼ Δⱼ - var delta = Scalar.zero() - var bigDelta = newPoint() - for (i in signers) { - delta = delta.add(Scalar(deltaShares[i]!!.mod(secp256k1Order()))) - bigDelta = bigDelta.add(bigDeltaShares[i]!!) - } - - // Δ == [δ]G - val deltaComputed = delta.actOnBase() - if (deltaComputed != bigDelta) { - throw Exception("computed Δ is inconsistent with [δ]G") - } - - // R = Γ^δ−1 - val deltaInv = delta.invert() - val bigR = deltaInv.act(gamma) - - return bigR -} \ No newline at end of file diff --git a/src/main/kotlin/sign/Signer.kt b/src/main/kotlin/sign/Signer.kt new file mode 100644 index 0000000..58aa6ee --- /dev/null +++ b/src/main/kotlin/sign/Signer.kt @@ -0,0 +1,318 @@ +package perun_network.ecdsa_threshold.sign + +import perun_network.ecdsa_threshold.ecdsa.* +import perun_network.ecdsa_threshold.math.shamir.lagrange +import perun_network.ecdsa_threshold.paillier.PaillierCipherText +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.precomp.SecretPrecomputation +import perun_network.ecdsa_threshold.sign.aux.Aux +import perun_network.ecdsa_threshold.sign.aux.AuxRound1Broadcast +import perun_network.ecdsa_threshold.sign.aux.AuxRound2Broadcast +import perun_network.ecdsa_threshold.sign.aux.AuxRound3Broadcast +import perun_network.ecdsa_threshold.sign.keygen.Keygen +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound1Broadcast +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound2Broadcast +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound3Broadcast +import perun_network.ecdsa_threshold.sign.presign.* +import java.math.BigInteger + +data class Signer( + val id : Int, + val ssid: ByteArray, + val threshold : Int, + private var secretPrecomp : SecretPrecomputation? = null, + var publicPrecomps: Map? = null, + + private var keygen : Keygen? = null, + private var aux : Aux? = null, + private var presigner : Presigner? = null, + + // KEYGEN + var xShare : Scalar? = null, + var XShares: Map? = null, + + // PRESIGN + var elGamalPublics: Map? = null, + + ) { + + fun keygenRound1(parties: List): Map { + if (keygen == null) { + keygen = Keygen( + ssid = ssid, + id = id + ) + } + + return keygen!!.keygenRound1(parties) + } + + fun keygenRound2(parties: List): Map { + if (keygen == null) { + throw NullPointerException("keygen is not initialized") + } + return keygen!!.keygenRound2(parties) + } + + fun keygenRound3( + parties: List, + keygenRound1Broadcasts: Map>, + keygenRound2Broadcasts: Map> + ): Map { + if (keygen == null) { + throw NullPointerException("keygen is not initialized") + } + + val incomingRound1Broadcasts = filterIncomingBroadcast(id, keygenRound1Broadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(id, keygenRound2Broadcasts) + + return keygen!!.keygenRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + + fun keygenOutput( + parties: List, + keygenRound2Broadcasts: Map>, + keygenRound3Broadcasts: Map> + ) : Point { + if (keygen == null) { + throw NullPointerException("keygen is not initialized") + } + + val incomingRound2Broadcasts = filterIncomingBroadcast(id, keygenRound2Broadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(id, keygenRound3Broadcasts) + + val (xShare, XShares, X) = keygen!!.keygenOutput(parties,incomingRound2Broadcasts, incomingRound3Broadcasts) + this.xShare = xShare + this.XShares = XShares + return X + } + + fun auxRound1(parties: List): Map { + if (aux == null) { + aux = Aux( + ssid = ssid, + id = id, + threshold = threshold, + previousShare = xShare, + previousPublic = XShares, + ) + } + + return aux!!.auxRound1(parties) + } + + fun auxRound2(parties: List) : Map { + if (aux == null) { + throw NullPointerException("aux is not initialized") + } + return aux!!.auxRound2(parties) + } + + fun auxRound3(parties: List, + auxRound1Broadcasts : Map>, + auxRound2Broadcasts : Map> + ) : Map { + if (aux == null) { + throw NullPointerException("aux is not initialized") + } + + val incomingRound1Broadcasts = filterIncomingBroadcast(id, auxRound1Broadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(id, auxRound2Broadcasts) + + return aux!!.auxRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + + fun auxOutput( + parties: List, + auxRound2Broadcasts: Map>, + auxRound3Broadcasts: Map>) : Map { + if (aux == null) { + throw NullPointerException("aux is not initialized") + } + + val incomingRound3Broadcasts = filterIncomingBroadcast(id, auxRound3Broadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(id, auxRound2Broadcasts) + + val auxOutput = aux!!.auxOutput(parties, incomingRound2Broadcasts, incomingRound3Broadcasts) + + this.secretPrecomp = auxOutput.first + this.publicPrecomps = auxOutput.second + return this.publicPrecomps!! + } + + fun scalePrecomputations(signers: List) : Pair, Point> { + val lagrangeCoefficients = lagrange(signers) + // Initialize a map to hold the scaled precomputations + val scaledPublics = mutableMapOf() + + val scaledEcdsaShare = lagrangeCoefficients[id]!!.multiply(this.secretPrecomp!!.ecdsaShare) + + // Scale secret and public ECDSA Shares + for (id in signers) { + val scaledPublicShare = lagrangeCoefficients[id]!!.act(publicPrecomps!![id]!!.publicEcdsa) + + scaledPublics[id] = PublicPrecomputation( + id = id, + ssid = ssid, + publicEcdsa = scaledPublicShare, + paillierPublic = publicPrecomps!![id]!!.paillierPublic, + aux = publicPrecomps!![id]!!.aux + ) + } + // Create a new SecretPrecomputation with the scaled private and public shares + val scaledSecret = SecretPrecomputation( + id = id, + ssid = ssid, + threshold = threshold, + ecdsaShare = scaledEcdsaShare, + paillierSecret = secretPrecomp!!.paillierSecret, + ) + + this.secretPrecomp = scaledSecret + this.publicPrecomps = scaledPublics + + var public = newPoint() + for (j in signers) { + public = public.add(scaledPublics[j]!!.publicEcdsa) + } + + return scaledPublics to public + } + + fun presignRound1(signers: List) : Map { + if (presigner == null) { + presigner = Presigner( + id = id, + private = secretPrecomp!!, + publicPrecomps = publicPrecomps!! + ) + } + + return presigner!!.presignRound1(signers) + } + + fun presignRound2( + parties: List, + presignRound1AllBroadcasts : Map> + ) : Map { + if (presigner == null) { + throw NullPointerException("presigner is not initialized") + } + + val incomingPresignRound1Broadcast = filterIncomingBroadcast(id, presignRound1AllBroadcasts) + + val Ks = mutableMapOf() + val Gs = mutableMapOf() + val elGamalPublics = mutableMapOf() + for (j in parties) { + if (j == id ) { + Ks [j] = presigner!!.K!! + Gs[j] = presigner!!.G!! + elGamalPublics[j] = presigner!!.elGamalPublic!! + } else { + Ks[j] = incomingPresignRound1Broadcast[j]!!.K + Gs[j] = incomingPresignRound1Broadcast[j]!!.G + elGamalPublics[j] = incomingPresignRound1Broadcast[j]!!.elGamalPublic + } + } + + this.elGamalPublics = elGamalPublics + + return presigner!!.presignRound2(parties, Ks, incomingPresignRound1Broadcast) + } + + fun presignRound3( + parties: List, + presignRound2AllBroadcasts : Map> + ) : Map { + if (presigner == null) { + throw NullPointerException("presigner is not initialized") + } + + val incomingPresignRound2Broadcasts = filterIncomingBroadcast(id, presignRound2AllBroadcasts) + + val bigGammaShares = mutableMapOf() + for ( j in parties ) { + if (j == id) { + bigGammaShares[j] = presigner!!.bigGammaShare!! + } else { + bigGammaShares[j] = incomingPresignRound2Broadcasts[j]!!.bigGammaShare + } + } + + return presigner!!.presignRound3(parties, bigGammaShares, elGamalPublics!!, incomingPresignRound2Broadcasts) + } + + fun presignOutput ( + parties: List, + presignRound3AllBroadcasts : Map> + ) : Point { + if (presigner == null) { + throw NullPointerException("presigner is not initialized") + } + + val incomingPresignRound3Broadcasts = filterIncomingBroadcast(id, presignRound3AllBroadcasts) + + val deltaShares = mutableMapOf() + val bigDeltaShares = mutableMapOf() + for ( j in parties ) { + if (j == id) { + deltaShares[j] = presigner!!.deltaShare!! + bigDeltaShares[j] = presigner!!.bigDeltaShare!! + } else { + deltaShares[j] = incomingPresignRound3Broadcasts[j]!!.deltaShare + bigDeltaShares[j] = incomingPresignRound3Broadcasts[j]!!.bigDeltaShare + } + } + + return presigner!!.processPresignOutput(parties, incomingPresignRound3Broadcasts, elGamalPublics!!, deltaShares, bigDeltaShares) + } + + fun partialSignMessage( + hash: ByteArray + ) : PartialSignature { + if (presigner == null) { + throw NullPointerException("presigner is not initialized") + } + + return presigner!!.partialSignMessage(ssid, hash) + } + + private fun filterIncomingBroadcast(id : Int, broadcasts : Map>) : Map { + val incomingBroadcasts = mutableMapOf() + for ((j, broadcast) in broadcasts) { + if (j != id) { + incomingBroadcasts[j] = broadcast[id]!! + } + } + return incomingBroadcasts + } +} + +/** + * Combines partial signatures to create the final ECDSA signature. + * + * This function combines all partial signatures from signers to produce the final valid ECDSA signature `(r, s)`. + * It ensures that the signature is valid with respect to the given public key and message hash. + * + * @param bigR The commitment point `R` from the pre-signing process. + * @param partialSignatures A list of partial signatures from all signers. + * @param publicPoint The public key point corresponding to the signers. + * @param hash The hash of the message that was signed. + * @return A complete ECDSA signature that is valid for the provided message and public key. + */ +fun combinePartialSignatures(bigR: Point, partialSignatures : List, publicPoint: Point, hash : ByteArray) : Signature { + val r = bigR.xScalar() + var sigma = Scalar.zero() + for (partial in partialSignatures) { + sigma = sigma.add(partial.sigmaShare) + } + + val signature = Signature.newSignature(r, sigma) + + if (!signature.verifyWithPoint(hash, publicPoint)) { + throw IllegalStateException("invalid signature") + } + + return signature +} diff --git a/src/main/kotlin/sign/aux/Aux.kt b/src/main/kotlin/sign/aux/Aux.kt new file mode 100644 index 0000000..03184f9 --- /dev/null +++ b/src/main/kotlin/sign/aux/Aux.kt @@ -0,0 +1,449 @@ +package perun_network.ecdsa_threshold.sign.aux + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.Scalar.Companion.scalarFromInt +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.precomp.SecretPrecomputation +import perun_network.ecdsa_threshold.math.SEC_BYTES +import perun_network.ecdsa_threshold.math.sampleRID +import perun_network.ecdsa_threshold.math.shamir.ExponentPolynomial +import perun_network.ecdsa_threshold.math.shamir.Polynomial +import perun_network.ecdsa_threshold.math.shamir.Polynomial.Companion.newPolynomial +import perun_network.ecdsa_threshold.math.shamir.sum +import perun_network.ecdsa_threshold.paillier.PaillierPublic +import perun_network.ecdsa_threshold.paillier.PaillierSecret +import perun_network.ecdsa_threshold.paillier.paillierKeyGenMock +import perun_network.ecdsa_threshold.pedersen.PedersenParameters +import perun_network.ecdsa_threshold.zero_knowledge.FacPrivate +import perun_network.ecdsa_threshold.zero_knowledge.FacProof +import perun_network.ecdsa_threshold.zero_knowledge.FacPublic +import perun_network.ecdsa_threshold.zero_knowledge.ModPrivate +import perun_network.ecdsa_threshold.zero_knowledge.ModProof +import perun_network.ecdsa_threshold.zero_knowledge.ModPublic +import perun_network.ecdsa_threshold.zero_knowledge.PrmPrivate +import perun_network.ecdsa_threshold.zero_knowledge.PrmProof +import perun_network.ecdsa_threshold.zero_knowledge.PrmPublic +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrCommitment +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrPrivate +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrProof +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrPublic +import java.math.BigInteger +import java.security.MessageDigest + +/** + * This class handles the auxiliary information for the threshold ECDSA signature generation, + * including key generation, polynomial management, cryptographic proofs, and communication + * between the parties in a multi-party signing protocol. + * + * @param ssid Session identifier used for the specific signing session. + * @param id The identifier of the current party (signer). + * @param threshold The threshold number of parties required to sign. + * @param previousShare The share of the previous round's secret, if applicable. + * @param previousPublic The public points from the previous round, if applicable. + * @param selfPolynomial The polynomial for the current party in the Shamir secret sharing. + * @param selfExpPolynomial The exponent polynomial used in the protocol. + * @param selfShare The share of the secret for the current party. + * @param rid The random identifier of the current iteration of precomputations. + * @param uShare The u_i value used in the Aux-Info protocol. + * @param paillierSecret The Paillier secret key for encryption. + * @param paillierPublic The Paillier public key for decryption. + * @param pedersenLambda The lambda value for the Pedersen commitment. + * @param pedersenPublic The public parameters for the Pedersen commitment. + * @param prmProof The proof for the Pedersen commitment. + * @param schnorrCommitments The Schnorr commitments for each party. + * @param As The set of public points associated with each Schnorr commitment. + */ +class Aux ( + val ssid: ByteArray, + val id: Int, + val threshold: Int, + + private val previousShare : Scalar?, + private val previousPublic: Map?, + + private var selfPolynomial : Polynomial? = null, + var selfExpPolynomial: ExponentPolynomial? = null, + private var selfShare: Scalar? = null, + + var rid: ByteArray? = null, + var uShare: ByteArray? = null, + + private var paillierSecret: PaillierSecret? = null, + var paillierPublic: PaillierPublic? = null, + private var pedersenLambda: BigInteger? = null, + var pedersenPublic: PedersenParameters? = null, + var prmProof : PrmProof? = null, + + private var schnorrCommitments: Map? = null, + var As : Map? = null +) { + /** + * Executes the first round of the auxiliary protocol. + * In this round, Paillier and Pedersen parameters are sampled, a new polynomial is created, + * and Schnorr commitments are made. + * + * @param parties A list of party identifiers involved in the protocol. + * @return A map of broadcasts to be sent to each party containing necessary information. + */ + fun auxRound1(parties: List) : Map { + // sample Paillier and Pedersen + val (paillierPublic, paillierSecret) = paillierKeyGenMock() + val (publicPedersen, secretLambda) = paillierSecret.generatePedersenParameters() + + // Compute ψi = M(prove, Πprm,(sid, i),(Nˆi, si, ti); λi). + val prmProof = PrmProof.newProof(id, PrmPublic(publicPedersen), PrmPrivate(paillierSecret.phi, secretLambda)) + + // Sample new polynomial + val polynomial = newPolynomial(threshold) + + val ePoly = polynomial.exponentPolynomial() + val selfShare = polynomial.eval(scalarFromInt(id)) + + // Schnorr Commitment + val schnorrCommitments = mutableMapOf() + val As = mutableMapOf() + for (j in parties) { + schnorrCommitments[j] = SchnorrCommitment.newCommitment() + As[j] = schnorrCommitments[j]!!.A + } + + // Sample u_i, rid_i + val rid = sampleRID() + val uShare = sampleRID() + val hash = hash(ssid, id, ePoly, As, paillierPublic, publicPedersen) + + this.selfPolynomial = polynomial + this.selfExpPolynomial = ePoly + this.selfShare = selfShare + this.rid = rid + this.uShare = uShare + this.paillierPublic = paillierPublic + this.paillierSecret = paillierSecret + this.pedersenLambda = secretLambda + this.pedersenPublic = publicPedersen + this.prmProof = prmProof + this.schnorrCommitments = schnorrCommitments + this.As = As + + val broadcasts = mutableMapOf() + for (i in parties) { + if (i != id) { + broadcasts[i] = AuxRound1Broadcast( + ssid = ssid, + from = id, + to = i, + VHash = hash + ) + } + } + + return broadcasts + } + + /** + * Executes the second round of the auxiliary protocol. + * In this round, shares and cryptographic commitments are broadcast to the other parties. + * + * @param parties A list of party identifiers involved in the protocol. + * @return A map of broadcasts containing shares and commitments to be sent. + */ + fun auxRound2(parties: List) : Map { + val broadcasts = mutableMapOf() + + for (j in parties) { + if (j != id) { + broadcasts[j] = AuxRound2Broadcast( + ssid = ssid, + from = id, + to = j, + ePolyShare = selfExpPolynomial!!, + As = As!!, + paillierPublic = paillierPublic!!, + pedersenPublic = pedersenPublic!!, + rid = rid!!, + uShare = uShare!!, + prmProof = prmProof!! + ) + } + } + + return broadcasts + } + + /** + * Executes the third round of the auxiliary protocol. + * This round verifies the second-round broadcasts, computes the final signatures, + * and generates the necessary proofs for each party. + * + * @param parties A list of party identifiers involved in the protocol. + * @param round1Broadcasts The broadcasts from the first round. + * @param round2Broadcasts The broadcasts from the second round. + * @return A map of broadcasts containing the final cryptographic proofs and encrypted shares. + */ + fun auxRound3( + parties: List, + round1Broadcasts: Map, + round2Broadcasts:Map + ) : Map { + // Verify round2 broadcasts + for (party in parties) { + if (party != id) { + if (!round2Broadcasts.containsKey(party) || !round1Broadcasts.containsKey(party)) { + throw AuxException("broacasts missing key $party") + } + val round2Broadcast = round2Broadcasts[party] + val round1Broadcast = round1Broadcasts[party] + + if (!round2Broadcast!!.ssid.contentEquals(ssid) || !round1Broadcast!!.ssid.contentEquals(ssid)) { + throw AuxException("mismatch ssid for key $party of signer $id") + } + + if (round2Broadcast.from != party || round1Broadcast.from != party) { + throw AuxException("sender's id mismatch for key $party of signer $id") + } + + if (round2Broadcast.to != id || round1Broadcast.to != id) { + throw AuxException("receiver's id mismatch for key $party of signer $id") + } + + // Check RID lengths + if (round2Broadcast.uShare.size != SEC_BYTES || round2Broadcast.rid.size != SEC_BYTES) { + throw AuxException("invalid rid length of broadcast from $party to $id") + } + + // Verify ZK-PRM Proof + if (!round2Broadcast.prmProof.verify(party, PrmPublic(round2Broadcast.pedersenPublic))) { + throw AuxException("invalid prmProof for key $party of signer $id") + } + + // Check hash + val hash = hash( + ssid, party, round2Broadcast.ePolyShare, round2Broadcast.As, round2Broadcast.paillierPublic, + round2Broadcast.pedersenPublic, + ) + if (!round1Broadcast.VHash.contentEquals(hash)) { + throw AuxException("vHash mismatch for key $party of signer $id") + } + } + } + + // RID = ⊕ⱼ RIDⱼ + var rid = this.rid!! + for (party in parties) { + if (party == id) continue + rid = xorByteArrays(rid, round2Broadcasts[party]!!.rid) + } + + this.rid = rid + + // Prove N is a blum prime with zkmod + val modProof = ModProof.newProof(id, rid, ModPublic(paillierPublic!!.n), ModPrivate(paillierSecret!!.p, paillierSecret!!.q, paillierSecret!!.phi)) + + // Prove Schnorr's commitment consistency. + val schProofs = mutableMapOf() + for (j in parties) { + val jScalar = scalarFromInt(j) + val x_j = selfPolynomial!!.eval(jScalar) + val X_j = x_j.actOnBase() + + schProofs[j] = SchnorrProof.newProofWithCommitment(id, rid, SchnorrPublic(X_j), SchnorrPrivate(x_j), schnorrCommitments!![j]!!) + } + + val broadcasts = mutableMapOf() + for (j in parties) { + if (j != id) { + // Prove that the factors of N are relatively large + val facProof = FacProof.newProof(id, rid, FacPublic(paillierPublic!!.n, round2Broadcasts[j]!!.pedersenPublic), + FacPrivate(paillierSecret!!.p, paillierSecret!!.q) + ) + + // compute fᵢ(j) + val share = selfPolynomial!!.eval(scalarFromInt(j)) + // Encrypt share + val (C,_) = round2Broadcasts[j]!!.paillierPublic.encryptRandom(share.value) + + broadcasts[j] = AuxRound3Broadcast( + ssid = ssid, + from = id, + to = j, + modProof = modProof, + facProof = facProof, + schProofs = schProofs, + CShare = C, + ) + } + } + + return broadcasts + } + + /** + * Finalizes the auxiliary protocol by verifying all broadcasts, combining the shares + * from the other parties, and generating the final secret and public precomputations. + * + * @param parties A list of party identifiers involved in the protocol. + * @param round2Broadcasts The broadcasts from the second round. + * @param round3Broadcasts The broadcasts from the third round. + * @return A pair consisting of the final secret precomputation and a map of public precomputations. + */ + fun auxOutput( + parties: List, + round2Broadcasts: Map, + round3Broadcasts: Map + ) : Pair> { + val shareReceiveds = mutableMapOf() + + // Verify round3 Broadcasts + for (party in parties) { + if (party == id) continue + if (!round3Broadcasts.containsKey(party) || !round2Broadcasts.containsKey(party)) { + throw AuxException("broacasts missing key $party") + } + val round3Broadcast = round3Broadcasts[party] + val round2Broadcast = round2Broadcasts[party] + + if (!round2Broadcast!!.ssid.contentEquals(ssid) || !round3Broadcast!!.ssid.contentEquals(ssid)) { + throw AuxException("mismatch ssid for key $party of signer $id") + } + + if (round2Broadcast.from != party || round3Broadcast.from != party) { + throw AuxException("sender's id mismatch for key $party of signer $id") + } + + if (round2Broadcast.to != id || round3Broadcast.to != id) { + throw AuxException("receiver's id mismatch for key $party of signer $id") + } + + // Validate Ciphertext C Share + if (!round2Broadcast.paillierPublic.validateCiphertexts(round3Broadcast.CShare)) { + throw AuxException("paillier public key cannot verify cipher text $party of signer $id") + } + + // Decrypt share and verify with polynomial + val decryptedShare = Scalar.scalarFromBigInteger(paillierSecret!!.decrypt(round3Broadcast.CShare)) + val expectedPublicShare = round2Broadcast.ePolyShare.eval(scalarFromInt(id)) + // X == Fⱼ(i) + if (expectedPublicShare != decryptedShare.actOnBase()) { + throw AuxException("failed to validate ECDSA Share of $party with signer $id") + } + + // Check ZK ModProof and FacProof + if (!round3Broadcast.modProof.verify(party, rid!!, ModPublic(round2Broadcast.paillierPublic.n))) { + throw AuxException("Mod ZK verification failed for key $party to signer $id") + } + + if (!round3Broadcast.facProof.verify(party, rid!!, FacPublic(round2Broadcast.pedersenPublic.n, pedersenPublic!!))) { + throw AuxException("Fac ZK verification failed for key $party of signer $id") + } + + // Check all Schnorr's proofs + for (j in parties) { + val jScalar = scalarFromInt(j) + if (!round3Broadcast.schProofs[j]!!.verify(party, rid!!, SchnorrPublic(round2Broadcast.ePolyShare.eval(jScalar)))) { + throw AuxException("Schnorr Proof ZK verification failed for key $party at index $j of signer $id") + } + } + + shareReceiveds[party] = decryptedShare + } + + // Calculate the new secret/ public shares + var secretECDSA = selfShare!! + val publicECDSA = mutableMapOf() + val shamirPolynomials = mutableListOf() + shamirPolynomials.add(this.selfExpPolynomial!!) + for (party in parties) { + if (party == id) continue + secretECDSA = secretECDSA.add(shareReceiveds[party]!!) + shamirPolynomials.add(round2Broadcasts[party]!!.ePolyShare) + } + + val shamirPublicPolynomial = sum(shamirPolynomials) + for (party in parties) { + publicECDSA[party] = shamirPublicPolynomial.eval(scalarFromInt(party)) + } + + if (previousShare != null && previousPublic != null) { + secretECDSA = secretECDSA.add(previousShare) + for (party in parties) { + publicECDSA[party] = publicECDSA[party]!!.add(previousPublic[party]!!) + } + } + + // Compute Precomp + val publicPrecomps = mutableMapOf() + for (party in parties) { + if (party == id) { + publicPrecomps[party] = PublicPrecomputation( + id = id, + ssid = ssid, + publicEcdsa = publicECDSA[party]!!, + paillierPublic = this.paillierPublic!!, + aux = this.pedersenPublic!! + ) + } else { + publicPrecomps[party] = PublicPrecomputation( + id = party, + ssid = ssid, + publicEcdsa = publicECDSA[party]!!, + paillierPublic = round2Broadcasts[party]!!.paillierPublic, + aux = round2Broadcasts[party]!!.pedersenPublic + ) + } + } + + // SecretPrecomputation + val secretPrecomp = SecretPrecomputation( + id = id, + ssid = ssid, + threshold = threshold, + ecdsaShare = secretECDSA, + paillierSecret = paillierSecret!! + ) + + return secretPrecomp to publicPrecomps + } +} + +/** + * Computes a hash of the given inputs using the SHA-256 algorithm. + * + * @param ssid Session identifier. + * @param id The identifier of the current party. + * @param epoly The exponent polynomial. + * @param As A map of Schnorr commitments. + * @param paillierPublic The Paillier public key. + * @param pedersenPublic The Pedersen public parameters. + * @return The resulting hash as a byte array. + */ +private fun hash(ssid: ByteArray, id: Int, epoly: ExponentPolynomial, As: Map, paillierPublic: PaillierPublic, pedersenPublic: PedersenParameters) : ByteArray { + // Initialize a MessageDigest for SHA-256 + val digest = MessageDigest.getInstance("SHA-256") + + // Update the digest with each input + digest.update(ssid) + digest.update(ByteArray(id)) + digest.update(epoly.toByteArray()) + for (a in As.values) { + digest.update(a.toByteArray()) + } + digest.update(paillierPublic.toByteArray()) + digest.update(pedersenPublic.toByteArray()) + + // Compute and return the hash + return digest.digest() +} + +/** + * Performs a bitwise XOR operation on two byte arrays. + * + * @param a The first byte array. + * @param b The second byte array. + * @return A new byte array containing the XORed result. + * @throws IllegalArgumentException If the byte arrays have different lengths. + */ +private fun xorByteArrays(a: ByteArray, b: ByteArray): ByteArray { + require(a.size == b.size) { "Byte arrays must have the same length" } + return ByteArray(a.size) { i -> (a[i].toInt() xor b[i].toInt()).toByte() } +} diff --git a/src/main/kotlin/sign/aux/AuxBroadcast.kt b/src/main/kotlin/sign/aux/AuxBroadcast.kt new file mode 100644 index 0000000..de7dba5 --- /dev/null +++ b/src/main/kotlin/sign/aux/AuxBroadcast.kt @@ -0,0 +1,76 @@ +package perun_network.ecdsa_threshold.sign.aux + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.math.shamir.ExponentPolynomial +import perun_network.ecdsa_threshold.paillier.PaillierCipherText +import perun_network.ecdsa_threshold.paillier.PaillierPublic +import perun_network.ecdsa_threshold.pedersen.PedersenParameters +import perun_network.ecdsa_threshold.sign.Broadcast +import perun_network.ecdsa_threshold.zero_knowledge.FacProof +import perun_network.ecdsa_threshold.zero_knowledge.ModProof +import perun_network.ecdsa_threshold.zero_knowledge.PrmProof +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrProof + +/** + * Represents the first round of auxiliary broadcasting in the protocol. + * + * @property ssid The unique session identifier for the protocol. + * @property from The identifier of the sender party. + * @property to The identifier of the recipient party. + * @property VHash A hash value used to verify the broadcast message. + */ +data class AuxRound1Broadcast ( + override val ssid: ByteArray, + override val from: Int, + override val to: Int, + val VHash: ByteArray +) : Broadcast(ssid, from, to) + + +/** + * Represents the second round of auxiliary broadcasting in the protocol. + * + * @property ssid The unique session identifier for the protocol. + * @property from The identifier of the sender party. + * @property to The identifier of the recipient party. + * @property ePolyShare The exponent polynomial shared by the sender. + * @property As A map of public Schnorr's commitments from the parties. + * @property paillierPublic The public key for Paillier encryption. + * @property pedersenPublic The public parameters for the Pedersen commitment. + * @property rid A random identifier used for the protocol. + * @property uShare A random value shared by the sender. + * @property prmProof A proof for the Pedersen commitment. + */ +data class AuxRound2Broadcast ( + override val ssid: ByteArray, + override val from: Int, + override val to: Int, + val ePolyShare: ExponentPolynomial, + val As: Map, + val paillierPublic: PaillierPublic, + val pedersenPublic: PedersenParameters, + val rid: ByteArray, + val uShare: ByteArray, + val prmProof: PrmProof +) : Broadcast(ssid, from, to) {} + +/** + * Represents the third round of auxiliary broadcasting in the protocol. + * + * @property ssid The unique session identifier for the protocol. + * @property from The identifier of the sender party. + * @property to The identifier of the recipient party. + * @property modProof A proof for the modulus of N in the Paillier encryption. + * @property facProof A proof for the factorization of the Paillier modulus. + * @property schProofs A map of Schnorr proofs for the commitments. + * @property CShare The Paillier ciphertext representing the sender's share to be sent. + */ +data class AuxRound3Broadcast ( + override val ssid: ByteArray, + override val from: Int, + override val to: Int, + val modProof : ModProof, + val facProof : FacProof, + val schProofs : Map, + val CShare: PaillierCipherText +) : Broadcast(ssid, from, to) {} \ No newline at end of file diff --git a/src/main/kotlin/sign/aux/AuxException.kt b/src/main/kotlin/sign/aux/AuxException.kt new file mode 100644 index 0000000..c7ee1ef --- /dev/null +++ b/src/main/kotlin/sign/aux/AuxException.kt @@ -0,0 +1,9 @@ +package perun_network.ecdsa_threshold.sign.aux + +/** + * Custom exception for handling errors related to Auxiliary-Info & Key Refresh protocols. + * + * @param message The detail message for the exception, which provides information + * about the error. + */ +class AuxException(message: String) : Exception(message) \ No newline at end of file diff --git a/src/main/kotlin/sign/keygen/Keygen.kt b/src/main/kotlin/sign/keygen/Keygen.kt new file mode 100644 index 0000000..aa26998 --- /dev/null +++ b/src/main/kotlin/sign/keygen/Keygen.kt @@ -0,0 +1,262 @@ +package perun_network.ecdsa_threshold.sign.keygen + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.math.sampleRID +import perun_network.ecdsa_threshold.math.sampleScalar +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrCommitment +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrPrivate +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrProof +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrPublic +import java.security.MessageDigest + +/** + * Keygen is used to generate the public and private keys for a threshold ECDSA signature + * scheme through multiple rounds of communication between parties in a decentralized manner. The process involves sampling shares, + * creating commitments, verifying proofs, and computing final keys. + * + * @property ssid The unique session identifier for the protocol. + * @property id The identifier of the current party (signer) in the protocol. + */ +data class Keygen ( + val ssid: ByteArray, + val id: Int, + + // KEYGEN ROUND 1 + private var xShare : Scalar? = null, // k_i + private var XShare : Point? = null, // gamma_i + private var rhoShare : Scalar? = null, + private var alphaCommitment : Scalar? = null, + private var AShare : Point? = null, + private var uShare: ByteArray? = null, + private var VShare : ByteArray? = null, + + // KEYGEN ROUND 3 + private var rho : Scalar? = null, +) { + /** + * Initiates the first round of key generation, which involves sampling a private share and generating + * corresponding public commitments. + * + * @param parties A list of party identifiers (signers) in the protocol. + * @return A map of `KeygenRound1Broadcast` messages to be sent to other parties. + */ + fun keygenRound1(parties: List) : Map { + val broadcasts = mutableMapOf() + + // Sample x_i, X_i + val xShare = sampleScalar() + val publicShare = xShare.actOnBase() + + // – Sample ρi ← {0, 1}κ and compute (Ai, τ ) ← M(com, Πsch). + val rhoShare = sampleScalar() + val schnorrCommitment = SchnorrCommitment.newCommitment() + + val uShare = sampleRID() + val VShare = hash(ssid, id, rhoShare, publicShare, schnorrCommitment.A, uShare) + + for (j in parties) { + if (j != id) { + broadcasts[j] = KeygenRound1Broadcast( + ssid = ssid, + from = id, + to = j, + VShare = VShare + ) + } + } + + this.xShare = xShare + this.XShare = publicShare + this.rhoShare = rhoShare + this.alphaCommitment = schnorrCommitment.alpha + this.AShare = schnorrCommitment.A + this.uShare = uShare + this.VShare = VShare + + return broadcasts + } + + /** + * Initiates the second round of key generation, sending necessary shares and commitments to other parties. + * + * @param parties A list of party identifiers (signers) in the protocol. + * @return A map of `KeygenRound2Broadcast` messages to be sent to other parties. + */ + fun keygenRound2( + parties: List, + ) : Map { + val broadcasts = mutableMapOf() + for (j in parties) { + if (j != id) { + broadcasts[j] = KeygenRound2Broadcast( + ssid = ssid, + from = id, + to = j, + rhoShare = rhoShare!!, + XShare = XShare!!, + AShare = AShare!!, + uShare = uShare!! + ) + } + } + + return broadcasts + } + + /** + * Initiates the third round of key generation, which involves computing and verifying the Schnorr proof + * and aggregating the shares to generate the final public key. + * + * @param parties A list of party identifiers (signers) in the protocol. + * @param keygenRound1Broadcasts A map of `KeygenRound1Broadcast` messages from the first round. + * @param keygenRound2Broadcasts A map of `KeygenRound2Broadcast` messages from the second round. + * @return A map of `KeygenRound3Broadcast` messages to be sent to other parties. + * @throws KeygenException if any of the broadcasts are invalid or mismatched. + */ + fun keygenRound3( + parties: List, + keygenRound1Broadcasts: Map, + keygenRound2Broadcasts: Map, + ) : Map { + // Validates Round 2 Broadcasts. + for (j in parties) { + if (j == id ) continue + if (!keygenRound1Broadcasts.containsKey(j) || !keygenRound2Broadcasts.containsKey(j)) { + throw KeygenException("broacasts missing key $j of signer $id") + } + + if (!keygenRound1Broadcasts[j]!!.ssid.contentEquals(keygenRound2Broadcasts[j]!!.ssid)) { + throw KeygenException("mismatch ssid for key $j of signer $id") + } + + if (keygenRound2Broadcasts[j]!!.from != j || keygenRound1Broadcasts[j]!!.from != j) { + throw KeygenException("sender's id mismatch for key $j of signer $id") + } + + if (id != keygenRound2Broadcasts[j]!!.to || keygenRound1Broadcasts[j]!!.to != id) { + throw KeygenException("receiver's id mismatch for key $j of signer $id") + } + + val hash = hash(keygenRound1Broadcasts[j]!!.ssid, j, + keygenRound2Broadcasts[j]!!.rhoShare, + keygenRound2Broadcasts[j]!!.XShare, + keygenRound2Broadcasts[j]!!.AShare, + keygenRound2Broadcasts[j]!!.uShare) + + if (!hash.contentEquals(keygenRound1Broadcasts[j]!!.VShare)) { + throw KeygenException("corrupted V hash for key $j of signer $id") + } + } + + // Compute rho, schnorrProof + val broadcasts = mutableMapOf() + var rho = this.rhoShare!! + for (j in parties) { + if (j == id) continue + rho = rho.add(keygenRound2Broadcasts[j]!!.rhoShare) + } + + this.rho = rho + + val schnorrProof = SchnorrProof.newProofWithCommitment(id, rho.toByteArray(), + SchnorrPublic(XShare!!), SchnorrPrivate(xShare!!), SchnorrCommitment(alphaCommitment!!, AShare!!) + ) + + for (j in parties) { + if (j != id ) { + broadcasts[j] = KeygenRound3Broadcast( + ssid = ssid, + from = id, + to = j, + schnorrProof = schnorrProof + ) + } + } + + return broadcasts + } + + /** + * Finalizes the key generation process, verifying the third round broadcasts and computing the final public key. + * + * @param parties A list of party identifiers (signers) in the protocol. + * @param keygenRound2Broadcasts A map of `KeygenRound2Broadcast` messages from the second round. + * @param keygenRound3Broadcasts A map of `KeygenRound3Broadcast` messages from the third round. + * @return A `Triple` containing the private share, the public shares from all parties, and the aggregated public key. + * @throws KeygenException if any of the broadcasts are invalid or mismatched. + */ + fun keygenOutput( + parties: List, + keygenRound2Broadcasts: Map, + keygenRound3Broadcasts: Map, + ) : Triple, Point> { + // Validates Round 3 Broadcasts. + for (j in parties) { + if (j == id) continue + + if (!keygenRound3Broadcasts.containsKey(j) || !keygenRound2Broadcasts.containsKey(j)) { + throw KeygenException("broacasts missing key $j of signer $id") + } + + if (!keygenRound3Broadcasts[j]!!.ssid.contentEquals(ssid)) { + throw KeygenException("mismatch ssid for key $j of signer $id") + } + + if (keygenRound3Broadcasts[j]!!.from != j || keygenRound2Broadcasts[j]!!.from != j) { + throw KeygenException("sender's id mismatch for key $j of signer $id") + } + + if (id != keygenRound3Broadcasts[j]!!.to || keygenRound2Broadcasts[j]!!.to != id) { + throw KeygenException("receiver's id mismatch for key $j of signer $id") + } + + if (keygenRound2Broadcasts[j]!!.AShare != keygenRound3Broadcasts[j]!!.schnorrProof.A) { + throw KeygenException("corrupted AShare for key $j of signer $id") + } + + if (!keygenRound3Broadcasts[j]!!.schnorrProof.verify(j, rho!!.toByteArray(), SchnorrPublic(keygenRound2Broadcasts[j]!!.XShare))) { + throw KeygenException("corrupted schnorr Proof for key $j of signer $id") + } + } + + // Output public point + val publics = mutableMapOf() + var public = XShare!! + publics[id] = public + for (j in parties) { + if (j == id) continue + publics[j] = keygenRound2Broadcasts[j]!!.XShare + public = public.add(keygenRound2Broadcasts[j]!!.XShare) + } + + return Triple(this.xShare!!, publics, public) + } +} + +/** + * Computes the hash based on multiple inputs using SHA-256. + * + * @param ssid The session identifier. + * @param id The identifier of the current party. + * @param rhoShare The scalar share for ρ. + * @param publicShare The public share corresponding to the private share. + * @param A The point associated with the Schnorr commitment. + * @param uShare A random value for the protocol. + * @return The computed hash value. + */ +private fun hash(ssid: ByteArray, id: Int, rhoShare: Scalar, publicShare: Point, A: Point, uShare: ByteArray) : ByteArray { + // Initialize a MessageDigest for SHA-256 + val digest = MessageDigest.getInstance("SHA-256") + + // Update the digest with each input + digest.update(ssid) + digest.update(ByteArray(id)) + digest.update(rhoShare.toByteArray()) + digest.update(publicShare.toByteArray()) + digest.update(A.toByteArray()) + digest.update(uShare) + + // Compute and return the hash + return digest.digest() +} \ No newline at end of file diff --git a/src/main/kotlin/sign/keygen/KeygenBroadcast.kt b/src/main/kotlin/sign/keygen/KeygenBroadcast.kt new file mode 100644 index 0000000..425160f --- /dev/null +++ b/src/main/kotlin/sign/keygen/KeygenBroadcast.kt @@ -0,0 +1,57 @@ +package perun_network.ecdsa_threshold.sign.keygen + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.sign.Broadcast +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrProof + +/** + * Represents the broadcast message in the first round of the key generation process. + * + * @property ssid The unique session identifier for the protocol. + * @property from The identifier of the party sending this broadcast message. + * @property to The identifier of the party receiving this broadcast message. + * @property VShare The shared value that is part of the key generation process. + */ +data class KeygenRound1Broadcast ( + override val ssid: ByteArray, + override val from: Int, + override val to: Int, + val VShare: ByteArray +) : Broadcast(ssid, from, to) + +/** + * Represents the broadcast message in the second round of the key generation process. + * + * @property ssid The unique session identifier for the protocol. + * @property from The identifier of the party sending this broadcast message. + * @property to The identifier of the party receiving this broadcast message. + * @property rhoShare The share of the value ρ, which is used in the protocol. + * @property XShare The public share of the private key for the sender. + * @property AShare The commitment associated with the private share. + * @property uShare A random value shared between parties. + */ +data class KeygenRound2Broadcast ( + override val ssid: ByteArray, + override val from: Int, + override val to: Int, + val rhoShare: Scalar, + val XShare: Point, + val AShare: Point, + val uShare: ByteArray, +) : Broadcast(ssid, from, to) {} + +/** + * Represents the broadcast message in the third round of the key generation process. + * + * @property ssid The unique session identifier for the protocol. + * @property from The identifier of the party sending this broadcast message. + * @property to The identifier of the party receiving this broadcast message. + * @property schnorrProof The Schnorr proof that verifies the validity of the public share. + */ +data class KeygenRound3Broadcast ( + override val ssid: ByteArray, + override val from: Int, + override val to: Int, + val schnorrProof: SchnorrProof, +) : Broadcast(ssid, from, to) \ No newline at end of file diff --git a/src/main/kotlin/sign/keygen/KeygenException.kt b/src/main/kotlin/sign/keygen/KeygenException.kt new file mode 100644 index 0000000..b6a5a7b --- /dev/null +++ b/src/main/kotlin/sign/keygen/KeygenException.kt @@ -0,0 +1,9 @@ +package perun_network.ecdsa_threshold.sign.keygen + +/** + * Custom exception for handling errors related to Key-Generation protocol. + * + * @param message The detail message for the exception, which provides information + * about the error. + */ +class KeygenException(message: String) : Exception(message) \ No newline at end of file diff --git a/src/main/kotlin/sign/presign/PresignException.kt b/src/main/kotlin/sign/presign/PresignException.kt new file mode 100644 index 0000000..877da09 --- /dev/null +++ b/src/main/kotlin/sign/presign/PresignException.kt @@ -0,0 +1,9 @@ +package perun_network.ecdsa_threshold.sign.presign + +/** + * Custom exception for handling errors related to the Presigning protocol. + * + * @param message The detail message for the exception, which provides information + * about the error. + */ +class PresignException(message: String) : Exception(message) \ No newline at end of file diff --git a/src/main/kotlin/sign/presign/PresignRound1.kt b/src/main/kotlin/sign/presign/PresignRound1.kt new file mode 100644 index 0000000..508091b --- /dev/null +++ b/src/main/kotlin/sign/presign/PresignRound1.kt @@ -0,0 +1,147 @@ +package perun_network.ecdsa_threshold.sign.presign + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.math.sampleScalar +import perun_network.ecdsa_threshold.paillier.PaillierCipherText +import perun_network.ecdsa_threshold.sign.Broadcast +import perun_network.ecdsa_threshold.tuple.Nonuple +import perun_network.ecdsa_threshold.zero_knowledge.EncElgPrivate +import perun_network.ecdsa_threshold.zero_knowledge.EncElgProof +import perun_network.ecdsa_threshold.zero_knowledge.EncElgPublic +import java.math.BigInteger + +/** + * Represents the public parameters for ElGamal encryption. + * + * @property A1 Elliptic curve point representing g^a. + * @property A2 Elliptic curve point representing h^a. + * @property B1 Elliptic curve point representing g^b. + * @property B2 Elliptic curve point representing h^b. + * @property Y Elliptic curve point representing the ciphertext or an associated value (e.g., g^x or g^m). + */ +data class ElGamalPublic( + val A1: Point, + val A2: Point, + val B1: Point, + val B2: Point, + val Y: Point +) + +/** + * Represents the secret parameters for ElGamal encryption. + * + * @property a Scalar representing the secret exponent a used for encryption of kShare. + * @property b Scalar representing the secret exponent b used for encryption of gammaShare. + */ +data class ElGamalSecret( + val a: Scalar, + val b: Scalar, +) + +/** + * Represents the content of the message that the signer sends to its peer after the first presign round. + * + * @property ssid A unique identifier for the session. + * @property from The identifier of the signer. + * @property to The identifier of the receiver. + * @property K The ciphertext representing Kᵢ. + * @property G The ciphertext representing Gᵢ. + * @property elGamalPublic The public points used for El-Gamal related encryption. + * @property proof0 The enc-elg proof associated with K. + * @property proof1 The enc-elg proof associated with G. + * + */ +class PresignRound1Broadcast ( + override val ssid: ByteArray, + override val from : Int, + override val to: Int, + val K : PaillierCipherText, // K = K_i + val G: PaillierCipherText, // G = G_i + val elGamalPublic: ElGamalPublic, + val proof0: EncElgProof, + val proof1: EncElgProof +) : Broadcast(ssid, from, to) + +/** + * Represents the input for the first round of the presigning process. + * + * @property ssid A unique identifier for the session. + * @property id The identifier of the signer. + * @property publicPrecomps A map of public precomputed values indexed by signer identifiers. + * + */ +class PresignRound1Input ( + val ssid: ByteArray, + val id: Int, + val publicPrecomps : Map +) { + /** + * Produces the output for the first round of the presigning process. + * + * This method generates necessary ciphertexts and a proof for each signer. + * + * @param signers A list of signer identifiers participating in the presigning. + * @return A [Nonuple] containing the results of the presigning, including the outputs for each signer, + * gamma share, k share, nonces, and ElGamal public and secret materials. + */ + internal fun producePresignRound1Output( + signers: List + ) : Nonuple, Scalar, Scalar, BigInteger, BigInteger, PaillierCipherText, PaillierCipherText, ElGamalPublic, ElGamalSecret> { + val result = mutableMapOf() + // sample gamma_i ← Fq + val gammaShare = sampleScalar() + // Gᵢ = Encᵢ(γᵢ;gammaᵢ) + val paillier = publicPrecomps[id]!!.paillierPublic + val (G, gNonce) = paillier.encryptRandom(gammaShare.value) + + // kᵢ <- 𝔽, + val kShare = sampleScalar() + val (K, kNonce) = paillier.encryptRandom(kShare.value) + + // sample Y_i <- G and ai, bi ← Fq + val yi = sampleScalar() + val Yi = yi.actOnBase() + val ai = sampleScalar() + val bi = sampleScalar() + + val A1 = ai.actOnBase() + val A2 = ai.act(Yi).add(kShare.actOnBase()) + val B1 = bi.actOnBase() + val B2 = bi.act(Yi).add(gammaShare.actOnBase()) + + val elGamalPublic = ElGamalPublic(A1, A2, B1, B2, Yi) + val elGamalSecret = ElGamalSecret(ai, bi) + + for (j in signers) { + if (id != j) { + // Compute ψ_0_j,i = M(prove, Πenc_j,(ssid, i),(Iε, Ki); (ki, ρi)) for every j 6= i. + val proof0 = EncElgProof.newProof( + id, + EncElgPublic(K, Yi, A1, A2, publicPrecomps[id]!!.paillierPublic, publicPrecomps[j]!!.aux), + EncElgPrivate(kShare.value, kNonce, yi, ai) + ) + + val proof1 = EncElgProof.newProof( + id, + EncElgPublic(G, Yi, B1, B2, publicPrecomps[id]!!.paillierPublic, publicPrecomps[j]!!.aux), + EncElgPrivate(gammaShare.value, gNonce, yi, bi) + ) + + result[j] = PresignRound1Broadcast( + ssid = ssid, + from = id, + to = j, + K = K, + G = G, + elGamalPublic = elGamalPublic, + proof0 = proof0, + proof1 = proof1 + ) + } + } + return Nonuple(result , gammaShare, kShare , gNonce, kNonce, K, G, elGamalPublic, elGamalSecret) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/sign/presign/PresignRound2.kt b/src/main/kotlin/sign/presign/PresignRound2.kt new file mode 100644 index 0000000..067ebd5 --- /dev/null +++ b/src/main/kotlin/sign/presign/PresignRound2.kt @@ -0,0 +1,200 @@ +package perun_network.ecdsa_threshold.sign.presign + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.newBasePoint +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.paillier.PaillierCipherText +import perun_network.ecdsa_threshold.paillier.PaillierSecret +import perun_network.ecdsa_threshold.sign.Broadcast +import perun_network.ecdsa_threshold.zero_knowledge.ElogPrivate +import perun_network.ecdsa_threshold.zero_knowledge.ElogProof +import perun_network.ecdsa_threshold.zero_knowledge.ElogPublic +import perun_network.ecdsa_threshold.zero_knowledge.EncElgPublic +import perun_network.ecdsa_threshold.zero_knowledge.AffgProof +import perun_network.ecdsa_threshold.zero_knowledge.produceAffGMaterials +import java.math.BigInteger + +/** + * Represents the content of the message that the signer sends to its peer after the second presign round. + * + * @property ssid A unique identifier for the session. + * @property from The identifier of the signer. + * @property to The identifier of the receiver. + * @property bigGammaShare The computed big gamma share for the signer. + * @property deltaD The Paillier ciphertext representing Delta D. + * @property deltaF The Paillier ciphertext representing Delta F. + * @property deltaProof The Aff-G proof associated with delta. + * @property chiD The Paillier ciphertext representing Chi D. + * @property chiF The Paillier ciphertext representing Chi F. + * @property chiProof The Aff-G proof associated with chi. + * @property elogProof The elog proof associated with the presigning process. + * @property chiBeta The beta value for chi. + * @property deltaBeta The beta value for delta. + */ +class PresignRound2Broadcast ( + override val ssid: ByteArray, + override val from : Int, + override val to: Int, + val bigGammaShare : Point, + val deltaD: PaillierCipherText, + val deltaF: PaillierCipherText, + val deltaProof: AffgProof, + val chiD: PaillierCipherText, + val chiF: PaillierCipherText, + val chiProof: AffgProof, + val elogProof: ElogProof, + val chiBeta: BigInteger, + val deltaBeta: BigInteger, +) : Broadcast(ssid, from, to) + +/** + * Represents the input for the second round of the presigning process. + * + * @property ssid A unique identifier for the session. + * @property id The identifier of the signer. + * @property gammaShare The gamma share for the signer. + * @property secretECDSA The ECDSA secret key for the signer. + * @property secretPaillier The Paillier secret key for the signer. + * @property gNonce The nonce used for generating the proof. + * @property publicPrecomps A map of public precomputed values indexed by signer identifiers. + */ +class PresignRound2Input ( + val ssid: ByteArray, + val id: Int, + val gammaShare: Scalar, + val secretECDSA: Scalar, + val secretPaillier : PaillierSecret, + val gNonce: BigInteger, + val publicPrecomps: Map +) { + /** + * Produces the output for the second round of the presigning process. + * + * This method generates necessary ciphertexts and proofs for each signer. + * + * @param signers A list of signer identifiers participating in the presigning. + * @param ks A map of public Paillier ciphertexts indexed by signer identifiers. + * @param B1 The first public point B used for El-Gamal encryption of gammaShare. + * @param B2 The second public point B used for El-Gamal encryption of gammaShare. + * @param Yi The public point used in all El-Gamal encryption. + * @return A pair containing a map of the presign outputs for each signer and the computed big gamma share. + */ + internal fun producePresignRound2Output( + signers : List, + ks : Map, + B1: Point, + B2: Point, + Yi: Point, + bi: Scalar + ): Pair, Point> { + val result = mutableMapOf() + // Γᵢ = [γᵢ]⋅G + val bigGammaShare = gammaShare.actOnBase() + + // ψi = M(prove, Πelog, (epid, i),(Γi, g, Bi,1, Bi,2, Yi); (γi, bi)) + val elogProof = ElogProof.newProof(id, + ElogPublic(B1, B2, Yi, bigGammaShare, newBasePoint()), + ElogPrivate(gammaShare, bi) + ) + + for (j in signers) { + if (j != id) { + // deltaBeta = βi,j + // compute DeltaD = Dᵢⱼ + // compute DeltaF = Fᵢⱼ + // compute deltaProof = ψj,i + val (deltaBeta, deltaD, deltaF, deltaProof) = produceAffGMaterials( + id, + gammaShare.value, + bigGammaShare, + ks[j]!!.clone(), + secretPaillier, + publicPrecomps[j]!!.paillierPublic, + publicPrecomps[j]!!.aux) + + // chiBeta = β^i,j + // compute chiD = D^ᵢⱼ + // compute chiF = F^ᵢⱼ + // compute chiProof = ψ^j,i + val (chiBeta, chiD, chiF, chiProof) = produceAffGMaterials( + id, + secretECDSA.value, + publicPrecomps[id]!!.publicEcdsa, + ks[j]!!.clone(), + secretPaillier, + publicPrecomps[j]!!.paillierPublic, + publicPrecomps[j]!!.aux) + + val presignOutput2 = PresignRound2Broadcast( + ssid = ssid, + from = id, + to = j, + bigGammaShare = bigGammaShare, + deltaD = deltaD, + deltaF = deltaF, + deltaProof = deltaProof, + chiD = chiD, + chiF = chiF, + chiProof = chiProof, + elogProof = elogProof, + deltaBeta = deltaBeta, + chiBeta = chiBeta, + ) + result[j] = presignOutput2 + } + } + + return result to bigGammaShare + } + + /** + * Verifies the broadcast message of the first round of the presigning process from a given signer. + * + * @param j The identifier of the signer whose output is being verified. + * @param presignRound1Broadcast The output from the first round for the given signer. + * @return True if the verification is successful; otherwise, false. + */ + internal fun verifyPresignRound1Broadcast( + j: Int, + presignRound1Broadcast : PresignRound1Broadcast, + ) : Boolean { + // Check ssid. + if (!this.ssid.contentEquals(presignRound1Broadcast.ssid)) { + throw PresignException("unknown ssid ${presignRound1Broadcast.ssid}") + } + + // Check identifier. + if (j == id || presignRound1Broadcast.from != j || id != presignRound1Broadcast.to ) { + throw PresignException("invalid id from ${presignRound1Broadcast.from} to ${presignRound1Broadcast.to} ") + } + + // Check enc-elg proof0. + val public0 = EncElgPublic( + C = presignRound1Broadcast.K, + A = presignRound1Broadcast.elGamalPublic.Y, + B = presignRound1Broadcast.elGamalPublic.A1, + X = presignRound1Broadcast.elGamalPublic.A2, + N0 = publicPrecomps[j]!!.paillierPublic, + aux = publicPrecomps[id]!!.aux, + ) + if (!presignRound1Broadcast.proof0.verify(presignRound1Broadcast.from, public0)) { + throw PresignException("failed to validated enc-elg zk proof 0 from ${presignRound1Broadcast.from} to ${presignRound1Broadcast.to}") + } + + // Check enc-elg proof1 + val public1 = EncElgPublic( + C = presignRound1Broadcast.G, + A = presignRound1Broadcast.elGamalPublic.Y, + B = presignRound1Broadcast.elGamalPublic.B1, + X = presignRound1Broadcast.elGamalPublic.B2, + N0 = publicPrecomps[j]!!.paillierPublic, + aux = publicPrecomps[id]!!.aux, + ) + if (!presignRound1Broadcast.proof1.verify(presignRound1Broadcast.from, public1)) { + throw PresignException("failed to validated enc-elg zk proof 1 from ${presignRound1Broadcast.from} to ${presignRound1Broadcast.to}") + } + + return true + } +} \ No newline at end of file diff --git a/src/main/kotlin/sign/presign/PresignRound3.kt b/src/main/kotlin/sign/presign/PresignRound3.kt new file mode 100644 index 0000000..42525d7 --- /dev/null +++ b/src/main/kotlin/sign/presign/PresignRound3.kt @@ -0,0 +1,233 @@ +package perun_network.ecdsa_threshold.sign.presign + +import perun_network.ecdsa_threshold.ecdsa.* +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.paillier.PaillierCipherText +import perun_network.ecdsa_threshold.paillier.PaillierSecret +import perun_network.ecdsa_threshold.sign.Broadcast +import perun_network.ecdsa_threshold.tuple.Quintuple +import perun_network.ecdsa_threshold.zero_knowledge.ElogPrivate +import perun_network.ecdsa_threshold.zero_knowledge.ElogProof +import perun_network.ecdsa_threshold.zero_knowledge.ElogPublic +import perun_network.ecdsa_threshold.zero_knowledge.AffgPublic +import java.math.BigInteger + +/** + * Represents the content of the message that the signer sends to its peer after the third presign round. + * + * @property ssid A unique identifier for the session. + * @property from The identifier of the signer. + * @property to The identifier of the receiver. + * @property chiShare The computed chi share for the signer. + * @property deltaShare The computed delta share for the signer. + * @property bigDeltaShare The computed big delta share for the signer. + * @property gamma The computed gamma point for the signer. + * @property elogProof The elog proof associated with the presigning process. + */ +data class PresignRound3Broadcast ( + override val ssid: ByteArray, + override val from : Int, + override val to: Int, + val chiShare : BigInteger, + val deltaShare : BigInteger, + val bigDeltaShare : Point, + val gamma : Point, + val elogProof: ElogProof +) : Broadcast(ssid, from, to) + +/** + * Represents the input for the third round of the presigning process. + * + * @property ssid A unique identifier for the session. + * @property id The identifier of the signer. + * @property gammaShare The gamma share for the signer. + * @property secretPaillier The Paillier secret key for the signer. + * @property kShare The scalar value for k. + * @property K The Paillier ciphertext for K. + * @property kNonce The nonce used for generating the proof. + * @property secretECDSA The ECDSA secret key for the signer. + * @property publicPrecomps A map of public precomputed values indexed by signer identifiers. + */ +class PresignRound3Input( + val ssid: ByteArray, + val id: Int, + val gammaShare : BigInteger, + val secretPaillier: PaillierSecret, + val kShare: Scalar, + val K : PaillierCipherText, + val kNonce: BigInteger, + val secretECDSA: BigInteger, + val publicPrecomps: Map +) { + /** + * Produces the output for the third round of the presigning process. + * + * This method generates the necessary shares and proofs for each signer. + * + * @param signers A list of signer identifiers participating in the presigning. + * @param bigGammaShares A map of big gamma shares indexed by signer identifiers. + * @param A1 The first public point A used for El-Gamal encryption of k. + * @param A2 The second public point A used for El-Gamal encryption of k. + * @param Yi The public point used in El-Gamal encryption. + * @param a The secret Scalar used to encrypt kShare. + * @param presignRound2Broadcasts A map of broadcasts from the second round indexed by signer identifiers. + * @return A quintuple containing a map of the presign outputs for each signer, the computed chi share, + * the computed delta share, the computed big delta share, and the computed gamma point. + */ + internal fun producePresignRound3Output( + signers : List, + bigGammaShares : Map, + A1 : Point, + A2: Point, + Yi: Point, + a: Scalar, + presignRound2Broadcasts: Map + ) : Quintuple, BigInteger, BigInteger, Point, Point>{ + val result = mutableMapOf() + val deltaShareAlphas= mutableMapOf() // DeltaShareAlpha[j] = αᵢⱼ + val deltaShareBetas= mutableMapOf() // DeltaShareBeta[j] = βᵢⱼ + val chiShareAlphas= mutableMapOf() // ChiShareAlpha[j] = α̂ᵢⱼ + val chiShareBetas= mutableMapOf() // ChiShareBeta[j] = β̂^ᵢⱼ + for (j in signers) { + if (j != id) { + deltaShareBetas[j] = presignRound2Broadcasts[j]!!.deltaBeta + chiShareBetas[j] = presignRound2Broadcasts[j]!!.chiBeta + deltaShareAlphas[j] = secretPaillier.decrypt(presignRound2Broadcasts[j]!!.deltaD) + chiShareAlphas[j] = secretPaillier.decrypt(presignRound2Broadcasts[j]!!.chiD) + chiShareAlphas[j] = secretPaillier.decrypt(presignRound2Broadcasts[j]!!.chiD) + } + } + + // Γ = ∑ⱼ Γⱼ + var bigGamma = newPoint() + for ((_, bigGammaShare) in bigGammaShares) { + bigGamma = bigGamma.add(bigGammaShare) + } + + // Δᵢ = [kᵢ]Γ + val bigDeltaShare = kShare.act(bigGamma) + + // δᵢ = γᵢ kᵢ + var deltaShare = gammaShare.multiply(kShare.value) + + // χᵢ = xᵢ kᵢ + var chiShare = secretECDSA.multiply(kShare.value) + + for (j in signers) { + if (j != this.id) { + //δᵢ += αᵢⱼ + βᵢⱼ + deltaShare = deltaShare.add(deltaShareAlphas[j]) + deltaShare = deltaShare.add(deltaShareBetas[j]) + + // χᵢ += α̂ᵢⱼ + ̂βᵢⱼ + chiShare = chiShare.add(chiShareAlphas[j]) + chiShare = chiShare.add(chiShareBetas[j]) + } + } + deltaShare = deltaShare.mod(secp256k1Order()) + chiShare = chiShare.mod(secp256k1Order()) + for (j in signers) { + if (j != id) { + val elogPublic = ElogPublic( + L = A1, + M = A2, + X = Yi, + Y = bigDeltaShare, + h = bigGamma, + ) + + val elogPrivate = ElogPrivate( + y= kShare, + lambda= a + ) + val elogProof = ElogProof.newProof(id, elogPublic, elogPrivate) + result[j] = PresignRound3Broadcast( + ssid = ssid, + from = id, + to = j, + chiShare = chiShare, + deltaShare = deltaShare, + bigDeltaShare = bigDeltaShare, + gamma = bigGamma, + elogProof = elogProof + ) + } + } + + return Quintuple(result, chiShare, deltaShare, bigDeltaShare, bigGamma) + } + + /** + * Verifies the output of the second round of the presigning process for a given signer. + * + * @param j The identifier of the signer whose output is being verified. + * @param presignRound2Broadcast The broadcast from the second round for the given signer. + * @param k_i The Paillier ciphertext for K. + * @param ecdsa_j The ECDSA point for the signer. + * @param bigGammaShareJ The broadcasted bigGammaShare by peer. + * @param Bj1 The first public point for El-Gamal encryption of peer's bigGammaShare. + * @param Bj2 The second public point for El-Gamal encryption of peer's bigGammaShare + * @param Yj The public point for verification of the zero knowledge proof of bigGammaShare. + * @return True if the verification is successful; otherwise, false. + */ + internal fun verifyPresignRound2Broadcast( + j : Int, + presignRound2Broadcast : PresignRound2Broadcast, + k_i : PaillierCipherText, + ecdsa_j: Point, + bigGammaShareJ: Point, + Bj1: Point, + Bj2: Point, + Yj: Point + ) : Boolean { + // Check ssid. + if (!this.ssid.contentEquals(presignRound2Broadcast.ssid)) { + throw PresignException("unknown ssid ${presignRound2Broadcast.ssid}") + } + + // Check identifier. + if (j == id || presignRound2Broadcast.from != j || id != presignRound2Broadcast.to ) { + throw PresignException("invalid id from ${presignRound2Broadcast.from} to ${presignRound2Broadcast.to} ") + } + + // Verify M(vrfy, Πaff-g_i ,(ssid, j),(Iε,Jε, Di,j , Ki, Fj,i, Γj ), ψi,j ) = 1. + val deltaPublic = AffgPublic( + C = k_i.clone(), + D = presignRound2Broadcast.deltaD, + Y = presignRound2Broadcast.deltaF, + X = presignRound2Broadcast.bigGammaShare, + n1 = publicPrecomps[j]!!.paillierPublic, + n0 = publicPrecomps[id]!!.paillierPublic, + aux = publicPrecomps[id]!!.aux + ) + if (!presignRound2Broadcast.deltaProof.verify(presignRound2Broadcast.from, deltaPublic)) { + throw PresignException("failed to verify zk proof delta from ${presignRound2Broadcast.from} to ${presignRound2Broadcast.to}") + } + + // Verify M(vrfy, Πaff-g_i,(ssid, j),(Iε,Jε, Dˆk,j , Ki, Fˆj,i, Xj ), ψˆi,j ) = 1 + val chiPublic = AffgPublic( + C = k_i.clone(), + D = presignRound2Broadcast.chiD, + Y = presignRound2Broadcast.chiF, + X = ecdsa_j, + n1 = publicPrecomps[j]!!.paillierPublic, + n0= publicPrecomps[id]!!.paillierPublic, + aux = publicPrecomps[id]!!.aux + ) + if (!presignRound2Broadcast.chiProof.verify(presignRound2Broadcast.from, chiPublic)) { + throw PresignException("failed to verify zk proof chi from ${presignRound2Broadcast.from} to ${presignRound2Broadcast.to}") + } + + // Verify M(vrfy, Πelog ,(epid, j),(Γj , g, Bj,1, Bj,2, Yj ), ψj ) = 1. + val logPublic = ElogPublic( + L = Bj1, + M = Bj2, + X = Yj, + Y = bigGammaShareJ, + h = newBasePoint(), + ) + return presignRound2Broadcast.elogProof.verify(presignRound2Broadcast.from, logPublic) + } + +} + diff --git a/src/main/kotlin/sign/presign/Presigner.kt b/src/main/kotlin/sign/presign/Presigner.kt new file mode 100644 index 0000000..7abbb7c --- /dev/null +++ b/src/main/kotlin/sign/presign/Presigner.kt @@ -0,0 +1,322 @@ +package perun_network.ecdsa_threshold.sign.presign + +import perun_network.ecdsa_threshold.ecdsa.* +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.precomp.SecretPrecomputation +import perun_network.ecdsa_threshold.paillier.PaillierCipherText +import perun_network.ecdsa_threshold.zero_knowledge.ElogPublic +import java.math.BigInteger + +/** + * Represents the secrets of the signer during the process of threshold signing. + * + * @property id The identifier of the signer. + * @property private The signer's private precomputation, containing secret values. + * @property publicPrecomps A map of public precomputations, indexed by the signer's IDs. + * + * @property kShare The signer's share of the secret value `kᵢ` in the presigning protocol (Round 1). + * @property gammaShare The signer's share of the secret value `gammaᵢ` in the presigning protocol (Round 1). + * @property kNonce The signer's nonce value used in the presigning protocol (Round 1). + * @property gNonce The group's nonce value used in the presigning protocol (Round 1). + * @property a The secret nonce used in El-Gamal encryption of kShare. + * @property b The secret nonce used in El-Gamal encryption of gammaShare. + * @property K The public K_i counterpart of kᵢ in the presigning protocol (Round 1). + * @property G The public G_i counterpart of gammaᵢ in the presigning protocol (Round 1). + * @property elGamalPublic The public points used in El-Gamal encryption. + * + * @property bigGammaShare The public `Γi` value produced after presigning round 2 protocol (Round 2). + * + * @property chiShare The signer's share of the secret value `Xᵢ` in the presigning protocol (Round 3). + * @property deltaShare The signer's public share 'δi' in the presigning protocol (Round 3). + * @property bigDeltaShare The signer's public delta share 'Δi' in the presigning protocol (Round 3). + * @property bigGamma The signer's public gamma 'Γ' which is the combined bigGammaShares of all the signers from second round (Round 3). + * + * @property bigR Represents the public Point to be used for signing partial signature and verification (Presign Output). + */ +class Presigner ( + val id : Int, + val private : SecretPrecomputation, + val publicPrecomps: Map, + + // PRESIGN ROUND 1 + private var kShare : Scalar? = null, // k_i + private var gammaShare: Scalar? = null, // gamma_i + private var kNonce : BigInteger? = null, + private var gNonce : BigInteger? = null, + private var a: Scalar? = null, + private var b: Scalar? = null, + + var K : PaillierCipherText? = null, + var G : PaillierCipherText? = null, + var elGamalPublic: ElGamalPublic? = null, + + // PRESIGN ROUND 2 + var bigGammaShare: Point? = null, + + // PRESIGN ROUND 3 + private var chiShare: Scalar? = null, // X_i + var deltaShare: BigInteger? = null, + var bigDeltaShare: Point? = null, + var bigGamma: Point? = null, + + var bigR : Point? = null, +) { + + /** + * Executes the first round of the threshold ECDSA pre-signing process. + * + * This method handles Round 1 of the protocol where each signer samples secret values `ki`, `γi` and + * generates corresponding Paillier ciphertexts `Ki`, `Gi` for the secret shares of `ki` and `γi`. + * + * @param signerIds List of all signers involved in the pre-signing process. + * @return A map of broadcast messages from each signer containing the ciphertexts `Ki`, `Gi`, and proof components. + */ + fun presignRound1(signerIds : List) : MutableMap { + val presignRound1Input = PresignRound1Input( + ssid = private.ssid, + id = id, + publicPrecomps = publicPrecomps + ) + + // Produce Presign Round1 outputs. + val (output, gammaShare, kShare, gNonce, kNonce, K, G, elGamalPublic, elGamalSecret) = presignRound1Input.producePresignRound1Output(signerIds) + + this.gammaShare = gammaShare + this.kShare = kShare + this.gNonce = gNonce + this.kNonce = kNonce + this.K = K + this.G = G + this.a = elGamalSecret.a + this.b = elGamalSecret.b + this.elGamalPublic = elGamalPublic + return output + } + + /** + * Executes the second round of the threshold ECDSA pre-signing process. + * + * During this round, each signer verifies the first round outputs from other signers and produces + * a share of the public `Γ` value, which will be used later in the signature. + * + * @param signerIds List of all signers. + * @param Ks Map of public `K_i` Paillier ciphertexts from all signers. + * @param presignRound1Broadcasts Broadcasts received in Round 1. + * @return A map of broadcast messages for the second round, including `Γi` shares and proofs. + */ + fun presignRound2( + signerIds: List, + Ks : Map, // public K_i from all peers + presignRound1Broadcasts: Map) : Map { + // Prepare Presign Round 2 Inputs + val presignRound2Input = PresignRound2Input( + ssid = private.ssid, + id = id, + gammaShare = gammaShare!!, + secretECDSA = private.ecdsaShare, + secretPaillier = private.paillierSecret , + gNonce = gNonce!!, + publicPrecomps = publicPrecomps + ) + + // Verify Presign Round 1 Broadcasts + for (j in signerIds) { + if (j != id) { + if (!presignRound2Input.verifyPresignRound1Broadcast(j, presignRound1Broadcasts[j]!!)) { + throw PresignException("failed to validate enc proof for K from $j to $id") + } + } + } + + // Produce Presign Round2 output + val (presign2Broadcasts, bigGammaShare) = presignRound2Input.producePresignRound2Output( + signerIds, + Ks, + elGamalPublic!!.B1, elGamalPublic!!.B2, elGamalPublic!!.Y, b!!) + this.bigGammaShare = bigGammaShare + + return presign2Broadcasts + } + + /** + * Executes the third round of the threshold ECDSA pre-signing process. + * + * In this round, each signer calculates `δi` and `χi` as partial contributions to the final signature, + * combining all the secret shares from previous rounds. + * + * @param signerIds List of all signers. + * @param bigGammaShares Map of `Γi` shares from all signers. + * @param presignRound2Broadcasts Broadcasts received in Round 2. + * @param elGamalPublics Public points broadcast by peers for ElGamal proofs. + * @return A map of broadcast messages for Round 3, including partial signature shares and proofs. + */ + fun presignRound3( + signerIds: List, + bigGammaShares : Map, + elGamalPublics: Map, + presignRound2Broadcasts: Map + ) : Map { + val presignRound3Input = PresignRound3Input( + ssid = private.ssid, + id = id, + gammaShare = gammaShare!!.value, + secretPaillier = private.paillierSecret, + kShare = kShare!!, + K = K!!, + kNonce = kNonce!!, + secretECDSA = private.ecdsaShare.value, + publicPrecomps = publicPrecomps + ) + + // Verify Presign Round 2 Broadcasts + for (j in signerIds) { + if (j != id) { + if (!presignRound3Input.verifyPresignRound2Broadcast( + j, + presignRound2Broadcasts[j]!!, + K!!, + publicPrecomps[j]!!.publicEcdsa, + bigGammaShares[j]!!, + elGamalPublics[j]!!.B1, + elGamalPublics[j]!!.B2, + elGamalPublics[j]!!.Y + ) + ) { + throw PresignException("failed to validate presign round 2 broadcast from $j to $id") + } + } + } + + // Produce Presign Round 3 outputs. + val (presign3Broadcast, chiShare, deltaShare, bigDeltaShare, bigGamma) = presignRound3Input.producePresignRound3Output( + signerIds, + bigGammaShares, + elGamalPublic!!.A1, elGamalPublic!!.A2, elGamalPublic!!.Y, a!!, + presignRound2Broadcasts) + + this.chiShare = Scalar(chiShare) + this.deltaShare = deltaShare + this.bigDeltaShare = bigDeltaShare + this.bigGamma = bigGamma + + return presign3Broadcast + } + + /** + * Verifies the broadcast of the third round of the presigning process from a given signer. + * + * @param j The identifier of the signer whose output is being verified. + * @param presignRound3Broadcast The output from the third round for the given signer. + * @param A1j First public point A by peer for used in El-Gamal encryption of kShare. + * @param A2j Second public point A by peer for used in El-Gamal encryption of kShare. + * @param Yj peer's public point to be used in all El-Gamal encryption. + * @return True if the verification is successful; otherwise, false. + */ + private fun verifyPresignRound3Broadcast( + j : Int, + presignRound3Broadcast: PresignRound3Broadcast, + A1j: Point, + A2j: Point, + Yj: Point + ) : Boolean { + // Check ssid. + if (!private.ssid.contentEquals(presignRound3Broadcast.ssid)) { + throw PresignException("unknown ssid ${presignRound3Broadcast.ssid}") + } + + // Check identifier. + if (j == id || presignRound3Broadcast.from != j || id != presignRound3Broadcast.to ) { + throw PresignException("invalid id from ${presignRound3Broadcast.from} to ${presignRound3Broadcast.to} ") + } + + // Check elog proof + val elogPublic = ElogPublic( + L = A1j, + M = A2j, + X = Yj, + Y = presignRound3Broadcast.bigDeltaShare, + h = presignRound3Broadcast.gamma + ) + return presignRound3Broadcast.elogProof.verify(presignRound3Broadcast.from, elogPublic) + } + + /** + * Processes the presign output from all the signers. + * + * After completing the three rounds, this method combines the results to calculate the final + * commitment point `R` to be used in the signature. The commitment `R` is derived using the + * combined `Γ` and `δi` shares from each signer. + * + * @param signers List of identifiers for all signers. + * @param presignRound3Broadcasts Broadcasts from all signers in Round 3. + * @param deltaShares Map of `δi` shares from all signers. + * @param bigDeltaShares Map of `Δi` shares from all signers. + * @return The final commitment point `R`. + */ + fun processPresignOutput( + signers : List, + presignRound3Broadcasts: Map, + elGamalPublics: Map, + deltaShares: Map, + bigDeltaShares: Map, + ) : Point { + // Verify broadcasts from all peers + for (j in signers) { + if (j != id) { + if (!verifyPresignRound3Broadcast( + j, + presignRound3Broadcasts[j]!!, + elGamalPublics[j]!!.A1, elGamalPublics[j]!!.A2, elGamalPublics[j]!!.Y)) { + throw PresignException("failed to validate presign round 3 broadcast from $j to $id") + } + } + } + + // δ = ∑ⱼ δⱼ + // Δ = ∑ⱼ Δⱼ + var delta = Scalar.zero() + var bigDelta = newPoint() + for (i in signers) { + delta = delta.add(Scalar(deltaShares[i]!!.mod(secp256k1Order()))) + bigDelta = bigDelta.add(bigDeltaShares[i]!!) + } + + // Δ == [δ]G + val deltaComputed = delta.actOnBase() + if (deltaComputed != bigDelta) { + throw PresignException("computed Δ is inconsistent with [δ]G") + } + + // R = Γ^δ−1 + val deltaInv = delta.invert() + this.bigR = deltaInv.act(bigGamma!!) + + return bigR!! + } + + /** + * Generates a partial signature from the signer. + * + * This method allows the signer to produce a partial signature `σi` for a given message hash `H(m)`. + * The partial signatures from all signers will be combined to form the final signature. + * + * @param ssid Unique session identifier. + * @param hash The hash of the message being signed. + * @return A partial signature containing the signer's contribution to the final signature. + */ + fun partialSignMessage(ssid: ByteArray, hash: ByteArray): PartialSignature { + // Check ssid. + if (!private.ssid.contentEquals(ssid)) { + throw PresignException("unknown ssid $ssid") + } + + val rX = bigR!!.xScalar() + val sigmaShare = rX.multiply(chiShare!!).add(Scalar.scalarFromByteArray(hash).multiply(kShare!!)) + return PartialSignature( + ssid = ssid, + id = id, + sigmaShare = sigmaShare + ) + } +} + diff --git a/src/main/kotlin/tuple/Tuples.kt b/src/main/kotlin/tuple/Tuples.kt index eaf174c..4967c2a 100644 --- a/src/main/kotlin/tuple/Tuples.kt +++ b/src/main/kotlin/tuple/Tuples.kt @@ -20,39 +20,43 @@ data class Sextuple( ) /** - * A data class representing a septuple, which holds seven values of potentially different types. + * A data class representing a quintuple, which holds five values of potentially different types. * * @param first The first value of type A. * @param second The second value of type B. * @param third The third value of type C. * @param fourth The fourth value of type D. * @param fifth The fifth value of type E. - * @param sixth The sixth value of type F. - * @param seventh The seventh value of type G. */ -data class Septuple( +data class Quintuple( val first: A, val second: B, val third: C, val fourth: D, - val fifth: E, - val sixth: F, - val seventh: G + val fifth: E ) /** - * A data class representing a quintuple, which holds five values of potentially different types. + * A data class representing a nonuple, which holds nine values of potentially different types. * * @param first The first value of type A. * @param second The second value of type B. * @param third The third value of type C. * @param fourth The fourth value of type D. * @param fifth The fifth value of type E. + * @param sixth The sixth value of type F. + * @param seventh The seventh value of type G. + * @param eighth The eighth value of type H. + * @param ninth The ninth value of type I. */ -data class Quintuple( +data class Nonuple( val first: A, val second: B, val third: C, val fourth: D, - val fifth: E + val fifth: E, + val sixth: F, + val seventh: G, + val eighth: H, + val ninth: I ) \ No newline at end of file diff --git a/src/main/kotlin/zero_knowledge/affg/Affg.kt b/src/main/kotlin/zero_knowledge/Affg.kt similarity index 94% rename from src/main/kotlin/zero_knowledge/affg/Affg.kt rename to src/main/kotlin/zero_knowledge/Affg.kt index 032d358..5cfc981 100644 --- a/src/main/kotlin/zero_knowledge/affg/Affg.kt +++ b/src/main/kotlin/zero_knowledge/Affg.kt @@ -1,4 +1,4 @@ -package perun_network.ecdsa_threshold.zkproof.affg +package perun_network.ecdsa_threshold.zero_knowledge import com.ionspin.kotlin.bignum.integer.Quadruple import perun_network.ecdsa_threshold.ecdsa.Point @@ -13,7 +13,7 @@ import perun_network.ecdsa_threshold.tuple.Quintuple import java.math.BigInteger /** - * Represents the public parameters for the Aff-g zero-knowledge proof. + * Represents the public parameters for the Aff-g (Paillier Affine Operation with Group Commitment in Range ZK) zero-knowledge proof. * * @property C The ciphertext related to a certain commitment. * @property D Another ciphertext used in the proof. @@ -34,7 +34,7 @@ data class AffgPublic ( ) /** - * Represents the private parameters for the Aff-g zero-knowledge proof. + * Represents the private parameters for the Aff-g (Paillier Affine Operation with Group Commitment in Range ZK) zero-knowledge proof. * * @property x The private value used in the proof. * @property y Another private value used in the proof. @@ -70,7 +70,7 @@ data class AffgCommitment( ) /** - * Represents the proof in the Aff-g zero-knowledge protocol. + * Represents the proof in the Aff-g (Paillier Affine Operation with Group Commitment in Range ZK) zero-knowledge protocol. * * @property commitment The commitment associated with this proof. * @property z1 The value z1 calculated from α and e·x. @@ -95,7 +95,7 @@ class AffgProof( * @param public The public parameters against which to validate the proof. * @return True if the proof is valid, false otherwise. */ - fun isValid(public: AffgPublic): Boolean { + private fun isValid(public: AffgPublic): Boolean { if (!public.n1.validateCiphertexts(commitment.A)) return false if (!public.n0.validateCiphertexts(commitment.By)) return false if (!isValidModN(public.n1.n, wY)) return false @@ -151,7 +151,6 @@ class AffgProof( return false } - val lhsEnc = n1.encryptWithNonce(z2, wY) val rhsEnc = (public.Y.modPowNSquared(n1, e)).modMulNSquared(n1, commitment.By) @@ -171,7 +170,7 @@ class AffgProof( * @param commitment The commitment associated with the proof. * @return The generated challenge value. */ - fun challenge(id: Int, public: AffgPublic, commitment: AffgCommitment): BigInteger { + private fun challenge(id: Int, public: AffgPublic, commitment: AffgCommitment): BigInteger { // Collect relevant parts to form the challenge val inputs = listOf( public.aux.n, @@ -214,8 +213,8 @@ class AffgProof( val beta = sampleLPrimeEps() // β ← ±2^(l'+ε) // r ← Z∗N0 , ry ← Z∗N1 - val r = sampleUnitModN(n0) - val ry = sampleUnitModN(n1) + val r = sampleModNStar(n0) + val ry = sampleModNStar(n1) // γ ← ±2^l+ε· N, m ˆ ← ±2^l· N val gamma = sampleLEpsN() @@ -268,7 +267,7 @@ class AffgProof( * @param receiver The receiver's Paillier public key. * @return A quintuple containing computed values necessary for the proof. */ -fun computeZKMaterials( +private fun computeZKMaterials( senderSecretShare: BigInteger, receiverEncryptedShare: PaillierCipherText, sender: PaillierSecret, @@ -332,7 +331,7 @@ fun produceAffGMaterials( x = senderSecretShare, y = y, rho = rho, - rhoY= rhoY + rhoY = rhoY ) ) val beta = y.negate() diff --git a/src/main/kotlin/zero_knowledge/Elog.kt b/src/main/kotlin/zero_knowledge/Elog.kt new file mode 100644 index 0000000..c43c7d5 --- /dev/null +++ b/src/main/kotlin/zero_knowledge/Elog.kt @@ -0,0 +1,166 @@ +package perun_network.ecdsa_threshold.zero_knowledge + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.secp256k1Order +import perun_network.ecdsa_threshold.math.* +import java.math.BigInteger + +/** + * Public parameters for the Dlog with El-Gamal Commitment (elog) zero-knowledge proof. + * + * @property L Elliptic curve point representing G^λ. + * @property M Elliptic curve point representing G^y * X^λ. + * @property X ElGamal public key. + * @property Y Elliptic curve point representing h^y. + * @property h Base point for ElGamal encryption. + */ +data class ElogPublic ( + val L: Point, // L = G^lambda + val M: Point, // M = G^y*X^lambda + val X : Point, // El-Gamal Public + val Y: Point, // Y = h^y + val h: Point, // base for El-Gamal + +) + +/** + * Private parameters for the Dlog with El-Gamal Commitment (elog) zero-knowledge proof. + * + * @property y Scalar representing a secret value used in the proof. + * @property lambda Scalar representing a secret exponent. + */ +data class ElogPrivate( + val y: Scalar, + val lambda : Scalar +) + +/** + * Commitment values generated during the zero-knowledge proof. + * + * @property A Elliptic curve point representing G^α. + * @property N Elliptic curve point representing G^m + X^α. + * @property B Elliptic curve point representing h^m. + */ +data class ElogCommitment( + val A : Point, // A = G^α + val N : Point, // N = G^m+X^α + val B : Point // B = h^m +) + +/** + * The zero-knowledge proof for the Dlog with El-Gamal Commitment (elog). + * + * @property commitment The commitments made during the proof. + * @property z Scalar representing α + eλ (mod q). + * @property u Scalar representing m + ey (mod q). + */ +data class ElogProof ( + val commitment : ElogCommitment, + val z: Scalar, // z = α+eλ (mod q) + val u: Scalar // u = m+ey (mod q) +) { + /** + * Validates the proof. + * + * @return True if the proof is valid, false otherwise. + */ + private fun isValid(): Boolean { + if (commitment.A.isIdentity() || commitment.N.isIdentity() || commitment.B.isIdentity()) { + return false + } + + return (!(z.isZero() || u.isZero())) + } + + /** + * Verifies the proof's integrity and correctness against public parameters. + * + * @param id The identifier for the session or proof. + * @param public The public parameters used for verification. + * @return True if the proof is verified, false otherwise. + */ + fun verify(id: Int, public: ElogPublic): Boolean { + if (!isValid()) return false + + val e = challenge(id, public, commitment) + + // G^z == A · L^e + if (z.actOnBase() != commitment.A.add(Scalar.scalarFromBigInteger(e).act(public.L))) { + return false + } + + // G^u * X^z == N*M^e + if (u.actOnBase().add(z.act(public.X)) != Scalar.scalarFromBigInteger(e).act(public.M).add(commitment.N)) { + return false + } + + // h^u == B*Y^e + if (u.act(public.h) != commitment.B.add(Scalar.scalarFromBigInteger(e).act(public.Y))) { + return false + } + return true + } + + companion object { + /** + * Generates a challenge based on public parameters and the commitment. + * + * @param id The identifier for the session or proof. + * @param public The public parameters. + * @param commitment The commitment associated with the proof. + * @return The generated challenge value. + */ + private fun challenge(id: Int, public: ElogPublic, commitment: ElogCommitment): BigInteger { + // Collect relevant parts to form the challenge + val inputs = listOf( + public.X.x, + public.X.y, + public.L.x, + public.L.y, + public.M.x, + public.M.y, + public.Y.x, + public.Y.y, + public.h.x, + public.h.y, + commitment.A.x, + commitment.A.y, + commitment.N.x, + commitment.N.y, + commitment.B.x, + commitment.B.y, + BigInteger.valueOf(id.toLong()) + ) + return inputs.fold(BigInteger.ZERO) { acc, value -> acc.add(value).mod(secp256k1Order()) }.mod( + secp256k1Order() + ) + } + + /** + * Creates a new proof based on public and private parameters. + * + * @param id The identifier for the session or proof. + * @param public The public parameters for the proof. + * @param private The private parameters for the proof. + * @return The newly created proof. + */ + fun newProof(id: Int, public: ElogPublic, private: ElogPrivate): ElogProof { + + val alpha = sampleScalar() + val m = sampleScalar() + + val A = alpha.actOnBase() // A = G^α + val N = m.actOnBase().add(alpha.act(public.X)) // N = G^m+X^α + val B = m.act(public.h) // B = h^m + + val commitment = ElogCommitment(A, N, B) + + val e = challenge(id, public, commitment) + + val z = (private.lambda.multiply(Scalar.scalarFromBigInteger(e))).add(alpha) // z = α+eλ (mod q) + val u = m.add(Scalar.scalarFromBigInteger(e).multiply(private.y)) // u = m+ey (mod q) + return ElogProof(commitment, z, u) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/zero_knowledge/enc/Enc.kt b/src/main/kotlin/zero_knowledge/Enc.kt similarity index 86% rename from src/main/kotlin/zero_knowledge/enc/Enc.kt rename to src/main/kotlin/zero_knowledge/Enc.kt index b9f9635..e2ef320 100644 --- a/src/main/kotlin/zero_knowledge/enc/Enc.kt +++ b/src/main/kotlin/zero_knowledge/Enc.kt @@ -1,4 +1,4 @@ -package perun_network.ecdsa_threshold.zkproof.enc +package perun_network.ecdsa_threshold.zero_knowledge import perun_network.ecdsa_threshold.ecdsa.secp256k1Order import perun_network.ecdsa_threshold.math.* @@ -8,9 +8,11 @@ import perun_network.ecdsa_threshold.pedersen.PedersenParameters import java.math.BigInteger /** - * Represents the public parameters for the encryption zero-knowledge proof. + * Represents the public parameters for the Π_enc (Paillier Encryption in Range ZK) zero-knowledge proof. + * The following relation verifies that the plaintext value of Paillier + * ciphertext C is in a desired range I = ±2^l * - * @property K The ciphertext related to the public key. + * @property K The public ciphertext related to the public key. * @property n0 The Paillier public key used for encryption. * @property aux The Pedersen parameters used for commitment. */ @@ -21,7 +23,7 @@ data class EncPublic( ) /** - * Represents the private parameters for the encryption zero-knowledge proof. + * Represents the private parameters for the Π_enc (Paillier Encryption in Range ZK) zero-knowledge proof. * * @property k The private key component, calculated as k ∈ 2ˡ = Dec₀(K). * @property rho The random value ρ used in the proof. @@ -32,7 +34,7 @@ data class EncPrivate( ) /** - * Represents the commitment values used in the encryption zero-knowledge proof. + * Represents the commitment values used in the Π_enc (Paillier Encryption in Range ZK) zero-knowledge proof. * * @property S The value calculated as S = sᵏtᵘ. * @property A The ciphertext calculated from the first private parameter. @@ -45,7 +47,7 @@ data class EncCommitment( ) /** - * Represents the proof in the encryption zero-knowledge protocol. + * Represents the proof in the Π_enc (Paillier Encryption in Range ZK) protocol. * * @property commitment The commitment associated with this proof. * @property z1 The value calculated as z₁ = α + e⋅k. @@ -64,7 +66,7 @@ data class EncProof( * @param public The public parameters against which to validate the proof. * @return True if the proof is valid, false otherwise. */ - fun isValid(public: EncPublic): Boolean { + private fun isValid(public: EncPublic): Boolean { return public.n0.validateCiphertexts(commitment.A) && isValidModN(public.n0.n, z2) } @@ -82,7 +84,7 @@ data class EncProof( val n = public.n0.n val alpha = sampleLEps() - val r = sampleUnitModN(n) + val r = sampleModNStar(n) val mu = sampleLN() val gamma = sampleLEpsN() diff --git a/src/main/kotlin/zero_knowledge/EncElg.kt b/src/main/kotlin/zero_knowledge/EncElg.kt new file mode 100644 index 0000000..26abffa --- /dev/null +++ b/src/main/kotlin/zero_knowledge/EncElg.kt @@ -0,0 +1,203 @@ +package perun_network.ecdsa_threshold.zero_knowledge + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.secp256k1Order +import perun_network.ecdsa_threshold.math.* +import perun_network.ecdsa_threshold.paillier.PaillierCipherText +import perun_network.ecdsa_threshold.paillier.PaillierPublic +import perun_network.ecdsa_threshold.pedersen.PedersenParameters +import java.math.BigInteger + +/** + * Public parameters for the Range Proof w/ EL-Gamal Commitment (Enc-Elg) zero-knowledge proof. + * + * @property C Paillier ciphertext representing an encrypted message. + * @property A Elliptic curve point representing g^a. + * @property B Elliptic curve point representing g^b. + * @property X Elliptic curve point representing g^(a*b + x). + * @property N0 Paillier public key for the prover. + * @property aux Pedersen parameters for commitment schemes. + */ +data class EncElgPublic ( + val C: PaillierCipherText, + val A: Point, // g^a + val B: Point, // g^b + val X: Point, // g^(a*b+x) + + val N0: PaillierPublic, + val aux: PedersenParameters +) + +/** + * Private parameters for the Range Proof w/ EL-Gamal Commitment (Enc-Elg) zero-knowledge proof. + * + * @property x Private scalar value, representing the decrypted value of C. + * @property rho Random nonce associated with the encryption of C. + * @property a Scalar value a used in the ElGamal encryption. + * @property b Scalar value b used in the ElGamal encryption. + */ +data class EncElgPrivate ( + val x : BigInteger, // x= dec(C) + val rho: BigInteger, // rho = Nonce(C) + val a: Scalar, + val b: Scalar, +) + +/** + * Commitment values generated during the zero-knowledge proof. + * + * @property S Commitment based on x and a random value. + * @property D Paillier ciphertext representing the encryption of alpha. + * @property Y Elliptic curve point representing A^β * G^α. + * @property Z Elliptic curve point representing G^β. + * @property T Pedersen commitment based on alpha and a random value. + */ +data class EncElgCommitment( + val S : BigInteger, // S = sˣtᵘ + val D : PaillierCipherText, // D = Enc(α, r) + val Y: Point, // Y = A^β*G^α + val Z: Point, // Z = G^β + val T: BigInteger // C = sᵃtᵍ +) + +/** + * The zero-knowledge proof for the Range Proof w/ EL-Gamal Commitment (Enc-Elg). + * + * @property commitment The commitments made during the proof. + * @property z1 Scalar representing α + ex. + * @property w Scalar representing β + eb (mod q). + * @property z2 Scalar representing r⋅ρᵉ (mod N₀). + * @property z3 Scalar representing γ + eμ. + */ +data class EncElgProof ( + val commitment: EncElgCommitment, + val z1: BigInteger, // z₁ = α + ex + val w: Scalar, // w = β + eb (mod q) + val z2: BigInteger, // z₂ = r⋅ρᵉ (mod N₀) + val z3: BigInteger, // z₃ = γ + eμ +) { + /** + * Validates the proof against the provided public parameters. + * + * @param public The public parameters against which to validate the proof. + * @return True if the proof is valid, false otherwise. + */ + private fun isValid(public: EncElgPublic): Boolean { + if (!public.N0.validateCiphertexts(commitment.D)) { + return false + } + + if (w.isZero() || commitment.Y.isIdentity() || commitment.Z.isIdentity()) { + return false + } + return isValidModN(public.N0.n, z2) + } + + /** + * Verifies the proof's integrity and correctness against public parameters. + * + * @param id The identifier for the session or proof. + * @param public The public parameters used for verification. + * @return True if the proof is verified, false otherwise. + */ + fun verify(id: Int, public: EncElgPublic): Boolean { + if (!isValid(public)) return false + + val prover = public.N0 + + if (!isInIntervalLEps(z1)) return false + + val e = challenge(id, public, commitment) + + // Enc(z₁;z₂) == D * C^e mod N0² + if (prover.encryptWithNonce(z1, z2) != (public.C.modPowNSquared(prover, e)).modMulNSquared(prover, commitment.D)) { + return false + } + + // A^w * G^z1 == Y*X^e + if ( + Scalar.scalarFromBigInteger(z1).actOnBase().add(w.act(public.A)) != + Scalar.scalarFromBigInteger(e).act(public.X).add(commitment.Y)) { + return false + } + + // G^w == Z*B^e + if (w.actOnBase() != commitment.Z.add(Scalar.scalarFromBigInteger(e).act(public.B))) { + return false + } + + // s^z1 * t^z3 = T · S^e + if (!public.aux.verifyCommit(z1, z3, e, commitment.T, commitment.S)) return false + return true + } + + companion object { + /** + * Creates a new proof based on public and private parameters. + * + * @param id The identifier for the session or proof. + * @param public The public parameters for the proof. + * @param private The private parameters for the proof. + * @return The newly created proof. + */ + fun newProof(id: Int, public: EncElgPublic, private: EncElgPrivate): EncElgProof { + val n = public.N0.n + + val alpha = sampleLEps() + val r = sampleModNStar(n) + val mu = sampleLN() + val beta = sampleScalar() + val gamma = sampleLEpsN() + + val S = public.aux.calculateCommit(private.x, mu) + val D = public.N0.encryptWithNonce(alpha, r) + val Y = beta.act(public.A).add(Scalar.scalarFromBigInteger(alpha).actOnBase()) + val Z = beta.actOnBase() + val T = public.aux.calculateCommit(alpha, gamma) + + val commitment = EncElgCommitment(S, D, Y, Z, T) + + val e = challenge(id, public, commitment) + + val z1 = (private.x.multiply(e)).add(alpha) + val w = beta.add(Scalar.scalarFromBigInteger(e).multiply(private.b)) + val z2 = (private.rho.modPow(e, n)).multiply(r).mod(n) + val z3 = (e.multiply(mu)).add(gamma) + return EncElgProof(commitment, z1, w, z2, z3) + } + + /** + * Generates a challenge based on public parameters and the commitment. + * + * @param id The identifier for the session or proof. + * @param public The public parameters. + * @param commitment The commitment associated with the proof. + * @return The generated challenge value. + */ + private fun challenge(id: Int, public: EncElgPublic, commitment: EncElgCommitment): BigInteger { + // Collect relevant parts to form the challenge + val inputs = listOf( + public.N0.n, + public.C.c, + public.B.x, + public.B.y, + public.A.x, + public.A.y, + public.X.x, + public.X.y, + commitment.S, + commitment.D.value(), + commitment.Y.x, + commitment.Y.y, + commitment.Z.x, + commitment.Z.y, + commitment.T, + BigInteger.valueOf(id.toLong()) + ) + return inputs.fold(BigInteger.ZERO) { acc, value -> acc.add(value).mod(secp256k1Order()) }.mod( + secp256k1Order() + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/zero_knowledge/Fac.kt b/src/main/kotlin/zero_knowledge/Fac.kt new file mode 100644 index 0000000..720298b --- /dev/null +++ b/src/main/kotlin/zero_knowledge/Fac.kt @@ -0,0 +1,180 @@ +package perun_network.ecdsa_threshold.zero_knowledge + +import perun_network.ecdsa_threshold.math.* +import perun_network.ecdsa_threshold.pedersen.PedersenParameters +import java.math.BigInteger +import java.security.MessageDigest + +/** + * Public parameters for the Small-Factor Proof (Πfac) zero-knowledge proof. + * + * @property n RSA Modulus of the prover. + * @property aux Pedersen parameters for commitment schemes. + */ +data class FacPublic( + val n: BigInteger, + val aux: PedersenParameters +) + +/** + * Private parameters for the Range Proof w/ EL-Gamal Commitment (Enc-Elg) zero-knowledge proof. + * + * @property p Prime factor of Paillier's secret. + * @property q Prime factor of Paillier's secret. + */ +data class FacPrivate( + val p: BigInteger, + val q: BigInteger +) + +/** + * Commitment values generated by the prover during the proof process. + * + * @property P Commitment to `p` using Pedersen commitments: `P = s^p * t^μ mod N̂`. + * @property Q Commitment to `q` using Pedersen commitments: `Q = s^q * t^ν mod N̂`. + * @property A Commitment to the randomly sampled `α`: `A = s^α * t^x mod N̂`. + * @property B Commitment to the randomly sampled `β`: `B = s^β * t^y mod N̂`. + * @property T Commitment based on `Q^α * t^r mod N̂`. + */ +data class FacCommitment( + val P: BigInteger, + val Q: BigInteger, + val A: BigInteger, + val B: BigInteger, + val T: BigInteger +) + +/** + * The proof structure for Small-Factor Proof (Πfac). + * + * @property comm Commitments generated by the prover. + * @property sigma Intermediate value derived during the proof process. + * @property z1 Response for `α + ep`, where `e` is the challenge. + * @property z2 Response for `β + eq`, where `e` is the challenge. + * @property w1 Response for `x + eμ`, where `e` is the challenge. + * @property w2 Response for `y + eν`, where `e` is the challenge. + * @property v Response for `r - eνp`, where `e` is the challenge. + */ +data class FacProof( + val comm: FacCommitment, + val sigma: BigInteger, + val z1: BigInteger, + val z2: BigInteger, + val w1: BigInteger, + val w2: BigInteger, + val v: BigInteger +) { + /** + * Verifies the validity of the proof against the public parameters and commitments. + * + * @param id Identifier for the proof session. + * @param rid Random identifier for uniqueness in the challenge generation. + * @param public The public parameters used for verification. + * @return `true` if the proof is valid, otherwise `false`. + */ + internal fun verify(id: Int, rid: ByteArray, public : FacPublic) : Boolean { + val e = challenge(id, rid, public, comm) + + val n0 = public.n + val n = public.aux.n + + if (!public.aux.verifyCommit(z1, w1, e, comm.A, comm.P)) return false + if (!public.aux.verifyCommit(z2, w2, e, comm.B, comm.Q)) return false + + val r = public.aux.s + .modPow(n0, n) + .multiply(public.aux.t.modPow(sigma, n)) + .mod(n) + + val lhs = comm.Q.modPow(z1, n) + .multiply(public.aux.t.modPow(v, n)) + .mod(n) + + val rhs = r.modPow(e, n) + .multiply(comm.T) + .mod(n) + + if (lhs != rhs) return false + + // Ensure z1 and z2 are within valid intervals + return isInIntervalLEpsPlus1RootN(z1) && isInIntervalLEpsPlus1RootN(z2) + } + + companion object { + /** + * Constructs a new proof for the Small-Factor Proof (Πfac). + * + * @param id Identifier for the proof session. + * @param rid Random identifier for uniqueness in the challenge generation. + * @param public The public parameters for the proof. + * @param private The private parameters (witnesses) of the proof. + * @return The constructed proof. + */ + internal fun newProof(id: Int, rid: ByteArray, public: FacPublic, private: FacPrivate): FacProof { + val n = public.aux.n + + // Random values for proof generation + val alpha = sampleLEpsRootN() + val beta = sampleLEpsRootN() + val mu = sampleLN() + val nu = sampleLN() + val sigma = sampleLN2() + val r = sampleLEpsN2() + val x = sampleLEpsN() + val y = sampleLEpsN() + + val pInt = private.p + val qInt = private.q + + val pCommit = public.aux.calculateCommit(pInt, mu) + val qCommit = public.aux.calculateCommit(qInt, nu) + val aCommit = public.aux.calculateCommit(alpha, x) + val bCommit = public.aux.calculateCommit(beta, y) + + val tCommit = qCommit.modPow(alpha, n) + .multiply(public.aux.t.modPow(r, n)) + .mod(n) + + val comm = FacCommitment(pCommit, qCommit, aCommit, bCommit, tCommit) + + // Generate challenge + val e = challenge(id, rid, public, comm) + + // Compute z1, z2, w1, w2, and v + val z1 = e.multiply(pInt).add(alpha) + val z2 = e.multiply(qInt).add(beta) + val w1 = e.multiply(mu).add(x) + val w2 = e.multiply(nu).add(y) + + val sigmaHat = nu.multiply(pInt).negate().add(sigma) + val v = e.multiply(sigmaHat).add(r) + + return FacProof(comm, sigma, z1, z2, w1, w2, v) + } + } +} + +/** + * Generates a cryptographic challenge `e` from the input parameters and commitments. + * + * @param id Identifier for the proof session. + * @param rid Random identifier for uniqueness. + * @param publicKey The public parameters of the proof. + * @param commitment The commitments provided by the prover. + * @return The challenge value `e`. + */ +fun challenge(id: Int, rid: ByteArray, publicKey: FacPublic, commitment: FacCommitment): BigInteger { + // Initialize a MessageDigest for SHA-256 + val hash = MessageDigest.getInstance("SHA-256") + hash.update(id.toByte()) + hash.update(rid) + hash.update(publicKey.n.toByteArray()) + hash.update(publicKey.aux.toByteArray()) + hash.update(commitment.P.toByteArray()) + hash.update(commitment.Q.toByteArray()) + hash.update(commitment.A.toByteArray()) + hash.update(commitment.B.toByteArray()) + hash.update(commitment.T.toByteArray()) + + return sampleNeg(hash.digest().inputStream(), L) +} \ No newline at end of file diff --git a/src/main/kotlin/zero_knowledge/logstar/LogStar.kt b/src/main/kotlin/zero_knowledge/LogStar.kt similarity index 95% rename from src/main/kotlin/zero_knowledge/logstar/LogStar.kt rename to src/main/kotlin/zero_knowledge/LogStar.kt index 468d689..42687bd 100644 --- a/src/main/kotlin/zero_knowledge/logstar/LogStar.kt +++ b/src/main/kotlin/zero_knowledge/LogStar.kt @@ -1,4 +1,4 @@ -package perun_network.ecdsa_threshold.zkproof.logstar +package perun_network.ecdsa_threshold.zero_knowledge import perun_network.ecdsa_threshold.ecdsa.* import perun_network.ecdsa_threshold.math.* @@ -70,7 +70,7 @@ class LogStarProof( * @param public The public parameters against which to validate the proof. * @return True if the proof is valid, false otherwise. */ - fun isValid(public: LogStarPublic): Boolean { + private fun isValid(public: LogStarPublic): Boolean { if (!public.n0.validateCiphertexts(commitment.A)) return false if (commitment.Y.isIdentity()) return false if (!isValidModN(public.n0.n, z2)) return false @@ -90,7 +90,7 @@ class LogStarProof( val n = public.n0.n val alpha = sampleLEps() - val r = sampleUnitModN(n) + val r = sampleModNStar(n) val mu = sampleLN() val gamma = sampleLEpsN() @@ -120,7 +120,7 @@ class LogStarProof( * @param commitment The commitment associated with the proof. * @return The generated challenge value. */ - fun challenge(id: Int, public: LogStarPublic, commitment: LogStarCommitment): BigInteger { + private fun challenge(id: Int, public: LogStarPublic, commitment: LogStarCommitment): BigInteger { // Collect relevant parts to form the challenge val inputs = listOf( public.aux.n, diff --git a/src/main/kotlin/zero_knowledge/Mod.kt b/src/main/kotlin/zero_knowledge/Mod.kt new file mode 100644 index 0000000..18a04d7 --- /dev/null +++ b/src/main/kotlin/zero_knowledge/Mod.kt @@ -0,0 +1,220 @@ +package perun_network.ecdsa_threshold.zero_knowledge + +import perun_network.ecdsa_threshold.math.jacobi +import perun_network.ecdsa_threshold.math.sampleQuadraticNonResidue +import java.math.BigInteger +import java.security.MessageDigest + +/** + * Public parameters of the Paillier-Blum Modulus Proof (Πmod). + * + * @property n The modulus N = pq + */ +data class ModPublic ( + val n : BigInteger, +) + +/** + * Data structure representing the private input of the prover for the Paillier-Blum Modulus Proof (Πmod).. + * @property p Prime factor of N + * @property q Prime factor of N + * @property phi Euler's totient φ(N) = (p-1)(q-1) + */ +data class ModPrivate( + val p: BigInteger, + val q: BigInteger, + val phi: BigInteger // φ(N) = (p-1)(q-1) +) + + +/** + * Data structure representing a single proof commitment. + * @property a Boolean flag indicating multiplication by -1 + * @property b Boolean flag indicating multiplication by w + * @property x Quartic root of the transformed challenge value y' + * @property z N-th root of the challenge value y + */ +data class ModCommitment( + val a: Boolean, + val b: Boolean, + val x: BigInteger, + val z: BigInteger +) { + /** + * Verifies the correctness of this commitment. + * @param n Modulus N + * @param w Random quadratic non-residue with Jacobi symbol -1 + * @param y Challenge value + * @return true if the commitment satisfies the verification conditions + */ + internal fun verify(n: BigInteger, w: BigInteger, y: BigInteger) : Boolean { + val lhsZ = z.modPow(n, n) // lhs = zⁿ mod n + if (lhsZ != y) { + return false + } + + val lhsX = x.modPow(BigInteger.valueOf(4), n) // lhs = x⁴ mod n + + // rhs = (-1)ᵃ * wᵇ * y mod n + var rhs = y + if (a) rhs = rhs.negate().mod(n) + if (b) rhs = rhs.multiply(w).mod(n) + + val result = lhsX == rhs + return result + } +} + + +/** + * Data structure representing a complete ZK proof. + * @property w Random quadratic non-residue with Jacobi symbol -1 + * @property responses List of commitments for each challenge + */ +data class ModProof( + val w: BigInteger, + val responses: List +) { + /** + * Verifies the ZK proof. + * @param id Unique identifier for the proof + * @param rid Random identifier for the proof + * @param public Public parameters containing the modulus N + * @return true if all commitments are valid + */ + internal fun verify(id: Int, rid:ByteArray, public: ModPublic) : Boolean { + val n = public.n + + // Check if (w/n) = -1 + if (jacobi(w, n) != -1) { + return false + } + + // Generate challenges [yᵢ] + val ys = challenge(id, rid, n, w) + + // Verify each response in parallel + return responses.zip(ys).all { (response, y) -> response.verify(n, w, y) } + } + + companion object { + /** + * Constructs a new ZK proof. + * @param id Unique identifier for the proof + * @param rid Random identifier for the proof + * @param public Public parameters containing the modulus N + * @param private Prover's private input (p, q, φ(N)) + * @return Constructed ModProof instance + */ + internal fun newProof(id: Int, rid: ByteArray, public: ModPublic, private: ModPrivate): ModProof { + val n = public.n + val p = private.p + val q = private.q + val phi = private.phi + + val pHalf = (p - BigInteger.ONE) / BigInteger.TWO + val qHalf = (q - BigInteger.ONE) / BigInteger.TWO + + val w = sampleQuadraticNonResidue(public.n) + + val nInverse = n.modInverse(phi) // N⁻¹ mod φ(N) + val e = fourthRootExponent(phi) // Fourth root exponent + + // Generate challenges [yᵢ] + val ys = challenge(id, rid, n, w) + + val commitments = mutableListOf() + ys.forEach { y -> + // Compute Z = yⁿ⁻¹ mod n + val z = y.modPow(nInverse, n) + + // Make y' a quadratic residue + val (a, b, yPrime) = makeQuadraticResidue(y, w, pHalf, qHalf, n, p, q) + + // Compute X = y'^¼ + val x = yPrime.modPow(e, n) + + // Add response + commitments.add(ModCommitment(a, b, x, z)) + } + + return ModProof(w, commitments) + } + } +} + +/** + * Computes whether y is a quadratic residue modulo p and q. + */ +private fun isQRModPQ(y: BigInteger, pHalf: BigInteger, qHalf: BigInteger, p: BigInteger, q: BigInteger): Boolean { + val one = BigInteger.ONE + + val pCheck = y.modPow(pHalf, p) == one + val qCheck = y.modPow(qHalf, q) == one + + return pCheck && qCheck +} + +/** + * Computes the fourth root exponent (φ(N) + 4) / 8 squared. + */ +internal fun fourthRootExponent(phi: BigInteger): BigInteger { + val four = BigInteger.valueOf(4) + val ePrime = phi.add(four).shiftRight(3) // e' = (φ + 4) / 8 + return ePrime.pow(2) // e = (e')² +} + +/** + * Converts y into a quadratic residue if necessary. + */ +internal fun makeQuadraticResidue( + y: BigInteger, w: BigInteger, pHalf: BigInteger, qHalf: BigInteger, n: BigInteger, p: BigInteger, q: BigInteger +): Triple { + var out = y.mod(n) + var a = false + var b = false + + if (isQRModPQ(out, pHalf, qHalf, p, q)) return Triple(a, b, out) + + // Multiply by -1 + out = out.negate().mod(n) + a = true + if (isQRModPQ(out, pHalf, qHalf, p, q)) return Triple(a, b, out) + + // Multiply by w + out = out.multiply(w).mod(n) + b = true + if (isQRModPQ(out, pHalf, qHalf, p, q)) return Triple(a, b, out) + + // Multiply by -1 again + out = out.negate().mod(n) + a = false + return Triple(a, b, out) +} + +/** + * Generates challenge values [yᵢ]. + * @param id Proof identifier + * @param rid Random identifier + * @param n Modulus N + * @param w Random quadratic non-residue with Jacobi symbol -1 + * @return List of challenge values + */ +private fun challenge(id: Int, rid: ByteArray, n: BigInteger, w: BigInteger): List { + // Initialize a MessageDigest for SHA-256 + val digest = MessageDigest.getInstance("SHA-256") + + digest.update(id.toByte()) + digest.update(rid) + digest.update(n.toByteArray()) + digest.update(w.toByteArray()) + + val tmpBytes = digest.digest() + + return List(PROOF_NUM) { + // Update the digest for each iteration + digest.update(tmpBytes) + val newDigest = digest.digest() + BigInteger(1, newDigest) + } +} \ No newline at end of file diff --git a/src/main/kotlin/zero_knowledge/Prm.kt b/src/main/kotlin/zero_knowledge/Prm.kt new file mode 100644 index 0000000..915ea18 --- /dev/null +++ b/src/main/kotlin/zero_knowledge/Prm.kt @@ -0,0 +1,182 @@ +package perun_network.ecdsa_threshold.zero_knowledge + +import perun_network.ecdsa_threshold.math.isValidModN +import perun_network.ecdsa_threshold.math.sampleModN +import perun_network.ecdsa_threshold.math.sampleModNStar +import perun_network.ecdsa_threshold.pedersen.PedersenParameters +import java.math.BigInteger +import java.security.MessageDigest + +/** + * [PROOF_NUM] is the standard numbers of challenges and proofs. + */ +const val PROOF_NUM = 80 + +/** + * Represents the public parameters of the Pedersen Parameters ZK proof (Πprm). + * + * @property aux The Pedersen parameters including modulus `n`, base `s`, and generator `t`. + */ +data class PrmPublic ( + val aux : PedersenParameters, +) + +/** + * Represents the private parameters of the Pedersen Parameters ZK proof (Πprm). + * + * @property phi Euler's totient function value φ(N), used for sampling random elements in Zφ(N). + * @property lambda The secret exponent λ such that s = t^λ mod N. + */ +data class PrmPrivate ( + val phi: BigInteger, + val lambda : BigInteger +) + +/** + * Represents the zero-knowledge proof for Pedersen parameters (Πprm). + * + * @property As List of commitments `A_i = t^a_i mod N` sent to the verifier. + * @property Zs List of responses `z_i = a_i + e_i * λ mod φ(N)` from the prover. + */ +data class PrmProof ( + val As : List, + val Zs: List +) { + /** + * Verifies the proof against the public parameters. + * + * @param id The unique identifier for the proof session. + * @param public The public parameters used in the protocol. + * @return `true` if the proof is valid, otherwise `false`. + * @throws Exception If the challenge generation encounters an error. + */ + fun verify(id: Int, public: PrmPublic) : Boolean { + val (eList, exc) = challenge(id, public, As) + if (exc != null) throw exc + + val n = public.aux.n + val s = public.aux.s + val t = public.aux.t + + val one = BigInteger.ONE + + for (i in 0..PROOF_NUM-1) { + var rhs: BigInteger + val z = Zs[i] + val a = As[i] + + // Check if `a` and `z` are valid under modulus `n` + if (!isValidModN(n, a, z)) { + return false + } + + // Check if `a` is not equal to 1 + if (a == one) { + return false + } + + val lhs = t.modPow(z, n) + + // Conditional multiplication + if (eList[i]) { + rhs = (a.multiply(s)).mod(n) + } else { + rhs = a + } + + // Compare lhs and rhs + if (lhs != rhs) { + return false + } + } + + return true + } + + companion object { + /** + * Generates a new zero-knowledge proof for the given public and private parameters. + * + * @param id The unique identifier for the proof session. + * @param public The public parameters used in the protocol. + * @param private The private parameters (secret inputs) of the prover. + * @return A valid instance of [PrmProof]. + * @throws Exception If the challenge generation encounters an error. + */ + internal fun newProof(id: Int, public: PrmPublic, private: PrmPrivate) : PrmProof { + val aList = mutableListOf() + val AList = mutableListOf() + val ZList = mutableListOf() + + for (i in 0..PROOF_NUM-1) { + val a = sampleModN(private.phi) + val A = public.aux.t.modPow(a, public.aux.n) + + aList.add(a) + AList.add(A) + } + + val (eList, exc) = challenge(id, public, AList) + if (exc != null) { + throw exc + } + + for (i in 0..PROOF_NUM-1) { + var z = aList[i] + + if (eList[i]) { + z = (z.add(private.lambda)).mod(private.phi) + } + + ZList.add(z) + } + + return PrmProof(AList, ZList) + } + } +} + +/** + * Generates the challenge values (e_i) for the proof, based on a hash function. + * + * @param id The unique identifier for the proof session. + * @param public The public parameters used in the protocol. + * @param A List of commitments sent by the prover. + * @return A pair consisting of the list of challenge bits and an optional exception. + */ +private fun challenge( + id: Int, + public: PrmPublic, + A: List +): Pair, Exception?> { + return try { + // Initialize the MessageDigest for SHA-256 + val digest = MessageDigest.getInstance("SHA-256") + + digest.update(id.toByte()) + + // Update the digest with the public auxiliary components + digest.update(public.aux.n.toByteArray()) + digest.update(public.aux.s.toByteArray()) + digest.update(public.aux.t.toByteArray()) + + // Update the digest with each element in A + A.forEach { element -> + digest.update(element.toByteArray()) + } + + // Generate the hash digest + val tmpBytes = digest.digest() + + // Create a list of booleans based on the hash digest + val es = List(PROOF_NUM) { i -> + (tmpBytes[i % tmpBytes.size].toInt() and 1) == 1 + } + + // Return the list of booleans and no error + Pair(es, null) + } catch (e: Exception) { + // Return an empty list and the exception if something goes wrong + Pair(emptyList(), e) + } +} \ No newline at end of file diff --git a/src/main/kotlin/zero_knowledge/Schnorr.kt b/src/main/kotlin/zero_knowledge/Schnorr.kt new file mode 100644 index 0000000..3d089fd --- /dev/null +++ b/src/main/kotlin/zero_knowledge/Schnorr.kt @@ -0,0 +1,155 @@ +package perun_network.ecdsa_threshold.zero_knowledge + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.secp256k1Order +import perun_network.ecdsa_threshold.math.sampleScalar +import java.math.BigInteger + +/** + * Represents the public parameters of the Schnorr proof (Πsch). + * + * @property X The public key, computed as X = g^x, where g is the base point of the group and x is the secret key. + */ +data class SchnorrPublic ( + val X : Point, +) + +/** + * Represents the private parameters of the Schnorr proof (Πsch). + * + * @property x The secret key, such that X = g^x. + */ +data class SchnorrPrivate ( + val x : Scalar +) + +/** + * Represents a commitment in the Schnorr protocol (Πsch). + * + * @property alpha A random scalar used for generating the commitment. + * @property A The commitment point, computed as A = g^alpha. + */ +data class SchnorrCommitment( + val alpha: Scalar, + val A: Point, +) { + companion object { + /** + * Generates a new random commitment for the Schnorr proof (Πsch). + * + * @return A new [SchnorrCommitment] containing a random scalar and its corresponding point. + */ + internal fun newCommitment() : SchnorrCommitment { + val alpha = sampleScalar() + return SchnorrCommitment(alpha, alpha.actOnBase()) + } + } +} + +/** + * Represents the proof in the Schnorr Zero Knowledge protocol (Πsch). + * + * @property z The proof scalar, computed as z = alpha + ex mod q. + * @property A The commitment point used in the proof. + */ +data class SchnorrProof( + val z: Scalar, + val A: Point, +) { + /** + * Validates the structure of the proof. + * + * @return `true` if the proof is structurally valid, `false` otherwise. + */ + private fun isValid(): Boolean { + if (z.isZero() || A.isIdentity()) { + return false + } + return true + } + + /** + * Verifies the Schnorr proof against the public parameters and commitment. + * + * @param id A unique identifier for the proof session. + * @param rid A random session identifier as a byte array. + * @param public The public parameters of the Schnorr proof. + * @return `true` if the proof is valid, otherwise `false`. + */ + fun verify(id: Int, rid: ByteArray, public: SchnorrPublic) : Boolean { + if (!isValid()) return false + + val e = challenge(id, rid, public, A) + + // Check g^z = A · X^e + if (z.actOnBase() != Scalar.scalarFromBigInteger(e).act(public.X).add(A)) { + return false + } + + return true + } + + companion object { + /** + * Generates a new Schnorr proof for the given inputs. + * + * @param id A unique identifier for the proof session. + * @param rid A random session identifier as a byte array. + * @param public The public parameters of the Schnorr proof. + * @param private The private parameters (secret key) of the Schnorr proof. + * @return A valid [SchnorrProof]. + */ + internal fun newProof(id: Int, rid: ByteArray, public: SchnorrPublic, private: SchnorrPrivate): SchnorrProof { + val alpha = sampleScalar() + + val A = alpha.actOnBase() + + val e = challenge(id, rid, public, A) + + val z = alpha.add(private.x.multiply(Scalar.scalarFromBigInteger(e))) + return SchnorrProof(z, A) + } + + /** + * Generates a Schnorr proof using a precomputed commitment. + * + * @param id A unique identifier for the proof session. + * @param rid A random session identifier as a byte array. + * @param public The public parameters of the Schnorr proof. + * @param private The private parameters (secret key) of the Schnorr proof. + * @param commitment A precomputed [SchnorrCommitment]. + * @return A valid [SchnorrProof]. + */ + internal fun newProofWithCommitment(id: Int, rid: ByteArray, public: SchnorrPublic, private: SchnorrPrivate, commitment: SchnorrCommitment): SchnorrProof { + val e = challenge(id, rid, public, commitment.A) + val z = commitment.alpha.add(private.x.multiply(Scalar.scalarFromBigInteger(e))) + + return SchnorrProof(z, commitment.A) + } + } +} + +/** + * Computes the challenge value for the Schnorr proof using the session parameters. + * + * @param id A unique identifier for the proof session. + * @param rid A random session identifier as a byte array. + * @param public The public parameters of the Schnorr proof. + * @param A The commitment point from the prover. + * @return A challenge value as a [BigInteger]. + */ +private fun challenge(id: Int, rid: ByteArray, public: SchnorrPublic, A: Point): BigInteger { + // Collect relevant parts to form the challenge + val inputs = listOf( + public.X.x, + public.X.y, + A.x, + A.y, + BigInteger.valueOf(id.toLong()), + BigInteger(rid) + ) + return inputs.fold(BigInteger.ZERO) { acc, value -> acc.add(value).mod(secp256k1Order()) }.mod( + secp256k1Order() + ) +} diff --git a/src/main/kotlin/zero_knowledge/ZeroKnowledgeException.kt b/src/main/kotlin/zero_knowledge/ZeroKnowledgeException.kt deleted file mode 100644 index 4434f00..0000000 --- a/src/main/kotlin/zero_knowledge/ZeroKnowledgeException.kt +++ /dev/null @@ -1,18 +0,0 @@ -package perun_network.ecdsa_threshold.zero_knowledge - -/** - * Custom exception for handling errors related to Zero-Knowledge protocols. - * - * This exception is thrown to indicate an error that occurs during the execution - * of zero-knowledge proofs or related operations. It provides constructors for - * different scenarios of error handling. - * - * @param message The detail message for the exception, which provides information - * about the error. - * - * @constructor Creates an instance of [ZeroKnowledgeException] with a specified message. - * - * @param message The detail message. - * @param cause The cause of the exception (another throwable). - */ -class ZeroKnowledgeException(message: String) : Exception(message) \ No newline at end of file diff --git a/src/test/kotlin/ecdsa/PointTest.kt b/src/test/kotlin/ecdsa/PointTest.kt new file mode 100644 index 0000000..f1dd569 --- /dev/null +++ b/src/test/kotlin/ecdsa/PointTest.kt @@ -0,0 +1,93 @@ +package ecdsa + +import org.junit.jupiter.api.Assertions.assertTrue +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.bigIntegerToByteArray +import java.math.BigInteger +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.fail + +class PointTest { + private val P = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) + private val A = BigInteger.ZERO + private val B = BigInteger("7") + private val G = Point( + BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16), + BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) + ) + + @Test + fun testPointOutOfBoundsX() { + // Test a point where x is out of bounds (greater than P) + val invalidX = P.add(BigInteger.ONE) + val validY = BigInteger.ONE // Choose a valid small value for y + try { + val point = Point(invalidX, validY) + fail("Expected IllegalArgumentException for x-coordinate out of bounds") + } catch (e: IllegalArgumentException) { + assertEquals("x-coordinate must be in range", e.message) + } + } + + @Test + fun testPointOutOfBoundsY() { + // Test a point where y is out of bounds (greater than P) + val validX = BigInteger.ONE // Choose a valid small value for x + val invalidY = P.add(BigInteger.ONE) + try { + val point = Point(validX, invalidY) + fail("Expected IllegalArgumentException for y-coordinate out of bounds") + } catch (e: IllegalArgumentException) { + assertEquals("y-coordinate must be in range", e.message) + } + } + + @Test + fun testIsOnCurve() { + assertTrue(G.isOnCurve(), "Generator point should lie on the curve") + val invalidPoint = Point(G.x, G.y.add(BigInteger.ONE)) + assertTrue(!invalidPoint.isOnCurve(), "Point with modified y-coordinate should not lie on the curve") + } + + @Test + fun testInverse() { + val inverse = G.inverse() + assertEquals(G.x, inverse.x, "Inverse should have the same x-coordinate") + assertEquals(perun_network.ecdsa_threshold.ecdsa.P.subtract(G.y).mod(perun_network.ecdsa_threshold.ecdsa.P), inverse.y, "Inverse y-coordinate should be -y mod P") + val sum = G.add(inverse) + assertTrue(sum.isIdentity(), "Adding a point to its inverse should result in the identity element") + } + + @Test + fun testAddition() { + val sum = G.add(G) + assertTrue(sum.isOnCurve(), "Result of point addition should lie on the curve") + val identity = G.add(Point(BigInteger.ZERO, BigInteger.ZERO)) + assertEquals(G, identity, "Adding the identity element should return the same point") + val doubleInverse = G.add(G.inverse()) + assertTrue(doubleInverse.isIdentity(), "Adding a point to its inverse should result in the identity element") + } + + @Test + fun testIdentity() { + val identity = Point(BigInteger.ZERO, BigInteger.ZERO) + assertTrue(identity.isIdentity(), "Point should be the identity element") + val result = G.add(identity) + assertEquals(G, result, "Adding the identity element should return the same point") + } + + + @Test + fun testInvalidPoint() { + val invalidPoint = Point(G.x, G.y.add(BigInteger.ONE)) + assertTrue(!invalidPoint.isOnCurve(), "Invalid point should not satisfy the curve equation") + } + + @Test + fun testDoublingEdgeCase() { + val edgeCasePoint = Point(G.x, BigInteger.ZERO) + val result = edgeCasePoint.double() + assertTrue(result.isIdentity(), "Doubling a point with y=0 should return the identity element") + } +} \ No newline at end of file diff --git a/src/test/kotlin/ecdsa/PrivateKeyTest.kt b/src/test/kotlin/ecdsa/PrivateKeyTest.kt new file mode 100644 index 0000000..b76f1e9 --- /dev/null +++ b/src/test/kotlin/ecdsa/PrivateKeyTest.kt @@ -0,0 +1,92 @@ +package ecdsa + +import fr.acinq.secp256k1.Secp256k1 +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.kotlincrypto.hash.sha2.SHA256 +import perun_network.ecdsa_threshold.ecdsa.PrivateKey.Companion.newPrivateKey +import perun_network.ecdsa_threshold.math.sampleScalar +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class PrivateKeyTest { + @Test + fun testPrivateKey() { + val x = sampleScalar() + val X = x.actOnBase() + val xBytes = x.toByteArray() + + val privateKey = newPrivateKey(xBytes) + val message = "Hello, Bitcoin!".toByteArray() + val hash = SHA256().digest(message) + val sig = privateKey.sign(hash) + assertTrue(sig.verifyWithPoint(hash , X)) + + val xScalar = privateKey.toScalar() + assertEquals(x, xScalar) + } + + @Test + fun testPrivateKeyAdd() { + val x = sampleScalar() + val xPriv = newPrivateKey(x.toByteArray()) + val y = sampleScalar() + val yPriv = newPrivateKey(y.toByteArray()) + + val z = x.add(y) + val zPriv = newPrivateKey(z.toByteArray()) + val zPriv2 = xPriv.add(yPriv) + + assertEquals(zPriv, zPriv2) + } + + @Test + fun testPrivateKeyNeg() { + val x = sampleScalar() + val xPriv = newPrivateKey(x.toByteArray()) + + // Perform negation + val negatedKey = xPriv.neg() + + // Check that the negated key is not null and valid + assertNotNull(negatedKey, "Negated key should not be null") + assert(Secp256k1.secKeyVerify(negatedKey.toByteArray())) { "Negated key must be valid" } + + // Apply negation twice and check if it equals the original key + val doubleNegatedKey = negatedKey.neg() + assertEquals(xPriv, doubleNegatedKey, "Double negation should yield the original key") + } + + @Test + fun testPrivateKeyMul() { + val x = sampleScalar() + val xPriv = newPrivateKey(x.toByteArray()) + val y = sampleScalar() + val yPriv = newPrivateKey(y.toByteArray()) + + val z = x.multiply(y) + val zPriv = newPrivateKey(z.toByteArray()) + val zPriv2 = xPriv.mul(yPriv) + + assertEquals(zPriv, zPriv2) + } + + @Test + fun testPrivateKeyFails() { + val invalidSizeData = ByteArray(16) // Less than 32 bytes + assertFailsWith("data must be 32 bytes") { + newPrivateKey(invalidSizeData) + } + + val oversizedData = ByteArray(33) // More than 32 bytes + assertFailsWith("data must be 32 bytes") { + newPrivateKey(oversizedData) + } + + val invalidKeyData = ByteArray(32) { 0 } // Invalid private key data + assertFailsWith("invalid private key") { + newPrivateKey(invalidKeyData) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/ecdsa/PublicKeyTest.kt b/src/test/kotlin/ecdsa/PublicKeyTest.kt new file mode 100644 index 0000000..dd88123 --- /dev/null +++ b/src/test/kotlin/ecdsa/PublicKeyTest.kt @@ -0,0 +1,28 @@ +package ecdsa + +import org.junit.jupiter.api.Assertions.assertEquals +import perun_network.ecdsa_threshold.ecdsa.PublicKey +import perun_network.ecdsa_threshold.math.sampleScalar +import kotlin.test.Test +import kotlin.test.assertFails + +class PublicKeyTest { + @Test + fun testPublicKey() { + val x = sampleScalar() + val X = x.actOnBase() + val validBytes = X.toPublicKey().value + val publicKey = PublicKey.newPublicKey(validBytes) + assertEquals(X.toPublicKey(), publicKey) + } + + @Test + fun testPublicKeyFails() { + val x = sampleScalar() + val X = x.actOnBase() + assertFails { + PublicKey.newPublicKey(X.toByteArray()) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/ecdsa/Secp256k1Test.kt b/src/test/kotlin/ecdsa/Secp256k1Test.kt index d0d1600..3b8f241 100644 --- a/src/test/kotlin/ecdsa/Secp256k1Test.kt +++ b/src/test/kotlin/ecdsa/Secp256k1Test.kt @@ -20,7 +20,6 @@ class Secp256k1Test { assertArrayEquals(secpBase, acinqBase) } - @Test fun testPointAddition() { val point1 = newBasePoint() @@ -30,7 +29,6 @@ class Secp256k1Test { val p1 = Secp256k1.pubkeyParse(Hex.decode("0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8".lowercase())) val p2 = Secp256k1.pubkeyParse(Hex.decode("0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8".lowercase())) - val sumAcinq = Secp256k1.pubKeyCombine(arrayOf(p1, p2)) // Perform point addition using your code @@ -48,7 +46,6 @@ class Secp256k1Test { // Expected scalar multiplication result from acinq-secp256k1 val base = Hex.decode("0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8".lowercase()) - val resultAcinq = Secp256k1.pubKeyTweakMul(base, scalar.value.toByteArray()) // Perform scalar multiplication using your code @@ -118,4 +115,65 @@ class Secp256k1Test { assertTrue(signature.verifySecp256k1(hash, privateKey.publicKey())) } + + @Test + fun `test big integer to byte array - exactly 32 bytes`() { + // Create a BigInteger that results in exactly 32 bytes + val bi = BigInteger("1234567890123456789012345678901234567890123456789012345678901234", 16) + + // Convert to byte array + val byteArray = bigIntegerToByteArray(bi) + + // Assert that the byte array is exactly 32 bytes + assertEquals(32, byteArray.size) + // Assert that the BigInteger can be recreated from the byte array + assertEquals(bi, BigInteger(1, byteArray)) + } + + @Test + fun `test big integer to byte array - less than 32 bytes`() { + // Create a BigInteger that is less than 32 bytes + val bi = BigInteger("1234567890", 16) // This should be much smaller than 32 bytes + + // Convert to byte array + val byteArray = bigIntegerToByteArray(bi) + + // Assert that the byte array is exactly 32 bytes + assertEquals(32, byteArray.size) + // Assert that the BigInteger can be recreated from the byte array, ignoring leading zeros + assertEquals(bi, BigInteger(1, byteArray)) + } + + @Test + fun `test big integer to byte array - more than 32 bytes`() { + // Create a BigInteger that is more than 32 bytes + val bi = BigInteger("1234567890123456789012345678901234567890123456789012345678901234567890", 16) + + // Convert to byte array + val byteArray = bigIntegerToByteArray(bi) + + // Assert that the byte array is exactly 32 bytes + assertEquals(32, byteArray.size) + + // Assert that the BigInteger can be recreated from the byte array (most significant 32 bytes) + val truncatedBi = BigInteger(1, bi.toByteArray().copyOfRange(bi.toByteArray().size - 32, bi.toByteArray().size)) + assertEquals(truncatedBi, BigInteger(1, byteArray)) + } + + + @Test + fun `test big integer to byte array - zero value`() { + // Create a BigInteger with value 0 + val bi = BigInteger.ZERO + + // Convert to byte array + val byteArray = bigIntegerToByteArray(bi) + + // Assert that the byte array is exactly 32 bytes and is all zeros + assertEquals(32, byteArray.size) + assertTrue(byteArray.all { it == 0.toByte() }) + + // Assert that the BigInteger can be recreated from the byte array + assertEquals(bi, BigInteger(byteArray)) + } } \ No newline at end of file diff --git a/src/test/kotlin/math/ExponentPolynomialTest.kt b/src/test/kotlin/math/ExponentPolynomialTest.kt new file mode 100644 index 0000000..5f27563 --- /dev/null +++ b/src/test/kotlin/math/ExponentPolynomialTest.kt @@ -0,0 +1,63 @@ +package math + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.newPoint +import perun_network.ecdsa_threshold.math.sampleScalar +import perun_network.ecdsa_threshold.math.shamir.Polynomial +import perun_network.ecdsa_threshold.math.shamir.sum +import kotlin.test.Test +import kotlin.test.assertEquals + +class ExponentPolynomialTest { + @Test + fun testExponentEvaluate() { + var lhs: Point + for (x in 0 until 5) { + val N = 1000 + val secret = if (x % 2 == 0) { + sampleScalar() + } else { + Scalar.zero() + } + + val poly = Polynomial.newPolynomial(N, secret) + val polyExp = poly.exponentPolynomial() + val randomIndex = sampleScalar() + + lhs = poly.eval(randomIndex).actOnBase() + val rhs1 = polyExp.eval(randomIndex) + + assertEquals(lhs, rhs1, "Base eval differs from Horner method at iteration $x") + } + } + + @Test + fun testSum() { + val N = 20 + val Deg = 10 + val randomIndex = sampleScalar() + + // Compute f1(x) + f2(x) + … + var evaluationScalar = Scalar.zero() + + // Compute F1(x) + F2(x) + … + var evaluationPartial = newPoint() + + val polys = Array(N) { Polynomial.newPolynomial(Deg) } + val polysExp = Array(N) { polys[it].exponentPolynomial() } + + for (i in polys.indices) { + evaluationScalar = evaluationScalar.add(polys[i].eval(randomIndex)) + evaluationPartial = evaluationPartial.add(polysExp[i].eval(randomIndex)) + } + + // Compute (F1 + F2 + …)(x) + val summedExp = sum(polysExp.toList()) + val evaluationSum = summedExp.eval(randomIndex) + + val evaluationFromScalar = evaluationScalar.actOnBase() + assertEquals(evaluationSum, evaluationFromScalar, "Summed exponent does not match evaluation from scalar.") + assertEquals(evaluationSum, evaluationPartial, "Summed exponent does not match partial evaluation.") + } +} \ No newline at end of file diff --git a/src/test/kotlin/math/MathTest.kt b/src/test/kotlin/math/MathTest.kt new file mode 100644 index 0000000..3cd8498 --- /dev/null +++ b/src/test/kotlin/math/MathTest.kt @@ -0,0 +1,105 @@ +package math + +import org.junit.jupiter.api.Assertions.* +import perun_network.ecdsa_threshold.math.isValidModN +import perun_network.ecdsa_threshold.math.jacobi +import perun_network.ecdsa_threshold.math.mustReadBits +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import java.math.BigInteger +import kotlin.test.Test + +class MathTest { + @Test + fun `test successful read`() { + val data = ByteArray(32) { it.toByte() } + val inputStream = ByteArrayInputStream(data) + val buffer = ByteArray(32) + + mustReadBits(inputStream, buffer) + assertArrayEquals(data, buffer, "The buffer should match the input data") + } + + @Test + fun `test failure after max iterations`() { + val failingStream = object : InputStream() { + override fun read(): Int = throw IOException("Simulated failure") + } + + val buffer = ByteArray(32) + assertThrows(IllegalStateException::class.java) { + mustReadBits(failingStream, buffer) + } + } + + @Test + fun `test jacobi symbol positive cases`() { + val result1 = jacobi(BigInteger.valueOf(2), BigInteger.valueOf(15)) + assertEquals(1, result1, "Jacobi(2/15) should be 1") + + val result2 = jacobi(BigInteger.valueOf(4), BigInteger.valueOf(7)) + assertEquals(1, result2, "Jacobi(4/7) should be 1") + } + + @Test + fun `test jacobi symbol negative cases`() { + val result = jacobi(BigInteger.valueOf(3), BigInteger.valueOf(5)) + assertEquals(-1, result, "Jacobi(3/15) should be -1") + } + + @Test + fun `test jacobi symbol zero cases`() { + val result = jacobi(BigInteger.ZERO, BigInteger.valueOf(15)) + assertEquals(0, result, "Jacobi(0/15) should be 0") + } + + @Test + fun `test jacobi invalid inputs`() { + assertThrows(IllegalArgumentException::class.java) { + jacobi(BigInteger.valueOf(5), BigInteger.valueOf(4)) // y is even + } + } + + @Test + fun `test valid integers that are co-prime to N`() { + val N = BigInteger("10") + val validInts = arrayOf(BigInteger("1"), BigInteger("3"), BigInteger("7"), BigInteger("9")) + assertTrue(isValidModN(N, *validInts)) + } + + @Test + fun `test integers outside valid range`() { + val N = BigInteger("10") + val invalidInts = arrayOf(BigInteger("0"), BigInteger("10"), BigInteger("11")) + assertFalse(isValidModN(N, *invalidInts)) + } + + @Test + fun `test integers not co-prime to N`() { + val N = BigInteger("10") + val nonCoprimeInts = arrayOf(BigInteger("2"), BigInteger("5"), BigInteger("8")) + assertFalse(isValidModN(N, *nonCoprimeInts)) + } + + @Test + fun `test null integer`() { + val N = BigInteger("10") + val intsWithNull = arrayOf(BigInteger("1"), null, BigInteger("7")) + assertFalse(isValidModN(N, *intsWithNull)) + } + + @Test + fun `test negative integer`() { + val N = BigInteger("10") + val negativeInts = arrayOf(BigInteger("-3"), BigInteger("1")) + assertFalse(isValidModN(N, *negativeInts)) + } + + @Test + fun `test edge case with N as 1`() { + val N = BigInteger("1") + val anyInts = arrayOf(BigInteger("1"), BigInteger("0")) + assertFalse(isValidModN(N, *anyInts)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/math/PolynomialTest.kt b/src/test/kotlin/math/PolynomialTest.kt new file mode 100644 index 0000000..d261245 --- /dev/null +++ b/src/test/kotlin/math/PolynomialTest.kt @@ -0,0 +1,63 @@ +package math + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.ecdsa.newPoint +import perun_network.ecdsa_threshold.math.sampleScalar +import perun_network.ecdsa_threshold.math.shamir.Polynomial +import perun_network.ecdsa_threshold.math.shamir.sum +import kotlin.test.Test +import kotlin.test.assertEquals + +class PolynomialTest { + @Test + fun testEvaluate() { + var lhs: Point + for (x in 0 until 5) { + val N = 1000 + val secret = if (x % 2 == 0) { + sampleScalar() + } else { + Scalar.zero() + } + + val poly = Polynomial.newPolynomial(N, secret) + val polyExp = poly.exponentPolynomial() + val randomIndex = sampleScalar() + + lhs = poly.eval(randomIndex).actOnBase() + val rhs1 = polyExp.eval(randomIndex) + + assertEquals(lhs, rhs1, "Base eval differs from Horner method at iteration $x") + } + } + + @Test + fun testSum() { + val N = 20 + val Deg = 10 + val randomIndex = sampleScalar() + + // Compute f1(x) + f2(x) + … + var evaluationScalar = Scalar.zero() + + // Compute F1(x) + F2(x) + … + var evaluationPartial = newPoint() + + val polys = Array(N) { Polynomial.newPolynomial(Deg) } + val polysExp = Array(N) { polys[it].exponentPolynomial() } + + for (i in polys.indices) { + evaluationScalar = evaluationScalar.add(polys[i].eval(randomIndex)) + evaluationPartial = evaluationPartial.add(polysExp[i].eval(randomIndex)) + } + + // Compute (F1 + F2 + …)(x) + val summedExp = sum(polysExp.toList()) + val evaluationSum = summedExp.eval(randomIndex) + + val evaluationFromScalar = evaluationScalar.actOnBase() + assertEquals(evaluationSum, evaluationFromScalar, "Summed exponent does not match evaluation from scalar.") + assertEquals(evaluationSum, evaluationPartial, "Summed exponent does not match partial evaluation.") + } +} \ No newline at end of file diff --git a/src/test/kotlin/math/PrimeTest.kt b/src/test/kotlin/math/PrimeTest.kt new file mode 100644 index 0000000..0f4523e --- /dev/null +++ b/src/test/kotlin/math/PrimeTest.kt @@ -0,0 +1,44 @@ +package math + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import perun_network.ecdsa_threshold.math.generatePaillierBlumPrimes +import perun_network.ecdsa_threshold.math.generateSafeBlumPrime +import java.math.BigInteger +import kotlin.test.Test +import kotlin.test.assertTrue + +class PrimeTest { + + @Test + fun testGenerateSafeBlumPrime() { + val bits = 256 + val blumPrime = generateSafeBlumPrime(bits) + + // Verify bit length + assertEquals(bits, blumPrime.bitLength(), "Generated Blum prime should have the specified bit length") + + // Verify Blum prime condition: p ≡ 3 mod 4 + assertEquals(BigInteger.valueOf(3), blumPrime.mod(BigInteger.valueOf(4)), "Blum prime must satisfy p ≡ 3 mod 4") + + // Verify safe prime condition: (p - 1) / 2 is prime + val halfPrime = blumPrime.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)) + assertTrue(halfPrime.isProbablePrime(20), "Blum prime must satisfy the safe prime condition") + } + + @Test + fun `test generatePaillierBlumPrimes`() { + val (p, q) = generatePaillierBlumPrimes() + + // Verify that p and q are Blum primes + listOf(p, q).forEach { prime -> + assertNotNull(prime, "Generated prime should not be null") + assertEquals(BigInteger.valueOf(3), prime.mod(BigInteger.valueOf(4)), "Prime must satisfy p ≡ 3 mod 4") + val halfPrime = prime.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)) + assertTrue(halfPrime.isProbablePrime(100), "Prime must satisfy the safe prime condition") + } + + // Verify that p and q are distinct + assertTrue(p != q, "Generated Blum primes p and q should be distinct") + } +} \ No newline at end of file diff --git a/src/test/kotlin/math/RandomTest.kt b/src/test/kotlin/math/RandomTest.kt new file mode 100644 index 0000000..8e5d883 --- /dev/null +++ b/src/test/kotlin/math/RandomTest.kt @@ -0,0 +1,4 @@ +package math + +class RandomTest { +} \ No newline at end of file diff --git a/src/test/kotlin/math/SecureRandomInputStreamTest.kt b/src/test/kotlin/math/SecureRandomInputStreamTest.kt new file mode 100644 index 0000000..3a13d15 --- /dev/null +++ b/src/test/kotlin/math/SecureRandomInputStreamTest.kt @@ -0,0 +1,85 @@ +package math + +import org.junit.jupiter.api.Assertions.assertEquals +import perun_network.ecdsa_threshold.math.SecureRandomInputStream +import java.security.SecureRandom +import kotlin.test.Test +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class SecureRandomInputStreamTest { + + @Test + fun `test read single byte`() { + val secureRandom = SecureRandom() + val secureStream = SecureRandomInputStream(secureRandom) + + val byteValue = secureStream.read() + assertTrue(byteValue in 0..255, "The byte should be between 0 and 255") + } + + @Test + fun `test read byte array full length`() { + val secureRandom = SecureRandom() + val secureStream = SecureRandomInputStream(secureRandom) + val buffer = ByteArray(10) + + val bytesRead = secureStream.read(buffer) + assertEquals(10, bytesRead, "Expected to read 10 bytes") + assertTrue(buffer.all { it in Byte.MIN_VALUE..Byte.MAX_VALUE }, "All bytes should be valid") + } + + @Test + fun `test read with offset and length`() { + val secureRandom = SecureRandom() + val secureStream = SecureRandomInputStream(secureRandom) + val buffer = ByteArray(10) + + val bytesRead = secureStream.read(buffer, 2, 5) + assertEquals(5, bytesRead, "Expected to read 5 bytes into the buffer with offset 2") + assertTrue(buffer.sliceArray(2 until 7).all { it in Byte.MIN_VALUE..Byte.MAX_VALUE }, "Bytes from offset 2 to 6 should be valid") + } + + @Test + fun `test invalid offset throws exception`() { + val secureRandom = SecureRandom() + val secureStream = SecureRandomInputStream(secureRandom) + val buffer = ByteArray(10) + + assertFailsWith { + secureStream.read(buffer, -1, 5) + } + } + + @Test + fun `test invalid length throws exception`() { + val secureRandom = SecureRandom() + val secureStream = SecureRandomInputStream(secureRandom) + val buffer = ByteArray(10) + + assertFailsWith { + secureStream.read(buffer, 0, 11) + } + } + + @Test + fun `test zero length read`() { + val secureRandom = SecureRandom() + val secureStream = SecureRandomInputStream(secureRandom) + val buffer = ByteArray(10) + + assertFailsWith { + secureStream.read(buffer, 0, 0) + } + } + + @Test + fun `test read byte array partially`() { + val secureRandom = SecureRandom() + val secureStream = SecureRandomInputStream(secureRandom) + val buffer = ByteArray(20) + + val bytesRead = secureStream.read(buffer, 5, 10) + assertEquals(10, bytesRead, "Expected to read 10 bytes into the buffer starting from offset 5") + } +} \ No newline at end of file diff --git a/src/test/kotlin/paillier/PaillierTest.kt b/src/test/kotlin/paillier/PaillierTest.kt new file mode 100644 index 0000000..022bcee --- /dev/null +++ b/src/test/kotlin/paillier/PaillierTest.kt @@ -0,0 +1,192 @@ +package paillier + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.assertThrows +import paillier.PaillierTestKeys.paillierPublic +import paillier.PaillierTestKeys.paillierSecret +import paillier.PaillierTestKeys.reinit +import perun_network.ecdsa_threshold.math.BitsPaillier +import perun_network.ecdsa_threshold.paillier.* +import java.math.BigInteger +import kotlin.test.Test +import kotlin.test.assertFailsWith + +object PaillierTestKeys { + var paillierPublic: PaillierPublic + var paillierSecret: PaillierSecret + + init { + val p = BigInteger("FD90167F42443623D284EA828FB13E374CBF73E16CC6755422B97640AB7FC77FDAF452B4F3A2E8472614EEE11CC8EAF48783CE2B4876A3BB72E9ACF248E86DAA5CE4D5A88E77352BCBA30A998CD8B0AD2414D43222E3BA56D82523E2073730F817695B34A4A26128D5E030A7307D3D04456DC512EBB8B53FDBD1DFC07662099B", 16) + val q = BigInteger("DB531C32024A262A0DF9603E48C79E863F9539A82B8619480289EC38C3664CC63E3AC2C04888827559FFDBCB735A8D2F1D24BAF910643CE819452D95CAFFB686E6110057985E93605DE89E33B99C34140EF362117F975A5056BFF14A51C9CD16A4961BE1F02C081C7AD8B2A5450858023A157AFA3C3441E8E00941F8D33ED6B7", 16) + paillierSecret = newPaillierSecretFromPrimes(p, q) + paillierPublic = paillierSecret.publicKey + } + + fun reinit() { + val keys = paillierKeyGen() + paillierPublic = keys.first + paillierSecret = keys.second + } +} + +class PaillierTest { + @Test + fun testCiphertextValidate() { + if (System.getProperty("runShortTests") == null) { + reinit() + } + + val C = BigInteger.ZERO + var ct = PaillierCipherText(C) + + assertFailsWith { paillierSecret.decrypt(ct) } + + val n = paillierPublic.n + val nSquared = paillierPublic.nSquared + + ct = PaillierCipherText(n) + assertFailsWith { paillierSecret.decrypt(ct) } + + ct = PaillierCipherText(n + n) + assertFailsWith { paillierSecret.decrypt(ct) } + + ct = PaillierCipherText(nSquared) + assertFailsWith { paillierSecret.decrypt(ct) } + } + + @Test + fun testEncDecRoundTrip() { + if (System.getProperty("runShortTests") == null) { + reinit() + } + + val m = BigInteger.valueOf(42) + val (ciphertext, _) = paillierPublic.encryptRandom(m) + val decrypted = paillierSecret.decrypt(ciphertext) + assertEquals(m, decrypted) + } + + @Test + fun testEncDecHomomorphic() { + if (System.getProperty("runShortTests") == null) { + reinit() + } + + val a = BigInteger.valueOf(15) + val b = BigInteger.valueOf(10) + val (ca, _) = paillierPublic.encryptRandom(a) + val (cb, _) = paillierPublic.encryptRandom(b) + + val expected = a + b + val actual = paillierSecret.decrypt(ca.modMulNSquared(paillierPublic, cb)) + assertEquals(expected, actual) + } + + @Test + fun testEncDecScalingHomomorphic() { + if (System.getProperty("runShortTests") == null) { + reinit() + } + + val x = BigInteger.valueOf(20) + val s = BigInteger.valueOf(5) + val (c, _) = paillierPublic.encryptRandom(x) + + val expected = x * s + val actual = paillierSecret.decrypt(c.modPowNSquared(paillierPublic, s)) + assertEquals(expected, actual) + } + + @Test + fun testDecWithRandomness() { + if (System.getProperty("runShortTests") == null) { + reinit() + } + + val x = BigInteger.valueOf(7) + val nonce = BigInteger.valueOf(13) + val ciphertext = paillierPublic.encryptWithNonce(x, nonce) + val (mActual, nonceActual) = paillierSecret.decryptRandom(ciphertext) + + assertEquals(x, mActual) + assertEquals(nonce, nonceActual) + } + + @Test + fun `validateN should accept valid modulus`() { + val validN = PRECOMPUTED_PRIMES[0].first.multiply(PRECOMPUTED_PRIMES[0].second) // p1 * q1 + assertNull(validateN(validN)) + } + + @Test + fun `validateN should reject zero or negative modulus`() { + assertEquals("modulus N is nil", validateN(BigInteger.ZERO)?.message) + assertEquals("modulus N is nil", validateN(BigInteger.valueOf(-1))?.message) + } + + @Test + fun `validateN should reject modulus with incorrect bit length`() { + val invalidN = BigInteger("1".repeat(BitsPaillier + 1), 2) + assertEquals("Expected bit length: $BitsPaillier, found: ${invalidN.bitLength()}", validateN(invalidN)?.message) + } + + @Test + fun `validateN should reject even modulus`() { + val evenN = BigInteger.valueOf(16) + assertEquals("Modulus N is even", validateN(evenN)?.message) + } + + @Test + fun `validatePrime should accept valid prime`() { + val validPrime = PRECOMPUTED_PRIMES[0].first + assertTrue(validatePrime(validPrime)) + } + + @Test + fun `validatePrime should reject prime of incorrect bit length`() { + val shortPrime = BigInteger.valueOf(23) // Too short + assertFailsWith { + validatePrime(shortPrime) + } + } + + @Test + fun `validatePrime should reject non-Blum primes`() { + val nonBlumPrime = BigInteger.valueOf(11) // 11 % 4 != 3 + assertFailsWith { + validatePrime(nonBlumPrime) + } + } + + @Test + fun `validatePrime should reject non-safe primes`() { + val nonSafePrime = BigInteger("19") // Not a safe prime + assertFailsWith { + validatePrime(nonSafePrime) + } + } + + @Test + fun `newPaillierSecretFromPrimes should generate correct secret`() { + val p = PRECOMPUTED_PRIMES[0].first + val q = PRECOMPUTED_PRIMES[0].second + val secret = newPaillierSecretFromPrimes(p, q) + + assertEquals(p, secret.p) + assertEquals(q, secret.q) + assertEquals(p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE)), secret.phi) + assertTrue(secret.phiInv.multiply(secret.phi).mod(secret.publicKey.n) == BigInteger.ONE) + assertEquals(secret.publicKey.n, p.multiply(q)) + assertEquals(secret.publicKey.nSquared, p.multiply(q).pow(2)) + } + + @Test + fun `newPaillierSecretFromPrimes should fail on invalid primes`() { + val invalidP = BigInteger("4") // Not prime + val q = PRECOMPUTED_PRIMES[0].second + + assertThrows { + newPaillierSecretFromPrimes(invalidP, q) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/precomp/PrecompTest.kt b/src/test/kotlin/precomp/PrecompTest.kt new file mode 100644 index 0000000..8dfc615 --- /dev/null +++ b/src/test/kotlin/precomp/PrecompTest.kt @@ -0,0 +1,44 @@ +package precomp + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import perun_network.ecdsa_threshold.precomp.generatePrecomputations +import kotlin.test.assertFails + +class PrecompTest { + @Test + fun testGeneratePrecomp() { + val n = 2 + val t = 1 + val (ids, secretPrecomps, publicPrecomps) = generatePrecomputations(n, t) + + // Verify the correct number of party IDs + assertEquals(n, ids.size, "The number of party IDs should equal n") + + // Verify secret and public precomputations for all IDs + assertEquals(ids.toSet(), secretPrecomps.keys.toSet(), "All IDs must have secret precomputations") + assertEquals(ids.toSet(), publicPrecomps.keys.toSet(), "All IDs must have public precomputations") + + // Validate that the precomputations are not null and correctly initialized + ids.forEach { id -> + val secret = secretPrecomps[id] + val public = publicPrecomps[id] + assertNotNull(secret, "Secret precomputation for ID $id should not be null") + assertNotNull(public, "Public precomputation for ID $id should not be null") + assertEquals(secret!!.id, public!!.id, "Secret and public precomputation IDs must match") + assertEquals(t, secret.threshold, "Threshold value in secret precomputation must match the input") + } + } + + @Test + fun testGeneratePrecompFails() { + val n = 3 + val t = 10 + + // Simulate a case where `idRange` is smaller than `n` + assertFails { + generatePrecomputations(n, t) // Adjust the internal logic to simulate idRange < n + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/sign/SignTest.kt b/src/test/kotlin/sign/SignTest.kt index f4c74e0..8814204 100644 --- a/src/test/kotlin/sign/SignTest.kt +++ b/src/test/kotlin/sign/SignTest.kt @@ -28,7 +28,6 @@ class SignTest { assertTrue(parsedSignature.verifySecp256k1(hash, privateKey.publicKey())) } - @Test fun testCustomSignature() { val message = "Hello".toByteArray() @@ -72,7 +71,10 @@ class SignTest { val sigmaShares = mutableMapOf() for ((id, preSignature) in presigs) { assertEquals(R, preSignature.R) - sigmaShares[id] = preSignature.signPartial(hash) + val partialSig = preSignature.signPartial(hash) + assertEquals(partialSig.ssid, hash) + assertEquals(partialSig.id, 0) + sigmaShares[id] =partialSig } for ((_, preSignature) in presigs) { val signature = preSignature.signature(sigmaShares) diff --git a/src/test/kotlin/sign/SignerTest.kt b/src/test/kotlin/sign/SignerTest.kt new file mode 100644 index 0000000..8627ecc --- /dev/null +++ b/src/test/kotlin/sign/SignerTest.kt @@ -0,0 +1,273 @@ +package sign + +import org.junit.jupiter.api.Assertions.assertEquals +import org.kotlincrypto.hash.sha2.SHA256 +import perun_network.ecdsa_threshold.ecdsa.PartialSignature +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.precomp.generateSessionId +import perun_network.ecdsa_threshold.precomp.publicKeyFromShares +import perun_network.ecdsa_threshold.randomSigners +import perun_network.ecdsa_threshold.sign.Signer +import perun_network.ecdsa_threshold.sign.aux.AuxRound1Broadcast +import perun_network.ecdsa_threshold.sign.aux.AuxRound2Broadcast +import perun_network.ecdsa_threshold.sign.aux.AuxRound3Broadcast +import perun_network.ecdsa_threshold.sign.combinePartialSignatures +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound1Broadcast +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound2Broadcast +import perun_network.ecdsa_threshold.sign.keygen.KeygenRound3Broadcast +import perun_network.ecdsa_threshold.sign.presign.PresignRound1Broadcast +import perun_network.ecdsa_threshold.sign.presign.PresignRound2Broadcast +import perun_network.ecdsa_threshold.sign.presign.PresignRound3Broadcast +import kotlin.test.Test +import kotlin.test.assertFails +import kotlin.test.assertTrue + +object SignerTest { + @Test + fun testSigner() { + val n = 5 // Number of total parties. + val t = 3 // Threshold of minimum required signers. + + val ssid = generateSessionId() + val parties = mutableMapOf() + for (i in 1..n) { + parties[i] = Signer( + id = i, + ssid = ssid, + threshold = t, + ) + } + + keygen(parties) + + val publicPrecomps = aux(parties) + + val signers = randomSigners(parties, t) + val publicKey = publicKeyFromShares(signers.keys.toList(), publicPrecomps) + + // Scale Secret/Public Precomputations + val (publicPoint, _) = scalePrecomputation(signers) + assertEquals(publicKey, publicPoint.toPublicKey()) + val bigR = presign(signers) + + val message = "Signer Test" + val hash = SHA256().digest(message.toByteArray()) + + val partialSignatures = partialSignMessage(signers, hash) + + val ecdsaSignature= combinePartialSignatures(bigR, partialSignatures, publicPoint, hash) + + assertTrue(ecdsaSignature.verifySecp256k1(hash, publicKey), "failed to convert and verified ecdsa signature") + } + + @Test + fun testSignerFails() { + // Wrong orders + val n = 5 // Number of total parties. + val t = 3 // Threshold of minimum required signers. + + val ssid = generateSessionId() + val parties = mutableMapOf() + val partyIds = parties.keys.toList() + for (i in 1..n) { + parties[i] = Signer( + id = i, + ssid = ssid, + threshold = t, + ) + } + + // Calling round 2/3 before round1 + assertFails { + parties[2]!!.keygenRound2(partyIds) + } + + assertFails { + val round2Broadcast = mutableMapOf>() + val round1Broadcast = mutableMapOf>() + parties[3]!!.keygenRound3(partyIds, round1Broadcast, round2Broadcast) + } + + assertFails { + val round2Broadcast = mutableMapOf>() + val round3Broadcast = mutableMapOf>() + parties[3]!!.keygenOutput(partyIds, round2Broadcast, round3Broadcast) + } + + assertFails { + parties[5]!!.auxRound2(partyIds) + } + + assertFails { + val round2Broadcast = mutableMapOf>() + val round1Broadcast = mutableMapOf>() + parties[3]!!.auxRound3(partyIds, round1Broadcast, round2Broadcast) + } + + assertFails { + val round2Broadcast = mutableMapOf>() + val round3Broadcast = mutableMapOf>() + parties[3]!!.auxOutput(partyIds, round2Broadcast, round3Broadcast) + } + + assertFails { + val round1Broadcast = mutableMapOf>() + parties[1]!!.presignRound2(partyIds, round1Broadcast) + } + + assertFails { + val round2Broadcast = mutableMapOf>() + parties[3]!!.presignRound3(partyIds, round2Broadcast) + } + + assertFails { + val round3Broadcast = mutableMapOf>() + parties[3]!!.presignOutput(partyIds, round3Broadcast) + } + + // Starting presign without key generation/aux + assertFails { + presign(parties) + } + } + + + private fun keygen(parties : Map) { + val partyIds = parties.keys.toList() + // KEYGEN ROUND 1 + val keygenRound1AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + keygenRound1AllBroadcasts[i] = parties[i]!!.keygenRound1(partyIds) + } + + // KEYGEN ROUND 2 + val keygenRound2AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + keygenRound2AllBroadcasts[i] = parties[i]!!.keygenRound2(partyIds) + } + + // KEYGEN ROUND 3 + val keygenRound3AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + keygenRound3AllBroadcasts[i] = parties[i]!!.keygenRound3(partyIds, keygenRound1AllBroadcasts, keygenRound2AllBroadcasts) + } + + // KEYGEN OUTPUT + val publicPoints = mutableMapOf() + for (i in partyIds) { + publicPoints[i] = parties[i]!!.keygenOutput(partyIds, keygenRound2AllBroadcasts, keygenRound3AllBroadcasts) + } + + // Check all public Points + val publicPoint = publicPoints[partyIds[0]]!! + for (i in partyIds) { + assertEquals(publicPoints[i], publicPoint) + } + } + + private fun aux(parties: Map) : Map { + val partyIds = parties.keys.toList() + + // AUX ROUND 1 + val auxRound1AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + auxRound1AllBroadcasts[i] = parties[i]!!.auxRound1(partyIds) + } + + // AUX ROUND 2 + val auxRound2AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + auxRound2AllBroadcasts[i] = parties[i]!!.auxRound2(partyIds) + } + + // AUX ROUND 3 + val auxRound3AllBroadcasts = mutableMapOf>() + for (i in partyIds) { + auxRound3AllBroadcasts[i] = parties[i]!!.auxRound3(partyIds, auxRound1AllBroadcasts, auxRound2AllBroadcasts) + } + + // AUX OUTPUT + val publicPrecomps = mutableMapOf>() + for (i in partyIds) { + publicPrecomps[i] = parties[i]!!.auxOutput(partyIds, auxRound2AllBroadcasts, auxRound3AllBroadcasts) + } + + // Check all public Points + val publicPrecomp = publicPrecomps[partyIds[0]]!! + for (i in partyIds) { + for (j in partyIds) { + assertEquals(publicPrecomps[i]!![j]!!, publicPrecomp[j]) + } + } + + return publicPrecomp + } + + + + private fun presign(signers: Map) : Point { + val signerIds = signers.keys.toList() + + // PRESIGN ROUND 1 + val presignRound1AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + presignRound1AllBroadcasts[i] = signers[i]!!.presignRound1(signerIds) + } + + // PRESIGN ROUND 2 + val presignRound2AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, presignRound1AllBroadcasts) + } + + // PRESIGN ROUND 3 + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, presignRound2AllBroadcasts) + } + + // PRESIGN OUTPUT + val bigRs = mutableMapOf() + for (i in signerIds) { + bigRs[i] = signers[i]!!.presignOutput(signerIds, presignRound3AllBroadcasts) + } + + // VERIFY OUTPUT CONSISTENCY + val referenceBigR = bigRs[signerIds[0]]!! + for (i in signerIds) { + assertEquals(referenceBigR, bigRs[i]) + } + + return referenceBigR + } + + private fun partialSignMessage(signers: Map, hash: ByteArray) : List { + val partialSignatures = mutableListOf() + + for (i in signers.keys.toList()) { + partialSignatures.add(signers[i]!!.partialSignMessage(hash)) + } + + return partialSignatures + } + + private fun scalePrecomputation(signers : Map) : Pair> { + val publicPoints = mutableMapOf() + val publicAllPrecomps = mutableMapOf>() + for (i in signers.keys.toList()) { + val (publicPrecomp, publicPoint) = signers[i]!!.scalePrecomputations(signers.keys.toList()) + publicPoints[i] = publicPoint + publicAllPrecomps[i] = publicPrecomp + } + + // Check output consistency + val referencePoint = publicPoints[signers.keys.first()]!! + val referencePrecomp = publicAllPrecomps[signers.keys.first()]!! + for (i in signers.keys) { + assertEquals(publicPoints[i], referencePoint) + } + + return referencePoint to referencePrecomp + } +} \ No newline at end of file diff --git a/src/test/kotlin/sign/ThresholdSignTest.kt b/src/test/kotlin/sign/ThresholdSignTest.kt index e3d9bc9..e69de29 100644 --- a/src/test/kotlin/sign/ThresholdSignTest.kt +++ b/src/test/kotlin/sign/ThresholdSignTest.kt @@ -1,201 +0,0 @@ -package sign - -import org.kotlincrypto.hash.sha2.SHA256 -import perun_network.ecdsa_threshold.ecdsa.PartialSignature -import perun_network.ecdsa_threshold.ecdsa.Point -import perun_network.ecdsa_threshold.ecdsa.Scalar -import perun_network.ecdsa_threshold.keygen.* -import perun_network.ecdsa_threshold.paillier.PaillierCipherText -import perun_network.ecdsa_threshold.presign.* -import perun_network.ecdsa_threshold.randomSigners -import perun_network.ecdsa_threshold.sign.SignParty -import perun_network.ecdsa_threshold.presign.ThresholdSigner -import perun_network.ecdsa_threshold.sign.combinePartialSignatures -import perun_network.ecdsa_threshold.sign.processPresignOutput -import java.math.BigInteger -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - - - - -class ThresholdSignTest { - @Test - fun testThresholdSign() { - val n = 7 - val t = 5 - - // Generate Precomputations (Assuming the secret primes are precomputed). - val (ids, secretPrecomps, publicPrecomps) = getSamplePrecomputations(n, t, n) // Use generatePrecomputation instead to generate new safe primes. - - // Message - val message = "Happy birthday to you!" - val hash = SHA256().digest(message.toByteArray()) - - // Determine signerIds - val signerIds = randomSigners(ids, t) - val publicKey = publicKeyFromShares(signerIds, publicPrecomps) - val (scaledPrecomps, scaledPublics, publicPoint) = scalePrecomputations(signerIds, secretPrecomps, publicPrecomps) - assertEquals(publicPoint.toPublicKey(), publicKey, "inconsistent public key") - - // Prepare the signers - val signers = mutableMapOf() - for (i in signerIds) { - signers[i] = ThresholdSigner( - id = i, - private = scaledPrecomps[i]!!, - publics = scaledPublics - ) - } - - // **PRESIGN** - // PRESIGN ROUND 1 - val presignRound1Inputs = mutableMapOf() - val presignRound1Outputs = mutableMapOf>() - val KShares = mutableMapOf() // K_i of every party - val GShares = mutableMapOf() // G_i of every party - - - for (i in signerIds) { - presignRound1Inputs[i] = PresignRound1Input( - ssid = scaledPrecomps[i]!!.ssid, - id = scaledPrecomps[i]!!.id, - publics = scaledPublics - ) - - // Produce Presign Round1 output - val (output, gammaShare, kShare, gNonce, kNonce, K, G) = presignRound1Inputs[i]!!.producePresignRound1Output(signerIds) - presignRound1Outputs[i] = output - signers[i]!!.gammaShare = gammaShare - signers[i]!!.kShare = kShare - signers[i]!!.gNonce = gNonce - signers[i]!!.kNonce = kNonce - KShares[i] = K - GShares[i] = G - } - - // PRESIGN ROUND 2 - val bigGammaShares = mutableMapOf() - val presignRound2Inputs = mutableMapOf() - val presignRound2Outputs = mutableMapOf>() - for (i in signerIds) { - // Prepare Presign Round 2 Inputs - presignRound2Inputs[i] = PresignRound2Input( - ssid = scaledPrecomps[i]!!.ssid, - id = scaledPrecomps[i]!!.id, - gammaShare = signers[i]!!.gammaShare!!, - secretECDSA = scaledPrecomps[i]!!.ecdsaShare, - secretPaillier = scaledPrecomps[i]!!.paillierSecret , - gNonce = signers[i]!!.gNonce!!, - publics = scaledPublics - ) - - // Verify Presign Round 1 Outputs - for ((j, presign1output) in presignRound1Outputs) { - if (j != i) { - assertTrue(presignRound2Inputs[i]!!.verifyPresignRound1Output(j, presign1output[i]!!), "failed to validate enc proof for K from $j to $i") - println("Validated presign round 1 output from $j to $i ") - } - } - - // Produce Presign Round2 output - val (presign2output, bigGammaShare) = presignRound2Inputs[i]!!.producePresignRound2Output( - signerIds, - KShares, - GShares) - - presignRound2Outputs[i] = presign2output - bigGammaShares[i] = bigGammaShare - } - - // PRESIGN ROUND 3 - val presignRound3Inputs = mutableMapOf() - val presignRound3Outputs = mutableMapOf>() - val deltaShares = mutableMapOf() - val bigDeltaShares = mutableMapOf() - val bigGammas = mutableMapOf() - for (i in signerIds) { - // Prepare Presign Round 3 Inputs - presignRound3Inputs[i] = PresignRound3Input( - ssid = scaledPrecomps[i]!!.ssid, - id = scaledPrecomps[i]!!.id, - gammaShare = signers[i]!!.gammaShare!!.value, - secretPaillier = scaledPrecomps[i]!!.paillierSecret, - kShare = signers[i]!!.kShare!!, - K = KShares[i]!!, - kNonce = signers[i]!!.kNonce!!, - secretECDSA = scaledPrecomps[i]!!.ecdsaShare.value, - publics = scaledPublics - ) - - // Verify Presign Round 2 Outputs - for ((j, presign2output) in presignRound2Outputs) { - if (j != i) { - assertTrue(presignRound3Inputs[i]!!.verifyPresignRound2Output( - j, - presign2output[i]!!, - KShares[i]!!, - GShares[j]!!, - scaledPublics[j]!!.publicEcdsa - ), "failed to validate presign round 2 output from $j to $i") - } - } - - // Produce Presign Round 3 output - val (presign3output, chiShare, deltaShare, bigDeltaShare, bigGamma) = presignRound3Inputs[i]!!.producePresignRound3Output( - signerIds, - bigGammaShares, - presignRound2Outputs) - - presignRound3Outputs[i] = presign3output - signers[i]!!.chiShare = Scalar(chiShare) - deltaShares[i] = deltaShare - bigDeltaShares[i] = bigDeltaShare - bigGammas[i] = bigGamma - } - - // ** PARTIAL SIGNING ** - - // process Presign output - val bigR = processPresignOutput( - signers= signerIds, - deltaShares = deltaShares, - bigDeltaShares = bigDeltaShares, - gamma= bigGammas[signerIds[0]]!! - ) - - val partialSigners = mutableMapOf() - val partialSignatures = mutableListOf() - for (i in signerIds) { - partialSigners[i] = SignParty( - ssid = scaledPrecomps[i]!!.ssid, - id = scaledPrecomps[i]!!.id, - publics = scaledPublics, - hash = hash, - ) - - // Verify Presign outputs - for (j in signerIds) { - if (j != i) { - assertTrue(partialSigners[i]!!.verifyPresignRound3Output(j, presignRound3Outputs[j]!![i]!!, KShares[j]!!), "failed to validate presign round 3 output from $j to $i") - println("Validated presign round 3 output from $j to $i ") - } - } - - // Produce partial signature - partialSignatures.add(partialSigners[i]!!.createPartialSignature( - kShare = signers[i]!!.kShare!!, - chiShare = signers[i]!!.chiShare!!, - bigR= bigR - )) - } - - - // ** ECDSA SIGNING ** - val ecdsaSignature= combinePartialSignatures(bigR, partialSignatures, publicPoint, hash) - - assertTrue(ecdsaSignature.verifySecp256k1(hash, publicKey), "failed to convert and verified ecdsa signature") - } - -} \ No newline at end of file diff --git a/src/test/kotlin/sign/aux/AuxTest.kt b/src/test/kotlin/sign/aux/AuxTest.kt new file mode 100644 index 0000000..ded1eaa --- /dev/null +++ b/src/test/kotlin/sign/aux/AuxTest.kt @@ -0,0 +1,726 @@ +package sign.aux + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.precomp.PublicPrecomputation +import perun_network.ecdsa_threshold.precomp.generateSessionId +import perun_network.ecdsa_threshold.precomp.getSamplePrecomputations +import perun_network.ecdsa_threshold.sign.Broadcast +import perun_network.ecdsa_threshold.sign.aux.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class AuxTest { + @Test + fun testAux() { + val n = 5 // Number of total parties. + val t = 3 // Threshold value. + + val ssid = generateSessionId() + + val auxSigners = mutableMapOf() + for (i in 1..n) { + auxSigners[i] = Aux( + ssid = ssid, + id = i, + threshold = t, + previousShare = null, + previousPublic = null, + ) + } + val parties = auxSigners.keys.toList() + + // AUX ROUND 1 + val round1AllBroadcasts = mutableMapOf>() + for (i in parties) { + round1AllBroadcasts[i] = auxSigners[i]!!.auxRound1(parties) + } + + // AUX ROUND 2 + val round2AllBroadcasts = mutableMapOf>() + for (i in parties) { + round2AllBroadcasts[i] = auxSigners[i]!!.auxRound2(parties) + } + + // AUX ROUND 3 + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + + // AUX OUTPUT + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, publics) = auxSigners[i]!!.auxOutput(parties, incomingRound2Broadcasts, incomingRound3Broadcasts) + allPublics[i] = publics + } + + // Check all public Points + val publicPrecomp = allPublics[1]!! + for (i in parties) { + for (j in parties) { + assertEquals(publicPrecomp[j], allPublics[i]!![j]!!, "Inconsistent public precomputation at index $j party $i") + } + } + } + + @Test + fun testAuxRefresh() { + val n = 5 // Number of total parties. + val t = 3 // Threshold value. + + val ssid = generateSessionId() + + val (_, secretPrecomps, publicPrecomps) = getSamplePrecomputations(n, t) + val publics = mutableMapOf>() + for (i in 1..n) { + val map = mutableMapOf() + for (j in 1..n) { + map[j] = publicPrecomps[j]!!.publicEcdsa + } + publics[i] = map + } + + val auxSigners = mutableMapOf() + for (i in 1..n) { + auxSigners[i] = Aux( + ssid = ssid, + id = i, + threshold = t, + previousShare = secretPrecomps[i]!!.ecdsaShare, + previousPublic = publics[i]!!, + ) + } + val parties = auxSigners.keys.toList() + + // AUX ROUND 1 + val round1AllBroadcasts = mutableMapOf>() + for (i in parties) { + round1AllBroadcasts[i] = auxSigners[i]!!.auxRound1(parties) + } + + // AUX ROUND 2 + val round2AllBroadcasts = mutableMapOf>() + for (i in parties) { + round2AllBroadcasts[i] = auxSigners[i]!!.auxRound2(parties) + } + + // AUX ROUND 3 + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + + // AUX OUTPUT + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, public) = auxSigners[i]!!.auxOutput(parties, incomingRound2Broadcasts, incomingRound3Broadcasts) + allPublics[i] = public + } + + // Check all public Points + val publicPrecomp = allPublics[1]!! + for (i in parties) { + for (j in parties) { + assertEquals(publicPrecomp[j], allPublics[i]!![j]!!, "Inconsistent public precomputation at index $j party $i") + } + } + } + + @Test + fun testAuxRound3Fails() { + val n = 5 // Number of total parties. + val t = 3 // Threshold value. + + val ssid = generateSessionId() + + val auxSigners = mutableMapOf() + for (i in 1..n) { + auxSigners[i] = Aux( + ssid = ssid, + id = i, + threshold = t, + previousShare = null, + previousPublic = null, + ) + } + val parties = auxSigners.keys.toList() + + // AUX ROUND 1 + val round1AllBroadcasts = mutableMapOf>() + for (i in parties) { + round1AllBroadcasts[i] = auxSigners[i]!!.auxRound1(parties) + } + + // AUX ROUND 2 + val round2AllBroadcasts = mutableMapOf>() + for (i in parties) { + round2AllBroadcasts[i] = auxSigners[i]!!.auxRound2(parties) + } + + // AUX ROUND 3 + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + + // Remove a round 2 broadcast entry. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts).filterKeys { j -> j == (i+1)%n + 1 } + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + } + + // Modify ssid. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = generateSessionId(), + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify sender's id. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.to, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify receiver's id. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.from, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify Paillier's Public. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[copyId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify Pedersen Public. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[copyId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify As. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[copyId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify prmProof. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i + 1) % n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[copyId]!!.prmProof, + ) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + + // Modify ePolyShare. + assertFailsWith { + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i + 1) % n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[copyId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + } + } + + @Test + fun testAuxOutputFails() { + val n = 5 // Number of total parties. + val t = 3 // Threshold value. + + val ssid = generateSessionId() + + val auxSigners = mutableMapOf() + for (i in 1..n) { + auxSigners[i] = Aux( + ssid = ssid, + id = i, + threshold = t, + previousShare = null, + previousPublic = null, + ) + } + val parties = auxSigners.keys.toList() + + // AUX ROUND 1 + val round1AllBroadcasts = mutableMapOf>() + for (i in parties) { + round1AllBroadcasts[i] = auxSigners[i]!!.auxRound1(parties) + } + + // AUX ROUND 2 + val round2AllBroadcasts = mutableMapOf>() + for (i in parties) { + round2AllBroadcasts[i] = auxSigners[i]!!.auxRound2(parties) + } + + // AUX ROUND 3 + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + round3AllBroadcasts[i] = auxSigners[i]!!.auxRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + + // AUX OUTPUT + // Remove an entry from Broadcasts. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts).filterKeys { j -> j == (i+1)%n + 1 } + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + incomingRound2Broadcasts, + incomingRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify ssid. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = generateSessionId(), + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + modifiedRound2Broadcasts, + incomingRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify sender's id. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.to, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + modifiedRound2Broadcasts, + incomingRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify receiver's id. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.from, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + modifiedRound2Broadcasts, + incomingRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify Paillier's Public. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[copyId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + modifiedRound2Broadcasts, + incomingRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify Pedersen Publics. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[modifiedId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[copyId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + modifiedRound2Broadcasts, + incomingRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify ModProof. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap() + val modifiedId = (i + 1) % n + 1 + val copyId = (i + 2) % n + 1 + modifiedRound3Broadcasts[modifiedId] = AuxRound3Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + modProof = incomingRound3Broadcasts[copyId]!!.modProof, + facProof = incomingRound3Broadcasts[modifiedId]!!.facProof, + schProofs = incomingRound3Broadcasts[modifiedId]!!.schProofs, + CShare = incomingRound3Broadcasts[modifiedId]!!.CShare, + ) + + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + incomingRound2Broadcasts, + modifiedRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify FacProof. + assertFailsWith { + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap() + val modifiedId = (i + 1) % n + 1 + val copyId = (i + 2) % n + 1 + modifiedRound3Broadcasts[modifiedId] = AuxRound3Broadcast( + ssid = incomingRound3Broadcasts[modifiedId]!!.ssid, + from = incomingRound3Broadcasts[modifiedId]!!.from, + to = incomingRound3Broadcasts[modifiedId]!!.to, + modProof = incomingRound3Broadcasts[modifiedId]!!.modProof, + facProof = incomingRound3Broadcasts[copyId]!!.facProof, + schProofs = incomingRound3Broadcasts[modifiedId]!!.schProofs, + CShare = incomingRound3Broadcasts[modifiedId]!!.CShare, + ) + + val (_, _) = auxSigners[i]!!.auxOutput( + parties, + incomingRound2Broadcasts, + modifiedRound3Broadcasts + ) + } + } + + // Modify Schnorr's Proof. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap() + val modifiedId = (i + 1) % n + 1 + val copyId = (i + 2) % n + 1 + modifiedRound3Broadcasts[modifiedId] = AuxRound3Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + modProof = incomingRound3Broadcasts[modifiedId]!!.modProof, + facProof = incomingRound3Broadcasts[modifiedId]!!.facProof, + schProofs = incomingRound3Broadcasts[copyId]!!.schProofs, + CShare = incomingRound3Broadcasts[modifiedId]!!.CShare, + ) + + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + incomingRound2Broadcasts, + modifiedRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify C_i. + assertFailsWith { + val allPublics = mutableMapOf>() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap() + val modifiedId = (i + 1) % n + 1 + val copyId = (i + 2) % n + 1 + modifiedRound3Broadcasts[modifiedId] = AuxRound3Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + modProof = incomingRound3Broadcasts[modifiedId]!!.modProof, + facProof = incomingRound3Broadcasts[modifiedId]!!.facProof, + schProofs = incomingRound3Broadcasts[modifiedId]!!.schProofs, + CShare = incomingRound3Broadcasts[copyId]!!.CShare, + ) + + val (_, publics) = auxSigners[i]!!.auxOutput( + parties, + incomingRound2Broadcasts, + modifiedRound3Broadcasts + ) + allPublics[i] = publics + } + } + + // Modify ePolyShare + assertFailsWith { + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = AuxRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + ePolyShare = incomingRound2Broadcasts[copyId]!!.ePolyShare, + As = incomingRound2Broadcasts[modifiedId]!!.As, + paillierPublic = incomingRound2Broadcasts[modifiedId]!!.paillierPublic, + pedersenPublic = incomingRound2Broadcasts[modifiedId]!!.pedersenPublic, + rid = incomingRound2Broadcasts[modifiedId]!!.rid, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare, + prmProof = incomingRound2Broadcasts[modifiedId]!!.prmProof, + ) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, _) = auxSigners[i]!!.auxOutput( + parties, + modifiedRound2Broadcasts, + incomingRound3Broadcasts + ) + } + } + } + + private fun filterIncomingBroadcast(id : Int, broadcasts : Map>) : Map { + val incomingBroadcasts = mutableMapOf() + for ((j, broadcast) in broadcasts) { + if (j != id) { + incomingBroadcasts[j] = broadcast[id]!! + } + } + return incomingBroadcasts + } +} \ No newline at end of file diff --git a/src/test/kotlin/sign/keygen/KeygenTest.kt b/src/test/kotlin/sign/keygen/KeygenTest.kt new file mode 100644 index 0000000..f1a0bdb --- /dev/null +++ b/src/test/kotlin/sign/keygen/KeygenTest.kt @@ -0,0 +1,392 @@ +package sign.keygen + +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.precomp.generateSessionId +import perun_network.ecdsa_threshold.sign.Broadcast +import perun_network.ecdsa_threshold.sign.keygen.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class KeygenTest { + @Test + fun testKeygen() { + val n = 5 // Number of total parties. + + val ssid = generateSessionId() + + val keygenSigners = mutableMapOf() + for (i in 1..n) { + keygenSigners[i] = Keygen( + ssid = ssid, + id = i + ) + } + val parties = keygenSigners.keys.toList() + + // KEYGEN ROUND 1 + val round1AllBroadcasts = mutableMapOf>() + for (i in parties) { + round1AllBroadcasts[i] = keygenSigners[i]!!.keygenRound1(parties) + } + + // KEYGEN ROUND 2 + val round2AllBroadcasts = mutableMapOf>() + for (i in parties) { + round2AllBroadcasts[i] = keygenSigners[i]!!.keygenRound2(parties) + } + + // KEYGEN ROUND 3 + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + round3AllBroadcasts[i] = keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + + // KEYGEN OUTPUT + val publics = mutableMapOf() + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val (_, _, public) = keygenSigners[i]!!.keygenOutput(parties, incomingRound2Broadcasts, incomingRound3Broadcasts) + publics[i] = public + } + + // Check all public Points + val publicPoint = publics[1]!! + for (i in parties) { + assertEquals(publicPoint, publics[i]) + } + } + + @Test + fun testKeygenRound3Fails() { + val n = 5 // Number of total parties. + + val ssid = generateSessionId() + + val keygenSigners = mutableMapOf() + for (i in 1..n) { + keygenSigners[i] = Keygen( + ssid = ssid, + id = i + ) + } + val parties = keygenSigners.keys.toList() + + // KEYGEN ROUND 1 + val round1AllBroadcasts = mutableMapOf>() + for (i in parties) { + round1AllBroadcasts[i] = keygenSigners[i]!!.keygenRound1(parties) + } + + // KEYGEN ROUND 2 + val round2AllBroadcasts = mutableMapOf>() + for (i in parties) { + round2AllBroadcasts[i] = keygenSigners[i]!!.keygenRound2(parties) + } + + // KEYGEN ROUND 3 + + // Filter an entry + assertFailsWith { + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap().filterKeys { j -> j == (i+1)%n + 1 } + round3AllBroadcasts[i] = + keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify ssid. + assertFailsWith { + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + modifiedRound2Broadcasts[modifiedId] = KeygenRound2Broadcast( + ssid = generateSessionId(), + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + rhoShare = incomingRound2Broadcasts[modifiedId]!!.rhoShare, + XShare = incomingRound2Broadcasts[modifiedId]!!.XShare, + AShare = incomingRound2Broadcasts[modifiedId]!!.AShare, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare + ) + round3AllBroadcasts[i] = + keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify sender's id. + assertFailsWith { + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = KeygenRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[copyId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + rhoShare = incomingRound2Broadcasts[modifiedId]!!.rhoShare, + XShare = incomingRound2Broadcasts[modifiedId]!!.XShare, + AShare = incomingRound2Broadcasts[modifiedId]!!.AShare, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare + ) + round3AllBroadcasts[i] = + keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify receiver's id. + assertFailsWith { + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = KeygenRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = copyId, + rhoShare = incomingRound2Broadcasts[modifiedId]!!.rhoShare, + XShare = incomingRound2Broadcasts[modifiedId]!!.XShare, + AShare = incomingRound2Broadcasts[modifiedId]!!.AShare, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare + ) + round3AllBroadcasts[i] = + keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify rho_i. + assertFailsWith { + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = KeygenRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + rhoShare = incomingRound2Broadcasts[copyId]!!.rhoShare, + XShare = incomingRound2Broadcasts[modifiedId]!!.XShare, + AShare = incomingRound2Broadcasts[modifiedId]!!.AShare, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare + ) + round3AllBroadcasts[i] = + keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify X_i. + assertFailsWith { + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = KeygenRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + rhoShare = incomingRound2Broadcasts[modifiedId]!!.rhoShare, + XShare = incomingRound2Broadcasts[copyId]!!.XShare, + AShare = incomingRound2Broadcasts[modifiedId]!!.AShare, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare + ) + round3AllBroadcasts[i] = + keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify A_i. + assertFailsWith { + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = KeygenRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + rhoShare = incomingRound2Broadcasts[modifiedId]!!.rhoShare, + XShare = incomingRound2Broadcasts[modifiedId]!!.XShare, + AShare = incomingRound2Broadcasts[copyId]!!.AShare, + uShare = incomingRound2Broadcasts[modifiedId]!!.uShare + ) + round3AllBroadcasts[i] = + keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + + // Modify u_i. + assertFailsWith { + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound2Broadcasts = incomingRound2Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound2Broadcasts[modifiedId] = KeygenRound2Broadcast( + ssid = incomingRound2Broadcasts[modifiedId]!!.ssid, + from = incomingRound2Broadcasts[modifiedId]!!.from, + to = incomingRound2Broadcasts[modifiedId]!!.to, + rhoShare = incomingRound2Broadcasts[modifiedId]!!.rhoShare, + XShare = incomingRound2Broadcasts[modifiedId]!!.XShare, + AShare = incomingRound2Broadcasts[modifiedId]!!.AShare, + uShare = incomingRound2Broadcasts[copyId]!!.uShare + ) + round3AllBroadcasts[i] = + keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, modifiedRound2Broadcasts) + } + } + } + + @Test + fun testKeygenOutputFails() { + val n = 5 // Number of total parties. + + val ssid = generateSessionId() + + val keygenSigners = mutableMapOf() + for (i in 1..n) { + keygenSigners[i] = Keygen( + ssid = ssid, + id = i + ) + } + val parties = keygenSigners.keys.toList() + + // KEYGEN ROUND 1 + val round1AllBroadcasts = mutableMapOf>() + for (i in parties) { + round1AllBroadcasts[i] = keygenSigners[i]!!.keygenRound1(parties) + } + + // KEYGEN ROUND 2 + val round2AllBroadcasts = mutableMapOf>() + for (i in parties) { + round2AllBroadcasts[i] = keygenSigners[i]!!.keygenRound2(parties) + } + + // KEYGEN ROUND 3 + val round3AllBroadcasts = mutableMapOf>() + for (i in parties) { + val incomingRound1Broadcasts = filterIncomingBroadcast(i, round1AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + round3AllBroadcasts[i] = keygenSigners[i]!!.keygenRound3(parties, incomingRound1Broadcasts, incomingRound2Broadcasts) + } + + val publics = mutableMapOf() + assertFailsWith { + for (i in parties) { + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap().filterKeys { j -> j == (i+1)%n + 1 } + val (_, _, public) = keygenSigners[i]!!.keygenOutput(parties, incomingRound2Broadcasts, modifiedRound3Broadcasts) + publics[i] = public + } + } + + // Modify ssid. + assertFailsWith { + for (i in parties) { + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + modifiedRound3Broadcasts[modifiedId] = KeygenRound3Broadcast( + ssid = generateSessionId(), + from = incomingRound3Broadcasts[modifiedId]!!.from, + to = incomingRound3Broadcasts[modifiedId]!!.to, + schnorrProof = incomingRound3Broadcasts[modifiedId]!!.schnorrProof, + ) + val (_, _, _) = keygenSigners[i]!!.keygenOutput(parties, incomingRound2Broadcasts, modifiedRound3Broadcasts) + } + } + + // Modify sender's id. + assertFailsWith { + for (i in parties) { + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound3Broadcasts[modifiedId] = KeygenRound3Broadcast( + ssid = incomingRound3Broadcasts[modifiedId]!!.ssid, + from = incomingRound3Broadcasts[copyId]!!.from, + to = incomingRound3Broadcasts[modifiedId]!!.to, + schnorrProof = incomingRound3Broadcasts[modifiedId]!!.schnorrProof, + ) + val (_, _, _) = keygenSigners[i]!!.keygenOutput(parties, incomingRound2Broadcasts, modifiedRound3Broadcasts) + } + } + + // Modify receiver's id. + assertFailsWith { + for (i in parties) { + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound3Broadcasts[modifiedId] = KeygenRound3Broadcast( + ssid = incomingRound3Broadcasts[modifiedId]!!.ssid, + from = incomingRound3Broadcasts[copyId]!!.from, + to = copyId, + schnorrProof = incomingRound3Broadcasts[modifiedId]!!.schnorrProof, + ) + val (_, _, _) = keygenSigners[i]!!.keygenOutput(parties, incomingRound2Broadcasts, modifiedRound3Broadcasts) + } + } + + // Modify Schnorr's Proof + assertFailsWith { + for (i in parties) { + val incomingRound3Broadcasts = filterIncomingBroadcast(i, round3AllBroadcasts) + val incomingRound2Broadcasts = filterIncomingBroadcast(i, round2AllBroadcasts) + val modifiedRound3Broadcasts = incomingRound3Broadcasts.toMutableMap() + val modifiedId = (i+1)%n + 1 + val copyId = (i+2)%n + 1 + modifiedRound3Broadcasts[modifiedId] = KeygenRound3Broadcast( + ssid = incomingRound3Broadcasts[modifiedId]!!.ssid, + from = incomingRound3Broadcasts[modifiedId]!!.from, + to = incomingRound3Broadcasts[modifiedId]!!.to, + schnorrProof = incomingRound3Broadcasts[copyId]!!.schnorrProof, + ) + val (_, _, _) = keygenSigners[i]!!.keygenOutput(parties, incomingRound2Broadcasts, modifiedRound3Broadcasts) + } + } + } + + private fun filterIncomingBroadcast(id : Int, broadcasts : Map>) : Map { + val incomingBroadcasts = mutableMapOf() + for ((j, broadcast) in broadcasts) { + if (j != id) { + incomingBroadcasts[j] = broadcast[id]!! + } + } + return incomingBroadcasts + } +} \ No newline at end of file diff --git a/src/test/kotlin/sign/presign/PresignTest.kt b/src/test/kotlin/sign/presign/PresignTest.kt new file mode 100644 index 0000000..f407739 --- /dev/null +++ b/src/test/kotlin/sign/presign/PresignTest.kt @@ -0,0 +1,957 @@ +package sign.presign + +import org.kotlincrypto.hash.sha2.SHA256 +import perun_network.ecdsa_threshold.ecdsa.PartialSignature +import perun_network.ecdsa_threshold.ecdsa.Point +import perun_network.ecdsa_threshold.math.sampleScalar +import perun_network.ecdsa_threshold.paillier.PaillierCipherText +import perun_network.ecdsa_threshold.precomp.generateSessionId +import perun_network.ecdsa_threshold.precomp.getSamplePrecomputations +import perun_network.ecdsa_threshold.precomp.publicKeyFromShares +import perun_network.ecdsa_threshold.precomp.scalePrecomputations +import perun_network.ecdsa_threshold.sign.Broadcast +import perun_network.ecdsa_threshold.sign.combinePartialSignatures +import perun_network.ecdsa_threshold.sign.presign.* +import java.math.BigInteger +import kotlin.test.* + + +class PresignTest { + @Test + fun testThresholdSign() { + val n = 5 // Number of total parties. + val t = 3 // Threshold of minimum required signers. + + // Generate Precomputations (Assuming the secret primes are precomputed). + val (ids, secretPrecomps, publicPrecomps) = getSamplePrecomputations(n, t) // Use generatePrecomputation instead to generate new safe primes. + + // Message + val message = "hello" + val hash = SHA256().digest(message.toByteArray()) + + // Determine signerIds + val signerIds = randomSignerIDs(ids, t) + val publicKey = publicKeyFromShares(signerIds, publicPrecomps) + val (scaledPrecomps, scaledPublics, publicPoint) = scalePrecomputations(signerIds, secretPrecomps, publicPrecomps) + assertEquals(publicKey, publicPoint.toPublicKey()) + + // Prepare the signers + val signers = mutableMapOf() + for (i in signerIds) { + signers[i] = Presigner( + id = i, + private = scaledPrecomps[i]!!, + publicPrecomps = scaledPublics + ) + } + + // **PRESIGN** + // PRESIGN ROUND 1 + val presignRound1AllBroadcasts = mutableMapOf>() + val Ks = mutableMapOf() // K_i of every party + val elGamalPublics = mutableMapOf() + + + for (i in signerIds) { + presignRound1AllBroadcasts[i] = signers[i]!!.presignRound1(signerIds) + Ks[i] = signers[i]!!.K!! + elGamalPublics[i] = signers[i]!!.elGamalPublic!! + } + + // PRESIGN ROUND 2 + val bigGammaShares = mutableMapOf() + val presignRound2AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, presignRound1Broadcasts) + + bigGammaShares[i] = signers[i]!!.bigGammaShare!! + } + + + // PRESIGN ROUND 3 + val presignRound3AllBroadcasts = mutableMapOf>() + val deltaShares = mutableMapOf() + val bigDeltaShares = mutableMapOf() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, presignRound2Broadcasts) + deltaShares[i] = signers[i]!!.deltaShare!! + bigDeltaShares[i] = signers[i]!!.bigDeltaShare!! + } + + // PROCESS PRESIGN OUTPUTS + for (i in signerIds) { + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + signers[i]!!.processPresignOutput(signerIds, presignRound3Broadcasts, elGamalPublics, deltaShares, bigDeltaShares) + } + + // ** PARTIAL SIGNING ** + val partialSignatures = mutableListOf() + + for (i in signerIds) { + partialSignatures.add(signers[i]!!.partialSignMessage(scaledPublics[i]!!.ssid, hash)) + } + + + // ** ECDSA SIGNING ** + val ecdsaSignature= combinePartialSignatures(signers[signerIds[0]]!!.bigR!!, partialSignatures, publicPoint, hash) + + assertTrue(ecdsaSignature.verifySecp256k1(hash, publicKey), "failed to convert and verified ecdsa signature") + } + + @Test + fun testPresignRound2Fails() { + val n = 5 // Number of total parties. + val t = 3 // Threshold of minimum required signers. + + // Generate Precomputations (Assuming the secret primes are precomputed). + val (ids, secretPrecomps, publicPrecomps) = getSamplePrecomputations(n, t) // Use generatePrecomputation instead to generate new safe primes. + + // Message + val message = "hello" + SHA256().digest(message.toByteArray()) + + // Determine signerIds + val signerIds = randomSignerIDs(ids, t) + val publicKey = publicKeyFromShares(signerIds, publicPrecomps) + val (scaledPrecomps, scaledPublics, publicPoint) = scalePrecomputations(signerIds, secretPrecomps, publicPrecomps) + assertEquals(publicKey, publicPoint.toPublicKey()) + + // Prepare the signers + val signers = mutableMapOf() + for (i in signerIds) { + signers[i] = Presigner( + id = i, + private = scaledPrecomps[i]!!, + publicPrecomps = scaledPublics + ) + } + + // **PRESIGN** + // PRESIGN ROUND 1 + val presignRound1AllBroadcasts = mutableMapOf>() + val Ks = mutableMapOf() // K_i of every party + val elGamalPublics = mutableMapOf() + + + for (i in signerIds) { + presignRound1AllBroadcasts[i] = signers[i]!!.presignRound1(signerIds) + Ks[i] = signers[i]!!.K!! + elGamalPublics[i] = signers[i]!!.elGamalPublic!! + } + + // PRESIGN ROUND 2 + mutableMapOf() + val presignRound2AllBroadcasts = mutableMapOf>() + + // Missing Round 1 Broadcast entry. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap().filterKeys { id -> id == modifiedId } + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + + // Modify ssid. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap() + modifiedRound1Broadcasts[modifiedId] = PresignRound1Broadcast( + ssid = generateSessionId(), + from = modifiedRound1Broadcasts[modifiedId]!!.from, + to = modifiedRound1Broadcasts[modifiedId]!!.to, + K = modifiedRound1Broadcasts[modifiedId]!!.K, + G = modifiedRound1Broadcasts[modifiedId]!!.G, + elGamalPublic = modifiedRound1Broadcasts[modifiedId]!!.elGamalPublic, + proof0 = modifiedRound1Broadcasts[modifiedId]!!.proof0, + proof1 = modifiedRound1Broadcasts[modifiedId]!!.proof1, + ) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + + // Modify sender's id. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap() + modifiedRound1Broadcasts[modifiedId] = PresignRound1Broadcast( + ssid = modifiedRound1Broadcasts[modifiedId]!!.ssid, + from = modifiedRound1Broadcasts[copyId]!!.from, + to = modifiedRound1Broadcasts[modifiedId]!!.to, + K = modifiedRound1Broadcasts[modifiedId]!!.K, + G = modifiedRound1Broadcasts[modifiedId]!!.G, + elGamalPublic = modifiedRound1Broadcasts[modifiedId]!!.elGamalPublic, + proof0 = modifiedRound1Broadcasts[modifiedId]!!.proof0, + proof1 = modifiedRound1Broadcasts[modifiedId]!!.proof1, + ) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + + // Modify receiver's id. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap() + modifiedRound1Broadcasts[modifiedId] = PresignRound1Broadcast( + ssid = modifiedRound1Broadcasts[modifiedId]!!.ssid, + from = modifiedRound1Broadcasts[modifiedId]!!.from, + to = copyId, + K = modifiedRound1Broadcasts[modifiedId]!!.K, + G = modifiedRound1Broadcasts[modifiedId]!!.G, + elGamalPublic = modifiedRound1Broadcasts[modifiedId]!!.elGamalPublic, + proof0 = modifiedRound1Broadcasts[modifiedId]!!.proof0, + proof1 = modifiedRound1Broadcasts[modifiedId]!!.proof1, + ) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + + // Modify K_i. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap() + modifiedRound1Broadcasts[modifiedId] = PresignRound1Broadcast( + ssid = modifiedRound1Broadcasts[modifiedId]!!.ssid, + from = modifiedRound1Broadcasts[modifiedId]!!.from, + to = modifiedRound1Broadcasts[modifiedId]!!.to, + K = modifiedRound1Broadcasts[copyId]!!.K, + G = modifiedRound1Broadcasts[modifiedId]!!.G, + elGamalPublic = modifiedRound1Broadcasts[modifiedId]!!.elGamalPublic, + proof0 = modifiedRound1Broadcasts[modifiedId]!!.proof0, + proof1 = modifiedRound1Broadcasts[modifiedId]!!.proof1, + ) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + + // Modify G_i. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap() + modifiedRound1Broadcasts[modifiedId] = PresignRound1Broadcast( + ssid = modifiedRound1Broadcasts[modifiedId]!!.ssid, + from = modifiedRound1Broadcasts[modifiedId]!!.from, + to = modifiedRound1Broadcasts[modifiedId]!!.to, + K = modifiedRound1Broadcasts[modifiedId]!!.K, + G = modifiedRound1Broadcasts[copyId]!!.G, + elGamalPublic = modifiedRound1Broadcasts[modifiedId]!!.elGamalPublic, + proof0 = modifiedRound1Broadcasts[modifiedId]!!.proof0, + proof1 = modifiedRound1Broadcasts[modifiedId]!!.proof1, + ) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + + // Modify ElGamal Public. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap() + modifiedRound1Broadcasts[modifiedId] = PresignRound1Broadcast( + ssid = modifiedRound1Broadcasts[modifiedId]!!.ssid, + from = modifiedRound1Broadcasts[modifiedId]!!.from, + to = modifiedRound1Broadcasts[modifiedId]!!.to, + K = modifiedRound1Broadcasts[modifiedId]!!.K, + G = modifiedRound1Broadcasts[modifiedId]!!.G, + elGamalPublic = modifiedRound1Broadcasts[copyId]!!.elGamalPublic, + proof0 = modifiedRound1Broadcasts[modifiedId]!!.proof0, + proof1 = modifiedRound1Broadcasts[modifiedId]!!.proof1, + ) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + + // Modify Enc-Elg Proof 1. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap() + modifiedRound1Broadcasts[modifiedId] = PresignRound1Broadcast( + ssid = modifiedRound1Broadcasts[modifiedId]!!.ssid, + from = modifiedRound1Broadcasts[modifiedId]!!.from, + to = modifiedRound1Broadcasts[modifiedId]!!.to, + K = modifiedRound1Broadcasts[modifiedId]!!.K, + G = modifiedRound1Broadcasts[modifiedId]!!.G, + elGamalPublic = modifiedRound1Broadcasts[modifiedId]!!.elGamalPublic, + proof0 = modifiedRound1Broadcasts[copyId]!!.proof0, + proof1 = modifiedRound1Broadcasts[modifiedId]!!.proof1, + ) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + + // Modify Enc-Elg Proof 2. + assertFailsWith { + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound1Broadcasts = presignRound1Broadcasts.toMutableMap() + modifiedRound1Broadcasts[modifiedId] = PresignRound1Broadcast( + ssid = modifiedRound1Broadcasts[modifiedId]!!.ssid, + from = modifiedRound1Broadcasts[modifiedId]!!.from, + to = modifiedRound1Broadcasts[modifiedId]!!.to, + K = modifiedRound1Broadcasts[modifiedId]!!.K, + G = modifiedRound1Broadcasts[modifiedId]!!.G, + elGamalPublic = modifiedRound1Broadcasts[modifiedId]!!.elGamalPublic, + proof0 = modifiedRound1Broadcasts[modifiedId]!!.proof0, + proof1 = modifiedRound1Broadcasts[copyId]!!.proof1, + ) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, modifiedRound1Broadcasts) + } + } + } + + @Test + fun testPresignRound3Fails() { + val n = 5 // Number of total parties. + val t = 3 // Threshold of minimum required signers. + + // Generate Precomputations (Assuming the secret primes are precomputed). + val (ids, secretPrecomps, publicPrecomps) = getSamplePrecomputations(n, t) // Use generatePrecomputation instead to generate new safe primes. + + // Message + val message = "hello" + SHA256().digest(message.toByteArray()) + + // Determine signerIds + val signerIds = randomSignerIDs(ids, t) + val publicKey = publicKeyFromShares(signerIds, publicPrecomps) + val (scaledPrecomps, scaledPublics, publicPoint) = scalePrecomputations(signerIds, secretPrecomps, publicPrecomps) + assertEquals(publicKey, publicPoint.toPublicKey()) + + // Prepare the signers + val signers = mutableMapOf() + for (i in signerIds) { + signers[i] = Presigner( + id = i, + private = scaledPrecomps[i]!!, + publicPrecomps = scaledPublics + ) + } + + // **PRESIGN** + // PRESIGN ROUND 1 + val presignRound1AllBroadcasts = mutableMapOf>() + val Ks = mutableMapOf() // K_i of every party + val elGamalPublics = mutableMapOf() + + + for (i in signerIds) { + presignRound1AllBroadcasts[i] = signers[i]!!.presignRound1(signerIds) + Ks[i] = signers[i]!!.K!! + elGamalPublics[i] = signers[i]!!.elGamalPublic!! + } + + // PRESIGN ROUND 2 + val bigGammaShares = mutableMapOf() + val presignRound2AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, presignRound1Broadcasts) + + bigGammaShares[i] = signers[i]!!.bigGammaShare!! + } + + // PRESIGN ROUND 3 + // Modify ElGamal's Public collection. + assertFailsWith { + val elGamal2 = elGamalPublics.toMap().toMutableMap() + elGamal2[signerIds[0]] = elGamal2[signerIds.last()]!! + val presignRound3AllBroadcasts = mutableMapOf>() + val deltaShares = mutableMapOf() + val bigDeltaShares = mutableMapOf() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamal2, presignRound2Broadcasts) + deltaShares[i] = signers[i]!!.deltaShare!! + bigDeltaShares[i] = signers[i]!!.bigDeltaShare!! + } + } + + // Modify ssid. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = generateSessionId(), + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify sender's id. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val copyId = signerIds[(signerIds.indexOf(i)+2)%signerIds.size] + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[copyId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify receiver's id. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = copyId, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify bigGammaShare. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[copyId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify Delta_D. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[copyId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify Delta_F. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds.get((signerIds.indexOf(i)+1)%signerIds.size) + val copyId = signerIds.get((signerIds.indexOf(i)+2)%signerIds.size) + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[copyId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify Delta's Proof. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val copyId = signerIds[(signerIds.indexOf(i)+2)%signerIds.size] + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[copyId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify Chi_D. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val copyId = signerIds[(signerIds.indexOf(i)+2)%signerIds.size] + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[copyId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify Chi_F. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val copyId = signerIds[(signerIds.indexOf(i)+2)%signerIds.size] + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[copyId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify Chi's Proof. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val copyId = signerIds[(signerIds.indexOf(i)+2)%signerIds.size] + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[copyId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[modifiedId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + // Modify ELog's Proof. + assertFailsWith { + val presignRound3AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val copyId = signerIds[(signerIds.indexOf(i)+2)%signerIds.size] + val modifiedRound2Broadcasts = presignRound2Broadcasts.toMutableMap() + modifiedRound2Broadcasts[modifiedId] = PresignRound2Broadcast( + ssid = modifiedRound2Broadcasts[modifiedId]!!.ssid, + from = modifiedRound2Broadcasts[modifiedId]!!.from, + to = modifiedRound2Broadcasts[modifiedId]!!.to, + bigGammaShare = modifiedRound2Broadcasts[modifiedId]!!.bigGammaShare, + deltaD = modifiedRound2Broadcasts[modifiedId]!!.deltaD, + deltaF = modifiedRound2Broadcasts[modifiedId]!!.deltaF, + deltaProof = modifiedRound2Broadcasts[modifiedId]!!.deltaProof, + chiD = modifiedRound2Broadcasts[modifiedId]!!.chiD, + chiF = modifiedRound2Broadcasts[modifiedId]!!.chiF, + chiProof = modifiedRound2Broadcasts[modifiedId]!!.chiProof, + elogProof = modifiedRound2Broadcasts[copyId]!!.elogProof, + chiBeta = modifiedRound2Broadcasts[modifiedId]!!.chiBeta, + deltaBeta = modifiedRound2Broadcasts[modifiedId]!!.deltaBeta, + ) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, modifiedRound2Broadcasts) + } + } + + } + + @Test + fun testPresignFails() { + val n = 5 // Number of total parties. + val t = 3 // Threshold of minimum required signers. + + // Generate Precomputations (Assuming the secret primes are precomputed). + val (ids, secretPrecomps, publicPrecomps) = getSamplePrecomputations(n, t) // Use generatePrecomputation instead to generate new safe primes. + + // Message. + val message = "hello" + SHA256().digest(message.toByteArray()) + + // Determine signerIds. + val signerIds = randomSignerIDs(ids, t) + val publicKey = publicKeyFromShares(signerIds, publicPrecomps) + val (scaledPrecomps, scaledPublics, publicPoint) = scalePrecomputations(signerIds, secretPrecomps, publicPrecomps) + assertEquals(publicKey, publicPoint.toPublicKey()) + + // Prepare the signers + val signers = mutableMapOf() + for (i in signerIds) { + signers[i] = Presigner( + id = i, + private = scaledPrecomps[i]!!, + publicPrecomps = scaledPublics + ) + } + + // **PRESIGN** + // PRESIGN ROUND 1 + val presignRound1AllBroadcasts = mutableMapOf>() + val Ks = mutableMapOf() // K_i of every party + val elGamalPublics = mutableMapOf() + + + for (i in signerIds) { + presignRound1AllBroadcasts[i] = signers[i]!!.presignRound1(signerIds) + Ks[i] = signers[i]!!.K!! + elGamalPublics[i] = signers[i]!!.elGamalPublic!! + } + + // PRESIGN ROUND 2 + val bigGammaShares = mutableMapOf() + val presignRound2AllBroadcasts = mutableMapOf>() + for (i in signerIds) { + val presignRound1Broadcasts = filterIncomingBroadcast(i, presignRound1AllBroadcasts) + presignRound2AllBroadcasts[i] = signers[i]!!.presignRound2(signerIds, Ks, presignRound1Broadcasts) + + bigGammaShares[i] = signers[i]!!.bigGammaShare!! + } + + // PRESIGN ROUND 3 + assertFailsWith { + val elGamal2 = elGamalPublics.toMap().toMutableMap() + elGamal2[signerIds[0]] = elGamal2[signerIds.last()]!! + val presignRound3AllBroadcasts = mutableMapOf>() + val deltaShares = mutableMapOf() + val bigDeltaShares = mutableMapOf() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamal2, presignRound2Broadcasts) + deltaShares[i] = signers[i]!!.deltaShare!! + bigDeltaShares[i] = signers[i]!!.bigDeltaShare!! + } + } + + + val presignRound3AllBroadcasts = mutableMapOf>() + val deltaShares = mutableMapOf() + val bigDeltaShares = mutableMapOf() + for (i in signerIds) { + val presignRound2Broadcasts = filterIncomingBroadcast(i, presignRound2AllBroadcasts) + presignRound3AllBroadcasts[i] = signers[i]!!.presignRound3(signerIds, bigGammaShares, elGamalPublics, presignRound2Broadcasts) + deltaShares[i] = signers[i]!!.deltaShare!! + bigDeltaShares[i] = signers[i]!!.bigDeltaShare!! + } + + + // PROCESS PRESIGN OUTPUT + + // Filter a random entry. + assertFailsWith { + for (i in signerIds) { + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val modifiedRound3Broadcasts = presignRound3Broadcasts.toMutableMap().filterKeys{id -> id == modifiedId} + signers[i]!!.processPresignOutput( + signerIds, + modifiedRound3Broadcasts, + elGamalPublics, + deltaShares, + bigDeltaShares + ) + } + } + + // Modify a Round 3 Broadcast. + assertFailsWith { + for (i in signerIds) { + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + val modifiedRound3Broadcast = presignRound3Broadcasts.toMutableMap() + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + modifiedRound3Broadcast[modifiedId] = PresignRound3Broadcast( + ssid = generateSessionId(), + from = modifiedRound3Broadcast[modifiedId]!!.from, + to = modifiedRound3Broadcast[modifiedId]!!.to, + chiShare = modifiedRound3Broadcast[modifiedId]!!.chiShare, + deltaShare = modifiedRound3Broadcast[modifiedId]!!.deltaShare, + bigDeltaShare = modifiedRound3Broadcast[modifiedId]!!.bigDeltaShare, + gamma = modifiedRound3Broadcast[modifiedId]!!.gamma, + elogProof = modifiedRound3Broadcast[modifiedId]!!.elogProof + ) + signers[i]!!.processPresignOutput( + signerIds, + modifiedRound3Broadcast, + elGamalPublics, + deltaShares, + bigDeltaShares + ) + } + } + + // Modify sender's id. + assertFailsWith { + for (i in signerIds) { + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + val modifiedRound3Broadcast = presignRound3Broadcasts.toMutableMap() + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + modifiedRound3Broadcast[modifiedId] = PresignRound3Broadcast( + ssid = modifiedRound3Broadcast[modifiedId]!!.ssid, + from = modifiedRound3Broadcast[modifiedId]!!.from + 1, + to = modifiedRound3Broadcast[modifiedId]!!.to, + chiShare = modifiedRound3Broadcast[modifiedId]!!.chiShare, + deltaShare = modifiedRound3Broadcast[modifiedId]!!.deltaShare, + bigDeltaShare = modifiedRound3Broadcast[modifiedId]!!.bigDeltaShare, + gamma = modifiedRound3Broadcast[modifiedId]!!.gamma, + elogProof = modifiedRound3Broadcast[modifiedId]!!.elogProof + ) + signers[i]!!.processPresignOutput( + signerIds, + modifiedRound3Broadcast, + elGamalPublics, + deltaShares, + bigDeltaShares + ) + } + } + + // Modify receiver's id. + assertFailsWith { + for (i in signerIds) { + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + val modifiedRound3Broadcast = presignRound3Broadcasts.toMutableMap() + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val copyId = signerIds[(signerIds.indexOf(i)+2)%signerIds.size] + modifiedRound3Broadcast[modifiedId] = PresignRound3Broadcast( + ssid = modifiedRound3Broadcast[modifiedId]!!.ssid, + from = modifiedRound3Broadcast[modifiedId]!!.from, + to = modifiedRound3Broadcast[copyId]!!.to+5, + chiShare = modifiedRound3Broadcast[modifiedId]!!.chiShare, + deltaShare = modifiedRound3Broadcast[modifiedId]!!.deltaShare, + bigDeltaShare = modifiedRound3Broadcast[modifiedId]!!.bigDeltaShare, + gamma = modifiedRound3Broadcast[modifiedId]!!.gamma, + elogProof = modifiedRound3Broadcast[modifiedId]!!.elogProof + ) + signers[i]!!.processPresignOutput( + signerIds, + modifiedRound3Broadcast, + elGamalPublics, + deltaShares, + bigDeltaShares + ) + } + } + + // Modify ELog's Proof. + assertFailsWith { + for (i in signerIds) { + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + val modifiedRound3Broadcast = presignRound3Broadcasts.toMutableMap() + val modifiedId = signerIds[(signerIds.indexOf(i)+1)%signerIds.size] + val copyId = signerIds[(signerIds.indexOf(i)+2)%signerIds.size] + modifiedRound3Broadcast[modifiedId] = PresignRound3Broadcast( + ssid = modifiedRound3Broadcast[modifiedId]!!.ssid, + from = modifiedRound3Broadcast[modifiedId]!!.from, + to = modifiedRound3Broadcast[modifiedId]!!.to, + chiShare = modifiedRound3Broadcast[modifiedId]!!.chiShare, + deltaShare = modifiedRound3Broadcast[modifiedId]!!.deltaShare, + bigDeltaShare = modifiedRound3Broadcast[modifiedId]!!.bigDeltaShare, + gamma = modifiedRound3Broadcast[modifiedId]!!.gamma, + elogProof = modifiedRound3Broadcast[copyId]!!.elogProof + ) + signers[i]!!.processPresignOutput( + signerIds, + modifiedRound3Broadcast, + elGamalPublics, + deltaShares, + bigDeltaShares + ) + } + } + + // Modify ElGamal Public. + assertFailsWith { + elGamalPublics[signerIds[0]] = elGamalPublics[signerIds.last()]!! + for (i in signerIds) { + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + signers[i]!!.processPresignOutput( + signerIds, + presignRound3Broadcasts, + elGamalPublics, + deltaShares, + bigDeltaShares + ) + } + } + + // Modify deltaShares. + assertFailsWith { + for (i in signerIds) { + deltaShares[i] = sampleScalar().value + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + signers[i]!!.processPresignOutput( + signerIds, + presignRound3Broadcasts, + elGamalPublics, + deltaShares, + bigDeltaShares + ) + } + } + + // Modify bigDeltaShare + assertFailsWith { + for (i in signerIds) { + bigDeltaShares[i] = sampleScalar().actOnBase() + val presignRound3Broadcasts = filterIncomingBroadcast(i, presignRound3AllBroadcasts) + signers[i]!!.processPresignOutput( + signerIds, + presignRound3Broadcasts, + elGamalPublics, + deltaShares, + bigDeltaShares + ) + } + } + } + + + + private fun randomSignerIDs(ids: List, t: Int) : List { + require(t <= ids.size) { "t must be less than or equal to the number of parties." } + require(t > 0) { "t must be greater than 0." } + + return ids.shuffled().take(t) + } + + private fun filterIncomingBroadcast(id: Int, broadcasts: MutableMap>) : Map { + val incomingBroadcasts = mutableMapOf() + for ((j, broadcast) in broadcasts) { + if (j != id) { + incomingBroadcasts[j] = broadcast[id]!! + } + } + return incomingBroadcasts + } +} diff --git a/src/test/kotlin/zk/AffgTest.kt b/src/test/kotlin/zk/AffgTest.kt index 99a32a5..693a801 100644 --- a/src/test/kotlin/zk/AffgTest.kt +++ b/src/test/kotlin/zk/AffgTest.kt @@ -1,16 +1,20 @@ package zk +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test +import perun_network.ecdsa_threshold.ecdsa.Point import perun_network.ecdsa_threshold.ecdsa.Scalar import perun_network.ecdsa_threshold.ecdsa.secp256k1Order import perun_network.ecdsa_threshold.math.sampleL import perun_network.ecdsa_threshold.math.sampleLPrime import perun_network.ecdsa_threshold.math.sampleScalar -import perun_network.ecdsa_threshold.zkproof.affg.AffgPrivate -import perun_network.ecdsa_threshold.zkproof.affg.AffgProof -import perun_network.ecdsa_threshold.zkproof.affg.AffgPublic -import perun_network.ecdsa_threshold.zkproof.affg.produceAffGMaterials +import perun_network.ecdsa_threshold.paillier.* +import perun_network.ecdsa_threshold.pedersen.PedersenParameters +import perun_network.ecdsa_threshold.zero_knowledge.AffgPrivate +import perun_network.ecdsa_threshold.zero_knowledge.AffgProof +import perun_network.ecdsa_threshold.zero_knowledge.AffgPublic +import perun_network.ecdsa_threshold.zero_knowledge.produceAffGMaterials import java.math.BigInteger import kotlin.test.assertEquals @@ -32,7 +36,7 @@ class AffgTest { val (Y, rhoY) = prover.encryptRandom(y) val Cx = C.clone().modPowNSquared(verifierPaillier, x) - var (Dtmp, rho) = verifierPaillier.encryptRandom(y) + val (Dtmp, rho) = verifierPaillier.encryptRandom(y) val D = Dtmp.modMulNSquared(verifierPaillier, Cx) val affgPublic = AffgPublic( @@ -99,4 +103,51 @@ class AffgTest { assertEquals(c, gammaS, "a•b should be equal to α + β") } + + @Test + fun testAffgFails() { + // Mocked invalid Paillier public and secret keys + val (paillierPublic, _) = paillierKeyGenMock() + + // Mocked invalid Pedersen parameters + val pedersenParametersInvalid = PedersenParameters( + BigInteger("17"), // Arbitrary invalid values + BigInteger("23"), + BigInteger("100") + ) + + // Random invalid values for ciphertexts (not valid Paillier encrypted values) + val invalidCipherText = PaillierCipherText(BigInteger("999999999999999")) + + // Mocked invalid elliptic curve point not on secp256k1 curve + val invalidPoint = Point(BigInteger("999999999"), BigInteger("123456789")) + + // Construct public parameters with invalid data + val affgPublicInvalid = AffgPublic( + C = invalidCipherText, + D = invalidCipherText, + Y = invalidCipherText, + X = invalidPoint, // Invalid point + n0 = paillierPublic, + n1 = paillierPublic, + aux = pedersenParametersInvalid + ) + + // Create invalid private parameters + val affgPrivateInvalid = AffgPrivate( + x = BigInteger("999999999"), + y = BigInteger("123456789"), + rho = BigInteger("222222222"), + rhoY = BigInteger("333333333") + ) + + // Create invalid proof based on invalid parameters + val affgProofInvalid = AffgProof.newProof(42, affgPublicInvalid, affgPrivateInvalid) + + // Verifying the proof should fail + val isProofValid = affgProofInvalid.verify(42, affgPublicInvalid) + + // Assert that the proof validation returns false, meaning the proof fails + assertFalse(isProofValid) + } } \ No newline at end of file diff --git a/src/test/kotlin/zk/ElogTest.kt b/src/test/kotlin/zk/ElogTest.kt new file mode 100644 index 0000000..6cb2c43 --- /dev/null +++ b/src/test/kotlin/zk/ElogTest.kt @@ -0,0 +1,42 @@ +package zk + +import org.junit.jupiter.api.Assertions.assertTrue +import perun_network.ecdsa_threshold.math.sampleScalar +import perun_network.ecdsa_threshold.zero_knowledge.ElogPrivate +import perun_network.ecdsa_threshold.zero_knowledge.ElogProof +import perun_network.ecdsa_threshold.zero_knowledge.ElogPublic +import kotlin.test.Test + +class ElogTest { + @Test + fun testElog() { + val H = sampleScalar().actOnBase() + val X = sampleScalar().actOnBase() + val y = sampleScalar() + val Y = y.act(H) + + val lambda = sampleScalar() + val L = lambda.actOnBase() + val M = y.actOnBase().add(lambda.act(X)) + + val public = ElogPublic( + L = L, + M = M, + X = X, + Y = Y, + h = H + ) + + val private = ElogPrivate( + y = y, + lambda = lambda + ) + + val proof = ElogProof.newProof( + id = 0, + public = public, + private = private + ) + assertTrue(proof.verify(0, public)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/zk/EncElgTest.kt b/src/test/kotlin/zk/EncElgTest.kt new file mode 100644 index 0000000..99e16a0 --- /dev/null +++ b/src/test/kotlin/zk/EncElgTest.kt @@ -0,0 +1,101 @@ +package zk + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import perun_network.ecdsa_threshold.ecdsa.Scalar +import perun_network.ecdsa_threshold.math.sampleL +import perun_network.ecdsa_threshold.math.sampleScalar +import perun_network.ecdsa_threshold.zero_knowledge.EncElgPrivate +import perun_network.ecdsa_threshold.zero_knowledge.EncElgProof +import perun_network.ecdsa_threshold.zero_knowledge.EncElgPublic +import kotlin.test.assertTrue + +class EncElgTest { + @Test + fun testEncElg() { + ZK.initialize() + val prover = ZK.proverPaillierPublic + val verifier = ZK.pedersenParams + + // Sample x + val x = sampleL() + val xScalar = Scalar.scalarFromBigInteger(x) + + // Sample a and b + val a = sampleScalar() + val b = sampleScalar() + + // Compute abx = a * b + xScalar + val abx = a.multiply(b).add(xScalar) + + // Generate points A, B, X + val A = a.actOnBase() + val B = b.actOnBase() + val X = abx.actOnBase() + + val (C, rho) = prover.encryptRandom(x) + + val public = EncElgPublic( + C = C, + A = A, + B = B, + X = X, + N0 = prover, + aux = verifier + ) + + val private = EncElgPrivate( + x = x, + rho = rho, + a = a, + b = b + ) + + val proof = EncElgProof.newProof(0, public, private) + assertTrue(proof.verify(0, public)) + } + + @Test + fun testEncElgFails() { + ZK.initialize() + val prover = ZK.proverPaillierPublic + val verifier = ZK.pedersenParams + + // Sample x + val x = sampleL() + val xScalar = Scalar.scalarFromBigInteger(x) + + // Sample a and b + val a = sampleScalar() + val b = sampleScalar() + + // Compute abx = a * b + xScalar + val abx = a.multiply(b).add(xScalar) + + // Generate points A, B, X + val A = a.actOnBase() + val B = b.actOnBase() + val X = abx.actOnBase() + + val (C, rho) = prover.encryptRandom(x) + + val public = EncElgPublic( + C = C, + A = A, + B = B, + X = X, + N0 = prover, + aux = verifier + ) + + val private = EncElgPrivate( + x = x, + rho = rho, + a = b, + b = a + ) + + val proof = EncElgProof.newProof(0, public, private) + assertFalse(proof.verify(0, public)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/zk/EncTest.kt b/src/test/kotlin/zk/EncTest.kt index 4259260..1f17f86 100644 --- a/src/test/kotlin/zk/EncTest.kt +++ b/src/test/kotlin/zk/EncTest.kt @@ -3,10 +3,9 @@ package zk import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import perun_network.ecdsa_threshold.math.sampleL -import perun_network.ecdsa_threshold.math.sampleLN -import perun_network.ecdsa_threshold.zkproof.enc.EncPrivate -import perun_network.ecdsa_threshold.zkproof.enc.EncProof -import perun_network.ecdsa_threshold.zkproof.enc.EncPublic +import perun_network.ecdsa_threshold.zero_knowledge.EncPrivate +import perun_network.ecdsa_threshold.zero_knowledge.EncProof +import perun_network.ecdsa_threshold.zero_knowledge.EncPublic class EncTest { @Test diff --git a/src/test/kotlin/zk/FacTest.kt b/src/test/kotlin/zk/FacTest.kt new file mode 100644 index 0000000..2c0f50d --- /dev/null +++ b/src/test/kotlin/zk/FacTest.kt @@ -0,0 +1,32 @@ +package zk + +import perun_network.ecdsa_threshold.math.sampleRID +import perun_network.ecdsa_threshold.paillier.paillierKeyGenMock +import perun_network.ecdsa_threshold.zero_knowledge.FacPrivate +import perun_network.ecdsa_threshold.zero_knowledge.FacProof +import perun_network.ecdsa_threshold.zero_knowledge.FacPublic +import kotlin.test.Test +import kotlin.test.assertTrue + +class FacTest { + @Test + fun testFacProof() { + val (aux, _) = paillierKeyGenMock().second.generatePedersenParameters() + val secretKey = paillierKeyGenMock().second + + val public = FacPublic( + n = secretKey.publicKey.n, + aux = aux + ) + + val private = FacPrivate( + p = secretKey.p, + q = secretKey.q + ) + + val rid = sampleRID() + + val proof = FacProof.newProof(0, rid, public, private) + assertTrue(proof.verify(0, rid, public), "Proof verification failed.") + } +} \ No newline at end of file diff --git a/src/test/kotlin/zk/LogStarTest.kt b/src/test/kotlin/zk/LogStarTest.kt index 34b118e..d62dfd9 100644 --- a/src/test/kotlin/zk/LogStarTest.kt +++ b/src/test/kotlin/zk/LogStarTest.kt @@ -7,9 +7,9 @@ import perun_network.ecdsa_threshold.ecdsa.newPoint import perun_network.ecdsa_threshold.ecdsa.secp256k1Order import perun_network.ecdsa_threshold.math.sampleL import perun_network.ecdsa_threshold.math.sampleScalar -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarPrivate -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarProof -import perun_network.ecdsa_threshold.zkproof.logstar.LogStarPublic +import perun_network.ecdsa_threshold.zero_knowledge.LogStarPrivate +import perun_network.ecdsa_threshold.zero_knowledge.LogStarProof +import perun_network.ecdsa_threshold.zero_knowledge.LogStarPublic import kotlin.test.Test class LogStarTest { @@ -38,7 +38,6 @@ class LogStarTest { val verifier = ZK.pedersenParams val prover = ZK.proverPaillierPublic - val gammaShares = mutableMapOf() for (i in 0 until 5) { gammaShares[i] = sampleScalar() @@ -50,7 +49,7 @@ class LogStarTest { } var bigGamma = newPoint() - for ((i, bigGammaShare) in bigGammaShares) { + for ((_, bigGammaShare) in bigGammaShares) { bigGamma = bigGamma.add(bigGammaShare) } diff --git a/src/test/kotlin/zk/ModTest.kt b/src/test/kotlin/zk/ModTest.kt new file mode 100644 index 0000000..cf9d53a --- /dev/null +++ b/src/test/kotlin/zk/ModTest.kt @@ -0,0 +1,59 @@ +package zk + +import org.junit.jupiter.api.Assertions.* +import perun_network.ecdsa_threshold.math.sampleQuadraticNonResidue +import perun_network.ecdsa_threshold.math.sampleRID +import perun_network.ecdsa_threshold.zero_knowledge.* +import java.math.BigInteger +import kotlin.test.Test + +class ModTest { + @Test + fun testMod() { + ZK.initialize() + val zkSecret = ZK.proverPaillierSecret // Initialize with proper Paillier key + val zkPublic = zkSecret.publicKey + val public = ModPublic(zkPublic.n) + val private = ModPrivate( + p = zkSecret.p, + q = zkSecret.q, + phi = zkSecret.phi + ) + + val rid = sampleRID() + + val proof = ModProof.newProof(0, rid, public, private) + assertTrue(proof.verify(0, rid, public)) + } + + @Test + fun testSet4thRoot() { + val p = 311.toBigInteger() + + val q = 331.toBigInteger() + + val pHalf = (p - BigInteger.ONE) / BigInteger.TWO + val qHalf = (q - BigInteger.ONE) / BigInteger.TWO + val n = p.multiply(q) + val phi = (p - BigInteger.ONE) * (q - BigInteger.ONE) + var y = BigInteger.valueOf(502) + val w = sampleQuadraticNonResidue(n) + + val nCRT = p.multiply(q) + val (a, b, x) = makeQuadraticResidue(y, w, pHalf, qHalf, n, p, q) + + val e = fourthRootExponent(phi) + var root = x.modPow(e, nCRT) + + if (b) { + y = y.multiply(w).mod(n) + } + if (a) { + y = y.negate().mod(n) + } + + assertNotEquals(BigInteger.ONE, root, "root cannot be 1") + root = root.modPow(BigInteger.valueOf(4), n) + assertEquals(y, root, "root^4 should be equal to y") + } +} \ No newline at end of file diff --git a/src/test/kotlin/zk/PrmTest.kt b/src/test/kotlin/zk/PrmTest.kt new file mode 100644 index 0000000..e8a8d20 --- /dev/null +++ b/src/test/kotlin/zk/PrmTest.kt @@ -0,0 +1,26 @@ +package zk + +import org.junit.jupiter.api.Assertions.assertTrue +import perun_network.ecdsa_threshold.paillier.paillierKeyGenMock +import perun_network.ecdsa_threshold.zero_knowledge.PrmPrivate +import perun_network.ecdsa_threshold.zero_knowledge.PrmProof +import perun_network.ecdsa_threshold.zero_knowledge.PrmPublic +import kotlin.test.Test + +class PrmTest { + @Test + fun testPrm() { + val (_, paillierSecret) = paillierKeyGenMock() + val (aux, lambda) = paillierSecret.generatePedersenParameters() + + val public = PrmPublic(aux) + + val private = PrmPrivate( + lambda = lambda, + phi = paillierSecret.phi, + ) + + val proof = PrmProof.newProof(0, public, private) + assertTrue(proof.verify(0, public)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/zk/SchrTest.kt b/src/test/kotlin/zk/SchrTest.kt new file mode 100644 index 0000000..c6a882d --- /dev/null +++ b/src/test/kotlin/zk/SchrTest.kt @@ -0,0 +1,51 @@ +package zk + +import perun_network.ecdsa_threshold.ecdsa.newBasePoint +import perun_network.ecdsa_threshold.math.sampleRID +import perun_network.ecdsa_threshold.math.sampleScalar +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrCommitment +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrPrivate +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrProof +import perun_network.ecdsa_threshold.zero_knowledge.SchnorrPublic +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class SchrTest { + @Test + fun testSchPass() { + + val schnorrCommitment = SchnorrCommitment.newCommitment() + val x = sampleScalar() + val X = x.actOnBase() + + val schnorrPublic = SchnorrPublic(X) + val schnorrPrivate = SchnorrPrivate(x) + + val rid = sampleRID() + + val proof = SchnorrProof.newProof(0, rid , schnorrPublic, schnorrPrivate) + assertTrue(proof.verify(0, rid, schnorrPublic), "Proof verification failed") + + val proof2 = SchnorrProof.newProofWithCommitment(0, rid, schnorrPublic, schnorrPrivate, schnorrCommitment) + assertTrue(proof2.verify(0, rid, schnorrPublic), "Proof with commitment verification failed") + } + + @Test + fun testSchFail() { + val schnorrCommitment = SchnorrCommitment.newCommitment() + val x = sampleScalar() + val X = newBasePoint() + + val schnorrPublic = SchnorrPublic(X) + val schnorrPrivate = SchnorrPrivate(x) + + val rid = sampleRID() + + val proof = SchnorrProof.newProof(0, rid, schnorrPublic, schnorrPrivate) + assertFalse(proof.verify(0, rid, schnorrPublic), "Proof should not accept identity point") + + val proof2 = SchnorrProof.newProofWithCommitment(0, rid, schnorrPublic, schnorrPrivate, schnorrCommitment) + assertFalse(proof2.verify(0, rid, schnorrPublic), "Proof should not accept identity point") + } +} \ No newline at end of file diff --git a/src/test/kotlin/zk/TestValues.kt b/src/test/kotlin/zk/TestValues.kt index 75fb540..5503802 100644 --- a/src/test/kotlin/zk/TestValues.kt +++ b/src/test/kotlin/zk/TestValues.kt @@ -34,22 +34,20 @@ object ZK { 16 ) - ZK.proverPaillierSecret = newPaillierSecretFromPrimes(p1, q1) - ZK.verifierPaillierSecret = newPaillierSecretFromPrimes(p2, q2) - ZK.proverPaillierPublic = ZK.proverPaillierSecret.publicKey - ZK.verifierPaillierPublic = ZK.verifierPaillierSecret.publicKey - + proverPaillierSecret = newPaillierSecretFromPrimes(p1, q1) + verifierPaillierSecret = newPaillierSecretFromPrimes(p2, q2) + proverPaillierPublic = proverPaillierSecret.publicKey + verifierPaillierPublic = verifierPaillierSecret.publicKey val s = BigInteger("2A1023ADD5BEF3F3C2DCAF8B99713C18CF5BC42F38797BAFC808E5856F45E7EC51C450DA2B03171DBA0F0FA29025A7ED910A8B1BC13772BD79D4718A6DC618DE354D8F46378AC1BD6E2030AB761C4A2878F859C692823B60E5F4E4BB7BCD16DCECCBFBE65016DE88BB576A897E73F32456C07AD7DC61013C4A90FD509C79200A8D04310AD5338D32D861A73398677C1D3A2CBA958F9232B4E83AA4B133E7D1E694FF4615BE9F4E73B51C13F1193402CE36BFA0970C8B4C67920B5122B3B77DC3AC8F8FE92C7912649808F999309AE8B8641EA330B5E8BFFF8528FC8D85B84BD61E2FF5A261E80434444CC407CBA4D5FAE2D2587AF7624D2B99F4FF33640BA0F0", 16) val t = BigInteger("376A2C4A49B8C27F943059A358BCD65BCC0BAB1ABBBE368FFD004580A49EE795B4ECF85B2FB2A24969129E34E9E5D91503D11DE9D11F51538AC66A418B2E31463A55AAFAA29B645C2D04FBC829E3B55F95BFB0B5DE464ED0516DF28D36B4225B4050B80271E1AD8F11866E01FF83D40A06A7F7298FD96B210BE56AA4D3C0524E7372E371D0C6E52E043D2E1BF38E435ED85EB032FAC86C049E9FB8280847ABED9F2025FE03C7B8B8E32914238E3281BA17A2DB4CB2ACAD033442EF55E1BF2E4A741A961833CBE87C8C751E8A59EF998528BA0658CB9342EEDBDF62894E4AE66414024361D916248801D2929326102081BB2F7AD1C57C55AE8038EE35CC2C9915", 16) - ZK.pedersenParams = PedersenParameters(ZK.verifierPaillierPublic.n, s, t) + pedersenParams = PedersenParameters(verifierPaillierPublic.n, s, t) } - @Test fun encDecTest() { - ZK.initialize() + initialize() val paillierPublic = proverPaillierPublic val paillierSecret = proverPaillierSecret