|
| 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 | +} |
0 commit comments