Skip to content

Commit

Permalink
Adding DLEQ proof for Qn, the subgroup of squares in (Z/nZ)*.
Browse files Browse the repository at this point in the history
  • Loading branch information
armfazh committed Jan 23, 2024
1 parent 3cde596 commit ddea170
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Alternatively, look at the [Cloudflare Go](https://github.com/cloudflare/go/tree

- [Schnorr](./zk/dl): Prove knowledge of the Discrete Logarithm. ([RFC-8235])
- [DLEQ](./zk/dleq): Prove knowledge of the Discrete Logarithm Equality. ([RFC-9497])

- [DLEQ in Qn](./zk/qndleq): Prove knowledge of the Discrete Logarithm Equality for subgroup of squares in (Z/nZ)*.

### Symmetric Cryptography

Expand Down
147 changes: 147 additions & 0 deletions zk/qndleq/qndleq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Package qndleq provides zero-knowledge proofs of Discrete-Logarithm Equivalence (DLEQ) on Qn.
//
// This package implements proofs on the group Qn (the subgroup of squares in (Z/nZ)*).
//
// # Notation
//
// Z/nZ is the ring of integers modulo N.
// (Z/nZ)* is the multiplicative group of Z/nZ, a.k.a. the units of Z/nZ, the elements with inverse mod N.
// Qn is the subgroup of squares in (Z/nZ)*.
//
// A number x belongs to Qn if
//
// gcd(x, N) = 1, and
// exists y such that x = y^2 mod N.
//
// # References
//
// [DLEQ Proof] "Wallet databases with observers" by Chaum-Pedersen.
// https://doi.org/10.1007/3-540-48071-4_7
//
// [Qn] "Practical Threshold Signatures" by Shoup.
// https://www.iacr.org/archive/eurocrypt2000/1807/18070209-new.pdf
package qndleq

import (
"crypto/rand"
"io"
"math/big"

"github.com/cloudflare/circl/internal/sha3"
)

type Proof struct {
Z, C *big.Int
SecParam uint
}

// SampleQn returns an element of Qn (the subgroup of squares in (Z/nZ)*).
// SampleQn will return error for any error returned by crypto/rand.Int.
func SampleQn(random io.Reader, N *big.Int) (*big.Int, error) {
one := big.NewInt(1)
gcd := new(big.Int)
x := new(big.Int)

for {
y, err := rand.Int(random, N)
if err != nil {
return nil, err
}
// x is a square by construction.
x.Mul(y, y).Mod(x, N)
gcd.GCD(nil, nil, x, N)
// now check whether h is coprime to N.
if gcd.Cmp(one) == 0 {
return x, nil
}
}
}

// Prove creates a DLEQ Proof that attests that the pairs (g,gx)
// and (h,hx) have the same discrete logarithm equal to x.
//
// Given g, h in Qn (the subgroup of squares in (Z/nZ)*), it holds
//
// gx = g^x mod N
// hx = h^x mod N
// x = Log_g(g^x) = Log_h(h^x)
//
// Note: this function does not run in constant time because it uses
// big.Int arithmetic.
func Prove(random io.Reader, x, g, gx, h, hx, N *big.Int, secParam uint) (*Proof, error) {
rSizeBits := uint(N.BitLen()) + 2*secParam
rSizeBytes := (rSizeBits + 7) / 8

rBytes := make([]byte, rSizeBytes)
_, err := io.ReadFull(random, rBytes)
if err != nil {
return nil, err
}
r := new(big.Int).SetBytes(rBytes)

gP := new(big.Int).Exp(g, r, N)
hP := new(big.Int).Exp(h, r, N)

c := doChallenge(g, gx, h, hx, gP, hP, N, secParam)
z := new(big.Int)
z.Mul(c, x).Add(z, r)
r.Xor(r, r)

return &Proof{Z: z, C: c, SecParam: secParam}, nil
}

// Verify checks whether x = Log_g(g^x) = Log_h(h^x).
func (p Proof) Verify(g, gx, h, hx, N *big.Int) bool {
gPNum := new(big.Int).Exp(g, p.Z, N)
gPDen := new(big.Int).Exp(gx, p.C, N)
ok := gPDen.ModInverse(gPDen, N)
if ok == nil {
return false
}
gP := gPNum.Mul(gPNum, gPDen)
gP.Mod(gP, N)

hPNum := new(big.Int).Exp(h, p.Z, N)
hPDen := new(big.Int).Exp(hx, p.C, N)
ok = hPDen.ModInverse(hPDen, N)
if ok == nil {
return false
}
hP := hPNum.Mul(hPNum, hPDen)
hP.Mod(hP, N)

c := doChallenge(g, gx, h, hx, gP, hP, N, p.SecParam)

return p.C.Cmp(c) == 0
}

func mustWrite(w io.Writer, b []byte) {
n, err := w.Write(b)
if err != nil {
panic(err)
}
if len(b) != n {
panic("qndleq: failed to write on hash")
}
}

func doChallenge(g, gx, h, hx, gP, hP, N *big.Int, secParam uint) *big.Int {
modulusLenBytes := (N.BitLen() + 7) / 8
nBytes := make([]byte, modulusLenBytes)
cByteLen := (secParam + 7) / 8
cBytes := make([]byte, cByteLen)

H := sha3.NewShake256()
mustWrite(&H, g.FillBytes(nBytes))
mustWrite(&H, h.FillBytes(nBytes))
mustWrite(&H, gx.FillBytes(nBytes))
mustWrite(&H, hx.FillBytes(nBytes))
mustWrite(&H, gP.FillBytes(nBytes))
mustWrite(&H, hP.FillBytes(nBytes))
_, err := H.Read(cBytes)
if err != nil {
panic(err)
}

return new(big.Int).SetBytes(cBytes)
}
84 changes: 84 additions & 0 deletions zk/qndleq/qndleq_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package qndleq_test

import (
"crypto/rand"
"math/big"
"testing"

"github.com/cloudflare/circl/internal/test"
"github.com/cloudflare/circl/zk/qndleq"
)

func TestProve(t *testing.T) {
const testTimes = 1 << 8
const SecParam = 128
one := big.NewInt(1)
max := new(big.Int).Lsh(one, 256)

for i := 0; i < testTimes; i++ {
N, _ := rand.Int(rand.Reader, max)
if N.Bit(0) == 0 {
N.Add(N, one)
}
x, _ := rand.Int(rand.Reader, N)
g, err := qndleq.SampleQn(rand.Reader, N)
test.CheckNoErr(t, err, "failed to sampleQn")
h, err := qndleq.SampleQn(rand.Reader, N)
test.CheckNoErr(t, err, "failed to sampleQn")
gx := new(big.Int).Exp(g, x, N)
hx := new(big.Int).Exp(h, x, N)

proof, err := qndleq.Prove(rand.Reader, x, g, gx, h, hx, N, SecParam)
test.CheckNoErr(t, err, "failed to generate proof")
test.CheckOk(proof.Verify(g, gx, h, hx, N), "failed to verify", t)
}
}

func TestSampleQn(t *testing.T) {
const testTimes = 1 << 7
one := big.NewInt(1)
max := new(big.Int).Lsh(one, 256)

for i := 0; i < testTimes; i++ {
N, _ := rand.Int(rand.Reader, max)
if N.Bit(0) == 0 {
N.Add(N, one)
}
a, err := qndleq.SampleQn(rand.Reader, N)
test.CheckNoErr(t, err, "failed to sampleQn")
jac := big.Jacobi(a, N)
test.CheckOk(jac == 1, "Jacoby symbol should be one", t)
gcd := new(big.Int).GCD(nil, nil, a, N)
test.CheckOk(gcd.Cmp(one) == 0, "should be coprime to N", t)
}
}

func Benchmark_qndleq(b *testing.B) {
const SecParam = 128
one := big.NewInt(1)
max := new(big.Int).Lsh(one, 256)

N, _ := rand.Int(rand.Reader, max)
if N.Bit(0) == 0 {
N.Add(N, one)
}
x, _ := rand.Int(rand.Reader, N)
g, _ := qndleq.SampleQn(rand.Reader, N)
h, _ := qndleq.SampleQn(rand.Reader, N)
gx := new(big.Int).Exp(g, x, N)
hx := new(big.Int).Exp(h, x, N)

proof, _ := qndleq.Prove(rand.Reader, x, g, gx, h, hx, N, SecParam)

b.Run("Prove", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = qndleq.Prove(rand.Reader, x, g, gx, h, hx, N, SecParam)
}
})

b.Run("Verify", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = proof.Verify(g, gx, h, hx, N)
}
})
}

0 comments on commit ddea170

Please sign in to comment.