diff --git a/.github/workflows/CodeCov.yml b/.github/workflows/CodeCov.yml new file mode 100644 index 0000000..3460e53 --- /dev/null +++ b/.github/workflows/CodeCov.yml @@ -0,0 +1,18 @@ +name: Workflow for Codecov example-julia +on: [push, pull_request] +jobs: + run: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Julia 1.10.0 + uses: julia-actions/setup-julia@v1 + with: + version: "1.10.0" + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..f389611 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,20 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: + inputs: + lookback: + default: 3 +permissions: + contents: write +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c8dcfd --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/*~ +**/*.bak +**/\#*\# +**/.DS_Store +Manifest.toml \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9..2f36fb6 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2024 Janis Erdmanis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..3b62dbd --- /dev/null +++ b/Project.toml @@ -0,0 +1,26 @@ +name = "SigmaProofs" +uuid = "f8559b4c-f045-44a2-8db2-503e40bb7416" +authors = ["Janis Erdmanis "] +version = "0.1.0" + +[deps] +CryptoGroups = "bc997328-bedd-407e-bcd3-5758e064a52d" +CryptoPRG = "d846c407-34c1-46cb-aa27-d51818cc05e2" +CryptoUtils = "04afed74-ac16-11e9-37b6-1352e3e05830" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[compat] +CryptoGroups = "0.5.0" +CryptoPRG = "0.1.1" +CryptoUtils = "0.1.1" +InteractiveUtils = "1.11.0" +Random = "1.11.0" +Test = "1.11.0" + +[extras] +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test", "SafeTestsets"] diff --git a/examples/auctions.jl b/examples/auctions.jl new file mode 100644 index 0000000..2b18487 --- /dev/null +++ b/examples/auctions.jl @@ -0,0 +1,89 @@ +# TODO: RANGE PROOFS ARE NOT YET IMPLEMENTED + +# Auctions: [AW13] uses range proofs for verifiable auctions. Range proofs help an auctioneer to prove to an auditor that the sale price was set correctly without revealing the values of the bids. For example, in a second-price auction with +# n n bids, (where the sale price is equal to the second-highest bid), the auctioneer can provide proofs that n 1โˆ’1 bids were at most the sale price and one bid was greater than the sale price + + +# The setup is that the Dealer registers participant public keys which are used to sign auction bids (which we shall skip). The bids themselves are ElGamal encrypted under an authorithy issued public key. + +# (1) Keygen of authorithy, providing pk +# (3) Bidders make a bids encrypted in ElGamal texts. Additionally proof of knowledge for the exponent can be provided if the bidding is made public +# (4) Authorithy selects highest bidder and decrypts the second highest bid and provides decryption ZKP +# (5) Now for every single bid the authorithy makes a range proof proving that it's value is lower than that of the winning bid. Before we can do so the authorithy needs to reencrypt ElGamal cyphertexts with their own exponent which it can prove to encrypt the same plaintext with plaintext equivalence test Dec((a, b)/(a', b')) = 1. Then one can proceed with NRange proofs as desired. + + +sk, pk = keygen(g) +๐บ = basis_generators[1] + +function bid(value) + + ฮฑ = rand(2:order(G)-1) + + a = g^ฮฑ + b = pk^ฮฑ * ๐บ^value + + return (a, b) +end + + +function dec((a, b)) + + m = b / a^sk + + isone(m) && return 0 + + for i in 1:30 + ๐บ^i == m && return i + end + + error("Value not in range") # In practice thoose are decrypted as a proof +end + + +# Made bids by third parties. In real situation they are signed. May also require knowledge proof of exponenent to avoid Pfitzman attack, but it is unpractical to execute it as only the winners value is decrypted. + +v_A = 10 +bid_A = bid(v_A) + +v_B = 20 +bid_B = bid(v_B) + +v_C = 15 +bid_C = bid(v_C) + +# The dealer decrypts every bid privatelly: + +@test dec(bid_A) == v_A +@test dec(bid_B) == v_B +@test dec(bid_C) == v_C + +# It sees that bid_B is the highest and hence had won. It needs to pay second largest bid bid_C. Now it needs to prove that it had acted honestly which can be done with range proofs. Before such proofs can be executed we need to make a substitutes. + +ฮฑ_A = rand(2:order(G)-1) +substitute_A = substitute(g, pk, bid_A..., sk, ฮฑ_A, verifier) + +ฮฑ_B = rand(2:order(G)-1) +substitute_B = substitute(g, pk, bid_B..., sk, ฮฑ_B, verifier) + +decryption_C = decrypt(g, bid_C, sk, verifer) + +C_C = ๐บ^v_C # ฮฑ = 0 +range_BC = rangecommit(4, g, pk, v_B - v_C, verifier; ฮฑ = ฮฑ_B) +range_CA = rangecommit(4, g, pk, v_C - v_A, verifier; ฮฑ = -ฮฑ_A) + +### The auditor then can do the audit as follows + +v_C = dec(decryption_C) +C_C = ๐บ^v_C +C_A = substitute_A.bโ€ฒ +C_B = substitute_B.bโ€ฒ + +@test range_A.proposition.y == C_B / C_C +@test range_A.proposition.y == C_C / C_A + +@test verify(substitute_A) +@test verify(substitute_B) +@test verify(decryption_C) + +@test verify(range_BC) +@test verify(range_CA) diff --git a/src/CommitmentShuffle.jl b/src/CommitmentShuffle.jl new file mode 100644 index 0000000..5c83ac5 --- /dev/null +++ b/src/CommitmentShuffle.jl @@ -0,0 +1,164 @@ +module CommitmentShuffle + +using CryptoGroups +using ..SigmaProofs: Proposition, Proof, Verifier, Simulator, challenge, generator_basis, gen_roprg +using ..SigmaProofs.LogProofs: SchnorrProof, LogKnowledge +import ..SigmaProofs: prove, verify, proof_type + +# This document lays out a draft protocol that is designed to achieve information-theoretic security for a shuffle. The goal is to ensure long-lasting privacy if cryptogrpahically relevant quantum computer ever gets built in the future (which is unlikelly). The protocol requires interaction from the secret owners and hence it can not be used in cascade in a simple way, but it's outputs can be passed further down the line in ordinary ElGamal reencryption shuffle methods as implemented in ShuffleProofs. The everlating privacy in this way ends up being ensured by one authorithy which in if corrupt in the presence of quantum computer could link individual voters to votes and hence significatnly reduces the risk as there is no near term incentives for a corrupt authorithy to keep this data. + +# Note that this is a preliminary draft, and the protocol may not be sound and have already been revised multiple times because of that. Thereโ€™s also possibility that similar protocols may already exist in published literature which would be good to know. + +# Goal: This protocol creates a system where each member gets a new, unlinkable public key, while allowing for public verification +# that each member received exactly one key, without revealing which key belongs to which member. + +# 1. Setup: +# - A verifiable generator list is established +# - A blinding generator 'h' is set + +# 2. Dealer-member interaction: +# - Dealer sends a verifiably random generator g_i to each member +# - Member generates x_i and ฮฒ_i +# - Member computes: +# * u_i <- g_i^x +# * C = h^ฮฒ * u_i +# * PoK{(x): u_i = g_i^x_i) +# - Member sends back to dealer: +# * Signed {C} +# * ฮฒ_i +# * u_i +# * PoK{(x): u_i <- g_i^x_i) +# - Dealer publishes the commitment on a public bulletin board + +# 3. Dealer's consistency proof: +# - Dealer generates a secret vector r_i +# - Computes A <- h^r * โˆ u_i^s_i +# - Computes e_i from anouncement A using Fiat-Shamir transform +# - Computes secrets: +# * z = r + \sum_i \beta_i * e_i +# * w_i = s_i + e_i +# - Announces on public bulletin board: +# * Shuffled list of (u_i, g_i, PoK{(x_i): u_i = g_i^x_i}, w_i) and anouncement A, z + +# 4. Verification process: +# - Verify that every g_i was generated verifiably random +# - Verify PoK for every generator used in the commitment proof PoK{(x): u_i = g_i^x_i} +# - Compute e_i from A +# - Check that h^z * โˆ u_i^(s_i) = A * โˆ C_i^e_i + +# This protocol aims to ensure everlasting privacy in a braiding scheme by using generalized Pedersen commitments and zero-knowledge proofs. It allows members to participate individually with a dealer over a confidential quantum-resistant channel, addressing the potential threat of future quantum computers breaking the privacy of current cryptographic schemes. + +# For convenience, in the case of braiding, this can be part of the registration protocol. Each member generates a list of key commitments by selecting verifiable generators at random locally, as well as the blinding factors. They then deliver the membership certificate that lists commitments along with the secrets via a quantum-resistant channel. The coresponding authetification can be performed via ordinary public key cryptography as currently there is no cryptographically relevant quantum computer that could compromise that and it does not help adversary in the future to break ever lasting privacy. + +# WARNING: THIS PROTOCOL IS CREATION OF MY OWN AND HAS NOT BEEN CHECKED RIGOROUSLY THAT IT IS BINDING + +struct CommittedRow{G <: Group, T} + g::G + u::G + m::T # Can be arbitrary type. A single group element, or ElGamalRow that can be passed further down to a mix cascade. + pok::SchnorrProof{G} +end + +Base.isless(x::T, y::T) where T <: CommittedRow = isless(x.u, y.u) + +function verify(row::CommittedRow, verifier::Verifier) + + (; m, g, u, pok) = row + proposition = LogKnowledge(g, u) + + return verify(proposition, pok, verifier; suffix = m) +end + +# perhaps naming it as dercom, derive_commit +function commit(m::G, g::G, h::G, verifier::Verifier; roprg = gen_roprg(), x = rand(roprg(:x), 2:order(G)-1)) where G <: Group + + u = g^x + ฮฒ = rand(roprg(:ฮฒ), 2:order(G)-1) + + C = h^ฮฒ * u + + pok = prove(LogKnowledge(g, u), verifier, x; suffix = m) + + row = CommittedRow(g, u, m, pok) + + return row, C, ฮฒ +end + +isbinding(row::CommittedRow{G}, C::G, h::G, ฮฒ::Integer) where G <: Group = h^ฮฒ * row.u == C + +struct Shuffle{G<:Group} <: Proposition # What is shown on public buletin board + h::G + ๐‚::Vector{G} # need to be individually signed + rows::Vector{<:CommittedRow{G}} +end + +Base.length(proposition::Shuffle) = length(proposition.๐‚) + +struct ShuffleProof{G<:Group} <: Proof + A::G + z::BigInt + ๐ฐ::Vector{BigInt} +end + +proof_type(::Type{Shuffle{G}}) where G <: Group = ShuffleProof{G} + +function verify(proposition::Shuffle{G}, proof::ShuffleProof{G}, verifier::Verifier; nmax = 10 * length(proposition)) where G <: Group + + (; h, ๐‚, rows) = proposition + (; A, z, ๐ฐ) = proof + + ๐  = (i.g for i in rows) + + basis = generator_basis(verifier, G, nmax) + + h in ๐  && return false # blinding factor can't be one of the generators + length(unique(๐ )) == length(๐ ) || return false # every generator must be unique + + all(x -> x.g in basis, rows) || return false # generator not in basis + + all(x -> verify(x, verifier), rows) || return false # pok for committed value exponent + + # Now we have established that ๐ฎ forms an independet generator basis set + + ๐ž = challenge(verifier, proposition, A) + ๐ฎ = (x.u for x in rows) + h^z * prod(๐ฎ .^ ๐ฐ) == A * prod(๐‚ .^ ๐ž) || return false + + # Hence the vector (๐ , ๐ฎ) is consistent with commitment vector ๐‚ + + return true +end + + +# The blinding factors are for commitment order +# the proofs pok can be oermuted sepereatelly in place + +function prove(proposition::Shuffle{G}, verifier::Verifier, ๐›ƒ::Vector{<:Integer}, ๐›™::Vector{<:Integer}; roprg = gen_roprg()) where G + + (; h, rows, ๐‚) = proposition + ๐ฎ = (x.u for x in rows) + + ๐ฌ = rand(roprg(:๐ฌ), 2:order(G) - 1, length(๐›ƒ)) + r = rand(roprg(:r), 2:order(G) - 1) + + A = h^r * prod(๐ฎ .^ ๐ฌ) + + ๐ž = challenge(verifier, proposition, A) + + z = r + sum(๐›ƒ .* ๐ž) + ๐ฐ = ๐ฌ .+ view(๐ž, ๐›™) + + return ShuffleProof(A, z, ๐ฐ) +end + + +function shuffle(rows::Vector{<:CommittedRow{G}}, h::G, ๐‚::Vector{G}, ๐›ƒ::Vector{<:Integer}, verifier::Verifier; ๐›™ = sortperm(rows)) where G <: Group + + proposition = Shuffle(h, ๐‚, rows[๐›™]) + proof = prove(proposition, verifier, ๐›ƒ, ๐›™) + + return Simulator(proposition, proof, verifier) +end + + +end diff --git a/src/DecryptionProofs.jl b/src/DecryptionProofs.jl new file mode 100644 index 0000000..b82db42 --- /dev/null +++ b/src/DecryptionProofs.jl @@ -0,0 +1,271 @@ +module DecryptionProofs + +using ..Serializer: Serializer, Path, load +using ..SigmaProofs.Parser: Parser, Tree +using ..SigmaProofs: Proposition, Verifier, Simulator +using ..ElGamal: ElGamalRow +using ..LogProofs: ChaumPedersenProof, LogEquality +using CryptoGroups: Group +using Base.Iterators: flatten +import ..SigmaProofs: prove, verify, proof_type + +a(e::ElGamalRow{<:Group, N}) where N = ntuple(n -> e[n].a, N) +b(e::ElGamalRow{<:Group, N}) where N = ntuple(n -> e[n].b, N) + +struct Decryption{G <: Group, N} <: Proposition + g::G + pk::G + cyphertexts::Vector{ElGamalRow{G, N}} + plaintexts::Vector{NTuple{N, G}} +end + +proof_type(::Type{Decryption}) = ChaumPedersenProof +proof_type(::Type{<:Decryption{G}}) where G <: Group = ChaumPedersenProof{G} + +Base.:(==)(x::T, y::T) where T <: Decryption = x.g == y.g && x.pk == y.pk && x.cyphertexts == y.cyphertexts && x.plaintexts == y.plaintexts + +function Base.permute!(decr::Decryption, perm::AbstractVector{<:Integer}) + + permute!(decr.cyphertexts, perm) + permute!(decr.plaintexts, perm) + + return +end + +verify(proposition::Decryption, secret::Integer) = decrypt(proposition.g, proposition.cyphertexts, secret) == proposition + +axinv(proposition::Decryption) = (mi ./ b(ei) for (ei, mi) in zip(proposition.cyphertexts, proposition.plaintexts)) +axinv(e::Vector{<:ElGamalRow}, secret::Integer) = (a(ei) .^ (-secret) for ei in e) + +function decrypt(g::G, cyphertexts::Vector{<:ElGamalRow{G}}, secret::Integer; axinv = axinv(cyphertexts, secret)) where G <: Group + + plaintexts = [b(ci) .* axi for (ci, axi) in zip(cyphertexts, axinv)] + pk = g^secret + + return Decryption(g, pk, cyphertexts, plaintexts) +end + +function prove(proposition::Decryption{G}, verifier::Verifier, secret::Integer; axinv = axinv(proposition)) where G <: Group + + g_vec = [proposition.g, flatten(a(c) for c in proposition.cyphertexts)...] + y_vec = [inv(proposition.pk), flatten(axinv)...] + + log_proposition = LogEquality(g_vec, y_vec) + + log_proof = prove(log_proposition, verifier, -secret) + + return log_proof +end + +function decrypt(g::G, cyphertexts::Vector{<:ElGamalRow{G}}, secret::Integer, verifier::Verifier; axinv = axinv(cyphertexts, secret)) where G <: Group + + proposition = decrypt(g, cyphertexts, secret; axinv) + proof = prove(proposition, verifier, secret; axinv) + + return Simulator(proposition, proof, verifier) +end + + +function verify(proposition::Decryption, proof::ChaumPedersenProof, verifier::Verifier) + + g_vec = [proposition.g, flatten(a(c) for c in proposition.cyphertexts)...] + y_vec = [inv(proposition.pk), flatten(axinv(proposition))...] + + log_proposition = LogEquality(g_vec, y_vec) + + return verify(log_proposition, proof, verifier) +end + + +# An alternative way to construct a proof +# benefit is that ax can be computed more efficeintly this way and messages do not need to be inverted +# A bit complex though +struct DecryptionInv{G <: Group, N} <: Proposition + g::G + pk::G + cyphertexts::Vector{ElGamalRow{G, N}} + trackers::Vector{NTuple{N, G}} +end + +proof_type(::Type{DecryptionInv}) = ChaumPedersenProof +proof_type(::Type{<:DecryptionInv{G}}) where G <: Group = ChaumPedersenProof{G} + +Base.:(==)(x::T, y::T) where T <: DecryptionInv = x.g == y.g && x.pk == y.pk && x.cyphertexts == y.cyphertexts && x.trackers == y.trackers + +function Base.permute!(decr::DecryptionInv, perm::AbstractVector{<:Integer}) + + permute!(decr.cyphertexts, perm) + permute!(decr.trackers, perm) + + return +end + +ax(proposition::DecryptionInv) = (ti .* b(ei) for (ei, ti) in zip(proposition.cyphertexts, proposition.trackers)) +ax(e::Vector{<:ElGamalRow}, secret::Integer) = (a(ei) .^ secret for ei in e) + +function decryptinv(g::G, cyphertexts::Vector{<:ElGamalRow{G}}, secret::Integer; ax = ax(cyphertexts, secret)) where G <: Group + + trackers = [axi ./ b(ci) for (ci, axi) in zip(cyphertexts, ax)] + pk = g^secret + + return DecryptionInv(g, pk, cyphertexts, trackers) +end + +# The same actually +function prove(proposition::DecryptionInv{G}, verifier::Verifier, secret::Integer; ax = ax(proposition)) where G <: Group + + g_vec = [proposition.g, flatten(a(c) for c in proposition.cyphertexts)...] + y_vec = [proposition.pk, flatten(ax)...] + + log_proposition = LogEquality(g_vec, y_vec) + + log_proof = prove(log_proposition, verifier, secret) + + return log_proof +end + + +function decryptinv(g::G, cyphertexts::Vector{<:ElGamalRow{G}}, secret::Integer, verifier::Verifier; ax = ax(cyphertexts, secret)) where G <: Group + + proposition = decryptinv(g, cyphertexts, secret; ax) + + proof = prove(proposition, verifier, secret; ax) + + return Simulator(proposition, proof, verifier) +end + +verify(proposition::DecryptionInv, secret::Integer) = decryptinv(proposition.g, proposition.cyphertexts, secret) == proposition + +function verify(proposition::DecryptionInv, proof::ChaumPedersenProof, verifier::Verifier) + + g_vec = [proposition.g, flatten(a(c) for c in proposition.cyphertexts)...] + y_vec = [proposition.pk, flatten(ax(proposition))...] + + log_proposition = LogEquality(g_vec, y_vec) + + return verify(log_proposition, proof, verifier) +end + + +function Serializer.save(proposition::Decryption, dir::Path) + + (; g, pk, cyphertexts, plaintexts) = proposition + + pbkey_tree = Parser.marshal_publickey(pk, g) + write(joinpath(dir, "publicKey.bt"), pbkey_tree) + + write(joinpath(dir, "Ciphertexts.bt"), Tree(cyphertexts)) + write(joinpath(dir, "Decryption.bt"), Tree(plaintexts)) # Decryption could be renamed to Plaintexts + + return +end + +Serializer.save(proof::ChaumPedersenProof, ::Type{<:Decryption}, path::Path) = Serializer.save(proof, path; prefix="Decryption") + +function Serializer.load(::Type{Decryption}, basedir::Path) + + publickey_tree = Parser.decode(read(joinpath(basedir, "publicKey.bt"))) + pk, g = Parser.unmarshal_publickey(publickey_tree; relative=true) + + G = typeof(g) + + cyphertexts_tree = Parser.decode(read(joinpath(basedir, "Ciphertexts.bt"))) + plaintexts_tree = Parser.decode(read(joinpath(basedir, "Decryption.bt"))) + + N = 1 # ToDo: extract that from the tree + + cyphertexts = convert(Vector{ElGamalRow{G, N}}, cyphertexts_tree) + plaintexts = convert(Vector{NTuple{N, G}}, plaintexts_tree) + + return Decryption(g, pk, cyphertexts, plaintexts) +end + +Serializer.load(::Type{P}, ::Type{Decryption}, path::Path) where P <: ChaumPedersenProof = Serializer.load(P, path; prefix="Decryption") + +# ToDO + +function Serializer.save(proposition::DecryptionInv, dir::Path) + + (; g, pk, cyphertexts, trackers) = proposition + + pbkey_tree = Parser.marshal_publickey(pk, g) + write(joinpath(dir, "publicKey.bt"), pbkey_tree) + + write(joinpath(dir, "Ciphertexts.bt"), Tree(cyphertexts)) + write(joinpath(dir, "DecryptionInv.bt"), Tree(trackers)) # Decryption could be renamed to Plaintexts + + return +end + +Serializer.save(proof::ChaumPedersenProof, ::Type{<:DecryptionInv}, path::Path) = Serializer.save(proof, path; prefix="DecryptionInv") + +function Serializer.load(::Type{DecryptionInv}, basedir::Path) + + publickey_tree = Parser.decode(read(joinpath(basedir, "publicKey.bt"))) + pk, g = Parser.unmarshal_publickey(publickey_tree; relative=true) + + G = typeof(g) + + cyphertexts_tree = Parser.decode(read(joinpath(basedir, "Ciphertexts.bt"))) + plaintexts_tree = Parser.decode(read(joinpath(basedir, "DecryptionInv.bt"))) + + N = 1 # ToDo: extract that from the tree + + cyphertexts = convert(Vector{ElGamalRow{G, N}}, cyphertexts_tree) + plaintexts = convert(Vector{NTuple{N, G}}, plaintexts_tree) + + return DecryptionInv(g, pk, cyphertexts, plaintexts) +end + +Serializer.load(::Type{P}, ::Type{DecryptionInv}, path::Path) where P <: ChaumPedersenProof = Serializer.load(P, path; prefix="DecryptionInv") + + + +#treespec(::Type{<:Decryption}) = + +# # Keeping simple +# Serializer.treespec(::Type{Simulator{<:Decryption}}) = ( +# "protInfo.xml", +# "publicKey.bt", +# "Ciphertexts.bt", +# "Decryption.bt", +# "nizkp/DecryptionCommitment.bt", +# "nizkp/DecryptionReply.bt" +# ) + +Serializer.treespec(::Type{<:Decryption}) = ( + "publicKey.bt", + "Ciphertexts.bt", + "Decryption.bt" +) + +Serializer.treespec(::Type{<:ChaumPedersenProof}, ::Type{<:Decryption}) = ( + "DecryptionCommitment.bt", + "DecryptionReply.bt" +) + +Serializer.treespec(::Type{<:DecryptionInv}) = ( + "publicKey.bt", + "Ciphertexts.bt", + "DecryptionInv.bt" +) + +Serializer.treespec(::Type{<:ChaumPedersenProof}, ::Type{<:DecryptionInv}) = ( + "DecryptionInvCommitment.bt", + "DecryptionInvReply.bt" +) + + +# # Keeping simple +# Serializer.treespec(::Type{Simulator{<:DecryptionInv}}) = ( +# "protInfo.xml", +# "publicKey.bt", +# "Ciphertexts.bt", +# "DecryptionInv.bt", +# "nizkp/DecryptionInvCommitment.bt", +# "nizkp/DecryptionInvReply.bt" +# ) + + + +end diff --git a/src/ElGamal.jl b/src/ElGamal.jl new file mode 100644 index 0000000..023364f --- /dev/null +++ b/src/ElGamal.jl @@ -0,0 +1,116 @@ +module ElGamal + +using CryptoGroups.Utils: @check +using CryptoGroups: Group +import Base: *, ^ + +struct ElGamalElement{G<:Group} + a::G + b::G +end + +ElGamalElement(m::G) where G <: Group = ElGamalElement(one(G), m) + +*(x::ElGamalElement{G}, y::ElGamalElement{G}) where G <: Group = ElGamalElement(x.a * y.a, x.b * y.b) +*(x::ElGamalElement{G}, y::Tuple{G, G}) where G <: Group = x * ElGamalElement(y[1], y[2]) +*(x::Tuple{G, G}, y::ElGamalElement{G}) where G <: Group = y * x + +^(x::ElGamalElement{G}, k::Integer) where G <: Group = ElGamalElement(x.a ^ k, x.b ^ k) + +Base.isless(x::ElGamalElement{G}, y::ElGamalElement{G}) where G <: Group = x.a == y.a ? x.b < y.b : x.a == y.a + +Base.:(==)(x::ElGamalElement{G}, y::ElGamalElement{G}) where G <: Group = x.a == y.a && x.b == y.b + +struct ElGamalRow{G<:Group, N} + row::NTuple{N, ElGamalElement{G}} +end + +ElGamalRow(a::G, b::G) where G <: Group = ElGamalRow(ElGamalElement(a, b)) + +ElGamalRow(row::NTuple{N, G}) where {N, G <: Group} = convert(ElGamalRow{G, N}, row) +ElGamalRow(element::ElGamalElement{G}) where {G <: Group} = convert(ElGamalRow{G, 1}, element) +ElGamalRow(row::AbstractVector{<:ElGamalElement{G}}) where {G <: Group} = convert(ElGamalRow{G}, row) + +Base.convert(::Type{ElGamalRow{G, N}}, row::NTuple{N, G}) where {N, G<:Group} = ElGamalRow(tuple([ElGamalElement(mi) for mi in row]...)) +Base.convert(::Type{ElGamalRow{G, 1}}, element::ElGamalElement{G}) where G <: Group = ElGamalRow((element,)) +Base.convert(::Type{ElGamalRow{G}}, row::AbstractVector{<:ElGamalElement{G}}) where G <: Group = ElGamalRow(tuple(row...)) +#Base.convert(::Type{ElGamalRow}, row) = ElGamalRow(row) + +Base.:(==)(x::ElGamalRow{G, N}, y::ElGamalRow{G, N}) where {G <: Group, N} = x.row == y.row + +Base.getindex(row::ElGamalRow, index::Integer) = row.row[index] +Base.getindex(row::ElGamalRow, range) = ElGamalRow(row.row[range]) + +Base.length(row::ElGamalRow) = length(row.row) +Base.lastindex(row::ElGamalRow) = length(row) + +*(x::ElGamalRow{G}, y::ElGamalRow{G}) where G <: Group = ElGamalRow(x.row .* y.row) +*(x::ElGamalRow{G}, y::Tuple{G, G}) where G <: Group = ElGamalRow(x.row .* y) +*(x::Tuple{G, G}, y::ElGamalRow{G}) where G <: Group = y * x +*(x::ElGamalRow{G}, y::ElGamalElement{G}) where G <: Group = ElGamalRow(ElGamalElement{G}[i * y for i in x.row]) +*(x::ElGamalElement{G}, y::ElGamalRow{G}) where G <: Group = y * x + +^(x::ElGamalRow{G}, k::Integer) where G <: Group = ElGamalRow(x.row .^ k) #(x[1]^k, x[2]^k) + +function Base.isless(x::ElGamalRow{G, N}, y::ElGamalRow{G, N}) where {G <: Group, N} + + for (xi, yi) in zip(x.row, y.row) + + if xi !== yi + return xi < yi + end + + end + + return false +end + + +struct Enc{T<:Group} + pk::T + g::T +end + +(enc::Enc)(r::Integer) = ElGamalElement(enc.g^r, enc.pk^r) # A tuple is treated as a scalar +(enc::Enc{G})(e::ElGamalElement{G}, r::Integer) where G <: Group = e * enc(r) +(enc::Enc{G})(m::G, r::Integer) where G <: Group = enc(ElGamalElement(one(G), m), r) +(enc::Enc{G})(m::AbstractVector{G}, r::AbstractVector{<:Integer}) where G <: Group = enc.(m, r) # Returns ElGamalElement vector +(enc::Enc{G})(m::AbstractVector{<:ElGamalElement{G}}, r::AbstractVector{<:Integer}) where G <: Group = enc.(m, r) + +(enc::Enc)(r::NTuple{N, <:Integer}) where N = ElGamalRow(enc.(r)) +(enc::Enc{G})(e::ElGamalRow{G}, r::Integer) where G <: Group = e * enc(r) # Tuple is treated as a scalar +(enc::Enc{G})(e::ElGamalRow{G}, r::NTuple{N, <:Integer}) where {N, G <: Group} = e * enc(r) +(enc::Enc{G})(m::NTuple{N, G}, r::Integer) where {N, G <: Group} = enc(ElGamalRow(m), r) +(enc::Enc{G})(m::NTuple{N, G}, r::NTuple{N, <:Integer}) where {N, G <: Group} = enc(ElGamalRow(m), r) + +(enc::Enc{G})(m::AbstractVector{<:ElGamalRow{G}}, r::AbstractVector{<:Integer}) where G <: Group = enc.(m, r) +(enc::Enc{G})(m::AbstractVector{<:NTuple{N, G}}, r::AbstractVector{<:Integer}) where {N, G <: Group} = enc.(m, r) +(enc::Enc{G})(m::AbstractVector{<:ElGamalRow{G}}, r::AbstractVector{<:NTuple{N, <:Integer}}) where {N, G <: Group} = enc.(m, r) +(enc::Enc{G})(m::AbstractVector{<:NTuple{N, G}}, r::AbstractVector{<:NTuple{N, <:Integer}}) where {N, G <: Group} = enc.(m, r) + + +function (enc::Enc{G})(m::AbstractVector{<:ElGamalRow{G}}, r::AbstractMatrix{<:Integer}) where G <: Group + @check length(r[:, 1]) == length(m[1]) "Dimensions not equal" + return [enc(mi, tuple(ri...)) for (mi, ri) in zip(m, eachcol(r))] +end + +function (enc::Enc{G})(m::AbstractVector{<:NTuple{N, G}}, r::AbstractMatrix{<:Integer}) where {N, G <: Group} + @check length(r[:, 1]) == N "Dimensions not equal" + return [enc(mi, tuple(ri...)) for (mi, ri) in zip(m, eachcol(r))] +end + + +struct Dec + sk::Integer +end + +(dec::Dec)(e::ElGamalElement) = e.b * e.a^(-dec.sk) +(dec::Dec)(e::ElGamalRow) = dec.(e.row) + +(dec::Dec)(e::Tuple{G, G}) where G <: Group = dec(ElGamalElement(e.a, e.b)) +(dec::Dec)(a::G, b::G) where G <: Group = dec(ElGamalElement(a, b)) + +(dec::Dec)(e::AbstractVector{<:ElGamalElement{<:Group}}) = dec.(e) +(dec::Dec)(e::AbstractVector{<:ElGamalRow{<:Group, N}}) where N = dec.(e) # Using this method over dot syntax offers more type safety + +end diff --git a/src/LogProofs.jl b/src/LogProofs.jl new file mode 100644 index 0000000..ed859dc --- /dev/null +++ b/src/LogProofs.jl @@ -0,0 +1,178 @@ +module LogProofs + +using CryptoGroups: Group, order, octet +using CryptoGroups.Utils: int2octet + +using CryptoPRG.Verificatum: ROPRG, PRG, HashSpec, bitlength +using ..Serializer: Serializer, Path +import ..SigmaProofs: prove, verify, proof_type +using ..SigmaProofs: Proposition, Verifier, Proof, Simulator, challenge, Parser, gen_roprg +using Base.Iterators: flatten +using ..SigmaProofs.Parser: Tree + + +# # ToDo: put this in CryptoPRG +# using Random: RandomDevice + +# function gen_roprg(ฯ::AbstractVector{UInt8}) + +# rohash = HashSpec("sha256") +# prghash = HashSpec("sha256") +# roprg = ROPRG(ฯ, rohash, prghash) + +# return roprg +# end + +# gen_roprg() = gen_roprg(rand(RandomDevice(), UInt8, 32)) + + +struct LogKnowledge{G <: Group} <: Proposition + g::G + y::G +end + +struct SchnorrProof{G <: Group} <: Proof + R::G + s::BigInt +end + +proof_type(::Type{LogKnowledge{G}}) where G <: Group = SchnorrProof{G} + +function prove(proposition::LogKnowledge{G}, verifier::Verifier, x::Integer; suffix = nothing) where G <: Group + + (; g, y) = proposition + + # we can construct proof deterministically without relying on randomness source + roprg = gen_roprg([octet(y)..., int2octet(x)...]) + #prg = PRG(HashSpec("sha256"), [octet(y)..., int2octet(x)...]) + + r = rand(roprg(:x), 2:order(G) - 1) + + R = g^r + + c = challenge(verifier, proposition, R; filter(!isnothing, (;suffix))...) # suffix is optional + + s = (r + c * x) % order(G) + + return SchnorrProof(R, s) +end + +function verify(proposition::LogKnowledge, proof::SchnorrProof, verifier::Verifier; suffix = nothing) + + (; g, y) = proposition + (; R, s) = proof + + c = challenge(verifier, proposition, R; filter(!isnothing, (;suffix))...) + + return g^s == R * y^c +end + + +# Proposition is that g .^ x = y +struct LogEquality{G <: Group} <: Proposition + g::Vector{G} + y::Vector{G} +end + +Base.length(proposition::LogEquality) = length(proposition.g) + +struct ChaumPedersenProof{G} <: Proof + commitment::Vector{G} + response::BigInt +end + +proof_type(::Type{LogEquality{G}}) where G <: Group = ChaumPedersenProof{G} + +Base.:(==)(x::T, y::T) where T <: ChaumPedersenProof = x.commitment == y.commitment && x.response == y.response + + + + +function prove(proposition::LogEquality{G}, verifier::Verifier, power::Integer; roprg = gen_roprg())::ChaumPedersenProof where G <: Group + + (; g, y) = proposition + + q = order(G) + + s = rand(roprg(:r), 2:q - 1) + + commitment = g .^ s + + c = challenge(verifier, proposition, commitment) + + response = s + c * power + + return ChaumPedersenProof(commitment, mod(response, q)) +end + +verify(proposition::LogEquality, power::Integer)::Bool = proposition.g .^ power == proposition.y + +function verify(proposition::LogEquality, proof::ChaumPedersenProof, verifier::Verifier)::Bool + + (; g, y) = proposition + (; commitment, response) = proof + + c = challenge(verifier, proposition, commitment) + + for i in 1:length(proposition) + g[i] ^ response == commitment[i] * y[i] ^ c || return false + end + + return true +end + + +function exponentiate(g::Vector{<:Group}, power::Integer)::LogEquality + y = g .^ power + return LogEquality(g, y) +end + +function exponentiate(g::Vector{<:Group}, power::Integer, verifier::Verifier)::Simulator + + proposition = exponentiate(g, power) + proof = prove(proposition, verifier, power) + + return Simulator(proposition, proof, verifier) +end + + + +# ToDo +function Serializer.save(proof::ChaumPedersenProof, dir::Path; prefix = "ChaumPedersen") + + (; commitment, response) = proof + + L = bitlength(commitment[1]) + + write(joinpath(dir, "$(prefix)Commitment.bt"), Tree(commitment)) + write(joinpath(dir, "$(prefix)Reply.bt"), Tree(response; L)) + + return +end + + +function Serializer.load(::Type{ChaumPedersenProof{G}}, dir::Path; prefix = "ChaumPedersen") where G <: Group +# I could use kwargs for save and load with ChaumPedersenProof +# Or perhaps it is better to leave it as ChaumPedersenCommitment.bt and ChaumPedersenReply.bt +# I could also have a prefix argument! prefix = "Decryption"|"DecryptionInv"|"ChaumPedersen" + + #G = typeof(g) + + ฯ„_tree = Parser.decode(read(joinpath(dir, "$(prefix)Commitment.bt"))) + ฯ„ = convert(Vector{G}, ฯ„_tree) + + r_tree = Parser.decode(read(joinpath(dir, "$(prefix)Reply.bt"))) + r = convert(BigInt, r_tree) + + #return DecryptionProof(ฯ„, r) + return ChaumPedersenProof(ฯ„, r) +end + +Serializer.treespec(::Type{ChaumPedersenProof}; prefix = "ChaumPedersen") = ( + "$(prefix)Commitment.bt", + "$(prefix)Reply.bt" +) + + + +end diff --git a/src/Parser.jl b/src/Parser.jl new file mode 100644 index 0000000..aa1b040 --- /dev/null +++ b/src/Parser.jl @@ -0,0 +1,443 @@ +module Parser + +using ...ElGamal: ElGamalRow +using CryptoGroups.Curves: a, b, field, gx, gy +using CryptoGroups: PGroup, ECGroup, Group, value, concretize_type, spec, generator, name, ECPoint, modulus, order +using CryptoPRG.Verificatum: HashSpec +import CryptoGroups.Fields: bitlength # TODO: import -> using +using CryptoGroups.Utils: int2octet, @check + +import Base.convert +import Base.== + +tobig(x) = parse(BigInt, bytes2hex(reverse(x)), base=16) +interpret(::Type{BigInt}, x::Vector{UInt8}) = tobig(reverse(x)) + +function interpret(::Type{T}, x::Vector{UInt8}) where T <: Integer + + L = bitlength(T) รท 8 + y = UInt8[zeros(UInt8, L - length(x))..., x...] + + r = reinterpret(T, reverse(y))[1] + return r +end + +function int2bytes(x::Integer) + + @check x > 0 + + hex = string(x, base=16) + if mod(length(hex), 2) != 0 + hex = string("0", hex) + end + + return reverse(hex2bytes(hex)) +end + +interpret(::Type{Vector{UInt8}}, x::BigInt) = reverse(int2bytes(x)) +interpret(::Type{Vector{UInt8}}, x::Integer) = reverse(reinterpret(UInt8, [x])) # Number of bytes are useful for construction for bytes. + +function interpret(::Type{Vector{T}}, ๐ซ::Vector{UInt8}, N::Int) where T <: Integer + M = length(๐ซ) รท N + ๐ฎ = reshape(๐ซ, (M, N)) + ๐ญ = [interpret(T, ๐ฎ[:, i]) for i in 1:N] + return ๐ญ +end + +const NODE = UInt8(0) +const LEAF = UInt8(1) + +abstract type Tree end + +Base.string(x::Tree) = bytes2hex(encode(x)) + +struct Leaf <: Tree + x::Vector{UInt8} # Bytes +end + +==(a::Leaf, b::Leaf) = a.x == b.x + +struct Node <: Tree + x::Vector{Tree} # Leaf or Node +end + +Base.getindex(node::Node, index::Int) = node.x[index] +Base.length(node::Node) = length(node.x) + +==(a::Node, b::Node) = a.x == b.x + +Node() = Node([]) + +Base.push!(n::Node, y) = push!(n.x, y) + +toint(x) = reinterpret(UInt32, x[4:-1:1])[1] ### TOREMOVE + +function parseb(x) + + if x[1] == LEAF + + L = interpret(UInt32, x[2:5]) + + bytes = x[6:5+L] + leaf = Leaf(bytes) + + if length(x) == L + 5 + rest = [] + else + rest = x[L+6:end] + end + + return leaf, rest + + elseif x[2] == NODE + + N = interpret(UInt32, x[2:5]) + + rest = x[6:end] + + node = Node() + + for i in 1:N + head, tail = parseb(rest) + push!(node, head) + rest = tail + end + + return node, rest + end +end + + +decode(x::Vector{UInt8}) = parseb(x)[1] +decode(x::AbstractString) = decode(hex2bytes(replace(x, " "=>""))) # I could have optional arguments here as well + + +function tobin(leaf::Leaf) + + N = UInt32(length(leaf.x)) + + Nbin = interpret(Vector{UInt8}, N) + bin = UInt8[LEAF, Nbin..., leaf.x...] + + return bin +end + +function tobin(node::Node) + + N = UInt32(length(node.x)) + Nbin = interpret(Vector{UInt8}, N) + + data = UInt8[] + + for n in node.x + b = tobin(n) + append!(data, b) + end + + bin = UInt8[NODE, Nbin..., data...] + + return bin +end + +encode(x::Tree) = tobin(x) + +convert(::Type{T}, x::Leaf) where T <: Integer = interpret(T, x.x) + +function convert(::Type{String}, x::Leaf) + return String(copy(x.x)) +end + +function convert(::Type{Vector{T}}, x::Node) where T #<: Integer + return T[convert(T, i) for i in x.x] +end + +function convert(cfact::Type{T}, x::Node) where T <: Tuple + return Tuple((convert(ci, xi) for (xi, ci) in zip(x.x, cfact.types))) +end + +function Leaf(x::Signed) + + bytes = interpret(Vector{UInt8}, x) + + # Adding a redundant byte to ensure that the number is positive. + if bytes[1] > 127 + return Leaf(UInt8[0, bytes...]) + else + return Leaf(bytes) + end +end + +Leaf(x::Unsigned) = Leaf(interpret(Vector{UInt8}, x)) + + +function Leaf(x::Integer, k::Integer) + + if x == 0 + + return Leaf(zeros(UInt8, k)) + + else + leaf = Leaf(x) + + N = findfirst(x -> x != UInt8(0), leaf.x) + bytes = leaf.x[N:end] + pad = k - length(bytes) + + return newleaf = Leaf(UInt8[zeros(UInt8, pad)...,bytes...]) + end +end + + +function Leaf(x::AbstractString) + bytes = Vector{UInt8}(x) + return Leaf(bytes) +end + +Tree(x::Any) = Leaf(x) +Tree(x::BigInt; L=bitlength(x)) = Leaf(x, div(L + 1, 8, RoundUp)) +Tree(x::Node) = x +Tree(x::Leaf) = x +Tree(x::Tuple; L=nothing) = Node(x; L) + + +function Node(x::Tuple; L=nothing) + node = Node() + for i in x + if isnothing(L) + r = Tree(i) + else + r = Tree(i; L) # This would make issues when i would be a string or a group element + end + push!(node, r) + end + return node +end + + +############################ COMPOSITE TYPE PARSING ############################ + + +function convert(::Type{Vector{G}}, x::Node; allow_one=false) where G <: Group + return G[convert(G, i; allow_one) for i in x.x] +end + + +(h::HashSpec)(t::Tree) = h(convert(Vector{UInt8}, t)) ### need to relocate + +# To be added to CryptoGroups +bitlength(::Type{G}) where G <: PGroup = bitlength(modulus(G)) +bitlength(x::PGroup) = bitlength(modulus(x)) + +bitlength(::Type{ECGroup{P}}) where P <: ECPoint = bitlength(modulus(field(P))) +bitlength(g::G) where G <: ECGroup = bitlength(G) + +Tree(x::PGroup; L = bitlength(x)) = Leaf(value(x), div(L + 1, 8, RoundUp)) + +# Probably I will need to replace +convert(::Type{G}, x::Leaf; allow_one=false) where G <: PGroup = convert(G, convert(BigInt, x); allow_one) + +### Note that only PrimeCurves are supported. +convert(::Type{G}, x::Node; allow_one=false) where G <: ECGroup = convert(G, convert(Tuple{BigInt, BigInt}, x); allow_one) +convert(::Type{ECGroup{P}}, x::Node; allow_one=false) where P <: ECPoint = convert(ECGroup{P}, convert(Tuple{BigInt, BigInt}, x); allow_one) + + +function Tree(g::G; L = bitlength(G)) where G <: ECGroup + + gxleaf = Leaf(value(gx(g)), div(L + 1, 8, RoundUp)) + gyleaf = Leaf(value(gy(g)), div(L + 1, 8, RoundUp)) + + gtree = Tree((gxleaf, gyleaf)) + + return gtree +end + +function Tree(x::Vector{<:Group}) + L = bitlength(x[1]) + s = Tree[Tree(i, L = L) for i in x] + return Node(s) +end + +function marshal(x::PGroup) + + java_name = "com.verificatum.arithm.ModPGroup" + p = modulus(x) + q = order(x) + g = Tree(x) + e = UInt32(1) + + msg = (java_name, (p, q, g, e)) + + tree = Tree(msg) + + return tree +end + + +normalize_ecgroup_name(x::String) = replace(x, "_"=>"-") +normalize_ecgroup_name(x::Symbol) = normalize_ecgroup_name(String(x)) + + +function marshal(g::ECGroup) + + java_name = "com.verificatum.arithm.ECqPGroup" + + # generator is not a group + # @check spec(g) == spec(name(g)) "wrong group name" + + v_name = normalize_ecgroup_name(name(g)) + + msg = (java_name, v_name) + + tree = Tree(msg) + + return tree +end + + +function unmarshal(tree::Tree) + + group_type = convert(String, tree.x[1]) + + if group_type == "com.verificatum.arithm.ModPGroup" + return _unmarshal_pgroup(tree.x[2]) + elseif group_type == "com.verificatum.arithm.ECqPGroup" + return _unmarshal_ecgroup(tree.x[2]) + else + error("Unrecognized group type: $group_type") + end +end + + +function _unmarshal_pgroup(x::Node) + + (p, q, g, e) = convert(Tuple{BigInt, BigInt, BigInt, UInt32}, x) + + G = concretize_type(PGroup, p, q) + x = G(g) + + return x +end + +spec_name(x::String) = Symbol(replace(x, "-"=>"_")) + +function _unmarshal_ecgroup(x::Leaf) + + group_spec_str = convert(String, x) + name = spec_name(group_spec_str) + + group_spec = spec(name) + G = concretize_type(ECGroup, group_spec; name) + g = G(generator(group_spec)) + + return g +end + + +function convert(::Type{Vector{ElGamalRow{G, 1}}}, tree::Node; allow_one=false) where G <: Group + + a_tree, b_tree = tree.x + ๐š = convert(Vector{G}, a_tree; allow_one) + ๐› = convert(Vector{G}, b_tree; allow_one) + ๐ž = [ElGamalRow(ai, bi) for (ai, bi) in zip(๐š, ๐›)] + + return ๐ž +end + +function convert(::Type{ElGamalRow{G, 1}}, tree::Node; allow_one=false) where G <: Group + + a_tree, b_tree = tree.x + + a = convert(G, a_tree; allow_one) + b = convert(G, b_tree; allow_one) + + return ElGamalRow(a, b) +end + +function Tree(row::ElGamalRow{<:Group, 1}) + + (; a, b) = row[1] + + return Tree((a, b)) +end + +function Tree(๐ž::Vector{<:ElGamalRow{<:Group, 1}}) + + ๐š = [i[1].a for i in ๐ž] + ๐› = [i[1].b for i in ๐ž] + + tree = Tree((๐š, ๐›)) + + return tree +end + +# The width would be extracted from the tree +function Tree(e::Vector{<:NTuple{1, <:Group}}) + return Tree(first.(e)) +end + +function convert(::Type{Vector{NTuple{1, G}}}, tree::Node) where G <: Group + + vec = convert(Vector{G}, tree) + + return [(i, ) for i in vec] +end + + +function marshal_publickey(y::G, g::G) where G <: Group + + group_spec = marshal(g) + + L = bitlength(G) # + + g_tree = Tree(g; L) + y_tree = Tree(y; L) + + public_key = Tree((g_tree, y_tree)) + tree = Tree((group_spec, public_key)) + + return tree +end + + + +function unmarshal_publickey(tree::Tree; relative::Bool = false) + + g = unmarshal(tree.x[1]) + G = typeof(g) + + gโ€ฒ, y = convert(Tuple{G, G}, tree.x[2]) + + if !relative + @check gโ€ฒ == g "Generator does not match specification of the group. Perhaps intentioanl, if so pass `relative=true` as keyword argument." + end + + return y, gโ€ฒ +end + +function marshal_privatekey(g::Group, s::BigInt) + group_spec = marshal(g) + + @check 2 < s < order(g) - 1 "Secret key must be with in the order of the group" + + sleaf = Tree(s; L = bitlength(g)) # The leaf constructor could be improved, but for now this shall work fine + + tree = Tree((group_spec, sleaf)) + + return tree +end + + +function unmarshal_privatekey(tree::Tree) + + g = unmarshal(tree.x[1]) + s = convert(BigInt, tree.x[2]) + + return (s, g) ### The group can often be omited when not needed. +end + + +Tree(x::Vector{BigInt}; L = bitlength(maximum(x))) = Node([Leaf(i, L) for i in x]) + +export Tree, Node, Leaf, encode, decode, marshal, unmarshal + +end diff --git a/src/RangeProofs.jl b/src/RangeProofs.jl new file mode 100644 index 0000000..640ba76 --- /dev/null +++ b/src/RangeProofs.jl @@ -0,0 +1,585 @@ +module RangeProofs + +# TODO: NEED TO DO TDD TO GET THE CODE IN WORKING STATE + +# A range proof is a cryptographic technique that demonstrates a secret message belongs to a set of group elements defined by a specific range of exponents, without revealing the actual message. This code focuses on a simplified version for a single bit, as described in reference [1]. This implementation proves two statements in parallel, with one serving as a "dummy" statement, exemplifying a basic form of OR composition in cryptography. This approach represents one of the simplest examples of OR composition. For those interested in exploring more complex OR and AND compositions in cryptographic protocols, lecture notes [2] provides a good starting point. + +# Although more efficient techniques like Bulletproofs exist for range proofs, this simpler version of range proofs remains widely used, particularly in electronic voting systems. This is due to the typically small ranges in voting scenarios (such as yes/no questions) and the need for straightforward encoding to easily match decrypted elements back to their original range. As a general principle, it's often advisable to implement prototypes using this simpler version before considering more complex optimization techniques to reduce proof size. This approach allows for easier development, testing, and verification of the core functionality before introducing additional complexities. + +# [1]: Ronald Cramer, Rosario Gennaro, and Berry Schoenmakers. 1997. A secure and optimally efficient multi-authority election scheme. In Proceedings of the 16th annual international conference on Theory and application of cryptographic techniques (EUROCRYPT'97). Springer-Verlag, Berlin, Heidelberg, 103โ€“118. https://link.springer.com/chapter/10.1007/3-540-69053-0_9 + +# [2] Schoenmakers, B. (2024). Lecture Notes Cryptographic Protocols (Version 1.9). Department of Mathematics and Computer Science, Technical University of Eindhoven. + + +# To encode larger ranges we can make proofs with ๐บ, ๐บ^2, ๐บ^4 and then do homomoprhic sum. +# For this to work we need to modify the proof a little by transforming v=-1 case to v=0 case +struct ElGamalBitRange{G<:Group} <: Proposition + g::G # Generator + h::G # The public key; It happens to be used in pedersen commitment which may be wrong!!! + ๐บ::G # The message used, may need to be chose independently from the generator + x::G + y::G +end + +struct ElGamalBitProof{G<:Group} <: Proof + # Commitment + aโ‚::G + bโ‚::G + aโ‚‚::G + bโ‚‚::G + + # Response + dโ‚::BigInt + dโ‚‚::BigInt + rโ‚::BigInt + rโ‚‚::BigInt +end + + + +function prove(proposition::ElGamalBitRange{G}, verifier::Verifier, v::Int, ฮฑ::BigInt) where G <: Group + + (; g, h, ๐บ, x, y) = proposition + + ๐‘ค = rand(2:order(G)-1) + + if v == 1 + + rโ‚ = rand(2:order(G)-1) + dโ‚ = rand(2:order(G)-1) + + #aโ‚ = g^rโ‚ * x^dโ‚ # Why not + aโ‚ = g^(rโ‚ + ฮฑ * dโ‚) + bโ‚ = h^rโ‚ * (y*๐บ)^dโ‚ + #bโ‚ = h^rโ‚ * (y/๐บ)^dโ‚ + + aโ‚‚ = g^๐‘ค + bโ‚‚ = h^๐‘ค + + c = challenge(verifier, proposition, aโ‚, bโ‚, aโ‚‚, bโ‚‚) + + dโ‚‚ = c - dโ‚ + rโ‚‚ = ๐‘ค - ฮฑ * dโ‚‚ + + elseif v == -1 + + rโ‚‚ = rand(2:order(G)-1) + dโ‚‚ = rand(2:order(G)-1) + + aโ‚ = g^๐‘ค + bโ‚ = h^๐‘ค + + aโ‚‚ = g^rโ‚‚ * x^dโ‚‚ + bโ‚‚ = h^rโ‚‚ * (y/๐บ)^dโ‚‚ + #bโ‚‚ = h^rโ‚‚ * (y * ๐บ)^dโ‚‚ + + c = challenge(verifier, proposition, aโ‚, bโ‚, aโ‚‚, bโ‚‚) + + dโ‚ = c - dโ‚‚ + rโ‚ = ๐‘ค - ฮฑ * dโ‚ + + else + error("v must be +-1") + end + + return ElGamalBitProof(aโ‚, bโ‚, aโ‚‚, bโ‚‚, dโ‚, dโ‚‚, rโ‚, rโ‚‚) +end + + +function verify(proposition::ElGamalBitRange{G}, proof::ElGamalBitProof{G}, verifier::Verifier) where G <: Group + + (; g, h, ๐บ, x, y) = proposition + (; aโ‚, bโ‚, aโ‚‚, bโ‚‚, dโ‚, dโ‚‚, rโ‚, rโ‚‚) = proof + + c = challenge(verifier, proposition, aโ‚, bโ‚, aโ‚‚, bโ‚‚) + + c == dโ‚ + dโ‚‚ || return false + + aโ‚ == g^rโ‚ * x^dโ‚ || return false + bโ‚ == h^rโ‚ * (y * ๐บ)^dโ‚ || return false + #bโ‚ == h^rโ‚ * (y / ๐บ)^dโ‚ || return false + + aโ‚‚ == g^rโ‚‚ * x^dโ‚‚ || return false + bโ‚‚ == h^rโ‚‚ * (y/G)^dโ‚‚ || return false + #bโ‚‚ == h^rโ‚‚ * (y * G)^dโ‚‚ || return false + + return true +end + + +function bitenc(g::G, pk::G, value::Bool, verifier::Verifier; ๐บ::G = g, ฮฑ = rand(2:order(G)-1))::Simulator where G <: Group + + x = g^ฮฑ + y = value ? pk^ฮฑ * ๐บ : pk^ฮฑ / ๐บ + + proposition = ElGamalBitRange(g, pk, ๐บ, x, y) + + proof = prove(proposition, verifier, value, ฮฑ) + + return Simulator(proposition, proof, verifier) +end + + + +abstract type RangeCommitProof{G<:Group} <: Proof end + +# To encode larger ranges we can make proofs with ๐บ, ๐บ^2, ๐บ^4 and then do homomoprhic sum. +# For this to work we need to modify the proof a little by transforming v=-1 case to v=0 case +struct BitCommit{G<:Group} <: Proposition + g::G # Generator + h::G # The public key; It happens to be used in pedersen commitment which may be wrong!!! + ๐บ::G # The message used, may need to be chose independently from the generator + y::G +end + +struct BitProof{G<:Group} <: RangeCommitProof{G} + # Commitment + bโ‚::G + bโ‚‚::G + + # Response + dโ‚::BigInt + dโ‚‚::BigInt + rโ‚::BigInt + rโ‚‚::BigInt +end + + +function prove(proposition::BitCommit{G}, verifier::Verifier, v::Bool, ฮฑ::BigInt) where G <: Group + + (; g, h, ๐บ, x, y) = proposition + + ๐‘ค = rand(2:order(G)-1) + + if v == 1 + + rโ‚ = rand(2:order(G)-1) + dโ‚ = rand(2:order(G)-1) + + bโ‚ = h^rโ‚ * y^dโ‚ + bโ‚‚ = h^๐‘ค + + c = challenge(verifier, proposition, bโ‚, bโ‚‚) + + dโ‚‚ = c - dโ‚ + rโ‚‚ = ๐‘ค - ฮฑ * dโ‚‚ + + elseif v == 0 + + rโ‚‚ = rand(2:order(G)-1) + dโ‚‚ = rand(2:order(G)-1) + + bโ‚ = h^๐‘ค + bโ‚‚ = h^rโ‚‚ * (y/๐บ)^dโ‚‚ + + c = challenge(verifier, proposition, bโ‚, bโ‚‚) + + dโ‚ = c - dโ‚‚ + rโ‚ = ๐‘ค - ฮฑ * dโ‚ + + else + error("v must be {0, 1}") + end + + return BitCommitProof(bโ‚, bโ‚‚, dโ‚, dโ‚‚, rโ‚, rโ‚‚) +end + + +function verify(proposition::BitCommit{G}, proof::BitCommitProof{G}, verifier::Verifier) where G <: Group + + (; g, h, ๐บ, x, y) = proposition + (; bโ‚, bโ‚‚, dโ‚, dโ‚‚, rโ‚, rโ‚‚) = proof + + c = challenge(verifier, proposition, bโ‚, bโ‚‚) + + c == dโ‚ + dโ‚‚ || return false + + bโ‚ == h^rโ‚ * y^dโ‚ || return false + bโ‚‚ == h^rโ‚‚ * (y/G)^dโ‚‚ || return false + + return true +end + + +function bitcommit(g::G, pk::G, value::Bool, verifier::Verifier; ๐บ::G = g, ฮฑ = rand(2:order(G)-1))::Simulator where G <: Group + + #x = g^ฮฑ + y = value ? pk^ฮฑ * ๐บ : pk^ฮฑ + + proposition = BitCommit(g, pk, ๐บ, y) + + proof = prove(proposition, verifier, value, ฮฑ) + + return Simulator(proposition, proof, verifier) +end + + +# struct ElGamalRange <: Proposition +# range:: +# g::G +# h::G +# ๐บ::G +# x::G +# y::G +# end + + +# BitCommit + +# NRangeCommit + +# RangeCommit + +# BitRangeProof + +# bitenc + +# nbitenc # would use a BitCommit +# rangeenc # also would use a BitCommit + + + + +struct NRangeCommit{G<:Group} <: Proposition + n::Int # 0 < ฮฝ < 2^n + g::G + h::G + ๐บ::G + y::G +end + + +struct NRangeProof{G<:Group} <: RangeCommitProof{G} + bitcommits::Vector{G} + bitproofs::Vector{BitProof{G}} +end + + +function prove(proposition::NRangeCommit{G<:Group}, verifier::Verifier, v::Int, ฮฑ::Integer) + + (; n, g, h, ๐บ) = proposition + + ### Need a way to extract the bit from v + + bits = G[] + proofs = BitProof{G}[] + + ฮฑ_vec = rand(n+1, 2:order(G)-1) + ฮฑ_rest = sum(ฮฑ_vec[2:end]) % order(G) + ฮฑ_vec[1] = ฮฑ - ฮฑ_rest % order(G) # This would ensure that resulting encryption is done with ฮฑ + + for i in 0:n + + ๐บแตข = ๐บ^(2^i) + value = v >> i % Bool + + simulator = bitcommit(g, h, value, verifier; ๐บ, ฮฑ = ฮฑ_vec[i]) + + push!(proofs, simulator.proof) + + (; y) = simulator.proposition + + push!(bits, y) + end + + return NRangeProof(encbits, bitproofs, verifier) +end + + +function verify(proposition::NRangeCommit{G}, proof::NRangeProof{G}, verifier::Verifier) where G <: Group + + (; n, g, h, ๐บ, y) = proposition + (; bitcommits, bitproofs) = proof + + for (yi, bitproof, i) in zip(bitcommits, bitproofs, 0:n) + + ๐บแตข = ๐บ^(2^i) + + bitproposition = BitCommit(g, h, ๐บแตข, yi) + verify(bitproposition, bitproof, verifier) || return false + end + + return true +end + + +function rangecommit(n::Int, g::G, pk::G, value::Int, verifier::Verifier; ๐บ::G = g, ฮฑ = rand(2:order(G)-1))::Simulator where G <: Group + @check 0 < value < 2^n + + y = pk^ฮฑ * ๐บ^value + + proposition = NRangeCommit(n, g, h, ๐บ, y) + proof = prove(proposition, verifier, v, ฮฑ) + + return Simulator(proposition, proof, verifier) +end + + +struct RangeCommit{G<:Group} <: Proposition + range::UnitRange # For the begining we shall assume that min is 0 + g::G + h::G + ๐บ::G + y::G +end + +struct RangeProof{G<:Group} <: RangeCommitProof{G} + left::NRangeProof{G} + right::NRangeProof{G} +end + + +function prove(proposition::RangeCommit{G<:Group}, verifier::Verifier, value::Int; ฮฑ = rand(2:order(G) - 1)) + + (; range, g, h, ๐บ, y) = proposition + + @assert minimum(range) == 0 "Not Implemented" + + n = bitlength(maximum(range) - minimum(range)) + + ฮ” = 2^n - maximum(range) # Let's now consider minimum(range) == 0 + + y = h^ฮฑ * ๐บ^value + + left_proposition = NRangeCommit(n, g, h, ๐บ, y) + left_proof = prove(left_proposition, verifer, value, ฮฑ) + + right_proposition = NRangeCommit(n, g, h, ๐บ, y * ๐บ^ฮ”) + right_proof = prove(right_proposition, verifer, value + ฮ”, ฮฑ) + + return RangeProof(left_proof, right_proof) +end + + +function verify(proposition::RangeCommit{G}, proof::RangeProof{G}, verifier::Verifier) where G <: Group + + (; range, g, h, ๐บ, y) = proposition + + ฮ” = 2^n - maximum(range) + + left_proposition = NRange(n, g, h, ๐บ, y) + verify(left_proposition, proof.left, verifier) || return false + + right_proposition = NRange(n, g, h, ๐บ, y * ๐บ^ฮ”) + verify(right_proposition, proof.right, verifier) || return false + + # The link is established through the construction + + return true +end + + +function rangecommit(range::UnitRange, g::G, pk::G, value::Int, verifier::Verifier; ๐บ::G = g, ฮฑ = rand(2:order(G)-1))::Simulator + + @check minimum(range) == 0 "Not implemented" + @check minimum(range) < value < maximum(range) + + y = pk^ฮฑ * ๐บ^value + + proposition = Range(range, g, h, ๐บ, y) + proof = prove(proposition, verifier, v, ฮฑ) + + return Simulator(proposition, proof, verifier) +end + + +# This proof ensures that ElGamal encryption is correct and is represeted as e = (a, b) = (g^ฮฑ, pk^ฮฑ * h^m) +# as long as h is independent generator from g. This proof is intended to be combined with range proof that +# is only done on `b` coordinate treating it as blinded commitment. +struct ElGamalEnc{G <: Group} <: Proposition + g::G + pk::G + h::G + x::G + y::G +end + + +struct ElGamalEncProof{G <: Group} <: Proof + tโ‚::G + tโ‚‚::G + sโ‚::BigInt + sโ‚‚::BigInt +end + + +function prove(proposition::ElGamalEnc{G}, verifier::Verifier, m::Int, ฮฑ::BigInt) where G <: Group + + (; g, pk, h, x, y) = proposition + + rโ‚ = rand(2:order(G)-2) + rโ‚‚ = rand(2:order(G)-2) + + tโ‚ = g^rโ‚ + tโ‚‚ = pk^rโ‚ * h^rโ‚‚ + + c = challenge(proposition, verifier, tโ‚, tโ‚‚) + + sโ‚ = rโ‚ + ฮฑ * c + sโ‚‚ = rโ‚‚ + c * m + + return ElGamalEncProof(tโ‚, tโ‚‚, sโ‚, sโ‚‚) +end + + +function verify(proposition::ElGamalEnc{G}, proof::ElGamalEncProof{G}, verifier::Verifier) where G <: Group + + (; g, pk, h, x, y) = proposition + (; tโ‚, tโ‚‚, sโ‚, sโ‚‚) = proof + + c = challenge(proposition, verifier, tโ‚, tโ‚‚) + + g^sโ‚ == tโ‚ * a^c || return false + pk^sโ‚ * h^sโ‚‚ == tโ‚‚ * b^c || return false + + return true +end + + +function enc(g::G, pk::G, h::G, m::Int, verifier::Verifier; ฮฑ = rand(2:order(G) - 1)) where G <: Group + + x = g^ฮฑ + y = iszero(m) ? pk^ฮฑ : pk^ฮฑ * h^m + + proposition = ElGamalEnc(g, pk, h, x, y) + proof = prove(proposition, verifier, m, ฮฑ) + + return Simulator(proposition, proof, verifier) +end + + +struct ElGamalRange{G<:Group} <: Proposition + range::Union{Bool, Int, UnitRange} + g::G + pk::G + h::G + x::G + y::G +end + + +struct ElGamalRangeProof{G<:Group} <: Proof + encryption::ElGamalEncProof{G} + range::RangeCommitProof{G} +end + + +_range_proposition(::Bool, g::G, pk::G, ๐บ::G, y::G) where G <: Group = BitCommit(g, pk, ๐บ, y) +_range_proposition(n::Int, g::G, pk::G, ๐บ::G, y::G) where G <: Group = NRangeCommit(n, g, pk, ๐บ, y) +_range_proposition(range::UnitRange, g::G, pk::G, ๐บ::G, y::G) where G <: Group = RangeCommit(range, g, pk, ๐บ, y) + + +function prove(proposition::ElGamalRange{G<:Group}, verifier::Verifier, v::Int; ฮฑ = rand(2:order(G) - 1)) + + (; range, g, pk, h, x, y) = proposition + + encryption_prop = ElGamalEnc(g, pk, h, x, y) + encryption_proof = prove(encryption_prop, verifier, v, ฮฑ) + + range_prop = _range_proposition(range, g, pk, h, y) + range_proof = prove(range_prop, verifier, v; ฮฑ) + + return ElGamalRangeProof(encryption_proof, range_proof) +end + + +function verify(proposition::ElGamalRange{G}, proof::ElGamalRangeProof{G}, verifier::Verifier) where G <: Group + + (; range, g, pk, h, x, y) = proposition + + encryption_prop = ElGamalEnc(g, pk, h, x, y) + verify(encryption_prop, proof.encryption, verifier) || return false + + range_prop = _range_proposition(range, g, pk, h, y) + verify(range_prop, proof.range, verifier) || return false + + return true +end + + +function rangeenc(range, g::G, pk::G, value::Int, verifier::Verifier; ๐บ::G = g, ฮฑ = rand(2:order(G)-1)) where G <: Group + + x = g^ฮฑ + y = pk^ฮฑ * h^value + + proposition = ElGamalRange(range, g, pk, ๐บ, x, y) + proof = prove(proposition, verifier, value; ฮฑ) + + return Simulator(proposition, proof, verifier) +end + + +### Now let's implement plaintext equivalence proofs +# only works as long as message is encrypted in an independent generator from g, pk +struct PlaintextEquivalence{G<:Group} <: Proposition + g::G + pk::G + a::G + b::G + aโ€ฒ::G + bโ€ฒ::G +end + +# +struct PlaintextEquivalenceProof{G<:Group} <: Proof + blinding_factor::G + blinded_ax::G + blinded_axโ€ฒ::G + exponentiation::ChaumPedersenProof{G} +end + + +function prove(proposition::PlaintextEquivalence{G}, verifier::Verifier, power::BigInt; ฮฒ = rand(2:order(G)-1)) where G <: Group + + (; g, pk, a, b, aโ€ฒ, bโ€ฒ) = proposition + + # a random generator is actually better here, as it prevents to learn plaintext if LogEquality have been + # obtained from a distributed execution + blinding_factor = g^ฮฒ + + blinded_a = blinding_factor * a + blinded_aโ€ฒ = blinding_factor * aโ€ฒ + + simulator = exponentiate([g, blinded_a, blinded_aโ€ฒ], power, verifier) + + blinded_ax = simulator.proposition.pk[2] + blinded_axโ€ฒ = simulator.proposition.pk[3] + + return PlainTextEquivalenceProof(blinding_factor, blinded_ax, blinded_axโ€ฒ, simulator.proof) +end + + +function verify(proposition::PlaintextEquivalence{G}, proof::PlaintextEquivalenceProof{G}, verifier::Verifier) + + (; g, pk, a, b, aโ€ฒ, bโ€ฒ) = proposition + (; blinding_factor, blinded_ax, blinded_axโ€ฒ) = proof + + b/blinded_ax == bโ€ฒ/blinded_axโ€ฒ || return false + + g_vec = [g, blinding_factor * a, blinding_factor * aโ€ฒ] + y_vec = [pk, blinded_ax, blinded_axโ€ฒ] + + proposition = LogEquality(g_vec, y_vec) + + verify(proposition, proof.exponentiation, verifer) || return false + + return true +end + + +function substitute(g::G, pk::G, a::G, b::G, power::BigInt, ฮฑ::BigInt, verifier::Verifier) where G <: Group + + m = b / a^power + + aโ€ฒ = g^ฮฑ + bโ€ฒ = pk^ฮฑ * m + + proposition = PlaintextEquivalence(g, pk, a, b, aโ€ฒ, bโ€ฒ) + + proof = prove(proposition, verifier, power) + + return Simulator(proposition, proof, verifier) +end + + +end diff --git a/src/SecretSharing.jl b/src/SecretSharing.jl new file mode 100644 index 0000000..b179e11 --- /dev/null +++ b/src/SecretSharing.jl @@ -0,0 +1,244 @@ +module SecretSharing + +# TODO: NEED TO DO TDD TO GET THE CODE IN WORKING STATE + +# Feldman's Verifiable Secret Sharing (VSS) scheme is a cryptographic protocol that extends Shamir's Secret Sharing +# with a verification mechanism. It allows a dealer to distribute shares of a secret among participants in a way +# that enables the participants to verify the consistency of their shares without revealing the secret. + +# The Feldman VSS scheme works as follows: + +# 1) The dealer chooses a prime p and a generator g of the multiplicative group of integers modulo p. +# 2) The dealer selects a random polynomial f(x) = a0 + a1x + a2x^2 + ... + at-1x^(t-1) mod p, where a0 is the secret. +# 3) For each participant i, the dealer computes the share si = f(i) mod p. +# 4) The dealer publishes commitments to the coefficients: C0 = g^a0, C1 = g^a1, ..., Ct-1 = g^a(t-1) mod p. +# 5) Each participant i can verify their share by checking if g^si = C0 * C1^i * C2^(i^2) * ... * Ct-1^(i^(t-1)) mod p. + +# Decryption and Share Combination: +# To reconstruct the secret, a minimum number of t participants (where t is the threshold) must combine their shares. +# The secret is reconstructed using Lagrange interpolation: +# 1) Given t shares (x1, y1), (x2, y2), ..., (xt, yt), compute the Lagrange coefficients: +# li = ฮ (jโ‰ i) (xj / (xj - xi)) mod p +# 2) The secret a0 is then reconstructed as: +# a0 = ฮฃ(yi * li) mod p +# This process allows reconstruction of the secret without revealing individual shares, maintaining the scheme's security. + +# This implementation provides functions for generating shares, creating commitments, verifying shares, +# and reconstructing the secret using the Feldman VSS scheme. It includes utilities for polynomial evaluation +# and Lagrange interpolation, which are essential for both the sharing and reconstruction processes. + +# References +# - https://en.wikipedia.org/wiki/Verifiable_secret_sharing +# - Feldman, P. (1987). A practical scheme for non-interactive verifiable secret sharing. +# In 28th Annual Symposium on Foundations of Computer Science (sfcs 1987) (pp. 427-438). IEEE. +# https://www.cs.umd.edu/~gasarch/TOPICS/secretsharing/feldmanVSS.pdf + +function evaluate_poly(x::BigInt, poly::Vector{BigInt}, modulus::BigInt) + + s = BigInt(0) + + for (ai, i) in zip(coeff, 0:length(coeff)-1) + s = s + ai * x^i % modulus + end + + return s +end + +function lagrange_coef(i::Int, x::Vector{BigInt}, modulus::BigInt) + + l = BigInt(1) + + for j in 1:length(x) + + j == i && continue + + l *= x[j] * modinv(x[j] - x[i], modulus) % modulus + + end + + return l +end + +lagrange_coefs(nodes::Vector{BigInt}, modulus::BigInt) = [lagrange_coef(i, nodes, modulus) for i in 1:length(nodes)] + +# The blinding is completelly unnecessary here as the secret space is large. It could be an interesting addition for a situation +# where +struct ShardingSetup{G<:Group} <: Proposition + g::G + pk::G + poly_order::Int + nodes::Vector{BigInt} + public_keys::Vector{G} # g^d_i +end + +struct ShardingConsistency{G<:Group} <: Proof + coeff_commitments::Vector{G} +end + +function prove(proposition::ShardingSetup{G<:Group}, verifier::Verifier, coeff::Vector{BigInt}) + + (; g, pk, nodes, public_keys, poly_order) = proposition + + @check poly_order == length(coeff) - 1 + + coeff_commitments = [g^ai for ai in coeff] + + return ShardingConsistency(coeff_commitments) +end + + +function verify(proposition::ShardingSetup{G}, proof::ShardingConsistency{G}, verifier::Verifier) where G <: Group + + (; g, h, pk, nodes, public_keys, poly_order) = proposition + + for (xi, pki) in zip(nodes, public_keys) + prod(ci^(x^i) for (ci, i) in zip(proof.coeff_commitments, 0:poly_order)) == pki || return false + end + + return true +end + + +function sharding_setup(g::G, nodes::Vector{BigInt}, coeff::Vector{BigInt}, verifier::Verifier) where G <: Group + + @check !any(iszero, nodes) "A node can't be zero" + @check !any(>(0), nodes) "A node must be positive" + @check length(unique(nodes)) == length(nodes) "Nodes must be unique" + + pk = g^first(coeff) # first coefficient is a constant factor + + poly_order = length(coeff) - 1 + + d_vec = evaluate_poly.(nodes, coeff, order(G)) + + public_keys = g .^ d_vec + + proposition = ShardingSetup(g, pk, poly_order, nodes, public_keys) + + proof = prove(proposition, verifier, coeff) + + return Simulator(proposition, proof, verifier) +end + + +### Threshold decryption + +struct PartialDecryption{G<:Group} <: Proposition + g::G + pk::G # The public key must be one in the list of public_keys of sharding_setup + a::Vector{G} + ad::Vector{G} +end + +function prove(proposition::PartialDecrytion, verifier::Verifier, sk::BigInt) + + (; g, pk, a, ad) = proposition + + g_vec = [g, a...] + y_vec = [pk, ad...] + + proof = prove(LogEquality(g_vec, y_vec), verifier, sk) + + return proof +end + +function verify(proposition::PartialDecryption, proof::ChaumPedersenProof, verifier::Verifier) + + (; g, pk, a, ad) = proposition + + g_vec = [g, a...] + y_vec = [pk, ad...] + + return verify(LogEquality(g_vec, y_vec), proof, verifier) +end + + +function partialdecrypt(g::G, a::Vector{G}, sk::BigInt, verifier::Verifier) + + pk = g^sk + ad = a .^ sk + + proposition = PartialDecryption(g, ok, a, ad) + proof = prove(proposition, verifier, sk) + + return Simulator(proposition, proof, verifier) +end + +struct FullDecryption{G<:Group, N} <: Proposition + setup::ShardingSetup{G} + cyphertexts::Vector{ElGamalRow{G, N}} + plaintexts::Vector{NTuple{N, G}} +end + +struct FullDecryptionProof{G<:Group} <: Proof + partial_decryptions::Vector{Vector{G}} + decryption_proofs::Vector{ChaumPedersenProof{G}} +end + + +a(e::ElGamalRow{<:Group, N}) where N = ntuple(x -> x[N].a, 1:N) + +group_into_tuples(arr, N) = [Tuple(arr[i:i+N-1]) for i in 1:N:length(arr)] + +# merge +function combine(setup::ShardingSetup{G}, cyphertexts::Vector{ElGamalRow{G, N}}, decryptions::Vector{PartialDecryption{G}}, proofs::Vector{ChaumPedersenProof{G}}, verifier::Verifer)::Simulator where {G <: Group, N} + + #a_vec = [g, flatten(a(c) for c in cyphertexts)...] + a_vec = [flatten(a(c) for c in cyphertexts)...] + b_vec = [flatten(b(c) for c in cyphertexts)...] + + for dec in decryptions + decryption.g == setup.g || return false + decrytpion.pk in setup.public_keys || return false # + a_vec == dec.a || return false + end + + nodes = BigInt[] + partials = Vector{G}[] + + # We could skip the wrong proofs here + for (prop, proof) in zip(decryptions, proofs) + verify(prop, proof, verifier) || continue + + n = findfirst(=(prop.pk), setup.public_keys) + setup.nodes[n] in nodes && continue + + push!(setup.nodes[n], nodes) + push!(partials, prop.ad) # this adds a pointer to the list thus it is efficient + + length(nodes) == setup.poly_order && break + end + + @check length(nodes) == setup.poly_order "Not enough valid shares decrypted to reconstruct the proof" + + l_vec = lagrange_coefs(nodes, modulus) + + plaintext_vec = prod(Pi .^ li for (Pi, li) in zip(partials, l_vec)) .* b_vec + + plaintexts = group_into_tuples(plaintext_vec, N) + + proposition = FullDecryption(setup, cyphertexts, plaintexts) + + proof = FullDecryptionProof(decryptions, proofs) + + return Simulator(proposition, proof, verifier) +end + + +function verify(proposition::FullDecryption{G}, proof::FullDecryptionProof, verifier::Verifier) + + (; g, pk, cyphertexts, plaintexts) = proposition.setup + + # We could run the combine method + + a_vec = [g, flatten(a(c) for c in cyphertexts)...] + + propositions = [PartialDecryption(g, pk, a_vec, ad_vec) for ad_vec in proof.partial_decryptions] + + simulator = combine(proposition.setup, cyphertexts, propositions, proof.decryption_proofs, verifier) + + return simulator.proposition.plaintexts == plaintexts +end + + +end diff --git a/src/Serializer.jl b/src/Serializer.jl new file mode 100644 index 0000000..b123a74 --- /dev/null +++ b/src/Serializer.jl @@ -0,0 +1,156 @@ +module Serializer + +using CryptoGroups.Utils: @check +using CryptoPRG: HashSpec +using ..Parser: Tree, encode +using ..SigmaProofs: Simulator, Verifier, Proposition, Proof, proof_type +using InteractiveUtils: subtypes + +global DEFAULT_VERIFIER::Type{<:Verifier} + +abstract type Path end + +#Base.write(path::Path, data::Tree) = Base.write(path, encode(data)) +#Base.write(path::Path, data::String) = Base.write(path, Vector{UInt8}(data)) + +_encode(x::Tree) = encode(x) +_encode(x::AbstractString) = Vector{UInt8}(x) +_encode(x::Vector{UInt8}) = x + +Base.write(path::Path, data) = Base.write(path, _encode(data)) + + +struct LocalPath <: Path + path::String +end + +Base.joinpath(path::LocalPath, args...) = LocalPath(joinpath(path.path, args...)) +Base.write(path::LocalPath, data::Vector{UInt8}) = write(path.path, data) +Base.read(path::LocalPath) = read(path.path) +Base.mkdir(path::LocalPath) = mkdir(path.path) +Base.mkpath(path::LocalPath) = mkpath(path.path) +Base.isfile(path::LocalPath) = isfile(path.path) + + +struct PathHasher <: Path + path::String + hasher::HashSpec + digests::Vector{Pair{String, Vector{UInt8}}} +end + +PathHasher(hasher::HashSpec) = PathHasher("", hasher, []) + +Base.joinpath(path::PathHasher, args...) = PathHasher(joinpath(path.path, args...), path.hasher, path.digests) +Base.write(path::PathHasher, data::Vector{UInt8}) = (push!(path.digests, path.path => path.hasher(data)); path) +Base.mkdir(path::PathHasher) = nothing +Base.mkpath(path::PathHasher) = nothing + + +load(type::Type, dir::String; kwargs...) = load(type, LocalPath(dir); kwargs...) + +save(obj, dir::String) = save(obj, LocalPath(dir)) + + +save(obj::Proof, ::Type{<:Proposition}, path::Path) = save(obj, path) + +function save(obj::Simulator{P}, path::Path) where P <: Proposition + + save(obj.proposition, path) + + mkdir(joinpath(path, "nizkp")) + save(obj.proof, P, joinpath(path, "nizkp")) + + verifier_path = joinpath(path, treespec(obj.verifier)) + save(obj.verifier, verifier_path; name = string(nameof(P))) + + return +end + +load(::Type{P}, ::Type{<:Proposition}, path::Path) where P <: Proof = load(P, path) + +function load(::Type{Simulator{P}}, path::Path; verifier_type = DEFAULT_VERIFIER) where P <: Proposition + + proposition = load(P, path) + proof = load(proof_type(proposition), P, joinpath(path, "nizkp")) + verifier = load(verifier_type, joinpath(path, treespec(verifier_type))) + + return Simulator(proposition, proof, verifier) +end + + +load(path::Path) = load(get_simulator_type(path), path) +load(path::String) = load(LocalPath(path)) + + +treespec(::T) where T <: Union{Proposition, Proof, Verifier, Simulator} = treespec(T) +treespec(::Type{T}, ::Type{<:Proposition}) where T <: Proof = treespec(T) # This allows prefix specialization +treespec(::Type{Simulator{P}}) where P <: Proposition = (treespec(DEFAULT_VERIFIER), treespec(P)..., joinpath.("nizkp", treespec(proof_type(P), P))...) + +treespec(::Type{Simulator}) = error("Tree specification for $(typeof(obj)) is not specified. Specify it by providing `treespec` argument manually to digest.") + + +function digest(obj::Union{Proposition, Proof, Simulator, Verifier} , hasher::HashSpec; treespec=treespec(obj)) + + path_hasher = PathHasher(hasher) + save(obj, path_hasher) + + @check length(path_hasher.digests) == length(treespec) "`treespec` is not compatable with $(typeof(obj)) output." + + digests = Vector{UInt8}[] + + for i in treespec + + N = findfirst(x -> first(x) == i, path_hasher.digests) + @assert !isnothing(N) "$i is not written in $(typeof(obj)) output." + push!(digests, last(path_hasher.digests[N])) + + end + + return hasher(vcat(digests...)) +end + +# This puts in assumption here +function get_simulator_type(dir::Path) + + xmlpath = joinpath(dir, "protInfo.xml") + + if !isfile(xmlpath) + error("protInfo.xml not found in $dir") + end + + xml = read(xmlpath) |> String + name = match(r"(.*?)", xml)[1] |> Symbol + + # Now I need to list all subtypes of proposition + + types = subtypes(Proposition) + N = findfirst(x -> nameof(x) == name, types) + + @assert !isnothing(N) "Simualtor type $name not found" + + return Simulator{types[N]} +end + +get_simulator_type(dir::String) = get_simulator_type(LocalPath(dir)) + +digest(dir::Path, hasher::HashSpec) = digest(get_simulator_type(dir), dir, hasher) +digest(dir::AbstractString, hasher::HashSpec) = digest(get_simulator_type(dir), LocalPath(dir), hasher) + +function digest(::Type{S}, dir::Path, hasher::HashSpec) where S <: Simulator + + digests = [] + + for path in treespec(S) + + bytes = read(joinpath(dir, path)) + push!(digests, hasher(bytes)) + + end + + return hasher(vcat(digests...)) +end + +digest(::Type{S}, dir::AbstractString, hasher::HashSpec) where S <: Simulator = digest(S, LocalPath(dir), hasher) + + +end diff --git a/src/SigmaProofs.jl b/src/SigmaProofs.jl new file mode 100644 index 0000000..bcdc4e4 --- /dev/null +++ b/src/SigmaProofs.jl @@ -0,0 +1,67 @@ +module SigmaProofs + +using Random: RandomDevice +using CryptoPRG: HashSpec +using CryptoPRG.Verificatum: ROPRG + +include("ElGamal.jl") +include("Parser.jl") + +abstract type Proposition end +abstract type Verifier end +abstract type Proof end + +proof_type(::T) where T <: Proposition = proof_type(T) + +struct Simulator{T<:Proposition} + proposition::T + proof::Proof + verifier::Verifier + + function Simulator(proposition::Proposition, proof::Proof, verifier::Verifier) + + # Alternative is to do conversion here if necessary + @assert isvalid(typeof(proof), proposition) "Inconsistent simulator" + + #@assert typeof(proof) <: proof_type(proposition) + + return new{typeof(proposition)}(proposition, proof, verifier) + end +end + +Base.:(==)(x::T, y::T) where T <: Simulator = x.proposition == y.proposition && x.proof == y.proof && x.verifier == y.verifier + +verify(simulator::Simulator) = verify(simulator.proposition, simulator.proof, simulator.verifier) +proof_type(::Simulator{T}) where T <: Proposition = proof_type(T) + +Base.isvalid(P::Type{<:Proof}, ::T) where T <: Proposition = P == proof_type(T) + +function challenge end +function prove end +function generator_basis end + +function gen_roprg(ฯ::AbstractVector{UInt8}) + + rohash = HashSpec("sha256") + prghash = HashSpec("sha256") + roprg = ROPRG(ฯ, rohash, prghash) + + return roprg +end + +gen_roprg() = gen_roprg(rand(RandomDevice(), UInt8, 32)) + + +include("Serializer.jl") +include("LogProofs.jl") +include("DecryptionProofs.jl") +include("CommitmentShuffle.jl") +#include("RangeProofs.jl") +#include("SecretSharing.jl") +include("Verificatum/Verificatum.jl") + +# It is highly unlikelly that one would need to work with multiple verifier implementations +# at the same time for the same object. This can be reconsidered in the future. +Serializer.DEFAULT_VERIFIER = Verificatum.ProtocolSpec + +end diff --git a/src/Verificatum/GeneratorBasis.jl b/src/Verificatum/GeneratorBasis.jl new file mode 100644 index 0000000..d2b6564 --- /dev/null +++ b/src/Verificatum/GeneratorBasis.jl @@ -0,0 +1,91 @@ +module GeneratorBasis + +using CryptoGroups.Utils: @check +using CryptoGroups: modulus, order, bitlength, Group, spec +using CryptoGroups.Specs: MODP, ECP +using CryptoPRG.Verificatum: PRG, RO +using CryptoUtils: is_quadratic_residue, sqrt_mod_prime + +# import ...SigmaProofs: generator_basis + +function modp_generator_basis(prg::PRG, p::Integer, q::Integer, N::Integer; nr::Integer = 0) + + np = bitlength(p) + + ๐ญ = rand(prg, BigInt, N; n = np + nr) + + ๐ญโ€ฒ = mod.(๐ญ, big(2)^(np + nr)) + + ๐ก = powermod.(๐ญโ€ฒ, (p - 1) รท q, p) + + return ๐ก +end + +modp_generator_basis(prg::PRG, spec::MODP, N::Integer; nr::Integer = 0) = modp_generator_basis(prg, modulus(spec), order(spec), N; nr) + +function ecp_generator_basis(prg::PRG, (a, b)::Tuple{Integer, Integer}, p::Integer, q::Integer, N::Integer; nr::Integer = 0) + + np = bitlength(p) # 1 + + ๐ญ = rand(prg, BigInt, N*10; n = np + nr) # OPTIMIZE (I would need it as an iterator) + + ๐ญโ€ฒ = mod.(๐ญ, big(2)^(np + nr)) + + ๐ณ = mod.(๐ญโ€ฒ, p) + + ๐ก = Vector{Tuple{BigInt, BigInt}}(undef, N) + + l = 1 + + f(x) = x^3 + a*x + b # This assumes that I do know how to do arithmetics with fields. + + for zi in ๐ณ + y2 = mod(f(zi), p) + + if is_quadratic_residue(y2, p) + + x = zi + y = sqrt_mod_prime(y2, p) + + # The smallest root is taken + if p - y < y + y = p - y + end + + ๐ก[l] = (x, y) + + if l == N + break + else + l += 1 + end + end + end + + if l != N + error("Not enough numbers for ๐ญ have been allocated") + end + + return ๐ก +end + +function ecp_generator_basis(prg::PRG, spec::ECP, N::Integer; nr::Integer = 0) + (; a, b) = spec + return ecp_generator_basis(prg, (a, b), modulus(spec), order(spec), N; nr) +end + + +# For pattern matching +_generator_basis(prg::PRG, spec::MODP, N::Integer; nr) = modp_generator_basis(prg, spec, N; nr) +_generator_basis(prg::PRG, spec::ECP, N::Integer; nr) = ecp_generator_basis(prg, spec, N; nr) + +function generator_basis(prg::PRG, ::Type{G}, N::Integer; nr::Integer = 0) where G <: Group + @check !isnothing(order(G)) "Order of the group must be known" + _spec = spec(G) + g_vec = _generator_basis(prg, _spec, N; nr) + return G.(g_vec) +end + +# export generator_basis + +end diff --git a/src/Verificatum/Verificatum.jl b/src/Verificatum/Verificatum.jl new file mode 100644 index 0000000..f7c201d --- /dev/null +++ b/src/Verificatum/Verificatum.jl @@ -0,0 +1,244 @@ +module Verificatum + +using CryptoPRG: HashSpec, bitlength +using CryptoPRG.Verificatum: ROPRG, RO, PRG +using CryptoGroups: Group, PGroup, ECGroup, order, name, octet +using ..SigmaProofs: Verifier +using ..LogProofs: LogEquality, LogKnowledge +using ..CommitmentShuffle: Shuffle +using ..Serializer: Serializer, Path +using ..Parser + +import ..SigmaProofs: challenge, generator_basis + +# This module represents Verifiactum verifier which implements verifier and associated generator basis +# functions. The Verificatum specification is only concrete with respect to ShuffleProofs, hence, implementation of verifier here is just an prelimenary imitation and would likelly be reviewed in the future. + +include("GeneratorBasis.jl") # exports only a single function +#include("io.jl") + + +using Base: @kwdef + +@kwdef struct ProtocolSpec{G<:Group} <: Verifier + g::G + nr::Int32 = Int32(100) + nv::Int32 = Int32(256) + ne::Int32 = Int32(256) + prghash::HashSpec = HashSpec("sha256") + rohash::HashSpec = HashSpec("sha256") + version::String = "3.0.4" + sid::String = "SessionID" + auxsid::String = "default" +end + +Base.:(==)(x::ProtocolSpec{G}, y::ProtocolSpec{G}) where G <: Group = x.g == y.g && x.nr == y.nr && x.nv == y.nv && x.ne == y.ne && x.prghash == y.prghash && x.rohash == y.rohash && x.version == y.version && x.sid == y.sid && x.auxsid == y.auxsid + +function marshal_s_Gq(g::PGroup) + + M = bitlength(order(g)) + + tree = marshal(g) + str = "ModPGroup(safe-prime modulus=2*order+1. order bit-length = $M)::" * string(tree) + + return Leaf(str) +end + + +function marshal_s_Gq(g::ECGroup) + + curve_name = Parser.normalize_ecgroup_name(name(g)) + tree = marshal(g) + + str = "com.verificatum.arithm.ECqPGroup($curve_name)::" * string(tree) + + return Leaf(str) +end + +function map_hash_name(x::AbstractString) + if x == "SHA-256" + return "sha256" + elseif x == "SHA-384" + return "sha384" + elseif x == "SHA-512" + return "sha512" + else + error("No corepsonding mapping for $x implemented") + end +end + +function map_hash_name_back(x::AbstractString) + if x == "sha256" + return "SHA-256" + elseif x == "sha384" + return "SHA-384" + elseif x == "sha512" + return "SHA-512" + else + error("No corepsonding mapping for $x implemented") + end +end + +map_hash_name_back(x::HashSpec) = map_hash_name_back(x.spec) + + +function ro_prefix(spec::ProtocolSpec) + + (; version, sid, auxsid, rohash, prghash, g, nr, nv, ne) = spec + + s_PRG = map_hash_name_back(prghash) + s_H = map_hash_name_back(rohash) + + s_Gq = marshal_s_Gq(g) + + data = (version, sid * "." * auxsid, nr, nv, ne, s_PRG, s_Gq, s_H) + + tree = Tree(data) + binary = encode(tree) + + ฯ = rohash(binary) + + return ฯ +end + + +function challenge(verifier::ProtocolSpec{G}, proposition::LogEquality{G}, commitment::Vector{G}) where G <: Group + + (; rohash, nv) = verifier + + ฯ = ro_prefix(verifier) + + tree = Tree((proposition.g, commitment)) # Need to have BinaryParser within this package + + ro = RO(rohash, nv) + ๐“ฟ = Parser.interpret(BigInt, ro([ฯ..., encode(tree)...])) + + return ๐“ฟ +end + + +function challenge(verifier::ProtocolSpec{G}, proposition::LogKnowledge{G}, commitment::G; suffix = nothing) where G <: Group + # the encoding is deserializable as `octet` returns fixed length output that depends on unerlying group + # nevertheless it is recommended to use a proper canoncial encoding for this purpose which we shall skip + + (; g, y) = proposition + + ฯ = ro_prefix(verifier) + + if !isnothing(suffix) + suffix_tree = Parser.Tree(suffix) + suffix_bytes = Parser.encode(suffix_tree) + else + suffix_bytes = UInt8[] + end + + prg = PRG(verifier.prghash, [ฯ..., octet(g)..., octet(y)..., octet(commitment)..., suffix_bytes...]) + return rand(prg, 2:order(G) - 1) +end + +function challenge(verifier::ProtocolSpec{G}, proposition::Shuffle{G}, A::G) where G <: Group + + (; h, ๐‚, rows) = proposition + ๐ฎ = (i.u for i in rows) |> collect + + ฯ = ro_prefix(verifier) + + tree = Tree((h, ๐ฎ, ๐‚, A)) + prg = PRG(verifier.prghash, [ฯ..., encode(tree)...]) + + return rand(prg, 2:order(G) - 1, length(๐‚)) +end + + +leaf(x::String) = Parser.encode(Parser.Leaf(x)) + +function gen_verificatum_basis(::Type{G}, prghash::HashSpec, rohash::HashSpec, N::Integer; nr::Integer = 0, ฯ = UInt8[], d = [ฯ..., leaf("generators")...]) where G <: Group + + roprg = ROPRG(d, rohash, prghash) + prg = roprg(UInt8[]) # d is a better argument than x + + # TODO + #return rand(prg, G, N; nr) + return GeneratorBasis.generator_basis(prg, G, N; nr) +end + + +function generator_basis(verifier::ProtocolSpec{G}, ::Type{G}, N::Integer; ฯ = ro_prefix(verifier)) where G <: Group + (; g, nr, rohash, prghash) = verifier + return gen_verificatum_basis(G, prghash, rohash, N; nr, ฯ) +end + + +function fill_xml_template(template_path::String, replacements) + # Read the template content + template_content = read(template_path, String) + + # Replace placeholders with actual values + for (placeholder, value) in replacements + # An alternative would be replacing the XML tags themselves, however, that in general does not work + # when XML is hierarchical and can have repeated tags. + template_content = replace(template_content, "{{$placeholder}}" => value) + end + + return template_content +end + +function fill_protinfo_template(spec::ProtocolSpec; name="ShuffleProofs", descr="") + + (; g, nr, nv, ne, prghash, rohash, version, sid) = spec + + pgroup = String(marshal_s_Gq(g).x) # could be improved + + prg_hash = map_hash_name_back(prghash) + ro_hash = map_hash_name_back(rohash) + + return fill_xml_template(joinpath(@__DIR__, "assets", "protInfo.xml"), [ + "VERSION" => version, + "SID" => sid, + "NAME" => name, + "DESCR" => descr, + "STATDIST" => nr, + "PGROUP" => pgroup, + "VBITLENRO" => nv, + "EBITLENRO" => ne, + "PRG" => prg_hash, + "ROHASH" => ro_hash + ]) +end + +function Serializer.save(spec::ProtocolSpec, path::Path; name="undefined") + + info = fill_protinfo_template(spec; name) + write(path, info) + + return +end + + +function Serializer.load(::Type{ProtocolSpec}, path::Path; auxsid = "default") + + xml = read(path) |> String + + rohash = HashSpec(match(r"(.*?)", xml)[1] |> map_hash_name) + prghash = HashSpec(match(r"(.*?)", xml)[1] |> map_hash_name) + s_Gq = match(r"(.*?)", xml)[1] + + nr = parse(Int32, match(r"(.*?)", xml)[1]) + nv = parse(Int32, match(r"(.*?)", xml)[1]) + ne = parse(Int32, match(r"(.*?)", xml)[1]) + + g = unmarshal(decode(split(s_Gq, "::")[2])) + + version = match(r"(.*?)", xml)[1] |> String + sid = match(r"(.*?)", xml)[1] |> String + + + return ProtocolSpec(; g, nr, nv, ne, prghash, rohash, version, sid, auxsid) +end + +#Serializer.load(::Type{ProtocolSpec}, path::Path; auxsid = "default") = load(ProtocolSpec, + +Serializer.treespec(::Type{<:ProtocolSpec}) = "ProtInfo.xml" + + +end diff --git a/src/Verificatum/assets/protInfo.xml b/src/Verificatum/assets/protInfo.xml new file mode 100644 index 0000000..d4b3728 --- /dev/null +++ b/src/Verificatum/assets/protInfo.xml @@ -0,0 +1,102 @@ + + + + + + {{VERSION}} + + + {{SID}} + + + {{NAME}} + + + {{DESCR}} + + + 1 + + + {{STATDIST}} + + + 1 + + + {{PGROUP}} + + + 1 + + + {{VBITLENRO}} + + + {{EBITLENRO}} + + + {{PRG}} + + + {{ROHASH}} + + + noninteractive + + + 1 + + + 0 + + diff --git a/test/Verificatum/crs.jl b/test/Verificatum/crs.jl new file mode 100644 index 0000000..5ba0d3a --- /dev/null +++ b/test/Verificatum/crs.jl @@ -0,0 +1,42 @@ +using Test +using SigmaProofs.Parser: Tree, decode, interpret, unmarshal +using SigmaProofs.Verificatum: gen_verificatum_basis +using CryptoPRG: HashSpec +using CryptoGroups: value, order, modulus + +### Let's make the setup complete. From repo I ahve a following public parameters: +nr = 100 +prghash = HashSpec("sha256") +rohash = HashSpec("sha256") + +ฯ = hex2bytes("15e6c97600bbe30125cbc08598dcde01a769c15c8afe08fe5b7f5542533159e9") + +group_spec = "00000000020100000020636f6d2e766572696669636174756d2e61726974686d2e4d6f645047726f757000000000040100000041009a91c3b704e382e0c772fa7cf0e5d6363edc53d156e841555702c5b6f906574204bf49a551b695bed292e0218337c0861ee649d2fe4039174514fe2c23c10f6701000000404d48e1db8271c17063b97d3e7872eb1b1f6e29e8ab7420aaab8162db7c832ba1025fa4d2a8db4adf69497010c19be0430f7324e97f201c8ba28a7f1611e087b3010000004100300763b0150525252e4989f51e33c4e6462091152ef2291e45699374a3aa8acea714ff30260338bddbb48fc7446b273aaada90e3ee8326f388b582ea8a073502010000000400000001" + +h_str = "(1da949a3dfbeb316e9b225bc7d75b78d0ddd5e44fc382e74f3de95ad10eac798c4cc7be7e57d3afb259964c90fe7eb7e28a7673228d6b35a789dabd0d8351675,2937c1c4771b70c8b3dc935681aa8cef45ed24cf0c74cd2f9599ea5876850936168c5a092270d6396e634b9e46ab59836f509d0a68a65edc1426a87fd157798f,3767a5e108ecc067c31cd8bf544bcebfa3e3002f2af499ad3568e6fdb2775f4147bcf9485aeea3180b81aad365e3f4375c280941fdae10812bf1ba445030ab00,5c83d2d4f9cf3b3c2267788dd95a14740964e89f177690d06c43adfd137a101698c6bdc7ea9565fd0b18b3cfa89a2d5ea860de3912af4ffbae2c1f722912f6b9,2c453b082a201f3410d0b2906e7f2998a2c50d84f8eb0af017bf76124424bd385c15446eaad77b3ef1831798093f688331475c754f9e737d1485d80f0ef8289b,534897b93e335b0de44f3426aa6597170857f610ce0e4ff8af8efc5010ab5dc205ec53da9759e0c5ad0d2fdbf3d116e1ad7a94629c2ee331e8ceb29417983c50,7d09ab7d0704e68376e933ccfbbaa0a2e8b10a270ce72a5add597ae66e692bd930b019cc9be30b25e859dd6ba5e3bd74a872588b1e14885bacaeca3b254cc86a,33ff08f3aae9132accec73805ec2cd98ce871779369d69d135a7e1fb75b653d415480be64116feda3176dc83b2d54cf8240f8c6f1d85168081efb4f013106cdb,921ce5123ccb3b891f9e1f314d84b8ca827c44dc2027dd4fd8272189ba076eba7c2cfe7e8f8a8915b263fc72232e5d5f193f15ff6aece5b9c55f4ca9c1887fcf,410494b95d3e453e46d0b8915765f2316fcf4db7d7eff0349fe674a6498d3aaa2c3bae2b4e3c7ec1a5d5fd90d88a1e9886914f4b8d039cd470d69642b0203b73)" + + +๐ก = interpret.(BigInt, hex2bytes.(split(h_str[2:end-1], ","))) + +tree = decode(group_spec) + +#๐“ฐ = convert(PrimeGenerator, tree) +๐“ฐ = unmarshal(tree) +G = typeof(๐“ฐ) + +p = modulus(๐“ฐ) +q = order(๐“ฐ) + +# Testing correct parsing of test sample +for hi in ๐ก + @test powermod(hi, q + 1, p) == hi +end + +๐กโ€ฒ = gen_verificatum_basis(G, prghash, rohash, 10; nr, ฯ) + + +for hi in ๐กโ€ฒ + @test hi^(q + 1) == hi +end + +@test ๐ก == value.(๐กโ€ฒ) diff --git a/test/Verificatum/gbasis.jl b/test/Verificatum/gbasis.jl new file mode 100644 index 0000000..2003d44 --- /dev/null +++ b/test/Verificatum/gbasis.jl @@ -0,0 +1,57 @@ +module BasisTest + +using Test +import SigmaProofs.Verificatum.GeneratorBasis: modp_generator_basis +import CryptoGroups.Specs: MODP +import CryptoPRG.Verificatum: HashSpec, ROPRG + +tobig(x) = parse(BigInt, bytes2hex(reverse(x)), base=16) +interpret(::Type{BigInt}, x::Vector{UInt8}) = tobig(reverse(x)) + +interpret(::Type{Vector{UInt8}}, x::Integer) = reverse(reinterpret(UInt8, [x])) + + +function leaf(x::Vector{UInt8}) + + N = UInt32(length(x)) + + LEAF = UInt8(1) + + Nbin = interpret(Vector{UInt8}, N) + bin = UInt8[LEAF, Nbin..., x...] + + return bin +end + +leaf(x::String) = leaf(Vector{UInt8}(x)) + + +### Let's make the setup complete. From repo I ahve a following public parameters: +nr = 100 +rohash = HashSpec("sha256") +prghash = HashSpec("sha256") + +ฯ = hex2bytes("15e6c97600bbe30125cbc08598dcde01a769c15c8afe08fe5b7f5542533159e9") + +# group_spec = "00000000020100000020636f6d2e766572696669636174756d2e61726974686d2e4d6f645047726f757000000000040100000041009a91c3b704e382e0c772fa7cf0e5d6363edc53d156e841555702c5b6f906574204bf49a551b695bed292e0218337c0861ee649d2fe4039174514fe2c23c10f6701000000404d48e1db8271c17063b97d3e7872eb1b1f6e29e8ab7420aaab8162db7c832ba1025fa4d2a8db4adf69497010c19be0430f7324e97f201c8ba28a7f1611e087b3010000004100300763b0150525252e4989f51e33c4e6462091152ef2291e45699374a3aa8acea714ff30260338bddbb48fc7446b273aaada90e3ee8326f388b582ea8a073502010000000400000001" + +p = 8095455969267383450536091939011431888343052744233774808515030596297626946131583007491317242326621464546135030140184007406503191036604066436734360427237223 +q = 4047727984633691725268045969505715944171526372116887404257515298148813473065791503745658621163310732273067515070092003703251595518302033218367180213618611 + + +h_str = "(1da949a3dfbeb316e9b225bc7d75b78d0ddd5e44fc382e74f3de95ad10eac798c4cc7be7e57d3afb259964c90fe7eb7e28a7673228d6b35a789dabd0d8351675,2937c1c4771b70c8b3dc935681aa8cef45ed24cf0c74cd2f9599ea5876850936168c5a092270d6396e634b9e46ab59836f509d0a68a65edc1426a87fd157798f,3767a5e108ecc067c31cd8bf544bcebfa3e3002f2af499ad3568e6fdb2775f4147bcf9485aeea3180b81aad365e3f4375c280941fdae10812bf1ba445030ab00,5c83d2d4f9cf3b3c2267788dd95a14740964e89f177690d06c43adfd137a101698c6bdc7ea9565fd0b18b3cfa89a2d5ea860de3912af4ffbae2c1f722912f6b9,2c453b082a201f3410d0b2906e7f2998a2c50d84f8eb0af017bf76124424bd385c15446eaad77b3ef1831798093f688331475c754f9e737d1485d80f0ef8289b,534897b93e335b0de44f3426aa6597170857f610ce0e4ff8af8efc5010ab5dc205ec53da9759e0c5ad0d2fdbf3d116e1ad7a94629c2ee331e8ceb29417983c50,7d09ab7d0704e68376e933ccfbbaa0a2e8b10a270ce72a5add597ae66e692bd930b019cc9be30b25e859dd6ba5e3bd74a872588b1e14885bacaeca3b254cc86a,33ff08f3aae9132accec73805ec2cd98ce871779369d69d135a7e1fb75b653d415480be64116feda3176dc83b2d54cf8240f8c6f1d85168081efb4f013106cdb,921ce5123ccb3b891f9e1f314d84b8ca827c44dc2027dd4fd8272189ba076eba7c2cfe7e8f8a8915b263fc72232e5d5f193f15ff6aece5b9c55f4ca9c1887fcf,410494b95d3e453e46d0b8915765f2316fcf4db7d7eff0349fe674a6498d3aaa2c3bae2b4e3c7ec1a5d5fd90d88a1e9886914f4b8d039cd470d69642b0203b73)" + + +๐ก = interpret.(BigInt, hex2bytes.(split(h_str[2:end-1], ","))) + +d = [ฯ..., leaf("generators")...] +roprg = ROPRG(d, rohash, prghash) +prg = roprg(UInt8[]) # d is a better argument than x + +sp = MODP(p; q) + +๐กโ€ฒ = modp_generator_basis(prg, sp, 10; nr) + +@test ๐ก == ๐กโ€ฒ + +end diff --git a/test/Verificatum/gecbasis.jl b/test/Verificatum/gecbasis.jl new file mode 100644 index 0000000..703c2f5 --- /dev/null +++ b/test/Verificatum/gecbasis.jl @@ -0,0 +1,62 @@ +module ECBasisTest + +using Test +import SigmaProofs.Verificatum.GeneratorBasis: ecp_generator_basis +import CryptoGroups: spec +import CryptoPRG.Verificatum: HashSpec, ROPRG + +tobig(x) = parse(BigInt, bytes2hex(reverse(x)), base=16) +interpret(::Type{BigInt}, x::Vector{UInt8}) = tobig(reverse(x)) +interpret(::Type{Vector{UInt8}}, x::Integer) = reverse(reinterpret(UInt8, [x])) + +function interpret(::Type{T}, x::AbstractString) where T + + if mod(length(x), 2) == 1 + x = "0" * x + end + + bytes = hex2bytes(x) + + return interpret(T, bytes) +end + + +function leaf(x::Vector{UInt8}) + + N = UInt32(length(x)) + + LEAF = UInt8(1) + + Nbin = interpret(Vector{UInt8}, N) + bin = UInt8[LEAF, Nbin..., x...] + + return bin +end + +leaf(x::String) = leaf(Vector{UInt8}(x)) + +### Let's make the setup complete. From repo I ahve a following public parameters: +nr = 100 +rohash = HashSpec("sha256") +prghash = HashSpec("sha256") + +ฯ = hex2bytes("355806458d6cd42655a52be242705c8e824584ccdb6b1c016cad36c591413de4") + +#group_spec = "com.verificatum.arithm.ECqPGroup(P-256)::00000000020100000020636f6d2e766572696669636174756d2e61726974686d2e4543715047726f75700100000005502d323536" + +h_str = "((760858b410b6fd5b5329457488f93eefd76f74753fc018a88a04e0d1015ccced, 750609534db7741d4ffd1f721dfca0cb3a190fba73ad71652999b6846ff6cc6e),(1bb070702fd72beb7d1d019c46bd55db16b510fcaf56ad1cf5d8f2ab46e47703, 017b045263708a3a42b81c67f2aeb0750b90693d7f177f40bb0ab71aaca2a7c7),(cca2a9b54997b340951ff8a80caffd332fc2d18cdbf10b29863960ead8754297, 2536dab68d208a689845b597bfa1cfd06ae859d381babf4afefbc49893dc55de),(19c089238d119fb04034c61481f0032cb9746b569e4ce6fdf9f8bfb019b9c300, 2737321900c4be759486508e4bec21e1850792bbbc98bc0207b992079a46c4ea),(150beafe47a388cbf6b7b62af3de801cf5f39b6c2aa07df15a14870195325d66, 4f0d59a61036e071600050a896a7206bd660b675793ae0bfefcc594157821fa9),(58d18ce2c66aedd896031d6cd791eed6cbb4fe38c805971e465ea44ff436ad, 5e93996474cf43b5f2e02c077334d2ac16120385b67f193d26dd4748252aaeb2),(cb44deb0a87c8154c7f49a3d128c60bf32825e391d09a1c604a2a8d35b110e58, 33d3316d12f822e047f48520ee774d558d3edbbfa9f5d782023f4aa22683873d),(9d378038b627c4ad3e97726bdd7189fdca7b964d842bb5b0b9eafccd84baced7, 1fb99e6bb8bdc5518dd1c8a06a925a9999e72bf74664ee9c2df76959af9b95ef),(5e6c2170c6176600bedb2efd5ef02a72a7561142754f29217b4ebfcc39fe725b, 3c1ff9ce2ac3516928cb2486e7426851ef7fd50d492907494bb275b11081d41a),(b161c3cb307823857de3c032b7c8570a93ceda3f4d8d53837907b517cba92ac9, 27736dfef353719a3d4d7b7a004e62a9e39a32ddff6a525b80b8023bfac469dd),)" + + +a = split(replace(h_str, "("=>"", ")"=>"", " "=>""), ",")[1:end-1] +numbers = interpret.(BigInt, a) +๐ก = collect(zip(numbers[1:2:end], numbers[2:2:end])) + +d = [ฯ..., leaf("generators")...] +roprg = ROPRG(d, rohash, prghash) +prg = roprg(UInt8[]) # d is a better argument than x + +๐กโ€ฒ = ecp_generator_basis(prg, spec(:P_256), 10; nr) + +@test ๐ก == ๐กโ€ฒ + +end diff --git a/test/Verificatum/generators.jl b/test/Verificatum/generators.jl new file mode 100644 index 0000000..8eba895 --- /dev/null +++ b/test/Verificatum/generators.jl @@ -0,0 +1,28 @@ +using Test +#import ShuffleProofs: Leaf, marshal, unmarshal, decode, encode, Tree +import SigmaProofs.Parser: Leaf, marshal, unmarshal, decode, encode, Tree +import CryptoGroups: @PGroup + +g = @PGroup{p = 23, q = 11}(3) + +leaf = Tree(g) + +@test convert(BigInt, leaf) == 3 +@test length(leaf.x) == 1 + +@test unmarshal(marshal(g)) == g + +x = "00000000020100000020636f6d2e766572696669636174756d2e61726974686d2e4d6f645047726f757000000000040100000041009a91c3b704e382e0c772fa7cf0e5d6363edc53d156e841555702c5b6f906574204bf49a551b695bed292e0218337c0861ee649d2fe4039174514fe2c23c10f6701000000404d48e1db8271c17063b97d3e7872eb1b1f6e29e8ab7420aaab8162db7c832ba1025fa4d2a8db4adf69497010c19be0430f7324e97f201c8ba28a7f1611e087b3010000004100300763b0150525252e4989f51e33c4e6462091152ef2291e45699374a3aa8acea714ff30260338bddbb48fc7446b273aaada90e3ee8326f388b582ea8a073502010000000400000001" + +tree = decode(x) +g = unmarshal(tree) + +@test encode(decode(x)) == hex2bytes(x) ### Belongs to original tests + +@test marshal(unmarshal(tree)) == tree + +@test string(marshal(unmarshal(tree))) == x + +@test decode(encode(marshal(g))) == marshal(g) + + diff --git a/test/Verificatum/io.jl b/test/Verificatum/io.jl new file mode 100644 index 0000000..9cfdeb3 --- /dev/null +++ b/test/Verificatum/io.jl @@ -0,0 +1,9 @@ +using Test +using SigmaProofs.Verificatum.Parser: unmarshal, decode +using SigmaProofs.Verificatum: marshal_s_Gq + +s_Gq = "ModPGroup(safe-prime modulus=2*order+1. order bit-length = 511)::00000000020100000020636f6d2e766572696669636174756d2e61726974686d2e4d6f645047726f757000000000040100000041009a91c3b704e382e0c772fa7cf0e5d6363edc53d156e841555702c5b6f906574204bf49a551b695bed292e0218337c0861ee649d2fe4039174514fe2c23c10f6701000000404d48e1db8271c17063b97d3e7872eb1b1f6e29e8ab7420aaab8162db7c832ba1025fa4d2a8db4adf69497010c19be0430f7324e97f201c8ba28a7f1611e087b3010000004100300763b0150525252e4989f51e33c4e6462091152ef2291e45699374a3aa8acea714ff30260338bddbb48fc7446b273aaada90e3ee8326f388b582ea8a073502010000000400000001" + +g = unmarshal(decode(split(s_Gq, "::")[2])) + +@test convert(String, marshal_s_Gq(g)) == s_Gq diff --git a/test/Verificatum/rho.jl b/test/Verificatum/rho.jl new file mode 100644 index 0000000..90e61df --- /dev/null +++ b/test/Verificatum/rho.jl @@ -0,0 +1,26 @@ +using Test +using SigmaProofs.Verificatum.Parser: Leaf, Tree, Node, encode, decode, HashSpec + +h = HashSpec("sha256") + +version = "3.0.4" +sid = "SessionID" +auxsid = "default" +nr = UInt32(100) +nv = UInt32(256) +ne = UInt32(256) + +s_H = "SHA-256" +s_PRG = "SHA-256" +s_Gq = "ModPGroup(safe-prime modulus=2*order+1. order bit-length = 511)::00000000020100000020636f6d2e766572696669636174756d2e61726974686d2e4d6f645047726f757000000000040100000041009a91c3b704e382e0c772fa7cf0e5d6363edc53d156e841555702c5b6f906574204bf49a551b695bed292e0218337c0861ee649d2fe4039174514fe2c23c10f6701000000404d48e1db8271c17063b97d3e7872eb1b1f6e29e8ab7420aaab8162db7c832ba1025fa4d2a8db4adf69497010c19be0430f7324e97f201c8ba28a7f1611e087b3010000004100300763b0150525252e4989f51e33c4e6462091152ef2291e45699374a3aa8acea714ff30260338bddbb48fc7446b273aaada90e3ee8326f388b582ea8a073502010000000400000001" + +data = (version, sid * "." * auxsid, nr, nv, ne, s_PRG, s_Gq, s_H) + +tree = Tree(data) +binary = encode(tree) + + +ฯ = "15e6c97600bbe30125cbc08598dcde01a769c15c8afe08fe5b7f5542533159e9" +@test bytes2hex(h(binary)) == ฯ + + diff --git a/test/Verificatum/runtests.jl b/test/Verificatum/runtests.jl new file mode 100644 index 0000000..e69de29 diff --git a/test/decryption.jl b/test/decryption.jl new file mode 100644 index 0000000..415f101 --- /dev/null +++ b/test/decryption.jl @@ -0,0 +1,39 @@ +using Test + +using CryptoGroups +import SigmaProofs.DecryptionProofs: prove, verify, decrypt, decryptinv +import SigmaProofs.Verificatum: ProtocolSpec +import SigmaProofs.ElGamal: Enc, ElGamalRow + +g = @ECGroup{P_192}() +verifier = ProtocolSpec(; g) + +๐ฆ = [g^4, g^2, g^3] + +sk = 123 +pk = g^sk + +encryptor = Enc(pk, g) + +cyphertexts = encryptor(๐ฆ, rand(2:order(g)-1, length(๐ฆ))) .|> ElGamalRow + +proposition = decrypt(g, cyphertexts, sk) +@test verify(proposition, sk) + +proof = prove(proposition, verifier, sk) +@test verify(proposition, proof, verifier) + +# Higher order API +simulator = decrypt(g, cyphertexts, sk, verifier) +@test verify(simulator) + +# Testing inverse + +propositioninv = decryptinv(g, cyphertexts, sk) +@test verify(propositioninv, sk) + +proof = prove(propositioninv, verifier, sk) +@test verify(propositioninv, proof, verifier) + +simulatorinv = decrypt(g, cyphertexts, sk, verifier) +@test verify(simulatorinv) diff --git a/test/elgamal.jl b/test/elgamal.jl new file mode 100644 index 0000000..9d6705c --- /dev/null +++ b/test/elgamal.jl @@ -0,0 +1,87 @@ +using Test +import CryptoGroups +import CryptoGroups: @PGroup, concretize_type, generator, ECGroup, @ECGroup +import SigmaProofs.ElGamal: Enc, Dec + +function elgamal_test(g) + + sk = 5 + pk = g^sk + r = 3 + m = g^5 + r2 = 6 + + enc = Enc(pk, g) + dec = Dec(sk) + + @test dec(enc(m, r)) == m + @test enc(enc(m, r), r2) == enc(m, r + r2) + + ### Shuffle generation + + sk = 5 + pk = g^sk + + enc = Enc(pk, g) + + m_vec = [g^4, g^2, g^3] + e_vec = enc.(m_vec, [2, 3, 7]) + + ### The shuffling + r_vec = Int[4, 2, 3] + + e_enc = enc.(e_vec, r_vec) + ฯˆ = sortperm(e_enc) + sort!(e_enc) + + @test sort(dec.(e_enc)) == sort(m_vec) + + messages = [ + (g, g^2), + (g^2, g^3), + (g^3, g^4) + ] + + messages_enc = enc(messages, [2, 3, 7]) + @test messages == dec(messages_enc) + + messages_enc = enc(messages, [(2, 3), (3, 5), (7, 2)]) + @test messages == dec(messages_enc) + + #m_vec = [g, g^2, g^3] + #e_vec = enc.(m_vec, 1) +end + + +let + g = @PGroup{p = 23, q = 11}(3) + elgamal_test(g) +end + + +import CryptoGroups +import CryptoGroups: concretize_type, generator, PGroup, ECGroup, Specs + + +let + g = @PGroup{RFC5114_1024}() + elgamal_test(g) +end + + +let + g = @ECGroup{P_192}() + elgamal_test(g) +end + + +let + spec = CryptoGroups.Specs.Curve_B_163_PB + G = concretize_type(ECGroup, spec; name = :B_163_PB) + g = G(generator(spec)) + + elgamal_test(g) +end + + + diff --git a/test/parser.jl b/test/parser.jl new file mode 100644 index 0000000..d06727b --- /dev/null +++ b/test/parser.jl @@ -0,0 +1,27 @@ +using Test +using SigmaProofs.Verificatum.Parser: Node, Leaf, Tree, encode, decode + +### Testing parser + +@test decode("01 00000002 2D52") == Leaf(UInt8[0x2d, 0x52]) +@test decode("00 00000002 01 00000001 AF 01 00000002 03E1") == Node([Leaf(UInt8[0xaf]), Leaf(UInt8[0x03, 0xe1])]) +@test decode("00 00000002 00 00000002 01 00000001 AF 01 00000002 03E1 01 00000002 2D52") == Node([Node([Leaf(UInt8[0xaf]), Leaf(UInt8[0x03, 0xe1])]), Leaf(UInt8[0x2d, 0x52])]) + +y = hex2bytes(replace("00 00000002 00 00000002 01 00000001 AF 01 00000002 03E1 01 00000002 2D52", " "=>"")) +tree = decode(y) +@test convert(Tuple{Tuple{Int32, Int32}, String}, tree) == ((175, 993), "-R") + +@test Tree(((UInt8(175), UInt16(993)), "-R")) == tree + +### Real life example for prime group specification: + +group_spec = "00000000020100000020636f6d2e766572696669636174756d2e61726974686d2e4d6f645047726f757000000000040100000041009a91c3b704e382e0c772fa7cf0e5d6363edc53d156e841555702c5b6f906574204bf49a551b695bed292e0218337c0861ee649d2fe4039174514fe2c23c10f6701000000404d48e1db8271c17063b97d3e7872eb1b1f6e29e8ab7420aaab8162db7c832ba1025fa4d2a8db4adf69497010c19be0430f7324e97f201c8ba28a7f1611e087b3010000004100300763b0150525252e4989f51e33c4e6462091152ef2291e45699374a3aa8acea714ff30260338bddbb48fc7446b273aaada90e3ee8326f388b582ea8a073502010000000400000001" + +@test encode(decode(group_spec)) == hex2bytes(group_spec); + +tree = decode(group_spec) + +(group_name, (p, q, g, e)) = convert(Tuple{String, Tuple{BigInt, BigInt, BigInt, UInt32}}, tree) + +@test p == 2*q + 1 +@test powermod(g, q + 1, p) == g diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 0000000..c033373 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,45 @@ +using SafeTestsets + +@safetestset "Testing ElGamal" begin + include("elgamal.jl") +end + +@safetestset "Testing PGroup Generator Basis" begin + include("Verificatum/gbasis.jl") +end + +@safetestset "Testing ECGroup Generator Basis" begin + include("Verificatum/gecbasis.jl") +end + +@safetestset "Testing Generators" begin + include("Verificatum/generators.jl") +end + +@safetestset "Testing CRS" begin + include("Verificatum/crs.jl") +end + +@safetestset "Testing Parser" begin + include("utils.jl") + include("parser.jl") +end + +@safetestset "Testing Parser Utils" begin + include("Verificatum/rho.jl") + include("Verificatum/io.jl") +end + +@safetestset "Testing Decryption" begin + include("decryption.jl") +end + +@safetestset "Testing Commitment Shuffle" begin + include("shuffle.jl") +end + +@safetestset "Testing Serialization" begin + include("serializer.jl") +end + + diff --git a/test/serializer.jl b/test/serializer.jl new file mode 100644 index 0000000..161d5b1 --- /dev/null +++ b/test/serializer.jl @@ -0,0 +1,45 @@ +using Test + +using CryptoGroups +import SigmaProofs.DecryptionProofs: prove, verify, decrypt, decryptinv, Decryption, DecryptionInv +import SigmaProofs.Verificatum: ProtocolSpec +import SigmaProofs.ElGamal: Enc, ElGamalRow +import SigmaProofs.Serializer: load, save, digest +import SigmaProofs: Simulator +import CryptoPRG: HashSpec + +g = @ECGroup{P_192}() +verifier = ProtocolSpec(; g) + +๐ฆ = [g^4, g^2, g^3] + +sk = 123 +pk = g^sk + +encryptor = Enc(pk, g) + +cyphertexts = encryptor(๐ฆ, rand(2:order(g)-1, length(๐ฆ))) .|> ElGamalRow + +DECRYPT_DIR = joinpath(tempdir(), "decrypt") +rm(DECRYPT_DIR, recursive=true, force=true) +mkpath(DECRYPT_DIR) + +simulator = decrypt(g, cyphertexts, sk, verifier) +save(simulator, DECRYPT_DIR) +loaded_simulator = load(Simulator{Decryption}, DECRYPT_DIR) # + +@test loaded_simulator == simulator +@test digest(simulator, HashSpec("sha256")) == digest(Simulator{Decryption}, DECRYPT_DIR, HashSpec("sha256")) + +# ToDo: DecryptionInv + +DECRYPTINV_DIR = joinpath(tempdir(), "decryptinv") +rm(DECRYPTINV_DIR, recursive=true, force=true) +mkpath(DECRYPTINV_DIR) + +simulator = decryptinv(g, cyphertexts, sk, verifier) +save(simulator, DECRYPTINV_DIR) +loaded_simulator = load(Simulator{DecryptionInv}, DECRYPTINV_DIR) # + +@test loaded_simulator == simulator +@test digest(simulator, HashSpec("sha256")) == digest(Simulator{DecryptionInv}, DECRYPTINV_DIR, HashSpec("sha256")) diff --git a/test/shuffle.jl b/test/shuffle.jl new file mode 100644 index 0000000..b1488d8 --- /dev/null +++ b/test/shuffle.jl @@ -0,0 +1,61 @@ +using Test +using CryptoGroups +using Random +using SigmaProofs.CommitmentShuffle: shuffle, commit, isbinding, verify, CommittedRow +using SigmaProofs: generator_basis +using SigmaProofs.Verificatum: ProtocolSpec + +N = 3 + +G = @ECGroup{P_192} +g = G() + +verifier = ProtocolSpec(; g) + +basis = generator_basis(verifier, G, 10 * N) +shuffle!(basis) + +h = basis[1] +g1, g2, g3 = basis[2:4] + +# Setting up a buletin board + +๐‚ = G[] # Published imediatelly +๐›ƒ = BigInt[] # secret +rows = CommittedRow{G, G}[] # Published afterwards + +function record(row, C, ฮฒ) + + @test isbinding(row, C, h, ฮฒ) + @test verify(row, verifier) + + @test !any(x->x.g == row.g, rows) # all generators must be distinct + + push!(๐‚, C) + push!(๐›ƒ, ฮฒ) + push!(rows, row) + + return +end + +# Some users commit on the messages + +m1 = g^2 +row1, C1, ฮฒ1 = commit(m1, g1, h, verifier) +record(row1, C1, ฮฒ1) + +m2 = g^4 +row2, C2, ฮฒ2 = commit(m2, g2, h, verifier) +record(row2, C2, ฮฒ2) + +m3 = g^6 +row3, C3, ฮฒ3 = commit(m3, g3, h, verifier) +record(row3, C3, ฮฒ3) + +# Finally a shuffle can be constructed + +simulator = shuffle(rows, h, ๐‚, ๐›ƒ, verifier) +# The rows can be made public along with proof + +@test verify(simulator) + diff --git a/test/utils.jl b/test/utils.jl new file mode 100644 index 0000000..2c9f3e9 --- /dev/null +++ b/test/utils.jl @@ -0,0 +1,42 @@ +using Test +using SigmaProofs.Parser: tobig, int2bytes, bitlength, interpret + +x = big(2)^100 + +@test tobig(int2bytes(x)) == x + +x = 2^63 - 1 + +@test int2bytes(big(x)) == reinterpret(UInt8, [x]) + +y = UInt64(2)^63 + UInt64(1) + +@test int2bytes(big(y)) == reinterpret(UInt8, [y]) + +z = UInt128(2)^128 - UInt128(1) + +@test int2bytes(big(z)) == reinterpret(UInt8, [z]) + + +u = 300 +q = interpret(Vector{UInt8}, u) +#@test Int(frombytes(UInt64, copy(q))) == u + +@test interpret(UInt64, copy(q)) == UInt64(u) + +#@test frombytes(BigInt, copy(q)) == big(u) +@test interpret(BigInt, copy(q)) == big(u) + + +# Test cases can be generated on: +# https://compiler.javatpoint.com/opr/test.jsp?filename=BigIntegerBitLengthExample + +x = 2323535352 ## Java says 32 +@test bitlength(x) == bitlength(big(x)) == 32 + +x = 121232 # Java says 17 +@test bitlength(x) == bitlength(big(x)) == 17 + +x = 23235323423415352 +@test bitlength(x) == bitlength(big(x)) == 55 +