diff --git a/ecc/bls12-377/ecdsa/doc.go b/ecc/bls12-377/ecdsa/doc.go new file mode 100644 index 000000000..6fdb39d25 --- /dev/null +++ b/ecc/bls12-377/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bls12-377 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bls12-377/ecdsa/ecdsa.go b/ecc/bls12-377/ecdsa/ecdsa.go new file mode 100644 index 000000000..739b46f80 --- /dev/null +++ b/ecc/bls12-377/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bls12377.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bls12377.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bls12-377/ecdsa/ecdsa_test.go b/ecc/bls12-377/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..d2f8c0fd0 --- /dev/null +++ b/ecc/bls12-377/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bls12-377/ecdsa/marshal.go b/ecc/bls12-377/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bls12-377/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bls12-377/ecdsa/marshal_test.go new file mode 100644 index 000000000..4cbe081d4 --- /dev/null +++ b/ecc/bls12-377/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bls12-377/g1.go index 3e44f69cf..b5b6f65e6 100644 --- a/ecc/bls12-377/g1.go +++ b/ecc/bls12-377/g1.go @@ -73,6 +73,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -527,6 +535,77 @@ func (p *G1Jac) ClearCofactor(a *G1Jac) *G1Jac { } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bls12-377/g1_test.go b/ecc/bls12-377/g1_test.go index 69fd99df0..26aa94d8c 100644 --- a/ecc/bls12-377/g1_test.go +++ b/ecc/bls12-377/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BLS12-377] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bls12-378/ecdsa/doc.go b/ecc/bls12-378/ecdsa/doc.go new file mode 100644 index 000000000..7172ada5c --- /dev/null +++ b/ecc/bls12-378/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bls12-378 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bls12-378/ecdsa/ecdsa.go b/ecc/bls12-378/ecdsa/ecdsa.go new file mode 100644 index 000000000..b40630124 --- /dev/null +++ b/ecc/bls12-378/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bls12378.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bls12378.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bls12-378/ecdsa/ecdsa_test.go b/ecc/bls12-378/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..7183e591c --- /dev/null +++ b/ecc/bls12-378/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bls12-378/ecdsa/marshal.go b/ecc/bls12-378/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bls12-378/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bls12-378/ecdsa/marshal_test.go new file mode 100644 index 000000000..5d41f7401 --- /dev/null +++ b/ecc/bls12-378/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bls12-378/g1.go index cd9155b76..85e515305 100644 --- a/ecc/bls12-378/g1.go +++ b/ecc/bls12-378/g1.go @@ -73,6 +73,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -527,6 +535,77 @@ func (p *G1Jac) ClearCofactor(a *G1Jac) *G1Jac { } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bls12-378/g1_test.go b/ecc/bls12-378/g1_test.go index b26c899a9..79308b3c0 100644 --- a/ecc/bls12-378/g1_test.go +++ b/ecc/bls12-378/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BLS12-378] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bls12-381/ecdsa/doc.go b/ecc/bls12-381/ecdsa/doc.go new file mode 100644 index 000000000..860389a54 --- /dev/null +++ b/ecc/bls12-381/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bls12-381 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bls12-381/ecdsa/ecdsa.go b/ecc/bls12-381/ecdsa/ecdsa.go new file mode 100644 index 000000000..bc31f1db7 --- /dev/null +++ b/ecc/bls12-381/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bls12381.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bls12381.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bls12-381/ecdsa/ecdsa_test.go b/ecc/bls12-381/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..6f0fb9b83 --- /dev/null +++ b/ecc/bls12-381/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bls12-381/ecdsa/marshal.go b/ecc/bls12-381/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bls12-381/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bls12-381/ecdsa/marshal_test.go new file mode 100644 index 000000000..1cf4318ed --- /dev/null +++ b/ecc/bls12-381/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bls12-381/g1.go index a6e2f174c..6f93fe409 100644 --- a/ecc/bls12-381/g1.go +++ b/ecc/bls12-381/g1.go @@ -73,6 +73,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -527,6 +535,77 @@ func (p *G1Jac) ClearCofactor(a *G1Jac) *G1Jac { } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bls12-381/g1_test.go b/ecc/bls12-381/g1_test.go index 93f2c75d2..61ef267da 100644 --- a/ecc/bls12-381/g1_test.go +++ b/ecc/bls12-381/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BLS12-381] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bls24-315/ecdsa/doc.go b/ecc/bls24-315/ecdsa/doc.go new file mode 100644 index 000000000..f06ac91fc --- /dev/null +++ b/ecc/bls24-315/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bls24-315 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bls24-315/ecdsa/ecdsa.go b/ecc/bls24-315/ecdsa/ecdsa.go new file mode 100644 index 000000000..503bae858 --- /dev/null +++ b/ecc/bls24-315/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bls24315.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bls24315.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bls24-315/ecdsa/ecdsa_test.go b/ecc/bls24-315/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..563f5c354 --- /dev/null +++ b/ecc/bls24-315/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bls24-315/ecdsa/marshal.go b/ecc/bls24-315/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bls24-315/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bls24-315/ecdsa/marshal_test.go new file mode 100644 index 000000000..c8e427470 --- /dev/null +++ b/ecc/bls24-315/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bls24-315/g1.go index d9c125045..762e42196 100644 --- a/ecc/bls24-315/g1.go +++ b/ecc/bls24-315/g1.go @@ -73,6 +73,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -529,6 +537,77 @@ func (p *G1Jac) ClearCofactor(a *G1Jac) *G1Jac { } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bls24-315/g1_test.go b/ecc/bls24-315/g1_test.go index d2bb9796c..080fdd33c 100644 --- a/ecc/bls24-315/g1_test.go +++ b/ecc/bls24-315/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BLS24-315] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bls24-317/ecdsa/doc.go b/ecc/bls24-317/ecdsa/doc.go new file mode 100644 index 000000000..52471c4cb --- /dev/null +++ b/ecc/bls24-317/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bls24-317 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bls24-317/ecdsa/ecdsa.go b/ecc/bls24-317/ecdsa/ecdsa.go new file mode 100644 index 000000000..a7bfe566c --- /dev/null +++ b/ecc/bls24-317/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bls24317.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bls24317.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bls24-317/ecdsa/ecdsa_test.go b/ecc/bls24-317/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..c189e6666 --- /dev/null +++ b/ecc/bls24-317/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bls24-317/ecdsa/marshal.go b/ecc/bls24-317/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bls24-317/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bls24-317/ecdsa/marshal_test.go new file mode 100644 index 000000000..1d04d711d --- /dev/null +++ b/ecc/bls24-317/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bls24-317/g1.go index 9a4d35cf7..ce5b72561 100644 --- a/ecc/bls24-317/g1.go +++ b/ecc/bls24-317/g1.go @@ -73,6 +73,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -529,6 +537,77 @@ func (p *G1Jac) ClearCofactor(a *G1Jac) *G1Jac { } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bls24-317/g1_test.go b/ecc/bls24-317/g1_test.go index 34ce1a8a4..045907075 100644 --- a/ecc/bls24-317/g1_test.go +++ b/ecc/bls24-317/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BLS24-317] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bn254/ecdsa/doc.go b/ecc/bn254/ecdsa/doc.go new file mode 100644 index 000000000..69dbd4c98 --- /dev/null +++ b/ecc/bn254/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bn254 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bn254/ecdsa/ecdsa.go b/ecc/bn254/ecdsa/ecdsa.go new file mode 100644 index 000000000..ffc709a4d --- /dev/null +++ b/ecc/bn254/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bn254.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bn254.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bn254/ecdsa/ecdsa_test.go b/ecc/bn254/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..7eb67274b --- /dev/null +++ b/ecc/bn254/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bn254/ecdsa/marshal.go b/ecc/bn254/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bn254/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bn254/ecdsa/marshal_test.go new file mode 100644 index 000000000..5697c241d --- /dev/null +++ b/ecc/bn254/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bn254/g1.go index 1965ca0af..718abd6f2 100644 --- a/ecc/bn254/g1.go +++ b/ecc/bn254/g1.go @@ -73,6 +73,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -498,6 +506,77 @@ func (p *G1Jac) mulGLV(a *G1Jac, s *big.Int) *G1Jac { return p } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bn254/g1_test.go b/ecc/bn254/g1_test.go index 9e4d7855e..bd4139a35 100644 --- a/ecc/bn254/g1_test.go +++ b/ecc/bn254/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BN254] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bw6-633/ecdsa/doc.go b/ecc/bw6-633/ecdsa/doc.go new file mode 100644 index 000000000..8be83117e --- /dev/null +++ b/ecc/bw6-633/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bw6-633 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bw6-633/ecdsa/ecdsa.go b/ecc/bw6-633/ecdsa/ecdsa.go new file mode 100644 index 000000000..811ef33b4 --- /dev/null +++ b/ecc/bw6-633/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bw6633.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bw6633.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bw6-633/ecdsa/ecdsa_test.go b/ecc/bw6-633/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..d060ccef1 --- /dev/null +++ b/ecc/bw6-633/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bw6-633/ecdsa/marshal.go b/ecc/bw6-633/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bw6-633/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bw6-633/ecdsa/marshal_test.go new file mode 100644 index 000000000..5fc8c2133 --- /dev/null +++ b/ecc/bw6-633/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bw6-633/g1.go index 95e71fb1d..f6398338b 100644 --- a/ecc/bw6-633/g1.go +++ b/ecc/bw6-633/g1.go @@ -78,6 +78,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -557,6 +565,77 @@ func (p *G1Jac) ClearCofactor(a *G1Jac) *G1Jac { } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bw6-633/g1_test.go b/ecc/bw6-633/g1_test.go index fa3ae952a..cbe043ae1 100644 --- a/ecc/bw6-633/g1_test.go +++ b/ecc/bw6-633/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BW6-633] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bw6-756/ecdsa/doc.go b/ecc/bw6-756/ecdsa/doc.go new file mode 100644 index 000000000..29c1b18ef --- /dev/null +++ b/ecc/bw6-756/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bw6-756 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bw6-756/ecdsa/ecdsa.go b/ecc/bw6-756/ecdsa/ecdsa.go new file mode 100644 index 000000000..bbf25a9bd --- /dev/null +++ b/ecc/bw6-756/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bw6756.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bw6756.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bw6-756/ecdsa/ecdsa_test.go b/ecc/bw6-756/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..25ec14e85 --- /dev/null +++ b/ecc/bw6-756/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bw6-756/ecdsa/marshal.go b/ecc/bw6-756/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bw6-756/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bw6-756/ecdsa/marshal_test.go new file mode 100644 index 000000000..48fd0f03c --- /dev/null +++ b/ecc/bw6-756/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bw6-756/g1.go index 9ad403028..28fd27815 100644 --- a/ecc/bw6-756/g1.go +++ b/ecc/bw6-756/g1.go @@ -78,6 +78,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -557,6 +565,77 @@ func (p *G1Jac) ClearCofactor(a *G1Jac) *G1Jac { return p } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bw6-756/g1_test.go b/ecc/bw6-756/g1_test.go index 14a225336..5b71da8d2 100644 --- a/ecc/bw6-756/g1_test.go +++ b/ecc/bw6-756/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BW6-756] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bw6-761/ecdsa/doc.go b/ecc/bw6-761/ecdsa/doc.go new file mode 100644 index 000000000..ea1c78355 --- /dev/null +++ b/ecc/bw6-761/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the bw6-761 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/bw6-761/ecdsa/ecdsa.go b/ecc/bw6-761/ecdsa/ecdsa.go new file mode 100644 index 000000000..ee7411090 --- /dev/null +++ b/ecc/bw6-761/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A bw6761.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U bw6761.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/bw6-761/ecdsa/ecdsa_test.go b/ecc/bw6-761/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..f088d4e84 --- /dev/null +++ b/ecc/bw6-761/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/bw6-761/ecdsa/marshal.go b/ecc/bw6-761/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/bw6-761/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/bw6-761/ecdsa/marshal_test.go new file mode 100644 index 000000000..3c41062a2 --- /dev/null +++ b/ecc/bw6-761/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/bw6-761/g1.go index 7457b4fdb..ab29f2f24 100644 --- a/ecc/bw6-761/g1.go +++ b/ecc/bw6-761/g1.go @@ -78,6 +78,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -568,6 +576,77 @@ func (p *G1Jac) ClearCofactor(a *G1Jac) *G1Jac { } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/bw6-761/g1_test.go b/ecc/bw6-761/g1_test.go index 2f978affa..e128bb8d5 100644 --- a/ecc/bw6-761/g1_test.go +++ b/ecc/bw6-761/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[BW6-761] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/secp256k1/ecdsa/doc.go b/ecc/secp256k1/ecdsa/doc.go new file mode 100644 index 000000000..ac59fef6f --- /dev/null +++ b/ecc/secp256k1/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the secp256k1 curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/secp256k1/ecdsa/ecdsa.go b/ecc/secp256k1/ecdsa/ecdsa.go new file mode 100644 index 000000000..0fdc3882b --- /dev/null +++ b/ecc/secp256k1/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = 2 * sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A secp256k1.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U secp256k1.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/secp256k1/ecdsa/ecdsa_test.go b/ecc/secp256k1/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..b4746cc39 --- /dev/null +++ b/ecc/secp256k1/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/secp256k1/ecdsa/marshal.go b/ecc/secp256k1/ecdsa/marshal.go new file mode 100644 index 000000000..ff04c7d94 --- /dev/null +++ b/ecc/secp256k1/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/secp256k1/ecdsa/marshal_test.go new file mode 100644 index 000000000..b5a7f1af1 --- /dev/null +++ b/ecc/secp256k1/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/secp256k1/g1.go index 7d0b4d4e2..82c797965 100644 --- a/ecc/secp256k1/g1.go +++ b/ecc/secp256k1/g1.go @@ -73,6 +73,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -498,6 +506,77 @@ func (p *G1Jac) mulGLV(a *G1Jac, s *big.Int) *G1Jac { return p } +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/secp256k1/g1_test.go b/ecc/secp256k1/g1_test.go index f7b3910be..a88a358f6 100644 --- a/ecc/secp256k1/g1_test.go +++ b/ecc/secp256k1/g1_test.go @@ -394,6 +394,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[SECP256K1] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } @@ -433,7 +450,7 @@ func TestG1AffineBatchScalarMultiplication(t *testing.T) { var expectedJac G1Jac var expected G1Affine var b big.Int - expectedJac.ScalarMultiplication(&g1Gen, sampleScalars[i].ToBigIntRegular(&b)) + expectedJac.ScalarMultiplication(&g1Gen, sampleScalars[i].BigInt(&b)) expected.FromJacobian(&expectedJac) if !result[i].Equal(&expected) { return false diff --git a/ecc/secp256k1/marshal.go b/ecc/secp256k1/marshal.go new file mode 100644 index 000000000..de81d4006 --- /dev/null +++ b/ecc/secp256k1/marshal.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 secp256k1 + +import ( + "errors" + "io" + + "github.com/consensys/gnark-crypto/ecc/secp256k1/fp" +) + +// SizeOfG1AffineCompressed represents the size in bytes that a G1Affine need in binary form, compressed +const SizeOfG1AffineCompressed = 32 + +// SizeOfG1AffineUncompressed represents the size in bytes that a G1Affine need in binary form, uncompressed +const SizeOfG1AffineUncompressed = SizeOfG1AffineCompressed * 2 + +// RawBytes returns binary representation of p (stores X and Y coordinate) +func (p *G1Affine) RawBytes() (res [SizeOfG1AffineUncompressed]byte) { + + // not compressed + // we store the Y coordinate + fp.BigEndian.PutElement((*[fp.Bytes]byte)(res[32:32+fp.Bytes]), p.Y) + + // we store the X coordinate + fp.BigEndian.PutElement((*[fp.Bytes]byte)(res[0:0+fp.Bytes]), p.X) + + return +} + +// SetBytes sets p from binary representation in buf and returns number of consumed bytes +// +// bytes in buf must match RawBytes() +// +// if buf is too short io.ErrShortBuffer is returned +// +// this check if the resulting point is on the curve and in the correct subgroup +func (p *G1Affine) SetBytes(buf []byte) (int, error) { + return p.setBytes(buf, true) +} + +// we store both X and Y and there is no spare bit for flagging +func (p *G1Affine) setBytes(buf []byte, subGroupCheck bool) (int, error) { + if len(buf) < SizeOfG1AffineCompressed { + return 0, io.ErrShortBuffer + } + + // uncompressed point + // read X and Y coordinates + if err := p.X.SetBytesCanonical(buf[:fp.Bytes]); err != nil { + return 0, err + } + if err := p.Y.SetBytesCanonical(buf[fp.Bytes : fp.Bytes*2]); err != nil { + return 0, err + } + + // subgroup check + if subGroupCheck && !p.IsInSubGroup() { + return 0, errors.New("invalid point: subgroup check failed") + } + + return SizeOfG1AffineUncompressed, nil + +} diff --git a/ecc/secp256k1/marshal_test.go b/ecc/secp256k1/marshal_test.go new file mode 100644 index 000000000..9195ed864 --- /dev/null +++ b/ecc/secp256k1/marshal_test.go @@ -0,0 +1,82 @@ +// 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 secp256k1 + +import ( + "math/big" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" + + "github.com/consensys/gnark-crypto/ecc/secp256k1/fp" +) + +func TestG1AffineSerialization(t *testing.T) { + t.Parallel() + // test round trip serialization of infinity + { + // uncompressed + { + var p1, p2 G1Affine + p2.X.SetRandom() + p2.Y.SetRandom() + buf := p1.RawBytes() + n, err := p2.SetBytes(buf[:]) + if err != nil { + t.Fatal(err) + } + if n != SizeOfG1AffineUncompressed { + t.Fatal("invalid number of bytes consumed in buffer") + } + if !(p2.X.IsZero() && p2.Y.IsZero()) { + t.Fatal("deserialization of uncompressed infinity point is not infinity") + } + } + } + + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[G1] Affine SetBytes(RawBytes) should stay the same", prop.ForAll( + func(a fp.Element) bool { + var start, end G1Affine + var ab big.Int + a.BigInt(&ab) + start.ScalarMultiplication(&g1GenAff, &ab) + + buf := start.RawBytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != SizeOfG1AffineUncompressed { + return false + } + return start.X.Equal(&end.X) && start.Y.Equal(&end.Y) + }, + GenFp(), + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/secp256k1/multiexp_test.go b/ecc/secp256k1/multiexp_test.go index 0dd2f1d29..8055985ce 100644 --- a/ecc/secp256k1/multiexp_test.go +++ b/ecc/secp256k1/multiexp_test.go @@ -112,7 +112,7 @@ func TestMultiExpG1(t *testing.T) { // compute expected result with double and add var finalScalar, mixerBigInt big.Int - finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt)) + finalScalar.Mul(&scalar, mixer.BigInt(&mixerBigInt)) expected.ScalarMultiplication(&g1Gen, &finalScalar) // mixer ensures that all the words of a fpElement are set @@ -147,7 +147,7 @@ func TestMultiExpG1(t *testing.T) { // compute expected result with double and add var finalScalar, mixerBigInt big.Int - finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt)) + finalScalar.Mul(&scalar, mixer.BigInt(&mixerBigInt)) expected.ScalarMultiplication(&g1Gen, &finalScalar) // mixer ensures that all the words of a fpElement are set @@ -220,7 +220,7 @@ func TestMultiExpG1(t *testing.T) { var finalBigScalarBi big.Int var op1ScalarMul G1Affine finalBigScalar.SetUint64(9455).Mul(&finalBigScalar, &mixer) - finalBigScalar.ToBigIntRegular(&finalBigScalarBi) + finalBigScalar.BigInt(&finalBigScalarBi) op1ScalarMul.ScalarMultiplication(&g1GenAff, &finalBigScalarBi) return op1ScalarMul.Equal(&op1MultiExp) diff --git a/ecc/stark-curve/ecdsa/doc.go b/ecc/stark-curve/ecdsa/doc.go new file mode 100644 index 000000000..f704026f7 --- /dev/null +++ b/ecc/stark-curve/ecdsa/doc.go @@ -0,0 +1,28 @@ +// 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 ecdsa provides ECDSA signature scheme on the stark-curve curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +package ecdsa diff --git a/ecc/stark-curve/ecdsa/ecdsa.go b/ecc/stark-curve/ecdsa/ecdsa.go new file mode 100644 index 000000000..d378fef06 --- /dev/null +++ b/ecc/stark-curve/ecdsa/ecdsa.go @@ -0,0 +1,296 @@ +// 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 ecdsa + +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 errInvalidSig = errors.New("invalid signature") + +const ( + sizeFr = fr.Bytes + sizeFp = fp.Bytes + sizePublicKey = sizeFp + sizePrivateKey = sizeFr + sizePublicKey + sizeSignature = 2 * sizeFr +) + +var order = fr.Modulus() + +// PublicKey represents an ECDSA public key +type PublicKey struct { + A starkcurve.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U starkcurve.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/ecc/stark-curve/ecdsa/ecdsa_test.go b/ecc/stark-curve/ecdsa/ecdsa_test.go new file mode 100644 index 000000000..ca3d2269c --- /dev/null +++ b/ecc/stark-curve/ecdsa/ecdsa_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 ecdsa + +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/ecc/stark-curve/ecdsa/marshal.go b/ecc/stark-curve/ecdsa/marshal.go new file mode 100644 index 000000000..72d74c5cb --- /dev/null +++ b/ecc/stark-curve/ecdsa/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 ecdsa + +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/ecdsa/marshal_test.go b/ecc/stark-curve/ecdsa/marshal_test.go new file mode 100644 index 000000000..da2130c6d --- /dev/null +++ b/ecc/stark-curve/ecdsa/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 ecdsa + +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] ECDSA 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/g1.go b/ecc/stark-curve/g1.go index a2c7d97b1..cc71db005 100644 --- a/ecc/stark-curve/g1.go +++ b/ecc/stark-curve/g1.go @@ -72,6 +72,14 @@ func (p *G1Jac) ScalarMultiplicationAffine(a *G1Affine, s *big.Int) *G1Jac { return p } +// ScalarMultiplication computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *G1Affine) ScalarMultiplicationBase(s *big.Int) *G1Affine { + var _p G1Jac + _p.mulWindowed(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + // Add adds two point in affine coordinates. // This should rarely be used as it is very inefficient compared to Jacobian func (p *G1Affine) Add(a, b *G1Affine) *G1Affine { @@ -422,6 +430,77 @@ func (p *G1Jac) mulWindowed(a *G1Jac, s *big.Int) *G1Jac { } +// JointScalarMultiplication computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *G1Jac) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *G1Jac { + + var res, p1, p2 G1Jac + res.Set(&g1Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]G1Jac + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} + // ------------------------------------------------------------------------------------------------- // Jacobian extended diff --git a/ecc/stark-curve/g1_test.go b/ecc/stark-curve/g1_test.go index be43cb492..265de978e 100644 --- a/ecc/stark-curve/g1_test.go +++ b/ecc/stark-curve/g1_test.go @@ -361,6 +361,23 @@ func TestG1AffineOps(t *testing.T) { genScalar, )) + properties.Property("[STARK-CURVE] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp G1Jac + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/stark-curve/marshal.go b/ecc/stark-curve/marshal.go new file mode 100644 index 000000000..755ad3a15 --- /dev/null +++ b/ecc/stark-curve/marshal.go @@ -0,0 +1,734 @@ +// 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 starkcurve + +import ( + "encoding/binary" + "errors" + "io" + "reflect" + "sync/atomic" + + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fr" + "github.com/consensys/gnark-crypto/internal/parallel" +) + +// To encode G1Affine points, we mask the most significant bits with these bits to specify without ambiguity +// metadata needed for point (de)compression +// we have less than 3 bits available on the msw, so we can't follow BLS12-381 style encoding. +// the difference is the case where a point is infinity and uncompressed is not flagged +const ( + mMask byte = 0b11 << 6 + mUncompressed byte = 0b00 << 6 + mCompressedSmallest byte = 0b10 << 6 + mCompressedLargest byte = 0b11 << 6 + mCompressedInfinity byte = 0b01 << 6 +) + +// Encoder writes stark-curve object values to an output stream +type Encoder struct { + w io.Writer + n int64 // written bytes + raw bool // raw vs compressed encoding +} + +// Decoder reads stark-curve object values from an inbound stream +type Decoder struct { + r io.Reader + n int64 // read bytes + subGroupCheck bool // default to true +} + +// NewDecoder returns a binary decoder supporting curve stark-curve objects in both +// compressed and uncompressed (raw) forms +func NewDecoder(r io.Reader, options ...func(*Decoder)) *Decoder { + d := &Decoder{r: r, subGroupCheck: true} + + for _, o := range options { + o(d) + } + + return d +} + +// Decode reads the binary encoding of v from the stream +// type must be *uint64, *fr.Element, *fp.Element, *G1Affine or *[]G1Affine +func (dec *Decoder) Decode(v interface{}) (err error) { + rv := reflect.ValueOf(v) + if v == nil || rv.Kind() != reflect.Ptr || rv.IsNil() || !rv.Elem().CanSet() { + return errors.New("stark-curve decoder: unsupported type, need pointer") + } + + // implementation note: code is a bit verbose (abusing code generation), but minimize allocations on the heap + // in particular, careful attention must be given to usage of Bytes() method on Elements and Points + // that return an array (not a slice) of bytes. Using this is beneficial to minimize memallocs + // in very large (de)serialization upstream in gnark. + // (but detrimental to code lisibility here) + + var buf [SizeOfG1AffineUncompressed]byte + var read int + + switch t := v.(type) { + case *fr.Element: + read, err = io.ReadFull(dec.r, buf[:fr.Bytes]) + dec.n += int64(read) + if err != nil { + return + } + err = t.SetBytesCanonical(buf[:fr.Bytes]) + return + case *fp.Element: + read, err = io.ReadFull(dec.r, buf[:fp.Bytes]) + dec.n += int64(read) + if err != nil { + return + } + err = t.SetBytesCanonical(buf[:fp.Bytes]) + return + case *[]fr.Element: + var sliceLen uint32 + sliceLen, err = dec.readUint32() + if err != nil { + return + } + if len(*t) != int(sliceLen) { + *t = make([]fr.Element, sliceLen) + } + + for i := 0; i < len(*t); i++ { + read, err = io.ReadFull(dec.r, buf[:fr.Bytes]) + dec.n += int64(read) + if err != nil { + return + } + if err = (*t)[i].SetBytesCanonical(buf[:fr.Bytes]); err != nil { + return + } + } + return + case *[]fp.Element: + var sliceLen uint32 + sliceLen, err = dec.readUint32() + if err != nil { + return + } + if len(*t) != int(sliceLen) { + *t = make([]fp.Element, sliceLen) + } + + for i := 0; i < len(*t); i++ { + read, err = io.ReadFull(dec.r, buf[:fp.Bytes]) + dec.n += int64(read) + if err != nil { + return + } + if err = (*t)[i].SetBytesCanonical(buf[:fp.Bytes]); err != nil { + return + } + } + return + case *G1Affine: + // we start by reading compressed point size, if metadata tells us it is uncompressed, we read more. + read, err = io.ReadFull(dec.r, buf[:SizeOfG1AffineCompressed]) + dec.n += int64(read) + if err != nil { + return + } + nbBytes := SizeOfG1AffineCompressed + // most significant byte contains metadata + if !isCompressed(buf[0]) { + nbBytes = SizeOfG1AffineUncompressed + // we read more. + read, err = io.ReadFull(dec.r, buf[SizeOfG1AffineCompressed:SizeOfG1AffineUncompressed]) + dec.n += int64(read) + if err != nil { + return + } + } + _, err = t.setBytes(buf[:nbBytes], dec.subGroupCheck) + return + case *[]G1Affine: + var sliceLen uint32 + sliceLen, err = dec.readUint32() + if err != nil { + return + } + if len(*t) != int(sliceLen) { + *t = make([]G1Affine, sliceLen) + } + compressed := make([]bool, sliceLen) + for i := 0; i < len(*t); i++ { + + // we start by reading compressed point size, if metadata tells us it is uncompressed, we read more. + read, err = io.ReadFull(dec.r, buf[:SizeOfG1AffineCompressed]) + dec.n += int64(read) + if err != nil { + return + } + nbBytes := SizeOfG1AffineCompressed + // most significant byte contains metadata + if !isCompressed(buf[0]) { + nbBytes = SizeOfG1AffineUncompressed + // we read more. + read, err = io.ReadFull(dec.r, buf[SizeOfG1AffineCompressed:SizeOfG1AffineUncompressed]) + dec.n += int64(read) + if err != nil { + return + } + _, err = (*t)[i].setBytes(buf[:nbBytes], false) + if err != nil { + return + } + } else { + var r bool + if r, err = ((*t)[i].unsafeSetCompressedBytes(buf[:nbBytes])); err != nil { + return + } + compressed[i] = !r + } + } + var nbErrs uint64 + parallel.Execute(len(compressed), func(start, end int) { + for i := start; i < end; i++ { + if compressed[i] { + if err := (*t)[i].unsafeComputeY(dec.subGroupCheck); err != nil { + atomic.AddUint64(&nbErrs, 1) + } + } else if dec.subGroupCheck { + if !(*t)[i].IsInSubGroup() { + atomic.AddUint64(&nbErrs, 1) + } + } + } + }) + if nbErrs != 0 { + return errors.New("point decompression failed") + } + + return nil + default: + n := binary.Size(t) + if n == -1 { + return errors.New("stark-curve encoder: unsupported type") + } + err = binary.Read(dec.r, binary.BigEndian, t) + if err == nil { + dec.n += int64(n) + } + return + } +} + +// BytesRead return total bytes read from reader +func (dec *Decoder) BytesRead() int64 { + return dec.n +} + +func (dec *Decoder) readUint32() (r uint32, err error) { + var read int + var buf [4]byte + read, err = io.ReadFull(dec.r, buf[:4]) + dec.n += int64(read) + if err != nil { + return + } + r = binary.BigEndian.Uint32(buf[:4]) + return +} + +func isCompressed(msb byte) bool { + mData := msb & mMask + return !(mData == mUncompressed) +} + +// NewEncoder returns a binary encoder supporting curve stark-curve objects +func NewEncoder(w io.Writer, options ...func(*Encoder)) *Encoder { + // default settings + enc := &Encoder{ + w: w, + n: 0, + raw: false, + } + + // handle options + for _, option := range options { + option(enc) + } + + return enc +} + +// Encode writes the binary encoding of v to the stream +// type must be uint64, *fr.Element, *fp.Element, *G1Affine, *G2Affine, []G1Affine or []G2Affine +func (enc *Encoder) Encode(v interface{}) (err error) { + if enc.raw { + return enc.encodeRaw(v) + } + return enc.encode(v) +} + +// BytesWritten return total bytes written on writer +func (enc *Encoder) BytesWritten() int64 { + return enc.n +} + +// RawEncoding returns an option to use in NewEncoder(...) which sets raw encoding mode to true +// points will not be compressed using this option +func RawEncoding() func(*Encoder) { + return func(enc *Encoder) { + enc.raw = true + } +} + +// NoSubgroupChecks returns an option to use in NewDecoder(...) which disable subgroup checks on the points +// the decoder will read. Use with caution, as crafted points from an untrusted source can lead to crypto-attacks. +func NoSubgroupChecks() func(*Decoder) { + return func(dec *Decoder) { + dec.subGroupCheck = false + } +} + +func (enc *Encoder) encode(v interface{}) (err error) { + rv := reflect.ValueOf(v) + if v == nil || (rv.Kind() == reflect.Ptr && rv.IsNil()) { + return errors.New(" encoder: can't encode ") + } + + // implementation note: code is a bit verbose (abusing code generation), but minimize allocations on the heap + + var written int + switch t := v.(type) { + case *fr.Element: + buf := t.Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + return + case *fp.Element: + buf := t.Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + return + case *G1Affine: + buf := t.Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + return + case []fr.Element: + // write slice length + err = binary.Write(enc.w, binary.BigEndian, uint32(len(t))) + if err != nil { + return + } + enc.n += 4 + var buf [fr.Bytes]byte + for i := 0; i < len(t); i++ { + buf = t[i].Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + if err != nil { + return + } + } + return nil + case []fp.Element: + // write slice length + err = binary.Write(enc.w, binary.BigEndian, uint32(len(t))) + if err != nil { + return + } + enc.n += 4 + var buf [fp.Bytes]byte + for i := 0; i < len(t); i++ { + buf = t[i].Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + if err != nil { + return + } + } + return nil + + case []G1Affine: + // write slice length + err = binary.Write(enc.w, binary.BigEndian, uint32(len(t))) + if err != nil { + return + } + enc.n += 4 + + var buf [SizeOfG1AffineCompressed]byte + + for i := 0; i < len(t); i++ { + buf = t[i].Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + if err != nil { + return + } + } + return nil + default: + n := binary.Size(t) + if n == -1 { + return errors.New(" encoder: unsupported type") + } + err = binary.Write(enc.w, binary.BigEndian, t) + enc.n += int64(n) + return + } +} + +func (enc *Encoder) encodeRaw(v interface{}) (err error) { + rv := reflect.ValueOf(v) + if v == nil || (rv.Kind() == reflect.Ptr && rv.IsNil()) { + return errors.New(" encoder: can't encode ") + } + + // implementation note: code is a bit verbose (abusing code generation), but minimize allocations on the heap + + var written int + switch t := v.(type) { + case *fr.Element: + buf := t.Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + return + case *fp.Element: + buf := t.Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + return + case *G1Affine: + buf := t.RawBytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + return + case []fr.Element: + // write slice length + err = binary.Write(enc.w, binary.BigEndian, uint32(len(t))) + if err != nil { + return + } + enc.n += 4 + var buf [fr.Bytes]byte + for i := 0; i < len(t); i++ { + buf = t[i].Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + if err != nil { + return + } + } + return nil + case []fp.Element: + // write slice length + err = binary.Write(enc.w, binary.BigEndian, uint32(len(t))) + if err != nil { + return + } + enc.n += 4 + var buf [fp.Bytes]byte + for i := 0; i < len(t); i++ { + buf = t[i].Bytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + if err != nil { + return + } + } + return nil + + case []G1Affine: + // write slice length + err = binary.Write(enc.w, binary.BigEndian, uint32(len(t))) + if err != nil { + return + } + enc.n += 4 + + var buf [SizeOfG1AffineUncompressed]byte + + for i := 0; i < len(t); i++ { + buf = t[i].RawBytes() + written, err = enc.w.Write(buf[:]) + enc.n += int64(written) + if err != nil { + return + } + } + return nil + default: + n := binary.Size(t) + if n == -1 { + return errors.New(" encoder: unsupported type") + } + err = binary.Write(enc.w, binary.BigEndian, t) + enc.n += int64(n) + return + } +} + +// SizeOfG1AffineCompressed represents the size in bytes that a G1Affine need in binary form, compressed +const SizeOfG1AffineCompressed = 32 + +// SizeOfG1AffineUncompressed represents the size in bytes that a G1Affine need in binary form, uncompressed +const SizeOfG1AffineUncompressed = SizeOfG1AffineCompressed * 2 + +// Marshal converts p to a byte slice (without point compression) +func (p *G1Affine) Marshal() []byte { + b := p.RawBytes() + return b[:] +} + +// Unmarshal is an allias to SetBytes() +func (p *G1Affine) Unmarshal(buf []byte) error { + _, err := p.SetBytes(buf) + return err +} + +// Bytes returns binary representation of p +// will store X coordinate in regular form and a parity bit +// as we have less than 3 bits available in our coordinate, we can't follow BLS12-381 style encoding (ZCash/IETF) +// +// we use the 2 most significant bits instead +// +// 00 -> uncompressed +// 10 -> compressed, use smallest lexicographically square root of Y^2 +// 11 -> compressed, use largest lexicographically square root of Y^2 +// 01 -> compressed infinity point +// the "uncompressed infinity point" will just have 00 (uncompressed) followed by zeroes (infinity = 0,0 in affine coordinates) +func (p *G1Affine) Bytes() (res [SizeOfG1AffineCompressed]byte) { + + // check if p is infinity point + if p.X.IsZero() && p.Y.IsZero() { + res[0] = mCompressedInfinity + return + } + + msbMask := mCompressedSmallest + // compressed, we need to know if Y is lexicographically bigger than -Y + // if p.Y ">" -p.Y + if p.Y.LexicographicallyLargest() { + msbMask = mCompressedLargest + } + + // we store X and mask the most significant word with our metadata mask + fp.BigEndian.PutElement((*[fp.Bytes]byte)(res[0:0+fp.Bytes]), p.X) + + res[0] |= msbMask + + return +} + +// RawBytes returns binary representation of p (stores X and Y coordinate) +// see Bytes() for a compressed representation +func (p *G1Affine) RawBytes() (res [SizeOfG1AffineUncompressed]byte) { + + // check if p is infinity point + if p.X.IsZero() && p.Y.IsZero() { + + res[0] = mUncompressed + + return + } + + // not compressed + // we store the Y coordinate + fp.BigEndian.PutElement((*[fp.Bytes]byte)(res[32:32+fp.Bytes]), p.Y) + + // we store X and mask the most significant word with our metadata mask + fp.BigEndian.PutElement((*[fp.Bytes]byte)(res[0:0+fp.Bytes]), p.X) + + res[0] |= mUncompressed + + return +} + +// SetBytes sets p from binary representation in buf and returns number of consumed bytes +// +// bytes in buf must match either RawBytes() or Bytes() output +// +// if buf is too short io.ErrShortBuffer is returned +// +// if buf contains compressed representation (output from Bytes()) and we're unable to compute +// the Y coordinate (i.e the square root doesn't exist) this function returns an error +// +// this check if the resulting point is on the curve and in the correct subgroup +func (p *G1Affine) SetBytes(buf []byte) (int, error) { + return p.setBytes(buf, true) +} + +func (p *G1Affine) setBytes(buf []byte, subGroupCheck bool) (int, error) { + if len(buf) < SizeOfG1AffineCompressed { + return 0, io.ErrShortBuffer + } + + // most significant byte + mData := buf[0] & mMask + + // check buffer size + if mData == mUncompressed { + if len(buf) < SizeOfG1AffineUncompressed { + return 0, io.ErrShortBuffer + } + } + + // if infinity is encoded in the metadata, we don't need to read the buffer + if mData == mCompressedInfinity { + p.X.SetZero() + p.Y.SetZero() + return SizeOfG1AffineCompressed, nil + } + + // uncompressed point + if mData == mUncompressed { + // read X and Y coordinates + if err := p.X.SetBytesCanonical(buf[:fp.Bytes]); err != nil { + return 0, err + } + if err := p.Y.SetBytesCanonical(buf[fp.Bytes : fp.Bytes*2]); err != nil { + return 0, err + } + + // subgroup check + if subGroupCheck && !p.IsInSubGroup() { + return 0, errors.New("invalid point: subgroup check failed") + } + + return SizeOfG1AffineUncompressed, nil + } + + // we have a compressed coordinate + // we need to + // 1. copy the buffer (to keep this method thread safe) + // 2. we need to solve the curve equation to compute Y + + var bufX [fp.Bytes]byte + copy(bufX[:fp.Bytes], buf[:fp.Bytes]) + bufX[0] &= ^mMask + + // read X coordinate + if err := p.X.SetBytesCanonical(bufX[:fp.Bytes]); err != nil { + return 0, err + } + + var YSquared, Y fp.Element + + // y^2=x^3+x+b + YSquared.Square(&p.X).Mul(&YSquared, &p.X) + YSquared.Add(&YSquared, &p.X). + Add(&YSquared, &bCurveCoeff) + + if Y.Sqrt(&YSquared) == nil { + return 0, errors.New("invalid compressed coordinate: square root doesn't exist") + } + + if Y.LexicographicallyLargest() { + // Y ">" -Y + if mData == mCompressedSmallest { + Y.Neg(&Y) + } + } else { + // Y "<=" -Y + if mData == mCompressedLargest { + Y.Neg(&Y) + } + } + + p.Y = Y + + // subgroup check + if subGroupCheck && !p.IsInSubGroup() { + return 0, errors.New("invalid point: subgroup check failed") + } + + return SizeOfG1AffineCompressed, nil +} + +// unsafeComputeY called by Decoder when processing slices of compressed point in parallel (step 2) +// it computes the Y coordinate from the already set X coordinate and is compute intensive +func (p *G1Affine) unsafeComputeY(subGroupCheck bool) error { + // stored in unsafeSetCompressedBytes + + mData := byte(p.Y[0]) + + // we have a compressed coordinate, we need to solve the curve equation to compute Y + var YSquared, Y fp.Element + + // y^2=x^3+x+b + YSquared.Square(&p.X).Mul(&YSquared, &p.X) + YSquared.Add(&YSquared, &p.X). + Add(&YSquared, &bCurveCoeff) + + if Y.Sqrt(&YSquared) == nil { + return errors.New("invalid compressed coordinate: square root doesn't exist") + } + + if Y.LexicographicallyLargest() { + // Y ">" -Y + if mData == mCompressedSmallest { + Y.Neg(&Y) + } + } else { + // Y "<=" -Y + if mData == mCompressedLargest { + Y.Neg(&Y) + } + } + + p.Y = Y + + // subgroup check + if subGroupCheck && !p.IsInSubGroup() { + return errors.New("invalid point: subgroup check failed") + } + + return nil +} + +// unsafeSetCompressedBytes is called by Decoder when processing slices of compressed point in parallel (step 1) +// assumes buf[:8] mask is set to compressed +// returns true if point is infinity and need no further processing +// it sets X coordinate and uses Y for scratch space to store decompression metadata +func (p *G1Affine) unsafeSetCompressedBytes(buf []byte) (isInfinity bool, err error) { + + // read the most significant byte + mData := buf[0] & mMask + + if mData == mCompressedInfinity { + p.X.SetZero() + p.Y.SetZero() + isInfinity = true + return isInfinity, nil + } + + // we need to copy the input buffer (to keep this method thread safe) + var bufX [fp.Bytes]byte + copy(bufX[:fp.Bytes], buf[:fp.Bytes]) + bufX[0] &= ^mMask + + // read X coordinate + if err := p.X.SetBytesCanonical(bufX[:fp.Bytes]); err != nil { + return false, err + } + // store mData in p.Y[0] + p.Y[0] = uint64(mData) + + // recomputing Y will be done asynchronously + return isInfinity, nil +} diff --git a/ecc/stark-curve/marshal_test.go b/ecc/stark-curve/marshal_test.go new file mode 100644 index 000000000..470f6fc0f --- /dev/null +++ b/ecc/stark-curve/marshal_test.go @@ -0,0 +1,254 @@ +// 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 starkcurve + +import ( + "bytes" + "io" + "math/big" + "math/rand" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" + + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fr" +) + +func TestEncoder(t *testing.T) { + t.Parallel() + // TODO need proper fuzz testing here + + var inA uint64 + var inB fr.Element + var inC fp.Element + var inD G1Affine + var inE G1Affine + var inG []G1Affine + var inI []fp.Element + var inJ []fr.Element + + // set values of inputs + inA = rand.Uint64() + inB.SetRandom() + inC.SetRandom() + inD.ScalarMultiplication(&g1GenAff, new(big.Int).SetUint64(rand.Uint64())) + // inE --> infinity + inG = make([]G1Affine, 2) + inG[1] = inD + inI = make([]fp.Element, 3) + inI[2] = inD.X + inJ = make([]fr.Element, 0) + + // encode them, compressed and raw + var buf, bufRaw bytes.Buffer + enc := NewEncoder(&buf) + encRaw := NewEncoder(&bufRaw, RawEncoding()) + toEncode := []interface{}{inA, &inB, &inC, &inD, &inE, inG, inI, inJ} + for _, v := range toEncode { + if err := enc.Encode(v); err != nil { + t.Fatal(err) + } + if err := encRaw.Encode(v); err != nil { + t.Fatal(err) + } + } + + testDecode := func(t *testing.T, r io.Reader, n int64) { + dec := NewDecoder(r) + var outA uint64 + var outB fr.Element + var outC fp.Element + var outD G1Affine + var outE G1Affine + outE.X.SetOne() + outE.Y.SetUint64(42) + var outG []G1Affine + var outI []fp.Element + var outJ []fr.Element + + toDecode := []interface{}{&outA, &outB, &outC, &outD, &outE, &outG, &outI, &outJ} + for _, v := range toDecode { + if err := dec.Decode(v); err != nil { + t.Fatal(err) + } + } + + // compare values + if inA != outA { + t.Fatal("didn't encode/decode uint64 value properly") + } + + if !inB.Equal(&outB) || !inC.Equal(&outC) { + t.Fatal("decode(encode(Element) failed") + } + if !inD.Equal(&outD) || !inE.Equal(&outE) { + t.Fatal("decode(encode(G1Affine) failed") + } + for i := 0; i < len(inG); i++ { + if !inG[i].Equal(&outG[i]) { + t.Fatal("decode(encode(slice(points))) failed") + } + } + if (len(inI) != len(outI)) || (len(inJ) != len(outJ)) { + t.Fatal("decode(encode(slice(elements))) failed") + } + for i := 0; i < len(inI); i++ { + if !inI[i].Equal(&outI[i]) { + t.Fatal("decode(encode(slice(elements))) failed") + } + } + if n != dec.BytesRead() { + t.Fatal("bytes read don't match bytes written") + } + } + + // decode them + testDecode(t, &buf, enc.BytesWritten()) + testDecode(t, &bufRaw, encRaw.BytesWritten()) + +} + +func TestIsCompressed(t *testing.T) { + t.Parallel() + var g1Inf, g1 G1Affine + + g1 = g1GenAff + + { + b := g1Inf.Bytes() + if !isCompressed(b[0]) { + t.Fatal("g1Inf.Bytes() should be compressed") + } + } + + { + b := g1Inf.RawBytes() + if isCompressed(b[0]) { + t.Fatal("g1Inf.RawBytes() should be uncompressed") + } + } + + { + b := g1.Bytes() + if !isCompressed(b[0]) { + t.Fatal("g1.Bytes() should be compressed") + } + } + + { + b := g1.RawBytes() + if isCompressed(b[0]) { + t.Fatal("g1.RawBytes() should be uncompressed") + } + } + +} + +func TestG1AffineSerialization(t *testing.T) { + t.Parallel() + // test round trip serialization of infinity + { + // compressed + { + var p1, p2 G1Affine + p2.X.SetRandom() + p2.Y.SetRandom() + buf := p1.Bytes() + n, err := p2.SetBytes(buf[:]) + if err != nil { + t.Fatal(err) + } + if n != SizeOfG1AffineCompressed { + t.Fatal("invalid number of bytes consumed in buffer") + } + if !(p2.X.IsZero() && p2.Y.IsZero()) { + t.Fatal("deserialization of uncompressed infinity point is not infinity") + } + } + + // uncompressed + { + var p1, p2 G1Affine + p2.X.SetRandom() + p2.Y.SetRandom() + buf := p1.RawBytes() + n, err := p2.SetBytes(buf[:]) + if err != nil { + t.Fatal(err) + } + if n != SizeOfG1AffineUncompressed { + t.Fatal("invalid number of bytes consumed in buffer") + } + if !(p2.X.IsZero() && p2.Y.IsZero()) { + t.Fatal("deserialization of uncompressed infinity point is not infinity") + } + } + } + + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + properties.Property("[G1] Affine SetBytes(RawBytes) should stay the same", prop.ForAll( + func(a fp.Element) bool { + var start, end G1Affine + var ab big.Int + a.BigInt(&ab) + start.ScalarMultiplication(&g1GenAff, &ab) + + buf := start.RawBytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != SizeOfG1AffineUncompressed { + return false + } + return start.X.Equal(&end.X) && start.Y.Equal(&end.Y) + }, + GenFp(), + )) + + properties.Property("[G1] Affine SetBytes(Bytes()) should stay the same", prop.ForAll( + func(a fp.Element) bool { + var start, end G1Affine + var ab big.Int + a.BigInt(&ab) + start.ScalarMultiplication(&g1GenAff, &ab) + + buf := start.Bytes() + n, err := end.SetBytes(buf[:]) + if err != nil { + return false + } + if n != SizeOfG1AffineCompressed { + return false + } + return start.X.Equal(&end.X) && start.Y.Equal(&end.Y) + }, + GenFp(), + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/internal/generator/config/stark-curve.go b/internal/generator/config/stark-curve.go index 8af4ef48c..34c267d8b 100644 --- a/internal/generator/config/stark-curve.go +++ b/internal/generator/config/stark-curve.go @@ -2,7 +2,7 @@ package config var STARK_CURVE = Curve{ Name: "stark-curve", - CurvePackage: "stark-curve", + CurvePackage: "starkcurve", EnumID: "STARK_CURVE", FrModulus: "3618502788666131213697322783095070105526743751716087489154079457884512865583", FpModulus: "3618502788666131213697322783095070105623107215331596699973092056135872020481", diff --git a/internal/generator/ecc/template/point.go.tmpl b/internal/generator/ecc/template/point.go.tmpl index a3adbdecb..59bf390c8 100644 --- a/internal/generator/ecc/template/point.go.tmpl +++ b/internal/generator/ecc/template/point.go.tmpl @@ -86,6 +86,15 @@ func (p *{{ $TJacobian }}) ScalarMultiplicationAffine(a *{{ $TAffine }}, s *big. {{- end }} return p } + +// ScalarMultiplicationBase computes and returns p = g ⋅ s where g is the prime subgroup generator +func (p *{{ $TAffine }}) ScalarMultiplicationBase(s *big.Int) *{{ $TAffine }} { + var _p G1Jac + _p.mulGLV(&g1Gen, s) + p.FromJacobian(&_p) + return p +} + {{- end}} // Add adds two point in affine coordinates. @@ -1123,7 +1132,78 @@ func (p *{{$TJacobian}}) ClearCofactor(a *{{$TJacobian}}) *{{$TJacobian}} { {{ end }} +{{ if eq .PointName "g1" }} +// JointScalarMultiplicationBase computes [s1]g+[s2]a using Straus-Shamir technique +// where g is the prime subgroup generator +func (p *{{$TJacobian}}) JointScalarMultiplicationBase(a *G1Affine, s1, s2 *big.Int) *{{$TJacobian}} { + + var res, p1, p2 {{$TJacobian}} + res.Set(&{{ toLower .PointName }}Infinity) + p1.Set(&g1Gen) + p2.FromAffine(a) + + var table [15]{{$TJacobian}} + + var k1, k2 big.Int + if s1.Sign() == -1 { + k1.Neg(s1) + table[0].Neg(&p1) + } else { + k1.Set(s1) + table[0].Set(&p1) + } + if s2.Sign() == -1 { + k2.Neg(s2) + table[3].Neg(&p2) + } else { + k2.Set(s2) + table[3].Set(&p2) + } + + // precompute table (2 bits sliding window) + table[1].Double(&table[0]) + table[2].Set(&table[1]).AddAssign(&table[0]) + table[4].Set(&table[3]).AddAssign(&table[0]) + table[5].Set(&table[3]).AddAssign(&table[1]) + table[6].Set(&table[3]).AddAssign(&table[2]) + table[7].Double(&table[3]) + table[8].Set(&table[7]).AddAssign(&table[0]) + table[9].Set(&table[7]).AddAssign(&table[1]) + table[10].Set(&table[7]).AddAssign(&table[2]) + table[11].Set(&table[7]).AddAssign(&table[3]) + table[12].Set(&table[11]).AddAssign(&table[0]) + table[13].Set(&table[11]).AddAssign(&table[1]) + table[14].Set(&table[11]).AddAssign(&table[2]) + + var s [2]fr.Element + s[0] = s[0].SetBigInt(&k1).Bits() + s[1] = s[1].SetBigInt(&k2).Bits() + maxBit := k1.BitLen() + if k2.BitLen() > maxBit { + maxBit = k2.BitLen() + } + hiWordIndex := (maxBit - 1) / 64 + + for i := hiWordIndex; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.Double(&res).Double(&res) + b1 := (s[0][i] & mask) >> (62 - 2*j) + b2 := (s[1][i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.AddAssign(&table[s-1]) + } + mask = mask >> 2 + } + } + + p.Set(&res) + return p + +} +{{ end }} // ------------------------------------------------------------------------------------------------- diff --git a/internal/generator/ecc/template/tests/point.go.tmpl b/internal/generator/ecc/template/tests/point.go.tmpl index 26fdad433..23b67956c 100644 --- a/internal/generator/ecc/template/tests/point.go.tmpl +++ b/internal/generator/ecc/template/tests/point.go.tmpl @@ -439,8 +439,30 @@ func Test{{ $TAffine }}Ops(t *testing.T) { }, genScalar, )) + + {{end}} + {{- if eq .PointName "g1" }} + properties.Property("[{{ toUpper .Name }}] JointScalarMultiplicationBase and ScalarMultiplication should output the same results", prop.ForAll( + func(s1, s2 fr.Element) bool { + + var op1, op2, temp {{ $TJacobian }} + + op1.JointScalarMultiplicationBase(&g1GenAff, s1.BigInt(new(big.Int)), s2.BigInt(new(big.Int))) + temp.ScalarMultiplication(&g1Gen, s2.BigInt(new(big.Int))) + op2.ScalarMultiplication(&g1Gen, s1.BigInt(new(big.Int))). + AddAssign(&temp) + + return op1.Equal(&op2) + + }, + genScalar, + genScalar, + )) + + + {{- end }} properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/internal/generator/ecdsa/generate.go b/internal/generator/ecdsa/generate.go new file mode 100644 index 000000000..00076625e --- /dev/null +++ b/internal/generator/ecdsa/generate.go @@ -0,0 +1,24 @@ +package ecdsa + +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 { + // ecdsa + conf.Package = "ecdsa" + baseDir = filepath.Join(baseDir, conf.Package) + + entries := []bavard.Entry{ + {File: filepath.Join(baseDir, "doc.go"), Templates: []string{"doc.go.tmpl"}}, + {File: filepath.Join(baseDir, "ecdsa.go"), Templates: []string{"ecdsa.go.tmpl"}}, + {File: filepath.Join(baseDir, "ecdsa_test.go"), Templates: []string{"ecdsa.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, "./ecdsa/template", entries...) + +} diff --git a/internal/generator/ecdsa/template/doc.go.tmpl b/internal/generator/ecdsa/template/doc.go.tmpl new file mode 100644 index 000000000..19acc324b --- /dev/null +++ b/internal/generator/ecdsa/template/doc.go.tmpl @@ -0,0 +1,13 @@ +// Package {{.Package}} provides ECDSA signature scheme on the {{.Name}} curve. +// +// The implementation is adapted from https://pkg.go.dev/crypto/ecdsa. +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Documentation: +// - Wikipedia: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm +// - FIPS 186-4: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +// - SEC 1, v-2: https://www.secg.org/sec1-v2.pdf +// +package {{.Package}} diff --git a/internal/generator/ecdsa/template/ecdsa.go.tmpl b/internal/generator/ecdsa/template/ecdsa.go.tmpl new file mode 100644 index 000000000..724715eb8 --- /dev/null +++ b/internal/generator/ecdsa/template/ecdsa.go.tmpl @@ -0,0 +1,287 @@ +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 errInvalidSig = errors.New("invalid signature") + +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 ECDSA public key +type PublicKey struct { + A {{ .CurvePackage }}.G1Affine +} + +// PrivateKey represents an ECDSA private key +type PrivateKey struct { + PublicKey PublicKey + scalar [sizeFr]byte // secret scalar, in big Endian +} + +// Signature represents an ECDSA 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 ECDSA signature +// +// k ← 𝔽r (random) +// P = k ⋅ g1Gen +// r = x_P (mod order) +// s = k⁻¹ . (m + sk ⋅ r) +// signature = {r, s} +// +// SEC 1, Version 2.0, Section 4.1.3 +func (privKey *PrivateKey) Sign(message []byte, hFunc hash.Hash) ([]byte, error) { + scalar, r, s, kInv := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + scalar.SetBytes(privKey.scalar[:sizeFr]) + for { + for { + 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) + kInv.ModInverse(k, order) + + P.X.BigInt(r) + r.Mod(r, order) + if r.Sign() != 0 { + break + } + } + s.Mul(r, scalar) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return nil, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + s.Add(m, s). + Mul(kInv, s). + Mod(s, order) // order != 0 + if s.Sign() != 0 { + break + } + } + + var sig Signature + r.FillBytes(sig.R[:sizeFr]) + s.FillBytes(sig.S[:sizeFr]) + + return sig.Bytes(), nil +} + +// Verify validates the ECDSA signature +// +// R ?= (s⁻¹ ⋅ m ⋅ Base + s⁻¹ ⋅ R ⋅ publiKey)_x +// +// SEC 1, Version 2.0, Section 4.1.4 +func (publicKey *PublicKey) Verify(sigBin, message []byte, hFunc hash.Hash) (bool, error) { + + // Deserialize the signature + var sig Signature + if _, err := sig.SetBytes(sigBin); err != nil { + return false, err + } + + r, s := new(big.Int), new(big.Int) + r.SetBytes(sig.R[:sizeFr]) + s.SetBytes(sig.S[:sizeFr]) + + sInv := new(big.Int).ModInverse(s, order) + + // compute the hash of the message as an integer + dataToHash := make([]byte, len(message)) + copy(dataToHash[:], message[:]) + hFunc.Reset() + _, err := hFunc.Write(dataToHash[:]) + if err != nil { + return false, err + } + hramBin := hFunc.Sum(nil) + m := HashToInt(hramBin) + + u1 := new(big.Int).Mul(m, sInv) + u1.Mod(u1, order) + u2 := new(big.Int).Mul(r, sInv) + u2.Mod(u2, order) + var U {{ .CurvePackage }}.G1Jac + U.JointScalarMultiplicationBase(&publicKey.A, u1, u2) + + var z big.Int + U.Z.Square(&U.Z). + Inverse(&U.Z). + Mul(&U.Z, &U.X). + BigInt(&z) + + z.Mod(&z, order) + + return z.Cmp(r) == 0, nil + +} diff --git a/internal/generator/ecdsa/template/ecdsa.test.go.tmpl b/internal/generator/ecdsa/template/ecdsa.test.go.tmpl new file mode 100644 index 000000000..bc91596d1 --- /dev/null +++ b/internal/generator/ecdsa/template/ecdsa.test.go.tmpl @@ -0,0 +1,60 @@ +import ( + "crypto/rand" + "crypto/sha512" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestECDSA(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 ECDSA") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + flag, _ := publicKey.Verify(sig, msg, md) + + return flag + }, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +// ------------------------------------------------------------ +// benches + +func BenchmarkSignECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.Sign(msg, md) + } +} + +func BenchmarkVerifyECDSA(b *testing.B) { + + privKey, _ := GenerateKey(rand.Reader) + msg := []byte("benchmarking ECDSA sign()") + md := sha512.New() + sig, _ := privKey.Sign(msg, md) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + privKey.PublicKey.Verify(sig, msg, md) + } +} diff --git a/internal/generator/ecdsa/template/marshal.go.tmpl b/internal/generator/ecdsa/template/marshal.go.tmpl new file mode 100644 index 000000000..2e76fe21e --- /dev/null +++ b/internal/generator/ecdsa/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/ecdsa/template/marshal.test.go.tmpl b/internal/generator/ecdsa/template/marshal.test.go.tmpl new file mode 100644 index 000000000..5fc66a474 --- /dev/null +++ b/internal/generator/ecdsa/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 }}] ECDSA 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/main.go b/internal/generator/main.go index f778063c4..23f7b8ba6 100644 --- a/internal/generator/main.go +++ b/internal/generator/main.go @@ -13,6 +13,7 @@ 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" @@ -67,6 +68,9 @@ func main() { ElementType: "fr.Element", } + // generate ecdsa + assertNoError(ecdsa.Generate(conf, curveDir, bgen)) + if conf.Equal(config.STARK_CURVE) { return // TODO @yelhousni } diff --git a/signature/ecdsa/ecdsa.go b/signature/ecdsa/ecdsa.go new file mode 100644 index 000000000..68daac09d --- /dev/null +++ b/signature/ecdsa/ecdsa.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 ecdsa + +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" + "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 ecdsa_bn254.GenerateKey(r) + case ecc.BLS12_381: + return ecdsa_bls12381.GenerateKey(r) + case ecc.BLS12_377: + return ecdsa_bls12377.GenerateKey(r) + case ecc.BLS12_378: + return ecdsa_bls12378.GenerateKey(r) + case ecc.BW6_761: + return ecdsa_bw6761.GenerateKey(r) + case ecc.BW6_756: + return ecdsa_bw6756.GenerateKey(r) + case ecc.BLS24_315: + return ecdsa_bls24315.GenerateKey(r) + case ecc.BLS24_317: + return ecdsa_bls24317.GenerateKey(r) + case ecc.BW6_633: + return ecdsa_bw6633.GenerateKey(r) + case ecc.SECP256K1: + return ecdsa_secp256k1.GenerateKey(r) + case ecc.STARK_CURVE: + return ecdsa_starkcurve.GenerateKey(r) + default: + panic("not implemented") + } +}