Skip to content

Commit 24eb204

Browse files
committed
Adding DLEQ proof for Qn, the subgroup of squares in (Z/nZ)*.
1 parent 099d313 commit 24eb204

File tree

3 files changed

+229
-0
lines changed

3 files changed

+229
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ go get -u github.com/cloudflare/circl
7979
#### Zero-knowledge Proofs
8080
- [Schnorr](./zk/dl): Prove knowledge of the Discrete Logarithm.
8181
- [DLEQ](./zk/dleq): Prove knowledge of the Discrete Logarithm Equality.
82+
- [DLEQ in Qn](./zk/qndleq): Prove knowledge of the Discrete Logarithm Equality for subgroup of squares in (Z/nZ)*.
8283

8384
## Testing and Benchmarking
8485

zk/qndleq/qndleq.go

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Package qndleq provides zero-knowledge proofs of Discrete-Logarithm Equivalence (DLEQ) on Qn.
2+
//
3+
// This package implements proofs on the group Qn (the subgroup of squares in (Z/nZ)*).
4+
//
5+
// # Notation
6+
//
7+
// Z/nZ is the ring of integers modulo N.
8+
// (Z/nZ)* is the multiplicative group of Z/nZ, a.k.a. the units of Z/nZ, the elements with inverse mod N.
9+
// Qn is the subgroup of squares in (Z/nZ)*.
10+
//
11+
// A number x belongs to Qn if
12+
//
13+
// gcd(x, N) = 1, and
14+
// exists y such that x = y^2 mod N.
15+
//
16+
// # References
17+
//
18+
// [DLEQ Proof] "Wallet databases with observers" by Chaum-Pedersen.
19+
// https://doi.org/10.1007/3-540-48071-4_7
20+
//
21+
// [Qn] "Practical Threshold Signatures" by Shoup.
22+
// https://www.iacr.org/archive/eurocrypt2000/1807/18070209-new.pdf
23+
package qndleq
24+
25+
import (
26+
"crypto/rand"
27+
"io"
28+
"math/big"
29+
30+
"github.com/cloudflare/circl/internal/sha3"
31+
)
32+
33+
type Proof struct {
34+
Z, C *big.Int
35+
SecParam uint
36+
}
37+
38+
// SampleQn returns an element of Qn (the subgroup of squares in (Z/nZ)*).
39+
// SampleQn will return error for any error returned by crypto/rand.Int.
40+
func SampleQn(random io.Reader, N *big.Int) (*big.Int, error) {
41+
one := big.NewInt(1)
42+
gcd := new(big.Int)
43+
x := new(big.Int)
44+
45+
for {
46+
y, err := rand.Int(random, N)
47+
if err != nil {
48+
return nil, err
49+
}
50+
// x is a square by construction.
51+
x.Mul(y, y).Mod(x, N)
52+
gcd.GCD(nil, nil, x, N)
53+
// now check whether h is coprime to N.
54+
if gcd.Cmp(one) == 0 {
55+
return x, nil
56+
}
57+
}
58+
}
59+
60+
// Prove creates a DLEQ Proof that attests that the pairs (g,gx)
61+
// and (h,hx) have the same discrete logarithm equal to x.
62+
//
63+
// Given g, h in Qn (the subgroup of squares in (Z/nZ)*), it holds
64+
//
65+
// gx = g^x mod N
66+
// hx = h^x mod N
67+
// x = Log_g(g^x) = Log_h(h^x)
68+
func Prove(random io.Reader, x, g, gx, h, hx, N *big.Int, secParam uint) (*Proof, error) {
69+
rSizeBits := uint(N.BitLen()) + 2*secParam
70+
rSizeBytes := (rSizeBits + 7) / 8
71+
72+
rBytes := make([]byte, rSizeBytes)
73+
_, err := io.ReadFull(random, rBytes)
74+
if err != nil {
75+
return nil, err
76+
}
77+
r := new(big.Int).SetBytes(rBytes)
78+
79+
gP := new(big.Int).Exp(g, r, N)
80+
hP := new(big.Int).Exp(h, r, N)
81+
82+
c := doChallenge(g, gx, h, hx, gP, hP, N, secParam)
83+
z := new(big.Int)
84+
z.Mul(c, x).Add(z, r)
85+
r.Xor(r, r)
86+
87+
return &Proof{Z: z, C: c, SecParam: secParam}, nil
88+
}
89+
90+
// Verify checks whether x = Log_g(g^x) = Log_h(h^x).
91+
func (p Proof) Verify(g, gx, h, hx, N *big.Int) bool {
92+
gPNum := new(big.Int).Exp(g, p.Z, N)
93+
gPDen := new(big.Int).Exp(gx, p.C, N)
94+
ok := gPDen.ModInverse(gPDen, N)
95+
if ok == nil {
96+
return false
97+
}
98+
gP := gPNum.Mul(gPNum, gPDen)
99+
gP.Mod(gP, N)
100+
101+
hPNum := new(big.Int).Exp(h, p.Z, N)
102+
hPDen := new(big.Int).Exp(hx, p.C, N)
103+
ok = hPDen.ModInverse(hPDen, N)
104+
if ok == nil {
105+
return false
106+
}
107+
hP := hPNum.Mul(hPNum, hPDen)
108+
hP.Mod(hP, N)
109+
110+
c := doChallenge(g, gx, h, hx, gP, hP, N, p.SecParam)
111+
112+
return p.C.Cmp(c) == 0
113+
}
114+
115+
func mustWrite(w io.Writer, b []byte) {
116+
n, err := w.Write(b)
117+
if err != nil {
118+
panic(err)
119+
}
120+
if len(b) != n {
121+
panic("qndleq: failed to write on hash")
122+
}
123+
}
124+
125+
func doChallenge(g, gx, h, hx, gP, hP, N *big.Int, secParam uint) *big.Int {
126+
modulusLenBytes := (N.BitLen() + 7) / 8
127+
nBytes := make([]byte, modulusLenBytes)
128+
cByteLen := (secParam + 7) / 8
129+
cBytes := make([]byte, cByteLen)
130+
131+
H := sha3.NewShake256()
132+
mustWrite(&H, g.FillBytes(nBytes))
133+
mustWrite(&H, h.FillBytes(nBytes))
134+
mustWrite(&H, gx.FillBytes(nBytes))
135+
mustWrite(&H, hx.FillBytes(nBytes))
136+
mustWrite(&H, gP.FillBytes(nBytes))
137+
mustWrite(&H, hP.FillBytes(nBytes))
138+
_, err := H.Read(cBytes)
139+
if err != nil {
140+
panic(err)
141+
}
142+
143+
return new(big.Int).SetBytes(cBytes)
144+
}

zk/qndleq/qndleq_test.go

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package qndleq_test
2+
3+
import (
4+
"crypto/rand"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/cloudflare/circl/internal/test"
9+
"github.com/cloudflare/circl/zk/qndleq"
10+
)
11+
12+
func TestProve(t *testing.T) {
13+
const testTimes = 1 << 8
14+
const SecParam = 128
15+
one := big.NewInt(1)
16+
max := new(big.Int).Lsh(one, 256)
17+
18+
for i := 0; i < testTimes; i++ {
19+
N, _ := rand.Int(rand.Reader, max)
20+
if N.Bit(0) == 0 {
21+
N.Add(N, one)
22+
}
23+
x, _ := rand.Int(rand.Reader, N)
24+
g, err := qndleq.SampleQn(rand.Reader, N)
25+
test.CheckNoErr(t, err, "failed to sampleQn")
26+
h, err := qndleq.SampleQn(rand.Reader, N)
27+
test.CheckNoErr(t, err, "failed to sampleQn")
28+
gx := new(big.Int).Exp(g, x, N)
29+
hx := new(big.Int).Exp(h, x, N)
30+
31+
proof, err := qndleq.Prove(rand.Reader, x, g, gx, h, hx, N, SecParam)
32+
test.CheckNoErr(t, err, "failed to generate proof")
33+
test.CheckOk(proof.Verify(g, gx, h, hx, N), "failed to verify", t)
34+
}
35+
}
36+
37+
func TestSampleQn(t *testing.T) {
38+
const testTimes = 1 << 7
39+
one := big.NewInt(1)
40+
max := new(big.Int).Lsh(one, 256)
41+
42+
for i := 0; i < testTimes; i++ {
43+
N, _ := rand.Int(rand.Reader, max)
44+
if N.Bit(0) == 0 {
45+
N.Add(N, one)
46+
}
47+
a, err := qndleq.SampleQn(rand.Reader, N)
48+
test.CheckNoErr(t, err, "failed to sampleQn")
49+
jac := big.Jacobi(a, N)
50+
test.CheckOk(jac == 1, "Jacoby symbol should be one", t)
51+
gcd := new(big.Int).GCD(nil, nil, a, N)
52+
test.CheckOk(gcd.Cmp(one) == 0, "should be coprime to N", t)
53+
}
54+
}
55+
56+
func Benchmark_qndleq(b *testing.B) {
57+
const SecParam = 128
58+
one := big.NewInt(1)
59+
max := new(big.Int).Lsh(one, 256)
60+
61+
N, _ := rand.Int(rand.Reader, max)
62+
if N.Bit(0) == 0 {
63+
N.Add(N, one)
64+
}
65+
x, _ := rand.Int(rand.Reader, N)
66+
g, _ := qndleq.SampleQn(rand.Reader, N)
67+
h, _ := qndleq.SampleQn(rand.Reader, N)
68+
gx := new(big.Int).Exp(g, x, N)
69+
hx := new(big.Int).Exp(h, x, N)
70+
71+
proof, _ := qndleq.Prove(rand.Reader, x, g, gx, h, hx, N, SecParam)
72+
73+
b.Run("Prove", func(b *testing.B) {
74+
for i := 0; i < b.N; i++ {
75+
_, _ = qndleq.Prove(rand.Reader, x, g, gx, h, hx, N, SecParam)
76+
}
77+
})
78+
79+
b.Run("Verify", func(b *testing.B) {
80+
for i := 0; i < b.N; i++ {
81+
_ = proof.Verify(g, gx, h, hx, N)
82+
}
83+
})
84+
}

0 commit comments

Comments
 (0)