swisspost-evoting-go-poc/pkg/mixnet/product_argument.go

104 lines
2.7 KiB
Go
Raw Normal View History

package mixnet
import (
"math/big"
"github.com/user/evote/pkg/elgamal"
emath "github.com/user/evote/pkg/math"
)
// ProductArgument proves that the product of all elements in a committed matrix equals b.
type ProductArgument struct {
Due-diligence hardening + Rust transport-security layer Correctness/security review of the whole PoC, with fixes and regression tests. Cryptographic soundness: - mixnet: enforce the multi-exponentiation c_{B_m}=commit(0;0) check that was stubbed out with an empty if — without it a malicious mixer can prove a non-permutation shuffle. - zkp: derive all four Fiat-Shamir challenges via RecursiveHashToZq instead of a biased `hash mod q` (which also capped the challenge space at 256 bits for production-sized groups). Verification honesty: - protocol: VerifyTally now actually calls zkp.VerifySchnorrProof and returns the true aggregate result instead of an unconditional true. - protocol: persist the padded mix input (event.MixInput) so the verifier checks shuffle 0 against the same padding the tally used (fixes false INVALID for N<2). Other correctness: - kdf: length-prefix BuildKDFInfo parts so the info encoding is injective. - math: GqElementFromSquareRoot accepts the valid root q (off-by-one that could panic in HashAndSquare); RandomGqElement samples the full canonical range. - cmd: validate demo --voters/--options instead of panicking on degenerate values. - protocol: use crypto/rand in the demo driver (drop the last math/rand import). Transport security (new): pkg/transportsec exposes Ed25519 signatures and X25519 ECDH — implemented in Rust (rust/transportsec: ed25519-dalek, x25519-dalek), linked into Go via cgo. No RSA. Cross-language conformance test proves the Rust Ed25519 signatures interoperate with Go's crypto/ed25519. Makefile builds the Rust static lib before the Go binary. Tests: added unit/round-trip/tamper coverage for math, hash, elgamal, zkp, mixnet, kdf, returncodes, protocol (end-to-end), and the Rust FFI bridge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 12:42:34 +00:00
CB *emath.GqElement // Commitment to Hadamard product (nil if m=1)
Hadamard *HadamardArgument // nil if m=1
SVP SingleValueProductArgument // Always present
}
// GenProductArgument generates a product argument.
func GenProductArgument(
Due-diligence hardening + Rust transport-security layer Correctness/security review of the whole PoC, with fixes and regression tests. Cryptographic soundness: - mixnet: enforce the multi-exponentiation c_{B_m}=commit(0;0) check that was stubbed out with an empty if — without it a malicious mixer can prove a non-permutation shuffle. - zkp: derive all four Fiat-Shamir challenges via RecursiveHashToZq instead of a biased `hash mod q` (which also capped the challenge space at 256 bits for production-sized groups). Verification honesty: - protocol: VerifyTally now actually calls zkp.VerifySchnorrProof and returns the true aggregate result instead of an unconditional true. - protocol: persist the padded mix input (event.MixInput) so the verifier checks shuffle 0 against the same padding the tally used (fixes false INVALID for N<2). Other correctness: - kdf: length-prefix BuildKDFInfo parts so the info encoding is injective. - math: GqElementFromSquareRoot accepts the valid root q (off-by-one that could panic in HashAndSquare); RandomGqElement samples the full canonical range. - cmd: validate demo --voters/--options instead of panicking on degenerate values. - protocol: use crypto/rand in the demo driver (drop the last math/rand import). Transport security (new): pkg/transportsec exposes Ed25519 signatures and X25519 ECDH — implemented in Rust (rust/transportsec: ed25519-dalek, x25519-dalek), linked into Go via cgo. No RSA. Cross-language conformance test proves the Rust Ed25519 signatures interoperate with Go's crypto/ed25519. Makefile builds the Rust static lib before the Go binary. Tests: added unit/round-trip/tamper coverage for math, hash, elgamal, zkp, mixnet, kdf, returncodes, protocol (end-to-end), and the Rust FFI bridge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 12:42:34 +00:00
cA *emath.GqVector, // Commitments to A columns (size m)
b emath.ZqElement, // Product b = Π A[i,j]
A *emath.ZqMatrix, // n×m matrix
r *emath.ZqVector, // Randomness for A columns
pk elgamal.PublicKey, // Public key (needed for sub-argument hashes)
ck CommitmentKey,
group *emath.GqGroup,
) ProductArgument {
n := A.NumRows()
m := A.NumCols()
if m == 1 {
// Single column: just use SVP directly
svp := GenSingleValueProductArgument(cA.Get(0), b, A.GetColumn(0), r.Get(0), pk, ck, group)
return ProductArgument{SVP: svp}
}
// m > 1: Hadamard + SVP
zqGroup := emath.ZqGroupFromGqGroup(group)
// Compute b_vector = row-wise products (Hadamard product of all columns)
bVector := make([]emath.ZqElement, n)
for i := 0; i < n; i++ {
prod := A.Get(i, 0)
for j := 1; j < m; j++ {
prod = prod.Multiply(A.Get(i, j))
}
bVector[i] = prod
}
bVec := emath.ZqVectorOf(bVector...)
// Commit to Hadamard product
s := emath.RandomZqElement(zqGroup)
cb := ck.Commit(bVec, s)
// Generate Hadamard argument (now with pk)
hadamardArg := GenHadamardArgument(cA, cb, A, bVec, r, s, pk, ck, group)
// Generate SVP argument (now with pk)
svpArg := GenSingleValueProductArgument(cb, b, bVec, s, pk, ck, group)
return ProductArgument{
CB: &cb,
Hadamard: &hadamardArg,
SVP: svpArg,
}
}
// VerifyProductArgument verifies a product argument.
func VerifyProductArgument(
arg ProductArgument,
cA *emath.GqVector,
b emath.ZqElement,
pk elgamal.PublicKey,
ck CommitmentKey,
group *emath.GqGroup,
) bool {
m := cA.Size()
if m == 1 {
return VerifySingleValueProductArgument(arg.SVP, cA.Get(0), b, pk, ck, group)
}
// Verify Hadamard
if arg.CB == nil || arg.Hadamard == nil {
return false
}
if !VerifyHadamardArgument(*arg.Hadamard, cA, *arg.CB, pk, ck, group) {
return false
}
// Verify SVP
return VerifySingleValueProductArgument(arg.SVP, *arg.CB, b, pk, ck, group)
}
func computeProduct(matrix *emath.ZqMatrix) emath.ZqElement {
one, _ := emath.NewZqElement(big.NewInt(1), matrix.Group())
result := one
for i := 0; i < matrix.NumRows(); i++ {
for j := 0; j < matrix.NumCols(); j++ {
result = result.Multiply(matrix.Get(i, j))
}
}
return result
}