From 3d86c3f5dfc87b85ef8f7a46d5b8472ad087825d Mon Sep 17 00:00:00 2001 From: Janis Erdmanis Date: Wed, 6 Nov 2024 17:07:56 +0200 Subject: [PATCH] openssl integration and benchmarks --- Project.toml | 5 +- README.md | 140 +++---- src/prover.jl | 29 +- src/utils.jl | 25 ++ src/verifier.jl | 6 +- test/benchmarks/Project.toml | 6 + test/benchmarks/barplot.jl | 158 +++++++ test/benchmarks/benchmark.jl | 155 +++++++ test/benchmarks/proofgen.jl | 25 ++ test/benchmarks/results/P-256_N=100000.svg | 362 ++++++++++++++++ .../{ => results}/VerifierProfile.png | Bin test/benchmarks/results/modp_2048_N=1000.svg | 369 +++++++++++++++++ test/benchmarks/results/modp_2048_N=10000.svg | 387 ++++++++++++++++++ test/openssl.jl | 20 +- test/proof.jl | 7 +- test/runtests.jl | 6 +- 16 files changed, 1588 insertions(+), 112 deletions(-) create mode 100644 test/benchmarks/Project.toml create mode 100644 test/benchmarks/barplot.jl create mode 100644 test/benchmarks/benchmark.jl create mode 100644 test/benchmarks/proofgen.jl create mode 100644 test/benchmarks/results/P-256_N=100000.svg rename test/benchmarks/{ => results}/VerifierProfile.png (100%) create mode 100644 test/benchmarks/results/modp_2048_N=1000.svg create mode 100644 test/benchmarks/results/modp_2048_N=10000.svg diff --git a/Project.toml b/Project.toml index ecd514d..348ab22 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ShuffleProofs" uuid = "31a120cc-b3cb-4d07-bbdb-d498660ddfd8" authors = ["Janis Erdmanis "] -version = "0.4.2" +version = "0.4.3" [deps] CryptoGroups = "bc997328-bedd-407e-bcd3-5758e064a52d" @@ -20,6 +20,7 @@ julia = "1" [extras] SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +OpenSSLGroups = "50771dd1-78b8-490c-a786-5ddc80ce15da" [targets] -test = ["Test", "SafeTestsets"] +test = ["Test", "SafeTestsets", "OpenSSLGroups"] \ No newline at end of file diff --git a/README.md b/README.md index e51a037..db22ecb 100644 --- a/README.md +++ b/README.md @@ -2,40 +2,22 @@ [![codecov](https://codecov.io/gh/PeaceFounder/ShuffleProofs.jl/graph/badge.svg?token=4VCLLS1YEF)](https://codecov.io/gh/PeaceFounder/ShuffleProofs.jl) -ShuffleProofs.jl is a Julia package that implements zero-knowledge proofs of shuffle, particularly useful for E2E verifiable e-voting systems and privacy-preserving applications. It provides a Verificatum-compatible implementation of the Wikstrom proof of shuffle protocol, widely used in real-world electronic voting systems. +ShuffleProofs.jl is a high-performance Julia package implementing zero-knowledge proofs of shuffle, primarily designed for E2E verifiable e-voting systems and privacy-preserving applications. The package implements the Wikstrรถm proof of shuffle protocol with Verificatum compatibility, matching the protocol deployed in national-scale electronic voting systems across Estonia, Norway, and Switzerland. -Unlike traditional cryptographic tools that focus solely on confidentiality and security, ShuffleProofs.jl addresses the more complex challenge of providing both privacy and verifiability. This is particularly crucial in systems where authorities cannot be blindly trusted, such as electronic voting or anonymous auction systems. +## Why ShuffleProofs.jl? -## Key Features +Traditional cryptographic tools often focus solely on confidentiality and security. However, modern systems like electronic voting require privacy to unlink voters from their votes and verifiability, proving that every vote tallied had come from at most one eligible voter. ShuffleProofs.jl addresses part of this complex challenge through ElGamal reencryption shuffle. -- **Core Functionality** - - Zero-knowledge proof generation and verification for ElGamal reencryption shuffles - - Full compatibility with Verificatum's Wikstrรถm proof of shuffle protocol - - Support for verifiable braiding proofs for public key anonymization - - Extended ciphertext width support for proof generation and verification - - Reorganised, flat directory structure for proof serializations +- **Standards-Compliant**: Compatible with the battle-tested Verificatum verifier specification +- **Flexible Design**: Supports various cryptographic groups and custom verification strategies +- **Developer-Friendly**: Clean, type-safe implementation with comprehensive testing that closely matches [Haenni et al.](https://link.springer.com/chapter/10.1007/978-3-319-70278-0_23#citeas) pseudocode +- **Enabling Ecosystem**: Easily reuses [CryptoPRG](https://github.com/PeaceFounder/CryptoPRG.jl), [CryptoGroups](https://github.com/PeaceFounder/CryptoGroups.jl), [OpenSSLGroups](https://github.com/PeaceFounder/OpenSSLGroups.jl) and [SigmaProofs](https://github.com/PeaceFounder/SigmaProofs.jl) for other supporting zero-knowledge proofs and transition seamlessly from prototyping to production environments +- **High Performance**: Competitive with Verificatum on single-core benchmarks -- **Cryptographic Infrastructure** - - Extensible group support for arbitrary cyclic groups - - Native elliptic curves over prime fields (P-192, P-256, secp256k1) - - Optional high-performance OpenSSL curve integration via [OpenSSLGroups.jl](https://github.com/PeaceFounder/OpenSSLGroups.jl) - - Modular prime groups with flexible parameter selection - - Secure hash based random number generation for proof components via [CryptoPRG.jl](https://github.com/PeaceFounder/CryptoPRG.jl) - - Flexible verifier interface for custom implementations +![P-256 Performance](test/benchmarks/results/P-256_N=100000.svg) +![ModP Performance](test/benchmarks/results/modp_2048_N=10000.svg) -- **Verificatum Compatibility** - - Loading and verification of Verificatum-generated proofs - - Proof generation matching Verificatum verifier specification - - Compliant with Verificatum file format specifications - -- **Developer-Friendly Design** - - Clean implementation following [Haenni et al.](https://link.springer.com/chapter/10.1007/978-3-319-70278-0_23#citeas) pseudocode - - Comprehensive test suite with high coverage - - Type-safe implementation leveraging Julia's type system - - Readiness to integrate with Juliaโ€™s high-performance computing ecosystem via threading, distributed parallelism, or even GPUs. - - Modular architecture supporting extension and customization - -The package implements state-of-the-art protocols according to the Verificatum verifier specification, with which Verificatum-generated proofs pass. The prover is implemented according to Haenni et al. pseudocode, which is mapped to the Verificatum verifier specification, so the created shuffle proofs are Verificatum verifier compatible. The Verificatum specification has been deployed in national-scale electronic voting systems in Estonia, Norway, and Switzerland, making this implementation suitable for aspiring production environments. +*Note: Light blue sections show the time taken for a proposition, proof deserialisation, and group membership validation in ShuffleProofs. The benchmarks for the P-256 group have been made using the implementation from OpenSSLGroups. Both quadratic residue prime group with 2048-bit prime modulus and NIST standard curve P-256 group offer 128-bit security for discrete logarithm problems. Performance tests made with Ubuntu 24.04 on M1 Pro. The benchmark figures have been made with code in `test/benchmarks/benchmark.jl`* ## Installation @@ -46,9 +28,28 @@ Pkg.add("ShuffleProofs") The package is registered in Julia's general registry and can be installed with the standard package manager on Julia-supported platforms: Linux, MacOS, Windows, FreeBSD and others. All dependencies are automatically handled during installation; no binary artefacts are compiled locally. Hence, the package shall work robustly for all future environments with few updates. -## Quick Start: Electronic Voting Example +## Core Features + +### Cryptographic Capabilities +- Zero-knowledge proof generation and verification for ElGamal reencryption shuffles +- Support for verifiable braiding proofs for public key anonymization +- Extended ciphertext width support + +### Group Support +- Native elliptic curves (P-192, P-256, secp256k1) +- High-performance OpenSSL curve integration via [OpenSSLGroups.jl](https://github.com/PeaceFounder/OpenSSLGroups.jl) +- Modular prime groups with flexible parameter selection +- Extensible interface for custom group implementations and hardware optimisations -Here's a simplified example of how ShuffleProofs.jl can be used in an electronic voting system: +### Verificatum Compatibility +- Full compatibility with Verificatum's proof of shuffle verifier specification +- Support for loading and verifying Verificatum-generated proofs +- Flexible verifier interface for custom implementations +- Reorganised, flat directory structure for proof serialisations while complying with file specifications + +## Quick Start: E-Voting Example + +Here's how to implement a basic e-voting system with anonymous vote collection and verifiable counting: ```julia using CryptoGroups @@ -64,23 +65,22 @@ sk = 123 # Secret key (in practice, distributed in threshold ceremony) pk = g^sk options = [g, g^2, g^3] # Voting options -# Step 1: Voters submit encrypted votes +# 1. Collect encrypted votes bbord = let enc = Enc(pk, g) ciphertexts_in = [enc(options[rand(1:3)], rand(1:10)) |> ElGamalRow for i in 1:10] (; ciphertexts_in) end -# Step 2: Re-encryption and shuffle +# 2. Shuffle votes anonymously with proof bbord = let - enc = Enc(pk, g) - simulator = shuffle(bbord.ciphertexts_in, enc, verifier) + simulator = shuffle(bbord.ciphertexts_in, g, pk, verifier) (; bbord..., ciphertexts_out = simulator.proposition.๐žโ€ฒ, shuffle_proof = simulator.proof) end -# Step 3: Decryption +# 3. Decrypt with proof bbord = let simulator = decrypt(g, bbord.ciphertexts_out, sk, verifier) (; bbord..., @@ -91,21 +91,18 @@ end This example demonstrates a complete electronic voting workflow: vote submission, shuffling, and decryption. The process ensures that while votes remain anonymous, the entire process is verifiable. Each step produces cryptographic proofs that can be independently verified, ensuring that no votes have been added, removed, or modified during the process. -The bulletin board (`bbord`) acts as a public ledger where all operations are recorded along with their proofs. This transparency allows anyone to verify the integrity of the election while maintaining voter privacy through the shuffle mechanism. - -## Verifying Shuffles - -To verify a shuffle proof: +The bulletin board (`bbord`) acts as a public ledger where all operations are recorded along with their proofs. This transparency allows anyone to verify the integrity of the election while maintaining voter privacy through the shuffle mechanism. +For ShuffleProofs, the important lines are: +```julia +simulator = shuffle(bbord.ciphertexts_in, enc, verifier) +``` +which creates a simulator containing proposition, proof and verifier as its fields, which can be verified with the: ```julia -# Generate proof -proof = prove(proposition, secret, verifier) - -# Verify verify(proposition, proof, verifier) ``` +This allows any party to independently verify that a shuffle was performed correctly without learning anything about the actual permutation used. To assure integrity, one then only needs to verify the verifier specification parameters accessible with the `simulator.verifier`, whereas `simulator.proposition` contains all data in one place to chain multiple shuffles together in a larger part of the protocol. -The verification process is a crucial component of the system's security. It allows any party to independently verify that a shuffle was performed correctly without learning anything about the actual permutation used. This is achieved through zero-knowledge proofs, which provide mathematical certainty about the correctness of the shuffle without revealing any information about how the shuffling was performed. ## Braiding Example @@ -139,7 +136,7 @@ Yโ€ฒ = output_members(simulator.proposition) @assert sort(h .^ y) == sort(Yโ€ฒ) ``` -Braiding is an advanced feature that creates knot-like structures where inputs are related to outputs through privately known exponents. This is particularly useful in scenarios where group members need to prove their membership without revealing their identity, such as in whistleblower protection systems or in voting systems where votes are signed pseudonymously. +Braiding is an advanced feature that creates knot-like structures where inputs are related to outputs through privately known exponents. This is particularly useful in scenarios where group members need to prove their membership without revealing their identity, such as whistleblower protection systems or voting systems where votes are signed pseudonymously. ## Working with Verificatum @@ -150,7 +147,7 @@ simulator = load_verificatum_simulator(DEMO_DIR) verify(simulator) ``` -Verificatum compatibility is a key feature of ShuffleProofs.jl, allowing it to interoperate with one of the most widely deployed mix-net systems. This means proofs generated by Verificatum can be verified using this package, and vice versa (in principle, if serialisation is done properly). The implementation follows Verificatum's rigorous specification, ensuring complete compatibility. +Verificatum compatibility is a key feature of ShuffleProofs.jl, allowing it to interoperate with one of the most widely deployed mix-net systems. This means proofs generated by Verificatum can be verified using this package and vice versa (in principle, if serialisation were to follow the directory structure of Verificatum specification). The implementation follows Verificatum's rigorous specification, ensuring complete compatibility. ## Custom Verifiers @@ -166,41 +163,38 @@ challenge_perm(verifier::HonestVerifier, proposition, ๐œ) = verifier.challenge challenge_reenc(verifier::HonestVerifier, proposition, ๐œ, ๐œฬ‚, t) = verifier.challenge.c ``` -The verifier architecture is designed to be extensible, allowing users to implement custom verification strategies. This is particularly useful for specialized applications or research purposes where the standard verification process needs to be modified. +The verifier architecture is designed to be extensible, allowing users to implement custom verification strategies. This is particularly useful for specialised applications or research purposes where the standard verification process needs to be modified. -## Using OpenSSL +## Performance +### Performance Analysis -OpenSSL's elliptic curve implementation is 10-20x faster than the one in CryptoGroups. We can leverage this performance advantage through the [OpenSSLGroups.jl](https://github.com/PeaceFounder/OpenSSLGroups.jl) package to accelerate ShuffleProofs operations: +- **Elliptic Curves**: ShuffleProofs matches Verificatum's single-core performance when using OpenSSL integration +- **Modular Prime Groups**: + - A group membership validation via [hand-crafted Jacobi symbol calculations](https://github.com/PeaceFounder/CryptoGroups.jl/blob/0f6b4e223225634ec1506e6999f8922c079f62c7/src/Utils.jl#L24) takes a significant amount of verification time in ShuffleProofs + - Verificatum likely uses optimised Montgomery arithmetic (not yet implemented in ShuffleProofs) and simultaneous exponentiation also for membership validation +- **Parallelism** Verificatum takes advantage of multiple cores in the system, as shown in performance benchmarks. -```julia -using CryptoGroups -using OpenSSLGroups -using ShuffleProofs: shuffle, verify -using SigmaProofs.ElGamal: Enc -using SigmaProofs.Verificatum: ProtocolSpec +In addition, Verificatum is much more optimal in memory usage, offloading intermediate calculations to the disk, whereas ShuffleProofs keeps them all in memory, hence the large memory footprint. It would be interesting to explore in the future whether performance can be preserved with a generic disk-supported vector type streamed from a disk. -# Set up ElGamal encryption with OpenSSL curve -g = @ECGroup{OpenSSLGroups.Prime256v1}() -sk = 123 -pk = g^sk +### Performance Profiling -# Create encryption helper -enc = Enc(pk, g) +![Verifier Profile](test/benchmarks/results/VerifierProfile.png) -# Example encryption and shuffle proof -plaintexts = [g^4, g^2, g^3] .|> tuple -ciphertexts = enc(plaintexts, [2, 3, 4]) +Performance profiling reveals three major computational bottlenecks in the code: generator basis computation via `generator_basis` consumes 40% of execution time (left), verification of proof through group operations takes another 40% (right), and challenge generation via `challenge_perm` and `challenge_reenc` accounts for the remaining 20% (middle). While the generator basis computation and proof verification stages can be readily parallelised using either multithreading or multiprocessing, the challenge generation stage presents parallelisation roadblocks. Within this challenge generation phase, approximately 1/3rd of processing time is spent computing hashes from byte vectors using Nettle (highlighted in the profile view with yellow pencil fill). Hence, the specification would need to introduce block-wise hashing to parallelise this part. Nevertheless, 10x speedup is feasible with current specifications, as Verificatum demonstrates. -verifier = ProtocolSpec(; g) -simulator = shuffle(ciphertexts, g, pk, verifier) -@assert verify(simulator) -``` +### Performance Tips -Early benchmarks suggest that with OpenSSL `Prime256v1` implementation, verification is about **25 times faster** than with `CryptoGroups` curve implementation. +1. **Elliptic Curves** significantly outperform modular prime groups at equivalent security levels and require less memory +2. **OpenSSL** provides a 25x speedup for elliptic curve operations over CryptoGroups basic implementation. OpenSSL can be used with the OpenSSLGroups package as follows: +```julia +using OpenSSLGroups +g = @ECGroup{OpenSSLGroups.Prime256v1}() +verifier = ProtocolSpec(; g) -![](test/benchmarks/VerifierProfile.png) +# Rest of your code remains the same but runs much faster! +``` +3. **Memory Usage:** ensure your machine has sufficient RAM to avoid disk swapping - for example, processing 1,000,000 ciphertexts requires approximately 16GB of RAM -Performance profiling reveals three major computational bottlenecks in the code: generator basis computation via `generator_basis` consumes 40% of execution time (left), verification of proof through group operations takes another 40% (right), and challenge generation via `challenge_perm` and `challenge_reenc` accounts for the remaining 20% (middle). While the generator basis computation and proof verification stages can be readily parallelised using either multithreading or multiprocessing, the challenge generation stage presents parallelisation roadblocks. Within this challenge generation phase, approximately 1/3rd of processing time is spent computing hashes from byte vectors using Nettle (highlighted in the profile view with yellow pencil fill). Hence, the specification would need to introduce block-wise hashing to parallelise this part. ## References diff --git a/src/prover.jl b/src/prover.jl index 3d66523..a4ef5e0 100644 --- a/src/prover.jl +++ b/src/prover.jl @@ -28,6 +28,8 @@ width(::Type{<:Shuffle{<:Group, N}}) where N = N Base.length(proposition::Shuffle) = length(proposition.๐ž) +seed(verifier::Verifier, proposition::Shuffle, ๐œ; ๐ก) = nothing # optional method + struct PoSProof{G <: Group, N} <: Proof ๐œ::Vector{G} ๐œฬ‚::Vector{G} @@ -41,10 +43,12 @@ import Base: == width(::Type{PoSProof{<:Group, N}}) where N = N -struct PoSChallenge - ๐ก::Vector{<:Group} # Independent set of generators +struct PoSChallenge{G<:Group} + ๐ก::Vector{G} # Independent set of generators ๐ฎ::Vector{BigInt} # PoS commitment challenge c::BigInt # reencryption challenge + + PoSChallenge(๐ก::Vector{G}, ๐ฎ::Vector{<:Integer}, c::Integer) where G <: Group = new{G}(๐ก, convert(Vector{BigInt}, ๐ฎ), convert(BigInt, c)) end function verify(proposition::Shuffle, ๐ซโ€ฒ::Matrix{<:Integer}, ๐›™::Vector{<:Integer}) @@ -107,10 +111,9 @@ function gen_commitment_chain(g::Group, c0::T, ๐ฎ::Vector, ๐ซ::Vector) where return ๐œ end - -โˆ‘(๐ฑ, q) = mod(sum(๐ฑ), q) ### Need to improve +โˆ‘(๐ฑ::Vector{T}, q::T) where T <: Integer = modsum(๐ฑ, q) #mod(sum(๐ฑ), q) ### Need to improve +โˆ(๐ž::Vector{T}, q::T) where T <: Integer = modprod(๐ž, q) โˆ(๐ฑ) = prod(๐ฑ) -โˆ(f, ๐ฑ) = prod(f, ๐ฑ) using Random: RandomDevice @@ -153,7 +156,8 @@ function prove(proposition::Shuffle{G}, verifier::Verifier, ๐ซโ€ฒ::Matrix{<:In ๐œ = gen_perm_commitment(g, ๐ก, ๐›™, ๐ซ) - ๐ฎ = challenge_perm(verifier, proposition, ๐œ) + _seed = seed(verifier, proposition, ๐œ; ๐ก) + ๐ฎ = challenge_perm(verifier, proposition, ๐œ; s = _seed) ๐ฎโ€ฒ = ๐ฎ[๐›™] @@ -162,7 +166,7 @@ function prove(proposition::Shuffle{G}, verifier::Verifier, ๐ซโ€ฒ::Matrix{<:In ๐ฏ = Vector{BigInt}(undef, N) ๐ฏ[N] = 1 for i in N-1:-1:1 - ๐ฏ[i] = ๐ฎโ€ฒ[i+1] * ๐ฏ[i+1] + ๐ฏ[i] = ๐ฎโ€ฒ[i+1] * ๐ฏ[i+1] % q end rฬ„ = โˆ‘(๐ซ, q) @@ -186,7 +190,7 @@ function prove(proposition::Shuffle{G}, verifier::Verifier, ๐ซโ€ฒ::Matrix{<:In t = (tโ‚, tโ‚‚, tโ‚ƒ, tโ‚„, ๐ญฬ‚) - c = challenge_reenc(verifier, proposition, ๐œ, ๐œฬ‚, t) + c = challenge_reenc(verifier, proposition, ๐œ, ๐œฬ‚, t; s = _seed) sโ‚ = mod(ฯ‰โ‚ + c * rฬ„, q) sโ‚‚ = mod(ฯ‰โ‚‚ + c * rฬ‚, q) @@ -214,12 +218,13 @@ end function verify(proposition::Shuffle{G}, proof::PoSProof{G}, verifier::Verifier) where G <: Group - + #ฯ = ro_prefix(verifier) # can be efficiently recomputed ๐ก = generator_basis(verifier, G, length(proposition)) + s = seed(verifier, proposition, proof.๐œ; ๐ก) - ๐ฎ = challenge_perm(verifier, proposition, proof.๐œ) + ๐ฎ = challenge_perm(verifier, proposition, proof.๐œ; s) - c = challenge_reenc(verifier, proposition, proof.๐œ, proof.๐œฬ‚, proof.t) + c = challenge_reenc(verifier, proposition, proof.๐œ, proof.๐œฬ‚, proof.t; s) chg = PoSChallenge(๐ก, ๐ฎ, c) @@ -242,7 +247,7 @@ function verify(proposition::Shuffle, proof::PoSProof, challenge::PoSChallenge; cฬ„ = โˆ(๐œ) / โˆ(๐ก) - u = mod(โˆ(๐ฎ), q) + u = โˆ(๐ฎ, q) cฬ‚ = ๐œฬ‚[N] / h^u cฬƒ = โˆ(๐œ .^ ๐ฎ) diff --git a/src/utils.jl b/src/utils.jl index 2237c8f..13448ac 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -55,3 +55,28 @@ function Base.println(io::IO, report::Report) end Base.isvalid(report::Report) = report.state + +function modprod(elements::Vector{T}, q::T) where T <: Integer + return reduce((x, y) -> (x * y) % q, elements; init=T(1)) +end + +# function modsum(elements::Vector{T}, q::T) where T <: Integer +# return reduce((x, y) -> (x + y) % q, elements; init=T(0)) +# end + +# Different implementations of modular sum +function modsum(elements::Vector{T}, q::T; batch_size::Int=1000) where T <: Integer + n = length(elements) + result = T(0) + + # Process in batches to reduce number of modulo operations + for i in 1:batch_size:n + batch_end = min(i + batch_size - 1, n) + # Sum within batch without modulo + batch_sum = sum(@view(elements[i:batch_end])) + # Apply modulo only once per batch + result = (result + batch_sum) % q + end + + return result +end diff --git a/src/verifier.jl b/src/verifier.jl index ee21c4b..19b8a51 100644 --- a/src/verifier.jl +++ b/src/verifier.jl @@ -86,7 +86,7 @@ end leaf(x::String) = encode(Leaf(x)) -function seed(spec, proposition, ๐ฎ; +function seed(spec::ProtocolSpec, proposition::Shuffle, ๐ฎ; ฯ = ro_prefix(spec), ๐ก = generator_basis(spec, typeof(proposition.g), length(proposition.๐ž); ฯ) ) @@ -191,7 +191,7 @@ function challenge_reenc(spec::ProtocolSpec{G}, proposition::Shuffle{G}, ๐œ, return challenge_reenc(spec, proposition, ๐œ, ฯ„; ฯ, s) end -function verify(proposition::Shuffle, proof::VShuffleProof, challenge::PoSChallenge; verbose=false) +function verify(proposition::Shuffle{G}, proof::VShuffleProof{G}, challenge::PoSChallenge{G}; verbose=false) where G <: Group ๐ก, ๐ž, ๐“ฟ = challenge.๐ก, challenge.๐ฎ, challenge.c @@ -209,7 +209,7 @@ function verify(proposition::Shuffle, proof::VShuffleProof, challenge::PoSChalle A = prod(๐ฎ .^ ๐ž) C = prod(๐ฎ) / prod(๐ก) - D = ๐[N] * inv(๐ก[1])^prod(๐ž) + D = ๐[N] * inv(๐ก[1])^modprod(๐ž, order(G)) F = โˆ(๐”€ .^ ๐ž) diff --git a/test/benchmarks/Project.toml b/test/benchmarks/Project.toml new file mode 100644 index 0000000..bbbfb67 --- /dev/null +++ b/test/benchmarks/Project.toml @@ -0,0 +1,6 @@ +[deps] +CryptoGroups = "bc997328-bedd-407e-bcd3-5758e064a52d" +Luxor = "ae8d54c2-7ccd-5906-9d76-62fc9837b5bc" +OpenSSLGroups = "50771dd1-78b8-490c-a786-5ddc80ce15da" +ShuffleProofs = "31a120cc-b3cb-4d07-bbdb-d498660ddfd8" +SigmaProofs = "f8559b4c-f045-44a2-8db2-503e40bb7416" diff --git a/test/benchmarks/barplot.jl b/test/benchmarks/barplot.jl new file mode 100644 index 0000000..c132420 --- /dev/null +++ b/test/benchmarks/barplot.jl @@ -0,0 +1,158 @@ +using Luxor +# The code was produced from a few instructions with Claude +# Using a general plotting package may have been better here + +function wrap_text(text, max_width, fontsize_val) + words = split(text, " ") + lines = String[] + current_line = String[] + + function get_text_width(text) + return textextents(text)[3] + end + + for word in words + test_line = join([current_line..., word], " ") + text_width = get_text_width(test_line) + + if text_width <= max_width + push!(current_line, word) + else + if !isempty(current_line) + push!(lines, join(current_line, " ")) + current_line = String[word] + else + push!(lines, word) + end + end + end + + if !isempty(current_line) + push!(lines, join(current_line, " ")) + end + + return lines +end + +function barplot(fname::String, values::Vector, implementations::Vector{String}; + bar_colors = ["#A8D0E6", "#F9C784", "#F9C784"], + border_color = "#404040", + ylabel = "Time (s)", + title = "Implementation Benchmarks", + width = 400, + height = 300, + yrange = 200, + #yticks = 0:50:200, + font_scale = min(width/400, height/300) + ) + + step = Int(div(yrange, 5)) + yticks = 0:step:(5 * step) + + Drawing(width, height, fname) + + scale_x = width/400 + scale_y = height/300 + + title_size = 16 * font_scale + label_size = 12 * font_scale + value_size = 14 * font_scale #min(scale_x, scale_y) # Size for values above bars + + background("white") + + padding_x = 60 * scale_x # + padding_y = 50 * scale_y + translate(padding_x, height - padding_y) + + bar_width = 60 * scale_x + gap = 40 * scale_x + first_bar_offset = 20 * scale_x + chart_height = 200 * scale_y + + setline(2 * min(scale_x, scale_y)) + setcolor(border_color) + + # Y-axis + line(Point(0, 0), Point(0, -chart_height), :stroke) + + # X-axis + chart_width = first_bar_offset + length(implementations) * (bar_width + gap) + line(Point(0, 0), Point(chart_width, 0), :stroke) + + # Y-axis label (vertical text) + setcolor("black") + fontsize(label_size) + gsave() + translate(-40 * scale_x, -chart_height/2) + rotate(-ฯ€/2) + text(ylabel, Point(0, 0), halign=:center) + grestore() + + # Y-axis labels + fontsize(label_size) + for (i, y) in enumerate(yticks) + scaled_y = (y/yrange) * chart_height + text(string(y), Point(-10 * scale_x, -scaled_y), halign=:right) + end + + # Draw bars and labels + for (i, (impl, val)) in enumerate(zip(implementations, values)) + x = first_bar_offset + (i-1) * (bar_width + gap) + + + bottom = 0 + for (vi, ci) in zip(val, bar_colors[i] isa String ? (bar_colors[i], ) : bar_colors[i]) + # Fill bar (representing total) + + setcolor(ci) # Alternate between colors + #setcolor(bar_colors[i]) # Alternate between colors + + scaled_height = (vi/yrange) * chart_height + rect(Point(x, -bottom), bar_width, -scaled_height, :fill) + + bottom += scaled_height + end + + # Draw border + scaled_height = (sum(val)/yrange) * chart_height + setcolor(border_color) + rect(Point(x, 0), bar_width, -scaled_height, :stroke) + + # Draw value on top of bar + fontsize(value_size) + setcolor("black") + text(string(trunc(sum(val)) |> Int), Point(x + bar_width/2, -scaled_height - 10 * scale_y), halign=:center) + + setcolor("black") + # Draw wrapped label + fontsize(label_size) + wrapped_lines = wrap_text(impl, bar_width * 1.1, label_size) # scale here + + for (line_num, line) in enumerate(wrapped_lines) + line_y = (20 + (line_num - 1) * label_size) * scale_y + text(line, Point(x + bar_width/2, line_y), halign=:center) + end + end + + # Add title + fontsize(title_size) + text(title, + Point(0.9 * chart_width/2, -(chart_height + 30 * scale_y)), + halign=:center) + + finish() + +end + + +function barplot_test(dir::String) + + barplot(joinpath(dir, "simple.svg"), [150, 100, 70], ["ShuffleProofs (single core)", "Verificatum (single core)", "Verificatum (2 cores)"]; title = "PoS Verification on P-256 (N = 1 000 000)") + + barplot(joinpath(dir, "modp.svg"), [80, 30, 20], ["ShuffleProofs (single core)", "Verificatum (single core)", "Verificatum (2 cores)"]; title = "PoS Verification on MODP 2048 bit (N = 10000)", yrange = 80) + + barplot(joinpath(dir, "benchmark_chart.svg"), [(120, 30), 100, 70], ["ShuffleProofs (single core)", "Verificatum (single core)", "Verificatum (2 cores)"]; bar_colors = [("#3a8ad9", "#7ac0f7"), "#a5b3b3", "#a5b3b3"], title = "PoS Verification on MODP 2048 bit (N = 10000)", + border_color = "#2d2d2d", width = 600, height = 400, font_scale = 1.4 + ) + +end diff --git a/test/benchmarks/benchmark.jl b/test/benchmarks/benchmark.jl new file mode 100644 index 0000000..4f79a17 --- /dev/null +++ b/test/benchmarks/benchmark.jl @@ -0,0 +1,155 @@ +using CryptoGroups +using OpenSSLGroups +using ShuffleProofs +using ShuffleProofs: verify +using SigmaProofs.Verificatum: marshal_s_Gq +using Base: @elapsed + +include("barplot.jl") + +function generate_sample(dir::String, g::Group, N::Int; width::Int = 1) + + group = marshal_s_Gq(g).x |> String + + cd(dir) do + + run(`vmnd -pkey "$group" "publicKey"`) + run(`vmnd -ciphs -width $width "publicKey" $N "ciphertexts"`) + + run(`vmni -prot -sid "SessionID" -name "Ellection" -nopart 1 -thres 1 -width $width -pgroup "$group" "stub.xml"`) + run(`vmni -party -name "Santa Claus" "stub.xml" "privInfo.xml" "protInfo.xml"`) + + run(`vmn -setpk "privInfo.xml" "protInfo.xml" "publicKey"`) + run(`vmn -shuffle "privInfo.xml" "protInfo.xml" "ciphertexts" "ciphertextsout"`) + + end + + return +end + +# function verificatum_verify(dir::String) +# run(`vmnv -shuffle "$dir/protInfo.xml" "$dir/dir/nizkp/default"`) +# end + +function verificatum_verify(dir::String; cpu_cores=0:(Sys.CPU_THREADS-1)) + + cores = collect(cpu_cores) + + cmd = [ + "taskset", + "-c", join(cores, ','), + "vmnv", + "-shuffle", + "$dir/protInfo.xml", + "$dir/dir/nizkp/default" + ] + + try + output = IOBuffer() + error = IOBuffer() + + process = run(pipeline(`$cmd`, stdout=output, stderr=error)) + + return ( + success = process.exitcode == 0, + output = String(take!(output)), + error = String(take!(error)), + cores_used = cores + ) + catch e + if isa(e, SystemError) + return ( + success = false, + output = "", + error = "Failed to execute command: $(e.msg)", + cores_used = cores + ) + end + return ( + success = false, + output = "", + error = "Unexpected error: $e", + cores_used = cores + ) + end +end + + +function shuffleproofs_verify(dir::String; G = nothing) + simulator = ShuffleProofs.load_verificatum_simulator(dir; G) + return verify(simulator) +end + + +# returns output +function benchmark(g::Group, N::Int; fname = nothing, dir = joinpath(tempdir(), "shuffle"), title = "PoS Verification (N = $N)") + + rm(dir, recursive=true, force=true) + mkpath(dir) + generate_sample(dir, g, N) + + println("\n$title\n") + + print("Verificatum single core: ") + verificatum_single_core = @elapsed verificatum_verify(dir; cpu_cores=0:0) + println("$verificatum_single_core seconds") + + print("Verificatum two cores: ") + verificatum_two_cores = @elapsed verificatum_verify(dir; cpu_cores=0:1) + println("$verificatum_two_cores seconds") + + print("ShuffleProofs deserialization and validation: ") + shuffleproofs_validation = @elapsed (simulator = ShuffleProofs.load_verificatum_simulator(dir; G = typeof(g))) + println("$shuffleproofs_validation seconds") + + print("ShuffleProofs verification: ") + shuffleproofs_verification = @elapsed verify(simulator) + println("$shuffleproofs_verification seconds") + + + if !isnothing(fname) + + barplot(fname, + [(shuffleproofs_verification, shuffleproofs_validation), verificatum_single_core, verificatum_two_cores], + ["ShuffleProofs (single core)", "Verificatum (single core)", "Verificatum (2 cores)"]; + bar_colors = [("#3a8ad9", "#7ac0f7"), "#a5b3b3", "#a5b3b3"], + title, + border_color = "#2d2d2d", + width = 600, + height = 400, + font_scale = 1.4, + yrange = (shuffleproofs_validation + shuffleproofs_verification) * 1.1 + ) + end + + +end + + +# The sage prime generated using CryptoUtils.safe_prime(2024) +# this shall offer 128-bit security for discre logarithm problem +p = 21382745824200386598440507155450597318975927850568186995307661892981811037297261168881144465428778639798715782750831989983017501552972925363716393805743204151139763219470696077677088259096041802733626281326473577556517292979132363218956032590346933829352728780752329828816013172971209616889980498790118738799399089082309627680694844858663585459481303691612999162995050524911804677247814527046388187786574610440977393491731898010663380241945581858168893650926617195358184864871828169737311334866919621079911356627255143495523080166648673556549049197527147687314012167794605916987177326707347677098977211856674727029519 + +N = 10 # a warmup +g = @PGroup{p = p, q = div(p - 1, 2)}(4) +benchmark(g, N; title = "PoS Verification on MODP 2048 bit (N = $N)") + +N = 1000 +g = @PGroup{p = p, q = div(p - 1, 2)}(4) +benchmark(g, N; fname = joinpath(@__DIR__, "results/modp_2048_N=$N.svg"), title = "PoS Verification on MODP 2048 bit (N = $N)") + +N = 10000 +g = @PGroup{p = p, q = div(p - 1, 2)}(4) +benchmark(g, N; fname = joinpath(@__DIR__, "results/modp_2048_N=$N.svg"), title = "PoS Verification on MODP 2048 bit (N = $N)") + +N = 10 # a warmup +g = @ECGroup{OpenSSLGroups.Prime256v1}() +benchmark(g, N) + +N = 100000 +g = @ECGroup{OpenSSLGroups.Prime256v1}() +benchmark(g, N; fname = joinpath(@__DIR__, "results/P-256_N=$N.svg"), title = "PoS Verification on P-256 (N = $N)") + +# N = 1000000 +# g = @ECGroup{OpenSSLGroups.Prime256v1}() +# benchmark(g, N; fname = joinpath(@__DIR__, "results/P-256_N=$N.svg"), title = "PoS Verification on P-256 (N = 1 000 000)") diff --git a/test/benchmarks/proofgen.jl b/test/benchmarks/proofgen.jl new file mode 100644 index 0000000..e2628e4 --- /dev/null +++ b/test/benchmarks/proofgen.jl @@ -0,0 +1,25 @@ +# - The needed time for proof generation is about the same as it is needed for verification +# - Required memory scales linearly. For N = 1 000 000 one needs around 16 GB of memory + +using CryptoGroups +using OpenSSLGroups +import ShuffleProofs: shuffle, verify, load_verificatum_simulator, braid, Braid, save, load, Simulator +import SigmaProofs.ElGamal: Enc, Dec +import SigmaProofs.Verificatum: ProtocolSpec + +#N = 1000000 +N = 10000 + +g = @ECGroup{OpenSSLGroups.Prime256v1}() + +verifier = ProtocolSpec(; g) + +sk = 123 +pk = g^sk + +enc = Enc(pk, g) +๐ฆ = [g^rand(2:order(g)) for i in 1:N] .|> tuple +๐ž = enc(๐ฆ, rand(2:order(g), N)) + +@time simulator = shuffle(๐ž, g, pk, verifier) +@time verify(simulator) diff --git a/test/benchmarks/results/P-256_N=100000.svg b/test/benchmarks/results/P-256_N=100000.svg new file mode 100644 index 0000000..d488c17 --- /dev/null +++ b/test/benchmarks/results/P-256_N=100000.svg @@ -0,0 +1,362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/benchmarks/VerifierProfile.png b/test/benchmarks/results/VerifierProfile.png similarity index 100% rename from test/benchmarks/VerifierProfile.png rename to test/benchmarks/results/VerifierProfile.png diff --git a/test/benchmarks/results/modp_2048_N=1000.svg b/test/benchmarks/results/modp_2048_N=1000.svg new file mode 100644 index 0000000..09df930 --- /dev/null +++ b/test/benchmarks/results/modp_2048_N=1000.svg @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/benchmarks/results/modp_2048_N=10000.svg b/test/benchmarks/results/modp_2048_N=10000.svg new file mode 100644 index 0000000..a604637 --- /dev/null +++ b/test/benchmarks/results/modp_2048_N=10000.svg @@ -0,0 +1,387 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/openssl.jl b/test/openssl.jl index 0ed02f4..3ee0cb0 100644 --- a/test/openssl.jl +++ b/test/openssl.jl @@ -18,9 +18,6 @@ enc = Enc(pk, g) ๐ฆ = [g^4, g^2, g^3] .|> tuple ๐ž = enc(๐ฆ, [2, 3, 4]) -๐ซโ€ฒ = [4, 2, 10] -e_enc = enc(๐ž, ๐ซโ€ฒ) - simulator = shuffle(๐ž, g, pk, verifier) @test verify(simulator) @@ -70,20 +67,13 @@ openssl_braid_simulator = load(Simulator{Braid{typeof(g)}}, BRAID_DIR) ### Testing proof loading BASE_DIR = "$(@__DIR__)/validation_sample/verificatum/P256/" - -@btime simulator = load_verificatum_simulator(BASE_DIR; G = @ECGroup{OpenSSLGroups.Prime256v1}) - -#@btime simulator = load_verificatum_simulator("$(@__DIR__)/validation_sample/verificatum/P256/"; G = @ECGroup{OpenSSLGroups.Prime256v1}) +simulator = load_verificatum_simulator(BASE_DIR; G = @ECGroup{OpenSSLGroups.Prime256v1}) @test verify(simulator) -# About 25 times faster than CryptoGroups implementation here. -@btime simulator_ord = load_verificatum_simulator("$(@__DIR__)/validation_sample/verificatum/P256/") -#@btime verify(simulator_ord) -#@btime verify(simulator) +simulator_ord = load_verificatum_simulator("$(@__DIR__)/validation_sample/verificatum/P256/") +@test verify(simulator_ord) ### For extended width -# simulator = load_verificatum_simulator("$(@__DIR__)/validation_sample/verificatum/P192w3/"; G = @ECGroup{OpenSSLGroups.Prime192v1}) -# @test verify(simulator) # only about 5 times faster - - +simulator = load_verificatum_simulator("$(@__DIR__)/validation_sample/verificatum/P192w3/"; G = @ECGroup{OpenSSLGroups.Prime192v1}) +@test verify(simulator) diff --git a/test/proof.jl b/test/proof.jl index 70b8463..d2481d8 100644 --- a/test/proof.jl +++ b/test/proof.jl @@ -7,7 +7,7 @@ import CryptoGroups import SigmaProofs: generator_basis import ShuffleProofs: prove, verify, Simulator, Verifier, PoSChallenge, Shuffle, shuffle, VShuffleProof, PoSProof -import ShuffleProofs: PoSChallenge, gen_roprg, challenge_perm, challenge_reenc +import ShuffleProofs: PoSChallenge, gen_roprg, challenge_perm, challenge_reenc, seed ### @@ -17,12 +17,11 @@ end PoSChallenge(verifier::HonestVerifier) = verifier.challenge - generator_basis(verifier::HonestVerifier, G, n) = verifier.challenge.๐ก -challenge_perm(verifier::HonestVerifier, proposition, ๐œ) = verifier.challenge.๐ฎ +challenge_perm(verifier::HonestVerifier, proposition, ๐œ; kwargs...) = verifier.challenge.๐ฎ -challenge_reenc(verifier::HonestVerifier, proposition, ๐œ, ๐œฬ‚, t) = verifier.challenge.c +challenge_reenc(verifier::HonestVerifier, proposition, ๐œ, ๐œฬ‚, t; kwargs...) = verifier.challenge.c function test_prover(g) diff --git a/test/runtests.jl b/test/runtests.jl index 2e7bee4..a412384 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,9 +31,9 @@ end include("serializer.jl") end -# @safetestset "Testing OpenSSL integration" begin -# include("openssl.jl") -# end +@safetestset "Testing OpenSSL integration" begin + include("openssl.jl") +end @safetestset "Testing examples" begin include("../examples/voting-PoS.jl")