github.com/cloudflare/circl@v1.5.0/zk/dl/dl.go (about)

     1  // Package dl provides a Schnorr NIZK discrete-log proof.
     2  //
     3  // This package implements a Schnorr NIZK discrete-log proof obtained from the
     4  // interactive Schnorr identification scheme through a Fiat-Shamir transformation.
     5  //
     6  // Given (k,G,kG) the Prove function returns a Proof struct attesting that
     7  // kG = [k]G, which can be validated using the Verify function.
     8  //
     9  // The userID label is a unique identifier for the prover.
    10  //
    11  // The otherInfo label is defined to allow flexible inclusion of contextual
    12  // information in the Schnorr NIZK proof.
    13  // The otherInfo is also used as a domain separation tag (dst) for the hash
    14  // to scalar function.
    15  //
    16  // Reference: https://datatracker.ietf.org/doc/html/rfc8235
    17  package dl
    18  
    19  import (
    20  	"encoding/binary"
    21  	"io"
    22  
    23  	"github.com/cloudflare/circl/group"
    24  )
    25  
    26  type Proof struct {
    27  	V group.Element
    28  	R group.Scalar
    29  }
    30  
    31  func calcChallenge(myGroup group.Group, G, V, A group.Element, userID, otherInfo []byte) group.Scalar {
    32  	// Hash transcript (G | V | A | UserID | OtherInfo) to get the random coin.
    33  	GByte, errByte := G.MarshalBinary()
    34  	if errByte != nil {
    35  		panic(errByte)
    36  	}
    37  	VByte, errByte := V.MarshalBinary()
    38  	if errByte != nil {
    39  		panic(errByte)
    40  	}
    41  	AByte, errByte := A.MarshalBinary()
    42  	if errByte != nil {
    43  		panic(errByte)
    44  	}
    45  
    46  	uPrefix := [4]byte{}
    47  	binary.BigEndian.PutUint32(uPrefix[:], uint32(len(userID)))
    48  	oPrefix := [4]byte{}
    49  	binary.BigEndian.PutUint32(oPrefix[:], uint32(len(otherInfo)))
    50  
    51  	hashByte := append(append(append(append(append(append(
    52  		GByte, VByte...), AByte...),
    53  		uPrefix[:]...), userID...),
    54  		oPrefix[:]...), otherInfo...)
    55  
    56  	return myGroup.HashToScalar(hashByte, otherInfo)
    57  }
    58  
    59  // Prove returns a proof attesting that kG = [k]G.
    60  func Prove(myGroup group.Group, G, kG group.Element, k group.Scalar, userID, otherInfo []byte, rnd io.Reader) Proof {
    61  	v := myGroup.RandomNonZeroScalar(rnd)
    62  	V := myGroup.NewElement()
    63  	V.Mul(G, v)
    64  
    65  	c := calcChallenge(myGroup, G, V, kG, userID, otherInfo)
    66  
    67  	r := myGroup.NewScalar()
    68  	r.Sub(v, myGroup.NewScalar().Mul(k, c))
    69  
    70  	return Proof{V, r}
    71  }
    72  
    73  // Verify checks whether the proof attests that kG = [k]G.
    74  func Verify(myGroup group.Group, G, kG group.Element, p Proof, userID, otherInfo []byte) bool {
    75  	c := calcChallenge(myGroup, G, p.V, kG, userID, otherInfo)
    76  
    77  	rG := myGroup.NewElement()
    78  	rG.Mul(G, p.R)
    79  
    80  	ckG := myGroup.NewElement()
    81  	ckG.Mul(kG, c)
    82  
    83  	rG.Add(rG, ckG)
    84  
    85  	return p.V.IsEqual(rG)
    86  }