Files
zeekayandHanzo Dev 74806677e2 A proof whose challenge is the word "challenge" proves nothing
ProveKnowledge and VerifyKnowledge in the Paillier helper both computed the
Fiat-Shamir challenge as the bytes of the literal string "challenge". A
challenge that does not depend on the statement or the commitment is known to
the prover before it commits, and the verification equation g^z = e · c^chal
can then be satisfied for ANY ciphertext by choosing z first and solving for
e. It is not a weak proof of plaintext knowledge; it is not a proof, and
anything that trusted it would accept a forgery from a party that holds no
plaintext at all.

The comments said as much — "in practice, use Fiat-Shamir", "Simplified
verification. In production, implement full verification as per CGGMP21" —
but the names promise a proof, and a name is what a caller reads. Nothing in
the estate calls either function, and the five bridge watchers that import
this package import it blank, for its side effects. So the placeholder is
removed rather than annotated: dead code that offers a security property it
does not have is worse than no code, because the next caller will not read
the comment.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-05 14:39:00 -07:00

147 lines
3.8 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cggmp21
import (
"crypto/rand"
"errors"
"math/big"
)
// PaillierPublicKey represents a Paillier public key
type PaillierPublicKey struct {
N *big.Int // n = p*q
NSq *big.Int // n^2
G *big.Int // generator (typically n+1)
}
// PaillierPrivateKey represents a Paillier private key
type PaillierPrivateKey struct {
PublicKey *PaillierPublicKey
// The trapdoor, unexported. Any one of these recovers the factorisation, and
// an exported field is emitted by whatever walks the value.
lambda *big.Int // lcm(p-1, q-1)
mu *big.Int // modular multiplicative inverse
primeP *big.Int // prime p
primeQ *big.Int // prime q
}
// GeneratePaillierKeyPair generates a new Paillier keypair
func GeneratePaillierKeyPair(bits int) (*PaillierPrivateKey, *PaillierPublicKey, error) {
// Generate two large primes p and q
p, err := rand.Prime(rand.Reader, bits/2)
if err != nil {
return nil, nil, err
}
q, err := rand.Prime(rand.Reader, bits/2)
if err != nil {
return nil, nil, err
}
// Compute n = p*q
n := new(big.Int).Mul(p, q)
nSq := new(big.Int).Mul(n, n)
// Compute lambda = lcm(p-1, q-1)
pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
qMinus1 := new(big.Int).Sub(q, big.NewInt(1))
gcd := new(big.Int).GCD(nil, nil, pMinus1, qMinus1)
lambda := new(big.Int).Mul(pMinus1, qMinus1)
lambda.Div(lambda, gcd)
// Set g = n+1 (standard choice)
g := new(big.Int).Add(n, big.NewInt(1))
// Compute mu = (L(g^lambda mod n^2))^(-1) mod n
// where L(x) = (x-1)/n
gLambda := new(big.Int).Exp(g, lambda, nSq)
l := L(gLambda, n)
mu := new(big.Int).ModInverse(l, n)
if mu == nil {
return nil, nil, errors.New("failed to compute modular inverse")
}
pubKey := &PaillierPublicKey{
N: n,
NSq: nSq,
G: g,
}
privKey := &PaillierPrivateKey{
PublicKey: pubKey,
lambda: lambda,
mu: mu,
primeP: p,
primeQ: q,
}
return privKey, pubKey, nil
}
// Encrypt encrypts a plaintext using Paillier encryption
func (pub *PaillierPublicKey) Encrypt(plaintext *big.Int) (*big.Int, error) {
// Check plaintext is in valid range
if plaintext.Cmp(pub.N) >= 0 || plaintext.Sign() < 0 {
return nil, errors.New("plaintext out of range")
}
// Generate random r where gcd(r, n) = 1
var r *big.Int
for {
r, _ = rand.Int(rand.Reader, pub.N)
if new(big.Int).GCD(nil, nil, r, pub.N).Cmp(big.NewInt(1)) == 0 {
break
}
}
// Compute ciphertext = g^m * r^n mod n^2
gm := new(big.Int).Exp(pub.G, plaintext, pub.NSq)
rn := new(big.Int).Exp(r, pub.N, pub.NSq)
ciphertext := new(big.Int).Mul(gm, rn)
ciphertext.Mod(ciphertext, pub.NSq)
return ciphertext, nil
}
// Decrypt decrypts a ciphertext using Paillier decryption
func (priv *PaillierPrivateKey) Decrypt(ciphertext *big.Int) (*big.Int, error) {
// Check ciphertext is in valid range
if ciphertext.Cmp(priv.PublicKey.NSq) >= 0 || ciphertext.Sign() <= 0 {
return nil, errors.New("ciphertext out of range")
}
// Compute plaintext = L(c^lambda mod n^2) * mu mod n
cLambda := new(big.Int).Exp(ciphertext, priv.lambda, priv.PublicKey.NSq)
l := L(cLambda, priv.PublicKey.N)
plaintext := new(big.Int).Mul(l, priv.mu)
plaintext.Mod(plaintext, priv.PublicKey.N)
return plaintext, nil
}
// Add performs homomorphic addition of two ciphertexts
func (pub *PaillierPublicKey) Add(c1, c2 *big.Int) *big.Int {
// E(m1) * E(m2) = E(m1 + m2)
result := new(big.Int).Mul(c1, c2)
result.Mod(result, pub.NSq)
return result
}
// Multiply performs homomorphic multiplication by a constant
func (pub *PaillierPublicKey) Multiply(ciphertext, constant *big.Int) *big.Int {
// E(m)^k = E(k*m)
result := new(big.Int).Exp(ciphertext, constant, pub.NSq)
return result
}
// L computes L(x) = (x-1)/n
func L(x, n *big.Int) *big.Int {
return new(big.Int).Div(new(big.Int).Sub(x, big.NewInt(1)), n)
}