diff --git a/ecc/bls12-377/ecdsa/doc.go b/ecc/bls12-377/signature/ecdsa/doc.go similarity index 100% rename from ecc/bls12-377/ecdsa/doc.go rename to ecc/bls12-377/signature/ecdsa/doc.go diff --git a/ecc/bls12-377/ecdsa/ecdsa.go b/ecc/bls12-377/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bls12-377/ecdsa/ecdsa.go rename to ecc/bls12-377/signature/ecdsa/ecdsa.go index 6f27e1535..395313d7f 100644 --- a/ecc/bls12-377/ecdsa/ecdsa.go +++ b/ecc/bls12-377/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bls12-377/ecdsa/ecdsa_test.go b/ecc/bls12-377/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bls12-377/ecdsa/ecdsa_test.go rename to ecc/bls12-377/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bls12-377/ecdsa/marshal.go b/ecc/bls12-377/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bls12-377/ecdsa/marshal.go rename to ecc/bls12-377/signature/ecdsa/marshal.go diff --git a/ecc/bls12-377/ecdsa/marshal_test.go b/ecc/bls12-377/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bls12-377/ecdsa/marshal_test.go rename to ecc/bls12-377/signature/ecdsa/marshal_test.go diff --git a/ecc/bls12-377/signature/schnorr/doc.go b/ecc/bls12-377/signature/schnorr/doc.go new file mode 100644 index 000000000..5d1c86957 --- /dev/null +++ b/ecc/bls12-377/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bls12-377 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bls12-377/signature/schnorr/marshal.go b/ecc/bls12-377/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bls12-377/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bls12-377/signature/schnorr/marshal_test.go b/ecc/bls12-377/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..fedca5a07 --- /dev/null +++ b/ecc/bls12-377/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS12-377] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bls12-377/signature/schnorr/schnorr.go b/ecc/bls12-377/signature/schnorr/schnorr.go new file mode 100644 index 000000000..bd0571b1b --- /dev/null +++ b/ecc/bls12-377/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bls12-377" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bls12377.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bls12377.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bls12377.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bls12377.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bls12377.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bls12-377/signature/schnorr/schnorr_test.go b/ecc/bls12-377/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..68b7c8c57 --- /dev/null +++ b/ecc/bls12-377/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS12-377] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/bls12-378/ecdsa/doc.go b/ecc/bls12-378/signature/ecdsa/doc.go similarity index 100% rename from ecc/bls12-378/ecdsa/doc.go rename to ecc/bls12-378/signature/ecdsa/doc.go diff --git a/ecc/bls12-378/ecdsa/ecdsa.go b/ecc/bls12-378/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bls12-378/ecdsa/ecdsa.go rename to ecc/bls12-378/signature/ecdsa/ecdsa.go index bfee7fe4e..57d13d572 100644 --- a/ecc/bls12-378/ecdsa/ecdsa.go +++ b/ecc/bls12-378/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bls12-378/ecdsa/ecdsa_test.go b/ecc/bls12-378/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bls12-378/ecdsa/ecdsa_test.go rename to ecc/bls12-378/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bls12-378/ecdsa/marshal.go b/ecc/bls12-378/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bls12-378/ecdsa/marshal.go rename to ecc/bls12-378/signature/ecdsa/marshal.go diff --git a/ecc/bls12-378/ecdsa/marshal_test.go b/ecc/bls12-378/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bls12-378/ecdsa/marshal_test.go rename to ecc/bls12-378/signature/ecdsa/marshal_test.go diff --git a/ecc/bls12-378/signature/schnorr/doc.go b/ecc/bls12-378/signature/schnorr/doc.go new file mode 100644 index 000000000..6d056ac70 --- /dev/null +++ b/ecc/bls12-378/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bls12-378 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bls12-378/signature/schnorr/marshal.go b/ecc/bls12-378/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bls12-378/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bls12-378/signature/schnorr/marshal_test.go b/ecc/bls12-378/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..a5bed6622 --- /dev/null +++ b/ecc/bls12-378/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS12-378] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bls12-378/signature/schnorr/schnorr.go b/ecc/bls12-378/signature/schnorr/schnorr.go new file mode 100644 index 000000000..17ca91369 --- /dev/null +++ b/ecc/bls12-378/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bls12-378" + "github.com/consensys/gnark-crypto/ecc/bls12-378/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-378/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bls12378.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bls12378.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bls12378.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bls12378.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bls12378.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bls12-378/signature/schnorr/schnorr_test.go b/ecc/bls12-378/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..69d54ce8f --- /dev/null +++ b/ecc/bls12-378/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS12-378] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/bls12-381/ecdsa/doc.go b/ecc/bls12-381/signature/ecdsa/doc.go similarity index 100% rename from ecc/bls12-381/ecdsa/doc.go rename to ecc/bls12-381/signature/ecdsa/doc.go diff --git a/ecc/bls12-381/ecdsa/ecdsa.go b/ecc/bls12-381/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bls12-381/ecdsa/ecdsa.go rename to ecc/bls12-381/signature/ecdsa/ecdsa.go index 82925c627..f110a9483 100644 --- a/ecc/bls12-381/ecdsa/ecdsa.go +++ b/ecc/bls12-381/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bls12-381/ecdsa/ecdsa_test.go b/ecc/bls12-381/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bls12-381/ecdsa/ecdsa_test.go rename to ecc/bls12-381/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bls12-381/ecdsa/marshal.go b/ecc/bls12-381/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bls12-381/ecdsa/marshal.go rename to ecc/bls12-381/signature/ecdsa/marshal.go diff --git a/ecc/bls12-381/ecdsa/marshal_test.go b/ecc/bls12-381/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bls12-381/ecdsa/marshal_test.go rename to ecc/bls12-381/signature/ecdsa/marshal_test.go diff --git a/ecc/bls12-381/signature/schnorr/doc.go b/ecc/bls12-381/signature/schnorr/doc.go new file mode 100644 index 000000000..dcf3bb586 --- /dev/null +++ b/ecc/bls12-381/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bls12-381 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bls12-381/signature/schnorr/marshal.go b/ecc/bls12-381/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bls12-381/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bls12-381/signature/schnorr/marshal_test.go b/ecc/bls12-381/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..254233609 --- /dev/null +++ b/ecc/bls12-381/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS12-381] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bls12-381/signature/schnorr/schnorr.go b/ecc/bls12-381/signature/schnorr/schnorr.go new file mode 100644 index 000000000..a23d4cf37 --- /dev/null +++ b/ecc/bls12-381/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bls12381.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bls12381.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bls12381.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bls12381.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bls12381.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bls12-381/signature/schnorr/schnorr_test.go b/ecc/bls12-381/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..ca692100c --- /dev/null +++ b/ecc/bls12-381/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS12-381] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/bls24-315/ecdsa/doc.go b/ecc/bls24-315/signature/ecdsa/doc.go similarity index 100% rename from ecc/bls24-315/ecdsa/doc.go rename to ecc/bls24-315/signature/ecdsa/doc.go diff --git a/ecc/bls24-315/ecdsa/ecdsa.go b/ecc/bls24-315/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bls24-315/ecdsa/ecdsa.go rename to ecc/bls24-315/signature/ecdsa/ecdsa.go index c9fe0878f..1e419e34e 100644 --- a/ecc/bls24-315/ecdsa/ecdsa.go +++ b/ecc/bls24-315/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bls24-315/ecdsa/ecdsa_test.go b/ecc/bls24-315/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bls24-315/ecdsa/ecdsa_test.go rename to ecc/bls24-315/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bls24-315/ecdsa/marshal.go b/ecc/bls24-315/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bls24-315/ecdsa/marshal.go rename to ecc/bls24-315/signature/ecdsa/marshal.go diff --git a/ecc/bls24-315/ecdsa/marshal_test.go b/ecc/bls24-315/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bls24-315/ecdsa/marshal_test.go rename to ecc/bls24-315/signature/ecdsa/marshal_test.go diff --git a/ecc/bls24-315/signature/schnorr/doc.go b/ecc/bls24-315/signature/schnorr/doc.go new file mode 100644 index 000000000..ff4bf0324 --- /dev/null +++ b/ecc/bls24-315/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bls24-315 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bls24-315/signature/schnorr/marshal.go b/ecc/bls24-315/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bls24-315/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bls24-315/signature/schnorr/marshal_test.go b/ecc/bls24-315/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..f75a8bccf --- /dev/null +++ b/ecc/bls24-315/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS24-315] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bls24-315/signature/schnorr/schnorr.go b/ecc/bls24-315/signature/schnorr/schnorr.go new file mode 100644 index 000000000..8743a7dc7 --- /dev/null +++ b/ecc/bls24-315/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bls24-315" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fp" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bls24315.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bls24315.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bls24315.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bls24315.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bls24315.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bls24-315/signature/schnorr/schnorr_test.go b/ecc/bls24-315/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..a36590b40 --- /dev/null +++ b/ecc/bls24-315/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS24-315] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/bls24-317/ecdsa/doc.go b/ecc/bls24-317/signature/ecdsa/doc.go similarity index 100% rename from ecc/bls24-317/ecdsa/doc.go rename to ecc/bls24-317/signature/ecdsa/doc.go diff --git a/ecc/bls24-317/ecdsa/ecdsa.go b/ecc/bls24-317/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bls24-317/ecdsa/ecdsa.go rename to ecc/bls24-317/signature/ecdsa/ecdsa.go index 97c553246..ce3ff756d 100644 --- a/ecc/bls24-317/ecdsa/ecdsa.go +++ b/ecc/bls24-317/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bls24-317/ecdsa/ecdsa_test.go b/ecc/bls24-317/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bls24-317/ecdsa/ecdsa_test.go rename to ecc/bls24-317/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bls24-317/ecdsa/marshal.go b/ecc/bls24-317/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bls24-317/ecdsa/marshal.go rename to ecc/bls24-317/signature/ecdsa/marshal.go diff --git a/ecc/bls24-317/ecdsa/marshal_test.go b/ecc/bls24-317/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bls24-317/ecdsa/marshal_test.go rename to ecc/bls24-317/signature/ecdsa/marshal_test.go diff --git a/ecc/bls24-317/signature/schnorr/doc.go b/ecc/bls24-317/signature/schnorr/doc.go new file mode 100644 index 000000000..12245de88 --- /dev/null +++ b/ecc/bls24-317/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bls24-317 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bls24-317/signature/schnorr/marshal.go b/ecc/bls24-317/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bls24-317/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bls24-317/signature/schnorr/marshal_test.go b/ecc/bls24-317/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..cc55bde47 --- /dev/null +++ b/ecc/bls24-317/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS24-317] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bls24-317/signature/schnorr/schnorr.go b/ecc/bls24-317/signature/schnorr/schnorr.go new file mode 100644 index 000000000..9c5fd5a59 --- /dev/null +++ b/ecc/bls24-317/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bls24-317" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fp" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bls24317.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bls24317.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bls24317.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bls24317.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bls24317.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bls24-317/signature/schnorr/schnorr_test.go b/ecc/bls24-317/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..49ec67942 --- /dev/null +++ b/ecc/bls24-317/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BLS24-317] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/bn254/ecdsa/doc.go b/ecc/bn254/signature/ecdsa/doc.go similarity index 100% rename from ecc/bn254/ecdsa/doc.go rename to ecc/bn254/signature/ecdsa/doc.go diff --git a/ecc/bn254/ecdsa/ecdsa.go b/ecc/bn254/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bn254/ecdsa/ecdsa.go rename to ecc/bn254/signature/ecdsa/ecdsa.go index e55205377..165ff0b75 100644 --- a/ecc/bn254/ecdsa/ecdsa.go +++ b/ecc/bn254/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bn254/ecdsa/ecdsa_test.go b/ecc/bn254/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bn254/ecdsa/ecdsa_test.go rename to ecc/bn254/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bn254/ecdsa/marshal.go b/ecc/bn254/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bn254/ecdsa/marshal.go rename to ecc/bn254/signature/ecdsa/marshal.go diff --git a/ecc/bn254/ecdsa/marshal_test.go b/ecc/bn254/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bn254/ecdsa/marshal_test.go rename to ecc/bn254/signature/ecdsa/marshal_test.go diff --git a/ecc/bn254/signature/schnorr/doc.go b/ecc/bn254/signature/schnorr/doc.go new file mode 100644 index 000000000..630879533 --- /dev/null +++ b/ecc/bn254/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bn254 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bn254/signature/schnorr/marshal.go b/ecc/bn254/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bn254/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bn254/signature/schnorr/marshal_test.go b/ecc/bn254/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..f0bdd21eb --- /dev/null +++ b/ecc/bn254/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BN254] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bn254/signature/schnorr/schnorr.go b/ecc/bn254/signature/schnorr/schnorr.go new file mode 100644 index 000000000..b62b8668a --- /dev/null +++ b/ecc/bn254/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fp" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bn254.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bn254.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bn254.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bn254.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bn254.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bn254/signature/schnorr/schnorr_test.go b/ecc/bn254/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..9e8496fc0 --- /dev/null +++ b/ecc/bn254/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BN254] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/bw6-633/ecdsa/doc.go b/ecc/bw6-633/signature/ecdsa/doc.go similarity index 100% rename from ecc/bw6-633/ecdsa/doc.go rename to ecc/bw6-633/signature/ecdsa/doc.go diff --git a/ecc/bw6-633/ecdsa/ecdsa.go b/ecc/bw6-633/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bw6-633/ecdsa/ecdsa.go rename to ecc/bw6-633/signature/ecdsa/ecdsa.go index 67efe8ea1..9e7ff866f 100644 --- a/ecc/bw6-633/ecdsa/ecdsa.go +++ b/ecc/bw6-633/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bw6-633/ecdsa/ecdsa_test.go b/ecc/bw6-633/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bw6-633/ecdsa/ecdsa_test.go rename to ecc/bw6-633/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bw6-633/ecdsa/marshal.go b/ecc/bw6-633/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bw6-633/ecdsa/marshal.go rename to ecc/bw6-633/signature/ecdsa/marshal.go diff --git a/ecc/bw6-633/ecdsa/marshal_test.go b/ecc/bw6-633/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bw6-633/ecdsa/marshal_test.go rename to ecc/bw6-633/signature/ecdsa/marshal_test.go diff --git a/ecc/bw6-633/signature/schnorr/doc.go b/ecc/bw6-633/signature/schnorr/doc.go new file mode 100644 index 000000000..8688835c3 --- /dev/null +++ b/ecc/bw6-633/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bw6-633 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bw6-633/signature/schnorr/marshal.go b/ecc/bw6-633/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bw6-633/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bw6-633/signature/schnorr/marshal_test.go b/ecc/bw6-633/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..dae0361ae --- /dev/null +++ b/ecc/bw6-633/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BW6-633] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bw6-633/signature/schnorr/schnorr.go b/ecc/bw6-633/signature/schnorr/schnorr.go new file mode 100644 index 000000000..74abc730f --- /dev/null +++ b/ecc/bw6-633/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bw6-633" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fp" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bw6633.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bw6633.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bw6633.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bw6633.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bw6633.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bw6-633/signature/schnorr/schnorr_test.go b/ecc/bw6-633/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..ae4fd01ee --- /dev/null +++ b/ecc/bw6-633/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BW6-633] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/bw6-756/ecdsa/doc.go b/ecc/bw6-756/signature/ecdsa/doc.go similarity index 100% rename from ecc/bw6-756/ecdsa/doc.go rename to ecc/bw6-756/signature/ecdsa/doc.go diff --git a/ecc/bw6-756/ecdsa/ecdsa.go b/ecc/bw6-756/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bw6-756/ecdsa/ecdsa.go rename to ecc/bw6-756/signature/ecdsa/ecdsa.go index 1131bb1f7..ca52448b9 100644 --- a/ecc/bw6-756/ecdsa/ecdsa.go +++ b/ecc/bw6-756/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bw6-756/ecdsa/ecdsa_test.go b/ecc/bw6-756/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bw6-756/ecdsa/ecdsa_test.go rename to ecc/bw6-756/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bw6-756/ecdsa/marshal.go b/ecc/bw6-756/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bw6-756/ecdsa/marshal.go rename to ecc/bw6-756/signature/ecdsa/marshal.go diff --git a/ecc/bw6-756/ecdsa/marshal_test.go b/ecc/bw6-756/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bw6-756/ecdsa/marshal_test.go rename to ecc/bw6-756/signature/ecdsa/marshal_test.go diff --git a/ecc/bw6-756/signature/schnorr/doc.go b/ecc/bw6-756/signature/schnorr/doc.go new file mode 100644 index 000000000..ef808bee8 --- /dev/null +++ b/ecc/bw6-756/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bw6-756 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bw6-756/signature/schnorr/marshal.go b/ecc/bw6-756/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bw6-756/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bw6-756/signature/schnorr/marshal_test.go b/ecc/bw6-756/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..8d8ba9b2c --- /dev/null +++ b/ecc/bw6-756/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BW6-756] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bw6-756/signature/schnorr/schnorr.go b/ecc/bw6-756/signature/schnorr/schnorr.go new file mode 100644 index 000000000..035fd172f --- /dev/null +++ b/ecc/bw6-756/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bw6-756" + "github.com/consensys/gnark-crypto/ecc/bw6-756/fp" + "github.com/consensys/gnark-crypto/ecc/bw6-756/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bw6756.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bw6756.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bw6756.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bw6756.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bw6756.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bw6-756/signature/schnorr/schnorr_test.go b/ecc/bw6-756/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..becbf1070 --- /dev/null +++ b/ecc/bw6-756/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BW6-756] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/bw6-761/ecdsa/doc.go b/ecc/bw6-761/signature/ecdsa/doc.go similarity index 100% rename from ecc/bw6-761/ecdsa/doc.go rename to ecc/bw6-761/signature/ecdsa/doc.go diff --git a/ecc/bw6-761/ecdsa/ecdsa.go b/ecc/bw6-761/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/bw6-761/ecdsa/ecdsa.go rename to ecc/bw6-761/signature/ecdsa/ecdsa.go index 8ad854c43..e996c7672 100644 --- a/ecc/bw6-761/ecdsa/ecdsa.go +++ b/ecc/bw6-761/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/bw6-761/ecdsa/ecdsa_test.go b/ecc/bw6-761/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/bw6-761/ecdsa/ecdsa_test.go rename to ecc/bw6-761/signature/ecdsa/ecdsa_test.go diff --git a/ecc/bw6-761/ecdsa/marshal.go b/ecc/bw6-761/signature/ecdsa/marshal.go similarity index 100% rename from ecc/bw6-761/ecdsa/marshal.go rename to ecc/bw6-761/signature/ecdsa/marshal.go diff --git a/ecc/bw6-761/ecdsa/marshal_test.go b/ecc/bw6-761/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/bw6-761/ecdsa/marshal_test.go rename to ecc/bw6-761/signature/ecdsa/marshal_test.go diff --git a/ecc/bw6-761/signature/schnorr/doc.go b/ecc/bw6-761/signature/schnorr/doc.go new file mode 100644 index 000000000..205afb4a8 --- /dev/null +++ b/ecc/bw6-761/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the bw6-761 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/bw6-761/signature/schnorr/marshal.go b/ecc/bw6-761/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/bw6-761/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/bw6-761/signature/schnorr/marshal_test.go b/ecc/bw6-761/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..cded0c9ad --- /dev/null +++ b/ecc/bw6-761/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[BW6-761] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bw6-761/signature/schnorr/schnorr.go b/ecc/bw6-761/signature/schnorr/schnorr.go new file mode 100644 index 000000000..697be7d85 --- /dev/null +++ b/ecc/bw6-761/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bw6-761" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A bw6761.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, _, g, _ := bw6761.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P bw6761.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P bw6761.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P bw6761.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/bw6-761/signature/schnorr/schnorr_test.go b/ecc/bw6-761/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..f4ab6ce56 --- /dev/null +++ b/ecc/bw6-761/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[BW6-761] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/secp256k1/ecdsa/doc.go b/ecc/secp256k1/signature/ecdsa/doc.go similarity index 100% rename from ecc/secp256k1/ecdsa/doc.go rename to ecc/secp256k1/signature/ecdsa/doc.go diff --git a/ecc/secp256k1/ecdsa/ecdsa.go b/ecc/secp256k1/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/secp256k1/ecdsa/ecdsa.go rename to ecc/secp256k1/signature/ecdsa/ecdsa.go index a2abdbbab..f2acadbdc 100644 --- a/ecc/secp256k1/ecdsa/ecdsa.go +++ b/ecc/secp256k1/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/secp256k1/ecdsa/ecdsa_test.go b/ecc/secp256k1/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/secp256k1/ecdsa/ecdsa_test.go rename to ecc/secp256k1/signature/ecdsa/ecdsa_test.go diff --git a/ecc/secp256k1/ecdsa/marshal.go b/ecc/secp256k1/signature/ecdsa/marshal.go similarity index 100% rename from ecc/secp256k1/ecdsa/marshal.go rename to ecc/secp256k1/signature/ecdsa/marshal.go diff --git a/ecc/secp256k1/ecdsa/marshal_test.go b/ecc/secp256k1/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/secp256k1/ecdsa/marshal_test.go rename to ecc/secp256k1/signature/ecdsa/marshal_test.go diff --git a/ecc/secp256k1/signature/schnorr/doc.go b/ecc/secp256k1/signature/schnorr/doc.go new file mode 100644 index 000000000..dde769425 --- /dev/null +++ b/ecc/secp256k1/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the secp256k1 curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/secp256k1/signature/schnorr/marshal.go b/ecc/secp256k1/signature/schnorr/marshal.go new file mode 100644 index 000000000..a010a563b --- /dev/null +++ b/ecc/secp256k1/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.RawBytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.RawBytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/secp256k1/signature/schnorr/marshal_test.go b/ecc/secp256k1/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..ee0dd08e5 --- /dev/null +++ b/ecc/secp256k1/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[SECP256K1] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/secp256k1/signature/schnorr/schnorr.go b/ecc/secp256k1/signature/schnorr/schnorr.go new file mode 100644 index 000000000..cf7413347 --- /dev/null +++ b/ecc/secp256k1/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/secp256k1" + "github.com/consensys/gnark-crypto/ecc/secp256k1/fp" + "github.com/consensys/gnark-crypto/ecc/secp256k1/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = 2 * sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A secp256k1.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, g := secp256k1.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P secp256k1.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P secp256k1.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P secp256k1.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/secp256k1/signature/schnorr/schnorr_test.go b/ecc/secp256k1/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..8af13bee5 --- /dev/null +++ b/ecc/secp256k1/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[SECP256K1] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/ecc/stark-curve/ecdsa/doc.go b/ecc/stark-curve/signature/ecdsa/doc.go similarity index 100% rename from ecc/stark-curve/ecdsa/doc.go rename to ecc/stark-curve/signature/ecdsa/doc.go diff --git a/ecc/stark-curve/ecdsa/ecdsa.go b/ecc/stark-curve/signature/ecdsa/ecdsa.go similarity index 98% rename from ecc/stark-curve/ecdsa/ecdsa.go rename to ecc/stark-curve/signature/ecdsa/ecdsa.go index bd1221196..9808f0f1e 100644 --- a/ecc/stark-curve/ecdsa/ecdsa.go +++ b/ecc/stark-curve/signature/ecdsa/ecdsa.go @@ -22,7 +22,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -33,8 +32,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -220,7 +217,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -270,7 +267,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/ecc/stark-curve/ecdsa/ecdsa_test.go b/ecc/stark-curve/signature/ecdsa/ecdsa_test.go similarity index 100% rename from ecc/stark-curve/ecdsa/ecdsa_test.go rename to ecc/stark-curve/signature/ecdsa/ecdsa_test.go diff --git a/ecc/stark-curve/ecdsa/marshal.go b/ecc/stark-curve/signature/ecdsa/marshal.go similarity index 100% rename from ecc/stark-curve/ecdsa/marshal.go rename to ecc/stark-curve/signature/ecdsa/marshal.go diff --git a/ecc/stark-curve/ecdsa/marshal_test.go b/ecc/stark-curve/signature/ecdsa/marshal_test.go similarity index 100% rename from ecc/stark-curve/ecdsa/marshal_test.go rename to ecc/stark-curve/signature/ecdsa/marshal_test.go diff --git a/ecc/stark-curve/signature/schnorr/doc.go b/ecc/stark-curve/signature/schnorr/doc.go new file mode 100644 index 000000000..a815596e4 --- /dev/null +++ b/ecc/stark-curve/signature/schnorr/doc.go @@ -0,0 +1,23 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package schnorr provides Schnorr signature scheme on the stark-curve curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +package schnorr diff --git a/ecc/stark-curve/signature/schnorr/marshal.go b/ecc/stark-curve/signature/schnorr/marshal.go new file mode 100644 index 000000000..2b92ad686 --- /dev/null +++ b/ecc/stark-curve/signature/schnorr/marshal.go @@ -0,0 +1,108 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte + pkBin := pk.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte + pubkBin := privKey.PublicKey.A.Bytes() + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/ecc/stark-curve/signature/schnorr/marshal_test.go b/ecc/stark-curve/signature/schnorr/marshal_test.go new file mode 100644 index 000000000..64bd65c14 --- /dev/null +++ b/ecc/stark-curve/signature/schnorr/marshal_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[STARK-CURVE] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/stark-curve/signature/schnorr/schnorr.go b/ecc/stark-curve/signature/schnorr/schnorr.go new file mode 100644 index 000000000..c42bcc188 --- /dev/null +++ b/ecc/stark-curve/signature/schnorr/schnorr.go @@ -0,0 +1,291 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/stark-curve" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fr" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A starkcurve.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + _, g := starkcurve.Generators() + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P starkcurve.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P starkcurve.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P starkcurve.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/ecc/stark-curve/signature/schnorr/schnorr_test.go b/ecc/stark-curve/signature/schnorr/schnorr_test.go new file mode 100644 index 000000000..2b8ccdb3a --- /dev/null +++ b/ecc/stark-curve/signature/schnorr/schnorr_test.go @@ -0,0 +1,78 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package schnorr + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[STARK-CURVE] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/internal/generator/main.go b/internal/generator/main.go index 23f7b8ba6..c45fd536b 100644 --- a/internal/generator/main.go +++ b/internal/generator/main.go @@ -13,7 +13,6 @@ import ( "github.com/consensys/gnark-crypto/internal/generator/config" "github.com/consensys/gnark-crypto/internal/generator/crypto/hash/mimc" "github.com/consensys/gnark-crypto/internal/generator/ecc" - "github.com/consensys/gnark-crypto/internal/generator/ecdsa" "github.com/consensys/gnark-crypto/internal/generator/edwards" "github.com/consensys/gnark-crypto/internal/generator/edwards/eddsa" "github.com/consensys/gnark-crypto/internal/generator/fft" @@ -25,6 +24,8 @@ import ( "github.com/consensys/gnark-crypto/internal/generator/permutation" "github.com/consensys/gnark-crypto/internal/generator/plookup" "github.com/consensys/gnark-crypto/internal/generator/polynomial" + "github.com/consensys/gnark-crypto/internal/generator/signature/ecdsa" + "github.com/consensys/gnark-crypto/internal/generator/signature/schnorr" "github.com/consensys/gnark-crypto/internal/generator/sumcheck" "github.com/consensys/gnark-crypto/internal/generator/test_vector_utils" "github.com/consensys/gnark-crypto/internal/generator/tower" @@ -68,8 +69,11 @@ func main() { ElementType: "fr.Element", } - // generate ecdsa - assertNoError(ecdsa.Generate(conf, curveDir, bgen)) + // generate ecdsa signature + assertNoError(ecdsa.Generate(conf, filepath.Join(curveDir, "signature"), bgen)) + + // generate schnorr signature + assertNoError(schnorr.Generate(conf, filepath.Join(curveDir, "signature"), bgen)) if conf.Equal(config.STARK_CURVE) { return // TODO @yelhousni diff --git a/internal/generator/ecdsa/generate.go b/internal/generator/signature/ecdsa/generate.go similarity index 90% rename from internal/generator/ecdsa/generate.go rename to internal/generator/signature/ecdsa/generate.go index 00076625e..98031d558 100644 --- a/internal/generator/ecdsa/generate.go +++ b/internal/generator/signature/ecdsa/generate.go @@ -19,6 +19,6 @@ func Generate(conf config.Curve, baseDir string, bgen *bavard.BatchGenerator) er {File: filepath.Join(baseDir, "marshal.go"), Templates: []string{"marshal.go.tmpl"}}, {File: filepath.Join(baseDir, "marshal_test.go"), Templates: []string{"marshal.test.go.tmpl"}}, } - return bgen.Generate(conf, conf.Package, "./ecdsa/template", entries...) + return bgen.Generate(conf, conf.Package, "./signature/ecdsa/template", entries...) } diff --git a/internal/generator/ecdsa/template/doc.go.tmpl b/internal/generator/signature/ecdsa/template/doc.go.tmpl similarity index 100% rename from internal/generator/ecdsa/template/doc.go.tmpl rename to internal/generator/signature/ecdsa/template/doc.go.tmpl diff --git a/internal/generator/ecdsa/template/ecdsa.go.tmpl b/internal/generator/signature/ecdsa/template/ecdsa.go.tmpl similarity index 98% rename from internal/generator/ecdsa/template/ecdsa.go.tmpl rename to internal/generator/signature/ecdsa/template/ecdsa.go.tmpl index dd536c0e1..2dfe2a951 100644 --- a/internal/generator/ecdsa/template/ecdsa.go.tmpl +++ b/internal/generator/signature/ecdsa/template/ecdsa.go.tmpl @@ -4,7 +4,6 @@ import ( "crypto/rand" "crypto/sha512" "crypto/subtle" - "errors" "hash" "io" "math/big" @@ -15,8 +14,6 @@ import ( "github.com/consensys/gnark-crypto/signature" ) -var errInvalidSig = errors.New("invalid signature") - const ( sizeFr = fr.Bytes sizeFp = fp.Bytes @@ -211,7 +208,7 @@ func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) } s.Mul(r, scalar) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) @@ -261,7 +258,7 @@ func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (boo sInv := new(big.Int).ModInverse(s, order) - m := new(big.Int) + var m *big.Int if hFunc != nil { // compute the hash of the message as an integer dataToHash := make([]byte, len(message)) diff --git a/internal/generator/ecdsa/template/ecdsa.test.go.tmpl b/internal/generator/signature/ecdsa/template/ecdsa.test.go.tmpl similarity index 100% rename from internal/generator/ecdsa/template/ecdsa.test.go.tmpl rename to internal/generator/signature/ecdsa/template/ecdsa.test.go.tmpl diff --git a/internal/generator/ecdsa/template/marshal.go.tmpl b/internal/generator/signature/ecdsa/template/marshal.go.tmpl similarity index 100% rename from internal/generator/ecdsa/template/marshal.go.tmpl rename to internal/generator/signature/ecdsa/template/marshal.go.tmpl diff --git a/internal/generator/ecdsa/template/marshal.test.go.tmpl b/internal/generator/signature/ecdsa/template/marshal.test.go.tmpl similarity index 100% rename from internal/generator/ecdsa/template/marshal.test.go.tmpl rename to internal/generator/signature/ecdsa/template/marshal.test.go.tmpl diff --git a/internal/generator/signature/schnorr/generate.go b/internal/generator/signature/schnorr/generate.go new file mode 100644 index 000000000..918b379eb --- /dev/null +++ b/internal/generator/signature/schnorr/generate.go @@ -0,0 +1,24 @@ +package schnorr + +import ( + "path/filepath" + + "github.com/consensys/bavard" + "github.com/consensys/gnark-crypto/internal/generator/config" +) + +func Generate(conf config.Curve, baseDir string, bgen *bavard.BatchGenerator) error { + // schnorr + conf.Package = "schnorr" + baseDir = filepath.Join(baseDir, conf.Package) + + entries := []bavard.Entry{ + {File: filepath.Join(baseDir, "doc.go"), Templates: []string{"doc.go.tmpl"}}, + {File: filepath.Join(baseDir, "schnorr.go"), Templates: []string{"schnorr.go.tmpl"}}, + {File: filepath.Join(baseDir, "schnorr_test.go"), Templates: []string{"schnorr.test.go.tmpl"}}, + {File: filepath.Join(baseDir, "marshal.go"), Templates: []string{"marshal.go.tmpl"}}, + {File: filepath.Join(baseDir, "marshal_test.go"), Templates: []string{"marshal.test.go.tmpl"}}, + } + return bgen.Generate(conf, conf.Package, "./signature/schnorr/template", entries...) + +} diff --git a/internal/generator/signature/schnorr/template/doc.go.tmpl b/internal/generator/signature/schnorr/template/doc.go.tmpl new file mode 100644 index 000000000..536926c48 --- /dev/null +++ b/internal/generator/signature/schnorr/template/doc.go.tmpl @@ -0,0 +1,8 @@ +// Package {{.Package}} provides Schnorr signature scheme on the {{.Name}} curve. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Schnorr_signature +// - RFC8235: https://www.rfc-editor.org/rfc/rfc8235 +// - BIP-340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +// +package {{.Package}} diff --git a/internal/generator/signature/schnorr/template/marshal.go.tmpl b/internal/generator/signature/schnorr/template/marshal.go.tmpl new file mode 100644 index 000000000..2e76fe21e --- /dev/null +++ b/internal/generator/signature/schnorr/template/marshal.go.tmpl @@ -0,0 +1,98 @@ +import ( + "crypto/subtle" + "io" +) + +// Bytes returns the binary representation of the public key +// follows https://tools.ietf.org/html/rfc8032#section-3.1 +// and returns a compressed representation of the point (x,y) +// +// x, y are the coordinates of the point +// on the curve as big endian integers. +// compressed representation store x with a parity bit to recompute y +func (pk *PublicKey) Bytes() []byte { + var res [sizePublicKey]byte +{{- if eq .Name "secp256k1"}} + pkBin := pk.A.RawBytes() +{{- else}} + pkBin := pk.A.Bytes() +{{- end}} + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pkBin[:]) + return res[:] +} + +// SetBytes sets p from binary representation in buf. +// buf represents a public key as x||y where x, y are +// interpreted as big endian binary numbers corresponding +// to the coordinates of a point on the curve. +// It returns the number of bytes read from the buffer. +func (pk *PublicKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePublicKey { + return n, io.ErrShortBuffer + } + if _, err := pk.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizeFp + return n, nil +} + +// Bytes returns the binary representation of pk, +// as byte array publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +func (privKey *PrivateKey) Bytes() []byte { + var res [sizePrivateKey]byte +{{- if eq .Name "secp256k1"}} + pubkBin := privKey.PublicKey.A.RawBytes() +{{- else}} + pubkBin := privKey.PublicKey.A.Bytes() +{{- end}} + subtle.ConstantTimeCopy(1, res[:sizePublicKey], pubkBin[:]) + subtle.ConstantTimeCopy(1, res[sizePublicKey:sizePrivateKey], privKey.scalar[:]) + return res[:] +} + +// SetBytes sets pk from buf, where buf is interpreted +// as publicKey||scalar +// where publicKey is as publicKey.Bytes(), and +// scalar is in big endian, of size sizeFr. +// It returns the number byte read. +func (privKey *PrivateKey) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizePrivateKey { + return n, io.ErrShortBuffer + } + if _, err := privKey.PublicKey.A.SetBytes(buf[:sizePublicKey]); err != nil { + return 0, err + } + n += sizePublicKey + subtle.ConstantTimeCopy(1, privKey.scalar[:], buf[sizePublicKey:sizePrivateKey]) + n += sizeFr + return n, nil +} + +// Bytes returns the binary representation of sig +// as a byte array of size 2*sizeFr r||s +func (sig *Signature) Bytes() []byte { + var res [sizeSignature]byte + subtle.ConstantTimeCopy(1, res[:sizeFr], sig.R[:]) + subtle.ConstantTimeCopy(1, res[sizeFr:], sig.S[:]) + return res[:] +} + +// SetBytes sets sig from a buffer in binary. +// buf is read interpreted as r||s +// It returns the number of bytes read from buf. +func (sig *Signature) SetBytes(buf []byte) (int, error) { + n := 0 + if len(buf) < sizeSignature { + return n, io.ErrShortBuffer + } + subtle.ConstantTimeCopy(1, sig.R[:], buf[:sizeFr]) + n += sizeFr + subtle.ConstantTimeCopy(1, sig.S[:], buf[sizeFr:2*sizeFr]) + n += sizeFr + return n, nil +} diff --git a/internal/generator/signature/schnorr/template/marshal.test.go.tmpl b/internal/generator/signature/schnorr/template/marshal.test.go.tmpl new file mode 100644 index 000000000..2e75d159b --- /dev/null +++ b/internal/generator/signature/schnorr/template/marshal.test.go.tmpl @@ -0,0 +1,46 @@ +import ( + "crypto/rand" + "crypto/subtle" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +const ( + nbFuzzShort = 10 + nbFuzz = 100 +) + +func TestSerialization(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[{{ toUpper .Name }}] Schnorr serialization: SetBytes(Bytes()) should stay the same", prop.ForAll( + func() bool { + privKey, _ := GenerateKey(rand.Reader) + + var end PrivateKey + buf := privKey.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != sizePrivateKey { + return false + } + + return end.PublicKey.Equal(&privKey.PublicKey) && subtle.ConstantTimeCompare(end.scalar[:], privKey.scalar[:]) == 1 + + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/internal/generator/signature/schnorr/template/schnorr.go.tmpl b/internal/generator/signature/schnorr/template/schnorr.go.tmpl new file mode 100644 index 000000000..01e83b44d --- /dev/null +++ b/internal/generator/signature/schnorr/template/schnorr.go.tmpl @@ -0,0 +1,282 @@ +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/{{ .Name }}" + "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fr" + "github.com/consensys/gnark-crypto/ecc/{{ .Name }}/fp" + "github.com/consensys/gnark-crypto/signature" +) + +var errHashNeeded = errors.New("hFunc cannot be nil. We need a hash for Fiat-Shamir.") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes +{{- if eq .Name "secp256k1"}} + sizePublicKey = 2 * sizeFp +{{- else}} + sizePublicKey = sizeFp +{{- end}} + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an Schnorr public key +type PublicKey struct { + A {{ .CurvePackage }}.G1Affine +} + +// PrivateKey represents an Schnorr private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an Schnorr signature +type Signature struct { + R, S [sizeFr]byte +} + +var one = new(big.Int).SetInt64(1) + +// randFieldElement returns a random element of the order of the given +// curve using the procedure given in FIPS 186-4, Appendix B.5.1. +func randFieldElement(rand io.Reader) (k *big.Int, err error) { + b := make([]byte, fr.Bits/8+8) + _, err = io.ReadFull(rand, b) + if err != nil { + return + } + + k = new(big.Int).SetBytes(b) + n := new(big.Int).Sub(order, one) + k.Mod(k, n) + k.Add(k, one) + return +} + +// GenerateKey generates a public and private key pair. +func GenerateKey(rand io.Reader) (*PrivateKey, error) { + + k, err := randFieldElement(rand) + if err != nil { + return nil, err + + } + + {{- if or (eq .Name "secp256k1") (eq .Name "stark-curve")}} + _, g := {{ .CurvePackage }}.Generators() + {{- else}} + _, _, g, _ := {{ .CurvePackage }}.Generators() + {{- end}} + + privateKey := new(PrivateKey) + k.FillBytes(privateKey.scalar[:sizeFr]) + privateKey.PublicKey.A.ScalarMultiplication(&g, k) + return privateKey, nil +} + +// HashToInt converts a hash value to an integer. Per FIPS 186-4, Section 6.4, +// we use the left-most bits of the hash to match the bit-length of the order of +// the curve. This also performs Step 5 of SEC 1, Version 2.0, Section 4.1.3. +func HashToInt(hash []byte) *big.Int { + if len(hash) > sizeFr { + hash = hash[:sizeFr] + } + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - sizeFr + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +type zr struct{} + +// Read replaces the contents of dst with zeros. It is safe for concurrent use. +func (zr) Read(dst []byte) (n int, err error) { + for i := range dst { + dst[i] = 0 + } + return len(dst), nil +} + +var zeroReader = zr{} + +const ( + aesIV = "gnark-crypto IV." // must be 16 chars (equal block size) +) + +func nonce(privateKey *PrivateKey, hash []byte) (csprng *cipher.StreamReader, err error) { + // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: + // + // SHA2-512(privateKey.scalar ∥ entropy ∥ hash)[:32] + // + // The CSPRNG key is indifferentiable from a random oracle as shown in + // [Coron], the AES-CTR stream is indifferentiable from a random oracle + // under standard cryptographic assumptions (see [Larsson] for examples). + // + // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf + // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf + + // Get 256 bits of entropy from rand. + entropy := make([]byte, 32) + _, err = io.ReadFull(rand.Reader, entropy) + if err != nil { + return + + } + + // Initialize an SHA-512 hash context; digest... + md := sha512.New() + md.Write(privateKey.scalar[:sizeFr]) // the private key, + md.Write(entropy) // the entropy, + md.Write(hash) // and the input hash; + key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), + // which is an indifferentiable MAC. + + // Create an AES-CTR instance to use as a CSPRNG. + block, _ := aes.NewCipher(key) + + // Create a CSPRNG that xors a stream of zeros with + // the output of the AES-CTR instance. + csprng = &cipher.StreamReader{ + R: zeroReader, + S: cipher.NewCTR(block, []byte(aesIV)), + } + + return csprng, err +} + +// Equal compares 2 public keys +func (pub *PublicKey) Equal(x signature.PublicKey) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + bpk := pub.Bytes() + bxx := xx.Bytes() + return subtle.ConstantTimeCompare(bpk, bxx) == 1 +} + +// Public returns the public key associated to the private key. +func (privKey *PrivateKey) Public() signature.PublicKey { + var pub PublicKey + pub.A.Set(&privKey.PublicKey.A) + return &pub +} + +// Sign performs the Schnorr signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = H ( P || m ) +// s = k - x ⋅ r +// signature = {r, s} +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return nil, errHashNeeded + } + + scalar, r, s := new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + csprng, err := nonce(privKey, message) + if err != nil { + return nil, err + } + k, err := randFieldElement(csprng) + if err != nil { + return nil, err + } + + var P {{ .CurvePackage }}.G1Affine + P.ScalarMultiplicationBase(k) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := P.X.Bytes() + PY := P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err = hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + + hramBin := hFunc.Sum(nil) + r = HashToInt(hramBin) + + s.Mul(scalar, r) + s.Sub(k, s). + Mod(s, order) + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the Schnorr signature +// +// R = s ⋅ Base + r ⋅ publiKey +// H ( R || m ) ?= r +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // hFunc cannot be nil. + // We need a hash function for the Fiat-Shamir. + if hFunc == nil { + return false, errHashNeeded + } + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s, e := new(big.Int), new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + var P {{ .CurvePackage }}.G1Jac + P.JointScalarMultiplicationBase(&publicKey.A, s, r) + var _P {{ .CurvePackage }}.G1Affine + _P.FromJacobian(&P) + + // compute H(R, M), all parameters in data are in Montgomery form + PX := _P.X.Bytes() + PY := _P.Y.Bytes() + sizeDataToHash := 2*sizeFp + len(message) + dataToHash := make([]byte, sizeDataToHash) + copy(dataToHash[:], PX[:]) + copy(dataToHash[sizeFp:], PY[:]) + copy(dataToHash[2*sizeFp:], message) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + + hramBin := hFunc.Sum(nil) + e = HashToInt(hramBin) + + return e.Cmp(r) == 0, nil + +} diff --git a/internal/generator/signature/schnorr/template/schnorr.test.go.tmpl b/internal/generator/signature/schnorr/template/schnorr.test.go.tmpl new file mode 100644 index 000000000..681c7fcf3 --- /dev/null +++ b/internal/generator/signature/schnorr/template/schnorr.test.go.tmpl @@ -0,0 +1,60 @@ +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestSchnorr(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + properties.Property("[{{ toUpper .Name }}] test the signing and verification", prop.ForAll( + func() bool { + + privKey, _ := GenerateKey(rand.Reader) + publicKey := privKey.PublicKey + + msg := []byte("testing Schnorr") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + flag, _ := publicKey.Verify(sig, msg, hFunc) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignSchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, hFunc) + } +} + +func BenchmarkVerifySchnorr(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking Schnorr sign()") + hFunc := sha256.New() + sig, _ := privKey.Sign(msg, hFunc) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, hFunc) + } +} diff --git a/signature/ecdsa/ecdsa.go b/signature/ecdsa/ecdsa.go index 68daac09d..ffe16b590 100644 --- a/signature/ecdsa/ecdsa.go +++ b/signature/ecdsa/ecdsa.go @@ -20,17 +20,17 @@ import ( "io" "github.com/consensys/gnark-crypto/ecc" - ecdsa_bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/ecdsa" - ecdsa_bls12378 "github.com/consensys/gnark-crypto/ecc/bls12-378/ecdsa" - ecdsa_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/ecdsa" - ecdsa_bls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315/ecdsa" - ecdsa_bls24317 "github.com/consensys/gnark-crypto/ecc/bls24-317/ecdsa" - ecdsa_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/ecdsa" - ecdsa_bw6633 "github.com/consensys/gnark-crypto/ecc/bw6-633/ecdsa" - ecdsa_bw6756 "github.com/consensys/gnark-crypto/ecc/bw6-756/ecdsa" - ecdsa_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/ecdsa" - ecdsa_secp256k1 "github.com/consensys/gnark-crypto/ecc/secp256k1/ecdsa" - ecdsa_starkcurve "github.com/consensys/gnark-crypto/ecc/stark-curve/ecdsa" + ecdsa_bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/signature/ecdsa" + ecdsa_bls12378 "github.com/consensys/gnark-crypto/ecc/bls12-378/signature/ecdsa" + ecdsa_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/signature/ecdsa" + ecdsa_bls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315/signature/ecdsa" + ecdsa_bls24317 "github.com/consensys/gnark-crypto/ecc/bls24-317/signature/ecdsa" + ecdsa_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/signature/ecdsa" + ecdsa_bw6633 "github.com/consensys/gnark-crypto/ecc/bw6-633/signature/ecdsa" + ecdsa_bw6756 "github.com/consensys/gnark-crypto/ecc/bw6-756/signature/ecdsa" + ecdsa_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/signature/ecdsa" + ecdsa_secp256k1 "github.com/consensys/gnark-crypto/ecc/secp256k1/signature/ecdsa" + ecdsa_starkcurve "github.com/consensys/gnark-crypto/ecc/stark-curve/signature/ecdsa" "github.com/consensys/gnark-crypto/signature" ) diff --git a/signature/schnorr/schnorr.go b/signature/schnorr/schnorr.go new file mode 100644 index 000000000..9c432cd4b --- /dev/null +++ b/signature/schnorr/schnorr.go @@ -0,0 +1,65 @@ +/* +Copyright © 2020 ConsenSys + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package schnorr + +import ( + "io" + + "github.com/consensys/gnark-crypto/ecc" + schnorr_bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/signature/schnorr" + schnorr_bls12378 "github.com/consensys/gnark-crypto/ecc/bls12-378/signature/schnorr" + schnorr_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/signature/schnorr" + schnorr_bls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315/signature/schnorr" + schnorr_bls24317 "github.com/consensys/gnark-crypto/ecc/bls24-317/signature/schnorr" + schnorr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/signature/schnorr" + schnorr_bw6633 "github.com/consensys/gnark-crypto/ecc/bw6-633/signature/schnorr" + schnorr_bw6756 "github.com/consensys/gnark-crypto/ecc/bw6-756/signature/schnorr" + schnorr_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/signature/schnorr" + schnorr_secp256k1 "github.com/consensys/gnark-crypto/ecc/secp256k1/signature/schnorr" + schnorr_starkcurve "github.com/consensys/gnark-crypto/ecc/stark-curve/signature/schnorr" + "github.com/consensys/gnark-crypto/signature" +) + +// New takes a source of randomness and returns a new key pair +func New(ss ecc.ID, r io.Reader) (signature.Signer, error) { + switch ss { + case ecc.BN254: + return schnorr_bn254.GenerateKey(r) + case ecc.BLS12_381: + return schnorr_bls12381.GenerateKey(r) + case ecc.BLS12_377: + return schnorr_bls12377.GenerateKey(r) + case ecc.BLS12_378: + return schnorr_bls12378.GenerateKey(r) + case ecc.BW6_761: + return schnorr_bw6761.GenerateKey(r) + case ecc.BW6_756: + return schnorr_bw6756.GenerateKey(r) + case ecc.BLS24_315: + return schnorr_bls24315.GenerateKey(r) + case ecc.BLS24_317: + return schnorr_bls24317.GenerateKey(r) + case ecc.BW6_633: + return schnorr_bw6633.GenerateKey(r) + case ecc.SECP256K1: + return schnorr_secp256k1.GenerateKey(r) + case ecc.STARK_CURVE: + return schnorr_starkcurve.GenerateKey(r) + default: + panic("not implemented") + } +}