diff --git a/.github/Makefile b/.github/Makefile index facf687..77e5c57 100644 --- a/.github/Makefile +++ b/.github/Makefile @@ -40,11 +40,6 @@ test: @echo "Running all tests ..." @go test -v -vet=all ../... -# .PHONY: vectors -# vectors: -# @echo "Testing vectors ..." -# @go test -v ../tests/... - .PHONY: cover cover: @echo "Testing with coverage ..." diff --git a/.github/licence-header.tmpl b/.github/licence-header.tmpl index 8eca910..cb152e4 100644 --- a/.github/licence-header.tmpl +++ b/.github/licence-header.tmpl @@ -1,6 +1,6 @@ SPDX-License-Identifier: MIT -Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree or at diff --git a/LICENSE b/LICENSE index 36d31c2..f17d27d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Bytemare +Copyright (c) 2020-2023 Bytemare Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/element.go b/element.go index bae2026..f7d93f8 100644 --- a/element.go +++ b/element.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/groups.go b/groups.go index 085c75f..ad14ec7 100644 --- a/groups.go +++ b/groups.go @@ -1,6 +1,6 @@ // SPDX-License-Group: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -21,6 +21,7 @@ import ( "github.com/bytemare/crypto/internal/edwards25519" "github.com/bytemare/crypto/internal/nist" "github.com/bytemare/crypto/internal/ristretto" + "github.com/bytemare/crypto/internal/secp256k1" ) // Group identifies prime-order groups over elliptic curves with hash-to-group operations. @@ -45,6 +46,9 @@ const ( // Edwards25519Sha512 identifies the Edwards25519 group with SHA2-512 hash-to-group hashing. Edwards25519Sha512 + // Secp256k1 identifies the Secp256k1 group with SHA2-256 hash-to-group hashing. + Secp256k1 + maxID dstfmt = "%s-V%02d-CS%02d-%s" @@ -163,6 +167,8 @@ func (g Group) init() { g.initGroup(nist.P521) case Edwards25519Sha512: g.initGroup(edwards25519.New) + case Secp256k1: + g.initGroup(secp256k1.New) case maxID: fallthrough default: diff --git a/internal/edwards25519/element.go b/internal/edwards25519/element.go index 2f9f70e..0ae8b29 100644 --- a/internal/edwards25519/element.go +++ b/internal/edwards25519/element.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/edwards25519/group.go b/internal/edwards25519/group.go index 4ba9cf3..9a4a27d 100644 --- a/internal/edwards25519/group.go +++ b/internal/edwards25519/group.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/edwards25519/map.go b/internal/edwards25519/map.go index 7f14be0..0fa4e27 100644 --- a/internal/edwards25519/map.go +++ b/internal/edwards25519/map.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/edwards25519/scalar.go b/internal/edwards25519/scalar.go index 230475d..0ea984a 100644 --- a/internal/edwards25519/scalar.go +++ b/internal/edwards25519/scalar.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/element.go b/internal/element.go index 917d764..b020282 100644 --- a/internal/element.go +++ b/internal/element.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/field/field.go b/internal/field/field.go new file mode 100644 index 0000000..d90cc1c --- /dev/null +++ b/internal/field/field.go @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +// Package field provides modular operations over very high integers. +package field + +import ( + "crypto/rand" + "fmt" + "math/big" +) + +var ( + zero = big.NewInt(0) + one = big.NewInt(1) +) + +// String2Int returns a big.Int representation of the integer s. +func String2Int(s string) big.Int { + if p, _ := new(big.Int).SetString(s, 0); p != nil { + return *p + } + + panic("invalid string to convert") +} + +// Field represents a Gaulois Field. +type Field struct { + order big.Int + pMinus1div2 big.Int // used in IsSquare + pMinus2 big.Int // used for Field big.Int inversion + exp big.Int +} + +// NewField returns a newly instantiated field for the given prime order. +func NewField(prime *big.Int) Field { + // pMinus1div2 is used to determine whether a big Int is a quadratic square. + pMinus1div2 := big.NewInt(1) + pMinus1div2.Sub(prime, pMinus1div2) + pMinus1div2.Rsh(pMinus1div2, 1) + + // pMinus2 is used for modular inversion. + pMinus2 := big.NewInt(2) + pMinus2.Sub(prime, pMinus2) + + // precompute e = (p + 1) / 4 + exp := big.NewInt(1) + exp.Add(prime, exp) + exp.Rsh(exp, 2) + + return Field{ + order: *prime, + pMinus1div2: *pMinus1div2, + pMinus2: *pMinus2, + exp: *exp, + } +} + +// Zero returns the zero big.Int of the finite Field. +func (f Field) Zero() *big.Int { + return zero +} + +// One returns one big.Int of the finite Field. +func (f Field) One() *big.Int { + return one +} + +// Random sets res to a random big.Int in the Field. +func (f Field) Random(res *big.Int) *big.Int { + tmp, err := rand.Int(rand.Reader, &f.order) + if err != nil { + // We can as well not panic and try again in a loop + panic(fmt.Errorf("unexpected error in generating random bytes : %w", err)) + } + + res.Set(tmp) + + return res +} + +// Order returns the size of the Field. +func (f Field) Order() *big.Int { + return &f.order +} + +// BitLen of the order. +func (f Field) BitLen() int { + return f.order.BitLen() +} + +// AreEqual returns whether both elements are equal. +func (f Field) AreEqual(f1, f2 *big.Int) bool { + return f.IsZero(f.Sub(&big.Int{}, f1, f2)) +} + +// IsZero returns whether the big.Int is equivalent to zero. +func (f Field) IsZero(e *big.Int) bool { + return e.Sign() == 0 +} + +// Inv sets res to the modular inverse of x mod field order. +func (f Field) Inv(res, x *big.Int) { + f.Exponent(res, x, &f.pMinus2) +} + +// LegendreSymbol applies the Legendre symbole on (a/p) and returns either {-1, 0, 1} mod field order. +func (f Field) LegendreSymbol(a *big.Int) *big.Int { + var res big.Int + return f.Exponent(&res, a, &f.pMinus1div2) +} + +// Exponent returns x^n mod field order. +func (f Field) Exponent(res, x, n *big.Int) *big.Int { + return res.Exp(x, n, &f.order) +} + +// IsSquare returns whether e is a quadratic square. +func (f Field) IsSquare(e *big.Int) bool { + return f.AreEqual(f.LegendreSymbol(e), f.One()) +} + +// IsEqual returns whether the two fields have the same order. +func (f Field) IsEqual(f2 *Field) bool { + return f.order.Cmp(&f2.order) == 0 +} + +// Mod reduces x modulo the field order. +func (f Field) Mod(x *big.Int) *big.Int { + return x.Mod(x, &f.order) +} + +// Neg sets res to the -x modulo the field order. +func (f Field) Neg(res, x *big.Int) *big.Int { + return f.Mod(res.Neg(x)) +} + +// CondNeg sets res to -x if cond == 1. +func (f Field) CondNeg(res, x *big.Int, cond int) { + var neg, cpy big.Int + cpy.Set(x) + f.Neg(&neg, x) + + if cond == 1 { + res.Set(&neg) + } else { + res.Set(&cpy) + } +} + +// Add sets res to x + y modulo the field order. +func (f Field) Add(res, x, y *big.Int) { + f.Mod(res.Add(x, y)) +} + +// Sub sets res to x - y modulo the field order. +func (f Field) Sub(res, x, y *big.Int) *big.Int { + return f.Mod(res.Sub(x, y)) +} + +// Lsh sets res to the left shift of n bits on x modulo the field order. +func (f Field) Lsh(res, x *big.Int, n uint) { + f.Mod(res.Lsh(x, n)) +} + +// Mul sets res to the multiplication of x and y modulo the field order. +func (f Field) Mul(res, x, y *big.Int) { + f.Mod(res.Mul(x, y)) +} + +// Square sets res to the square of x modulo the field order. +func (f Field) Square(res, x *big.Int) { + f.Mod(res.Mul(x, x)) +} + +// CondMov sets res to y if b true, and to x otherwise. +func (f Field) CondMov(res, x, y *big.Int, b bool) { + if b { + res.Set(y) + } else { + res.Set(x) + } +} + +// Sgn0 returns the first bit in the big-endian representation. +func (f Field) Sgn0(x *big.Int) int { + return int(x.Bit(0)) +} + +func (f Field) sqrt3mod4(res, e *big.Int) *big.Int { + return f.Exponent(res, e, &f.exp) +} + +// SquareRoot sets res to a square root of e mod the field's order, if such a square root exists. +func (f Field) SquareRoot(res, e *big.Int) *big.Int { + return f.sqrt3mod4(res, e) +} + +// SqrtRatio res result to the square root of (e/v), and indicates whether (e/v) is a square. +func (f Field) SqrtRatio(res, zMapConstant, e, v *big.Int) bool { + f.Inv(res, v) + f.Mul(res, res, e) + + square := f.IsSquare(res) + if !square { + f.Mul(res, res, zMapConstant) + } + + f.SquareRoot(res, res) + + return square +} diff --git a/internal/group.go b/internal/group.go index 4851746..ee42ccb 100644 --- a/internal/group.go +++ b/internal/group.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/h2c/sswu.go b/internal/h2c/sswu.go new file mode 100644 index 0000000..d656904 --- /dev/null +++ b/internal/h2c/sswu.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C)2020-2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +// Package h2c provides hash-to-curve primitives and mapping. +package h2c + +import ( + "math/big" + + "github.com/bytemare/crypto/internal/field" +) + +// MapToCurveSSWU implements the Simplified SWU method for Weierstrass curves for any base field. +func MapToCurveSSWU(f *field.Field, a, b, z, fe *big.Int) (x, y *big.Int) { + var tv1, tv2, tv3, tv4, tv5, tv6, _y1, _px, _py big.Int + + f.Square(&tv1, fe) // 1. tv1 = u^2 + f.Mul(&tv1, z, &tv1) // 2. tv1 = Z * tv1 + f.Square(&tv2, &tv1) // 3. tv2 = tv1^2 + f.Add(&tv2, &tv2, &tv1) // 4. tv2 = tv2 + tv1 + f.Add(&tv3, &tv2, f.One()) // 5. tv3 = tv2 + 1 + f.Mul(&tv3, b, &tv3) // 6. tv3 = B * tv3 + f.CondMov(&tv4, z, + f.Neg(&big.Int{}, &tv2), + !f.IsZero(&tv2)) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) + f.Mul(&tv4, a, &tv4) // 8. tv4 = A * tv4 + f.Square(&tv2, &tv3) // 9. tv2 = tv3^2 + f.Square(&tv6, &tv4) // 10. tv6 = tv4^2 + f.Mul(&tv5, a, &tv6) // 11. tv5 = A * tv6 + f.Add(&tv2, &tv2, &tv5) // 12. tv2 = tv2 + tv5 + f.Mul(&tv2, &tv2, &tv3) // 13. tv2 = tv2 * tv3 + f.Mul(&tv6, &tv6, &tv4) // 14. tv6 = tv6 * tv4 + f.Mul(&tv5, b, &tv6) // 15. tv5 = B * tv6 + f.Add(&tv2, &tv2, &tv5) // 16. tv2 = tv2 + tv5 + f.Mul(&_px, &tv1, &tv3) // 17. x = tv1 * tv3 + isGx1Square := f.SqrtRatio(&_y1, z, &tv2, &tv6) // 18. isGx1Square, y1 = sqrt_ratio(tv2, tv6) + f.Mul(&_py, &tv1, fe) // 19. y = tv1 * u + f.Mul(&_py, &_py, &_y1) // 20. y = y * y1 + f.CondMov(&_px, &_px, &tv3, isGx1Square) // 21. x = CMOV(x, tv3, isGx1Square) + f.CondMov(&_py, &_py, &_y1, isGx1Square) // 22. y = CMOV(y, y1, isGx1Square) + e1 := f.Sgn0(fe) == f.Sgn0(&_py) // 23. e1 = sgn0(u) == sgn0(y) + f.CondMov(&_py, f.Neg(&big.Int{}, &_py), &_py, e1) // 24. y = CMOV(-y, y, e1) + f.Inv(&tv4, &tv4) // 25. 1 / tv4 + f.Mul(&_px, &_px, &tv4) // 26. x = x / tv4 + + return &_px, &_py +} diff --git a/internal/misc.go b/internal/misc.go index 3d0ad0d..ab8d179 100644 --- a/internal/misc.go +++ b/internal/misc.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/nist/curve.go b/internal/nist/curve.go index 9a34eac..45ee8c1 100644 --- a/internal/nist/curve.go +++ b/internal/nist/curve.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -13,25 +13,20 @@ import ( "math/big" "github.com/bytemare/hash2curve" -) - -func s2int(s string) *big.Int { - if p, _ := new(big.Int).SetString(s, 0); p != nil { - return p - } - panic("invalid string to convert") -} + "github.com/bytemare/crypto/internal/field" + "github.com/bytemare/crypto/internal/h2c" +) type mapping struct { - z *big.Int + z big.Int hash crypto.Hash secLength int } type curve[point nistECPoint[point]] struct { - field *field - a, b *big.Int + field field.Field + b big.Int NewPoint func() point mapping } @@ -39,82 +34,33 @@ type curve[point nistECPoint[point]] struct { func (c *curve[point]) setMapping(hash crypto.Hash, z string, secLength int) { c.mapping.hash = hash c.mapping.secLength = secLength - c.mapping.z = s2int(z) + c.mapping.z = field.String2Int(z) } func (c *curve[point]) setCurveParams(prime *big.Int, b string, newPoint func() point) { - c.field = newField(prime) - c.a = s2int("-3") - c.b = s2int(b) + c.field = field.NewField(prime) + c.b = field.String2Int(b) c.NewPoint = newPoint } func (c *curve[point]) encodeXMD(input, dst []byte) point { - u := hash2curve.HashToFieldXMD(c.hash, input, dst, 1, 1, c.secLength, c.field.prime) + u := hash2curve.HashToFieldXMD(c.hash, input, dst, 1, 1, c.secLength, c.field.Order()) q := c.map2curve(u[0]) // We can save cofactor clearing because it is 1. return q } func (c *curve[point]) hashXMD(input, dst []byte) point { - u := hash2curve.HashToFieldXMD(c.hash, input, dst, 2, 1, c.secLength, c.field.prime) + u := hash2curve.HashToFieldXMD(c.hash, input, dst, 2, 1, c.secLength, c.field.Order()) q0 := c.map2curve(u[0]) q1 := c.map2curve(u[1]) // We can save cofactor clearing because it is 1. return q0.Add(q0, q1) } -func (c *curve[point]) sqrtRatio(e, v *big.Int) (bool, *big.Int) { - var result big.Int - field := c.field - field.inv(&result, v) - field.mul(&result, &result, e) - - if field.isSquare(&result) { - return true, field.sqrt(&result, &result) - } - - field.mul(&result, &result, c.z) - - return false, field.sqrt(&result, &result) -} - -// map2curve implements the Simplified SWU method. -func (c *curve[point]) map2curve(input *big.Int) point { - var tv1, tv2, tv3, tv4, tv5, tv6, _px, _py big.Int - - c.field.square(&tv1, input) // 1. tv1 = u^2 - c.field.mul(&tv1, c.z, &tv1) // 2. tv1 = Z * tv1 - c.field.square(&tv2, &tv1) // 3. tv2 = tv1^2 - c.field.add(&tv2, &tv2, &tv1) // 4. tv2 = tv2 + tv1 - c.field.add(&tv3, &tv2, one) // 5. tv3 = tv2 + 1 - c.field.mul(&tv3, c.b, &tv3) // 6. tv3 = B * tv3 - c.field.cmov(&tv4, c.z, - c.field.neg(&big.Int{}, &tv2), - !c.field.isZero(&tv2)) // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) - c.field.mul(&tv4, c.a, &tv4) // 8. tv4 = A * tv4 - c.field.square(&tv2, &tv3) // 9. tv2 = tv3^2 - c.field.square(&tv6, &tv4) // 10. tv6 = tv4^2 - c.field.mul(&tv5, c.a, &tv6) // 11. tv5 = A * tv6 - c.field.add(&tv2, &tv2, &tv5) // 12. tv2 = tv2 + tv5 - c.field.mul(&tv2, &tv2, &tv3) // 13. tv2 = tv2 * tv3 - c.field.mul(&tv6, &tv6, &tv4) // 14. tv6 = tv6 * tv4 - c.field.mul(&tv5, c.b, &tv6) // 15. tv5 = B * tv6 - c.field.add(&tv2, &tv2, &tv5) // 16. tv2 = tv2 + tv5 - c.field.mul(&_px, &tv1, &tv3) // 17. x = tv1 * tv3 - - isGx1Square, y1 := c.sqrtRatio(&tv2, &tv6) // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) - - c.field.mul(&_py, &tv1, input) // 19. y = tv1 * u - c.field.mul(&_py, &_py, y1) // 20. y = y * y1 - c.field.cmov(&_px, &_px, &tv3, isGx1Square) // 21. x = CMOV(x, tv3, is_gx1_square) - c.field.cmov(&_py, &_py, y1, isGx1Square) // 22. y = CMOV(y, y1, is_gx1_square) - e1 := c.field.sgn0(input) == c.field.sgn0(&_py) // 23. e1 = sgn0(u) == sgn0(y) - c.field.cmov(&_py, c.field.neg(&big.Int{}, &_py), &_py, e1) // 24. y = CMOV(-y, y, e1) - c.field.inv(&tv4, &tv4) // 25. x = x / tv4 - c.field.mul(&_px, &_px, &tv4) - - return c.affineToPoint(&_px, &_py) +func (c *curve[point]) map2curve(fe *big.Int) point { + x, y := h2c.MapToCurveSSWU(&c.field, &nistWa, &c.b, &c.z, fe) + return c.affineToPoint(x, y) } var ( @@ -126,7 +72,7 @@ var ( func (c *curve[point]) affineToPoint(pxc, pyc *big.Int) point { var decompressed []byte - byteLen := (c.field.bitLen() + 7) / 8 + byteLen := (c.field.BitLen() + 7) / 8 switch byteLen { case 32: decompressed = decompressed256[:] diff --git a/internal/nist/element.go b/internal/nist/element.go index dea991a..489d521 100644 --- a/internal/nist/element.go +++ b/internal/nist/element.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/nist/field.go b/internal/nist/field.go deleted file mode 100644 index 62bb979..0000000 --- a/internal/nist/field.go +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree or at -// https://spdx.org/licenses/MIT.html - -package nist - -import ( - "crypto/rand" - "fmt" - "math/big" -) - -var ( - zero = big.NewInt(0) - one = big.NewInt(1) -) - -type field struct { - prime *big.Int - pMinus1div2 *big.Int // used in isSquare - pMinus2 *big.Int // used for field big.Int inversion - exp *big.Int -} - -func newField(prime *big.Int) *field { - // pMinus1div2 is used to determine whether a big Int is a quadratic square. - pMinus1div2 := big.NewInt(1) - pMinus1div2.Sub(prime, pMinus1div2) - pMinus1div2.Rsh(pMinus1div2, 1) - - // pMinus2 is used for modular inversion. - pMinus2 := big.NewInt(2) - pMinus2.Sub(prime, pMinus2) - - // precompute e = (p + 1) / 4 - exp := big.NewInt(1) - exp.Add(prime, exp) - exp.Rsh(exp, 2) - - return &field{ - prime: prime, - pMinus1div2: pMinus1div2, - pMinus2: pMinus2, - exp: exp, - } -} - -// one sets res to the one big.Int of the finite field. -func (f field) one(res *big.Int) *big.Int { - return res.Set(one) -} - -// random sets res to a random field big.Int. -func (f field) random(res *big.Int) *big.Int { - tmp, err := rand.Int(rand.Reader, f.prime) - if err != nil { - // We can as well not panic and try again in a loop - panic(fmt.Errorf("unexpected error in generating random bytes : %w", err)) - } - - res.Set(tmp) - - return res -} - -// order returns the size of the field. -func (f field) order() *big.Int { - return f.prime -} - -// bitLen of prime order. -func (f field) bitLen() int { - return f.prime.BitLen() -} - -// areEqual returns whether both elements are equal. -func (f field) areEqual(f1, f2 *big.Int) bool { - return f.isZero(f.sub(&big.Int{}, f1, f2)) -} - -// isZero returns whether the big.Int is equivalent to zero. -func (f field) isZero(e *big.Int) bool { - return e.Sign() == 0 -} - -// isSquare returns whether the big.Int is a quadratic square. -func (f field) isSquare(e *big.Int) bool { - return f.areEqual(f.exponent(&big.Int{}, e, f.pMinus1div2), f.one(&big.Int{})) -} - -func (f field) isEqual(f2 *field) bool { - return f.prime.Cmp(f2.prime) == 0 -} - -func (f field) mod(x *big.Int) *big.Int { - return x.Mod(x, f.prime) -} - -func (f field) neg(res, x *big.Int) *big.Int { - return f.mod(res.Neg(x)) -} - -func (f field) add(res, x, y *big.Int) { - f.mod(res.Add(x, y)) -} - -func (f field) sub(res, x, y *big.Int) *big.Int { - return f.mod(res.Sub(x, y)) -} - -func (f field) mul(res, x, y *big.Int) { - f.mod(res.Mul(x, y)) -} - -func (f field) square(res, x *big.Int) { - f.mod(res.Mul(x, x)) -} - -func (f field) inv(res, x *big.Int) { - f.exponent(res, x, f.pMinus2) -} - -// Returns x^n. -func (f field) exponent(res, x, n *big.Int) *big.Int { - return res.Exp(x, n, f.prime) -} - -func (f field) cmov(res, x, y *big.Int, b bool) { - if b { - res.Set(y) - } else { - res.Set(x) - } -} - -func (f field) sgn0(x *big.Int) int { - return int(x.Bit(0)) -} - -func (f field) sqrt3mod4(res, e *big.Int) *big.Int { - return f.exponent(res, e, f.exp) -} - -func (f field) sqrt(res, e *big.Int) *big.Int { - return f.sqrt3mod4(res, e) -} diff --git a/internal/nist/group.go b/internal/nist/group.go index d46a14c..9c52076 100644 --- a/internal/nist/group.go +++ b/internal/nist/group.go @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -// Package nist implements a prime-order group over NIST P-256 with hash-to-curve. +// Package nist allows simple and abstracted operations in the NIST P-256, P-384, and P-521 groups. package nist import ( @@ -18,6 +18,7 @@ import ( "github.com/bytemare/hash2curve" "github.com/bytemare/crypto/internal" + "github.com/bytemare/crypto/internal/field" ) const ( @@ -61,12 +62,12 @@ func P521() internal.Group { // Group represents the prime-order group over the P256 curve. // It exposes a prime-order group API with hash-to-curve operations. type Group[Point nistECPoint[Point]] struct { - scalarField field + scalarField field.Field h2c string curve curve[Point] } -// NewScalar returns a new, empty, scalar. +// NewScalar returns a new scalar set to 0. func (g Group[P]) NewScalar() internal.Scalar { return newScalar(&g.scalarField) } @@ -97,7 +98,7 @@ func (g Group[P]) newPoint(p P) *Element[P] { // HashToScalar returns a safe mapping of the arbitrary input to a Scalar. // The DST must not be empty or nil, and is recommended to be longer than 16 bytes. func (g Group[P]) HashToScalar(input, dst []byte) internal.Scalar { - s := hash2curve.HashToFieldXMD(g.curve.hash, input, dst, 1, 1, g.curve.secLength, g.scalarField.prime)[0] + s := hash2curve.HashToFieldXMD(g.curve.hash, input, dst, 1, 1, g.curve.secLength, g.scalarField.Order())[0] // If necessary, build a buffer of right size, so it gets correctly interpreted. bytes := s.Bytes() @@ -110,7 +111,7 @@ func (g Group[P]) HashToScalar(input, dst []byte) internal.Scalar { } res := newScalar(&g.scalarField) - res.s.SetBytes(bytes) + res.scalar.SetBytes(bytes) return res } @@ -134,19 +135,19 @@ func (g Group[P]) Ciphersuite() string { // ScalarLength returns the byte size of an encoded element. func (g Group[P]) ScalarLength() int { - byteLen := (g.scalarField.bitLen() + 7) / 8 + byteLen := (g.scalarField.BitLen() + 7) / 8 return byteLen } // ElementLength returns the byte size of an encoded element. func (g Group[P]) ElementLength() int { - byteLen := (g.curve.field.bitLen() + 7) / 8 + byteLen := (g.curve.field.BitLen() + 7) / 8 return 1 + byteLen } // Order returns the order of the canonical group of scalars. func (g Group[P]) Order() string { - return g.scalarField.prime.String() + return g.scalarField.Order().String() } var ( @@ -157,6 +158,8 @@ var ( p256 Group[*nistec.P256Point] p384 Group[*nistec.P384Point] p521 Group[*nistec.P521Point] + + nistWa = field.String2Int("-3") ) func initP256() { @@ -206,5 +209,6 @@ func initP521() { } func (g *Group[Point]) setScalarField(order string) { - g.scalarField = *newField(s2int(order)) + prime := field.String2Int(order) + g.scalarField = field.NewField(&prime) } diff --git a/internal/nist/point.go b/internal/nist/point.go index 5bf4d7e..f6dca8c 100644 --- a/internal/nist/point.go +++ b/internal/nist/point.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/nist/scalar.go b/internal/nist/scalar.go index 962b775..461700e 100644 --- a/internal/nist/scalar.go +++ b/internal/nist/scalar.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -10,30 +10,25 @@ package nist import ( "crypto/subtle" - "errors" "fmt" "math/big" "github.com/bytemare/crypto/internal" -) - -var ( - errParamNegScalar = errors.New("negative scalar") - errParamScalarTooBig = errors.New("scalar too big") + "github.com/bytemare/crypto/internal/field" ) // Scalar implements the Scalar interface for group scalars. type Scalar struct { - field *field - s big.Int + field *field.Field + scalar big.Int } -func newScalar(field *field) *Scalar { +func newScalar(field *field.Field) *Scalar { s := &Scalar{ - field: field, - s: big.Int{}, + field: field, + scalar: big.Int{}, } - s.s.Set(zero) + s.scalar.Set(s.field.Zero()) return s } @@ -44,7 +39,7 @@ func (s *Scalar) assert(scalar internal.Scalar) *Scalar { panic(internal.ErrCastScalar) } - if !s.field.isEqual(_sc.field) { + if !s.field.IsEqual(_sc.field) { panic(internal.ErrWrongField) } @@ -53,13 +48,13 @@ func (s *Scalar) assert(scalar internal.Scalar) *Scalar { // Zero sets s to 0, and returns it. func (s *Scalar) Zero() internal.Scalar { - s.s.Set(zero) + s.scalar.Set(s.field.Zero()) return s } // One sets s to 1, and returns it. func (s *Scalar) One() internal.Scalar { - s.s.Set(one) + s.scalar.Set(s.field.One()) return s } @@ -67,7 +62,7 @@ func (s *Scalar) One() internal.Scalar { // The random source is crypto/rand, and this functions is guaranteed to return a non-zero scalar. func (s *Scalar) Random() internal.Scalar { for { - s.field.random(&s.s) + s.field.Random(&s.scalar) if !s.IsZero() { return s @@ -75,38 +70,38 @@ func (s *Scalar) Random() internal.Scalar { } } -// Add returns s+scalar, and returns s. +// Add sets the receiver to the sum of the input and the receiver, and returns the receiver. func (s *Scalar) Add(scalar internal.Scalar) internal.Scalar { if scalar == nil { return s } sc := s.assert(scalar) - s.field.add(&s.s, &s.s, &sc.s) + s.field.Add(&s.scalar, &s.scalar, &sc.scalar) return s } -// Subtract returns s-scalar, and returns s. +// Subtract subtracts the input from the receiver, and returns the receiver. func (s *Scalar) Subtract(scalar internal.Scalar) internal.Scalar { if scalar == nil { return s } sc := s.assert(scalar) - s.field.sub(&s.s, &s.s, &sc.s) + s.field.Sub(&s.scalar, &s.scalar, &sc.scalar) return s } -// Multiply sets s to s*scalar, and returns s. +// Multiply multiplies the receiver with the input, and returns the receiver. func (s *Scalar) Multiply(scalar internal.Scalar) internal.Scalar { if scalar == nil { return s.Zero() } sc := s.assert(scalar) - s.field.mul(&s.s, &s.s, &sc.s) + s.field.Mul(&s.scalar, &s.scalar, &sc.scalar) return s } @@ -122,18 +117,18 @@ func (s *Scalar) Pow(scalar internal.Scalar) internal.Scalar { } sc := s.assert(scalar) - s.field.exponent(&s.s, &s.s, &sc.s) + s.field.Exponent(&s.scalar, &s.scalar, &sc.scalar) return s } -// Invert sets s to its modular inverse ( 1 / s ). +// Invert sets the receiver to its modular inverse ( 1 / s ), and returns it. func (s *Scalar) Invert() internal.Scalar { - s.field.inv(&s.s, &s.s) + s.field.Inv(&s.scalar, &s.scalar) return s } -// Equal returns 1 if the s == scalar are equal, and 0 otherwise. +// Equal returns 1 if the scalars are equal, and 0 otherwise. func (s *Scalar) Equal(scalar internal.Scalar) int { if scalar == nil { return 0 @@ -141,7 +136,7 @@ func (s *Scalar) Equal(scalar internal.Scalar) int { sc := s.assert(scalar) - return subtle.ConstantTimeCompare(s.s.Bytes(), sc.s.Bytes()) + return subtle.ConstantTimeCompare(s.scalar.Bytes(), sc.scalar.Bytes()) } // LessOrEqual returns 1 if s <= scalar, and 0 otherwise. @@ -171,7 +166,7 @@ func (s *Scalar) LessOrEqual(scalar internal.Scalar) int { // IsZero returns whether the scalar is 0. func (s *Scalar) IsZero() bool { - return s.field.areEqual(&s.s, zero) + return s.field.AreEqual(&s.scalar, s.field.Zero()) } func (s *Scalar) set(scalar *Scalar) *Scalar { @@ -186,15 +181,15 @@ func (s *Scalar) Set(scalar internal.Scalar) internal.Scalar { } ec := s.assert(scalar) - s.s.Set(&ec.s) + s.scalar.Set(&ec.scalar) return s } // SetInt sets s to i modulo the field order, and returns an error if one occurs. func (s *Scalar) SetInt(i *big.Int) error { - s.s.Set(i) - s.field.mod(&s.s) + s.scalar.Set(i) + s.field.Mod(&s.scalar) return nil } @@ -202,36 +197,36 @@ func (s *Scalar) SetInt(i *big.Int) error { // Copy returns a copy of the Scalar. func (s *Scalar) Copy() internal.Scalar { cpy := newScalar(s.field) - cpy.s.Set(&s.s) + cpy.scalar.Set(&s.scalar) return cpy } // Encode returns the compressed byte encoding of the scalar. func (s *Scalar) Encode() []byte { - byteLen := (s.field.bitLen() + 7) / 8 + byteLen := (s.field.BitLen() + 7) / 8 scalar := make([]byte, byteLen) - return s.s.FillBytes(scalar) + return s.scalar.FillBytes(scalar) } // Decode sets the receiver to a decoding of the input data, and returns an error on failure. -func (s *Scalar) Decode(data []byte) error { - if len(data) == 0 { +func (s *Scalar) Decode(in []byte) error { + if len(in) == 0 { return internal.ErrParamNilScalar } // warning - SetBytes interprets the input as a non-signed integer, so this will always be false - tmp := new(big.Int).SetBytes(data) + tmp := new(big.Int).SetBytes(in) if tmp.Sign() < 0 { - return errParamNegScalar + return internal.ErrParamNegScalar } - if s.field.order().Cmp(tmp) <= 0 { - return errParamScalarTooBig + if s.field.Order().Cmp(tmp) <= 0 { + return internal.ErrParamScalarTooBig } - s.s.Set(tmp) + s.scalar.Set(tmp) return nil } diff --git a/internal/ristretto/element.go b/internal/ristretto/element.go index 0dfe16d..065c7fb 100644 --- a/internal/ristretto/element.go +++ b/internal/ristretto/element.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/ristretto/ristretto.go b/internal/ristretto/ristretto.go index d9b4d6e..3859282 100644 --- a/internal/ristretto/ristretto.go +++ b/internal/ristretto/ristretto.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/ristretto/scalar.go b/internal/ristretto/scalar.go index deea11a..c84add2 100644 --- a/internal/ristretto/scalar.go +++ b/internal/ristretto/scalar.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/scalar.go b/internal/scalar.go index daf9f3e..00d6663 100644 --- a/internal/scalar.go +++ b/internal/scalar.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/internal/secp256k1/curve.go b/internal/secp256k1/curve.go new file mode 100644 index 0000000..0c79bad --- /dev/null +++ b/internal/secp256k1/curve.go @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C)2020-2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +package secp256k1 + +import ( + "crypto" + "fmt" + "math/big" + + "github.com/bytemare/hash2curve" + + "github.com/bytemare/crypto/internal/field" + "github.com/bytemare/crypto/internal/h2c" +) + +const ( + fieldOrder = "115792089237316195423570985008687907853269984665640564039457584007908834671663" + groupOrder = "115792089237316195423570985008687907852837564279074904382605163141518161494337" + hash = crypto.SHA256 + secLength = 48 + scalarLength = 32 + elementLength = 33 +) + +var ( + fp = field.NewField(setString(fieldOrder, 10)) + fn = field.NewField(setString(groupOrder, 0)) + b = big.NewInt(7) + b3 = big.NewInt(21) + mapZ = new(big.Int).Mod(big.NewInt(-11), fp.Order()) + baseX = setString("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 0) + baseY = setString("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", 0) +) + +func setString(s string, base int) *big.Int { + i := new(big.Int) + if _, ok := i.SetString(s, base); !ok { + panic(fmt.Sprintf("setting int in base %d failed: %v", base, s)) + } + + return i +} + +func hashToScalar(input, dst []byte) *Scalar { + s := hash2curve.HashToFieldXMD(hash, input, dst, 1, 1, secLength, fn.Order())[0] + + // If necessary, build a buffer of right size, so it gets correctly interpreted. + bytes := s.Bytes() + + length := scalarLength + if l := length - len(bytes); l > 0 { + buf := make([]byte, l, length) + buf = append(buf, bytes...) + bytes = buf + } + + res := newScalar() + res.scalar.SetBytes(bytes) + + return res +} + +var ( + secp256k13ISOA = setString("0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533", 0) + secp256k13ISOB = setString("1771", 0) +) + +func map2IsoCurve(fe *big.Int) *Element { + x, y := h2c.MapToCurveSSWU(&fp, secp256k13ISOA, secp256k13ISOB, mapZ, fe) + return newElementWithAffine(x, y) +} + +func hashToCurve(input, dst []byte) *Element { + u := hash2curve.HashToFieldXMD(hash, input, dst, 2, 1, secLength, fp.Order()) + q0 := map2IsoCurve(u[0]) + q1 := map2IsoCurve(u[1]) + q0.addAffine(q1) // we use a generic affine add here because the others are tailored for a = 0 and b = 7. + p, isIdentity := isogeny3map(q0) + + if isIdentity { + return newElement() + } + + // We can save cofactor clearing because it is 1. + return p +} + +func encodeToCurve(input, dst []byte) *Element { + u := hash2curve.HashToFieldXMD(hash, input, dst, 1, 1, secLength, fp.Order()) + q0 := map2IsoCurve(u[0]) + p, isIdentity := isogeny3map(q0) + + if isIdentity { + return newElement() + } + + // We can save cofactor clearing because it is 1. + return p +} + +var ( + _k10 = setString("0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7", 0) + _k11 = setString("0x07d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581", 0) + _k12 = setString("0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262", 0) + _k13 = setString("0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c", 0) + _k20 = setString("0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b", 0) + _k21 = setString("0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14", 0) + _k30 = setString("0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c", 0) + _k31 = setString("0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3", 0) + _k32 = setString("0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931", 0) + _k33 = setString("0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84", 0) + _k40 = setString("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b", 0) + _k41 = setString("0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573", 0) + _k42 = setString("0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f", 0) +) + +// isogeny3map is a 3-degree isogeny from secp256k1 3-ISO to the secp256k1 elliptic curve. +func isogeny3map(e *Element) (*Element, bool) { + var isIdentity bool + var x2, x3, k11, k12, k13, k21, k31, k32, k33, k41, k42 big.Int + fp.Mul(&x2, &e.x, &e.x) + fp.Mul(&x3, &x2, &e.x) + + // x_num, x_den + var xNum big.Int + fp.Mul(&k13, _k13, &x3) // _k(1,3) * x'^3 + fp.Mul(&k12, _k12, &x2) // _k(1,2) * x'^2 + fp.Mul(&k11, _k11, &e.x) // _k(1,1) * x' + fp.Add(&xNum, &k13, &k12) + fp.Add(&xNum, &xNum, &k11) + fp.Add(&xNum, &xNum, _k10) + + var xDen big.Int + fp.Mul(&k21, _k21, &e.x) // _k(2,1) * x' + fp.Add(&xDen, &x2, &k21) + fp.Add(&xDen, &xDen, _k20) + + // y_num, y_den + var yNum big.Int + fp.Mul(&k33, _k33, &x3) // _k(3,3) * x'^3 + fp.Mul(&k32, _k32, &x2) // _k(3,2) * x'^2 + fp.Mul(&k31, _k31, &e.x) // _k(3,1) * x' + fp.Add(&yNum, &k33, &k32) + fp.Add(&yNum, &yNum, &k31) + fp.Add(&yNum, &yNum, _k30) + + var yDen big.Int + fp.Mul(&k42, _k42, &x2) // _k(4,2) * x'^2 + fp.Mul(&k41, _k41, &e.x) // _k(4,1) * x' + fp.Add(&yDen, &x3, &k42) + fp.Add(&yDen, &yDen, &k41) + fp.Add(&yDen, &yDen, _k40) + + // final x, y + var resX, resY big.Int + + fp.Inv(&resX, &xDen) + isIdentity = fp.IsZero(&resX) + fp.Mul(&resX, &resX, &xNum) + + fp.Inv(&resY, &yDen) + isIdentity = isIdentity || fp.IsZero(&resY) + fp.Mul(&resY, &resY, &yNum) + fp.Mul(&resY, &resY, &e.y) + + return newElementWithAffine(&resX, &resY), isIdentity +} diff --git a/internal/secp256k1/element.go b/internal/secp256k1/element.go new file mode 100644 index 0000000..4284dd0 --- /dev/null +++ b/internal/secp256k1/element.go @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C)2020-2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in theg +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +package secp256k1 + +import ( + "math/big" + + "github.com/bytemare/crypto/internal" +) + +type mode uint8 + +const ( + incomplete mode = 1 + complete mode = 2 + + formulaType = incomplete +) + +// Element implements the Element interface for the Secp256k1 group element. +type Element struct { + x, y, z big.Int +} + +var identity = Element{ + x: *fp.Zero(), + y: *fp.Zero(), + z: *fp.Zero(), // The Identity element is the only with z == 0 +} + +func newElementWithAffine(x, y *big.Int) *Element { + e := &Element{ + x: big.Int{}, + y: big.Int{}, + z: big.Int{}, + } + + e.x.Set(x) + e.y.Set(y) + e.z.Set(fp.One()) + + return e +} + +// newElement returns a new element set to the point at infinity. +func newElement() *Element { + e := &Element{ + x: big.Int{}, + y: big.Int{}, + z: big.Int{}, + } + + return e.set(&identity) +} + +func assertElement(element internal.Element) *Element { + if element == nil { + panic(internal.ErrParamNilPoint) + } + + ec, ok := element.(*Element) + if !ok { + panic(internal.ErrCastElement) + } + + return ec +} + +func (e *Element) affine() (x, y *big.Int) { + if e.z.Sign() == 0 { + return fp.Zero(), fp.Zero() + } + + if fp.AreEqual(&e.z, fp.One()) { + return &e.x, &e.y + } + + var zInv, zInvSq big.Int + + fp.Inv(&zInv, &e.z) + fp.Square(&zInvSq, &zInv) + + x = new(big.Int) + fp.Mul(x, &e.x, &zInvSq) + fp.Mul(&zInvSq, &zInvSq, &zInv) + + y = new(big.Int) + fp.Mul(y, &e.y, &zInvSq) + + return x, y +} + +// Base sets the element to the group's base point a.k.a. canonical generator. +func (e *Element) Base() internal.Element { + e.x.Set(baseX) + e.y.Set(baseY) + e.z.Set(scOne) + + return e +} + +// Identity sets the element to the point at infinity of the Group's underlying curve. +func (e *Element) Identity() internal.Element { + return e.set(&identity) +} + +func (e *Element) addAffine(element *Element) *Element { + var t0, t1, ll, x, y big.Int + x1, y1 := e.affine() + x2, y2 := element.affine() + + fp.Sub(&t0, y2, y1) // (y2-y1) + fp.Sub(&t1, x2, x1) // (x2-x1) + fp.Inv(&t1, &t1) // 1/(x2-x1) + fp.Mul(&ll, &t0, &t1) // l = (y2-y1)/(x2-x1) + + fp.Square(&t0, &ll) // l^2 + fp.Sub(&t0, &t0, x1) // l^2-x1 + fp.Sub(&x, &t0, x2) // x' = l^2-x1-x2 + + fp.Sub(&t0, x1, &x) // x1-x3 + fp.Mul(&t0, &t0, &ll) // l(x1-x3) + fp.Sub(&y, &t0, y1) // y3 = l(x1-x3)-y1 + + e.x.Set(&x) + e.y.Set(&y) + e.z.Set(fp.One()) + + return e +} + +// From http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl. +func (e *Element) addJacobianIncomplete(element *Element) *Element { + var u1, u2, h, i, j, s1, s2, r, v, z1z1, z2z2, x3, y3, z3 big.Int + + fp.Square(&z1z1, &e.z) // Z1Z1 = Z12 + fp.Square(&z2z2, &element.z) // Z2Z2 = Z22 + + fp.Mul(&u1, &e.x, &z2z2) // U1 = X1*Z2Z2 + fp.Mul(&u2, &element.x, &z1z1) // U2 = X2*Z1Z1 + fp.Sub(&h, &u2, &u1) // H = U2-U1 + fp.Lsh(&i, &h, 1) // I = (2*H)2 + fp.Square(&i, &i) // + fp.Mul(&j, &h, &i) // J = H*I + + fp.Mul(&s1, &e.y, &element.z) + fp.Mul(&s1, &s1, &z2z2) // S1 = Y1*Z2*Z2Z2 + fp.Mul(&s2, &element.y, &e.z) + fp.Mul(&s2, &s2, &z1z1) // S2 = Y2*Z1*Z1Z1 + fp.Sub(&r, &s2, &s1) // r = 2*(S2-S1) + fp.Lsh(&r, &r, 1) + fp.Mul(&v, &u1, &i) // V = U1*I + + x3.Set(&r) + fp.Square(&x3, &x3) + fp.Sub(&x3, &x3, &j) + fp.Sub(&x3, &x3, &v) + fp.Sub(&x3, &x3, &v) // X3 = r2-J-2*V + + y3.Set(&r) + fp.Sub(&v, &v, &x3) + fp.Mul(&y3, &y3, &v) + fp.Mul(&s1, &s1, &j) // S1 = Y1*Z2*Z2Z2 + fp.Lsh(&s1, &s1, 1) + fp.Sub(&y3, &y3, &s1) // Y3 = r*(V-X3)-2*S1*J + + fp.Add(&z3, &e.z, &element.z) + fp.Square(&z3, &z3) + fp.Sub(&z3, &z3, &z1z1) + fp.Sub(&z3, &z3, &z2z2) + fp.Mul(&z3, &z3, &h) // Z3 = ((Z1+Z2)2-Z1Z1-Z2Z2)*H + + e.x.Set(&x3) + e.y.Set(&y3) + e.z.Set(&z3) + + return e +} + +// https://eprint.iacr.org/2015/1060.pdf +func (e *Element) addJacobianComplete(element *Element) *Element { + var t0, t1, t2, t3, t4, x3, y3, z3 big.Int + + fp.Mul(&t0, &e.x, &element.x) // t0 := X1 * X2 + fp.Mul(&t1, &e.y, &element.y) // t1 := Y1 * Y2 + fp.Mul(&t2, &e.z, &element.z) // t2 := Z1 * Z2 + + fp.Add(&t3, &e.x, &e.y) // t3 := X1 + Y1 + fp.Add(&t4, &element.x, &element.y) // t4 := X2 + Y2 + fp.Mul(&t3, &t3, &t4) // t3 := t3 * t4 + + fp.Add(&t4, &t0, &t1) // t4 := t0 + t1 + fp.Sub(&t3, &t3, &t4) // t3 := t3 - t4 + fp.Add(&t4, &e.y, &e.z) // t4 := Y1 + Z1 + + fp.Add(&x3, &element.y, &element.z) // X3 := Y2 + Z2 + fp.Mul(&t4, &t4, &x3) // t4 := t4 * X3 + fp.Add(&x3, &t1, &t2) // X3 := t1 + t2 + + fp.Sub(&t4, &t4, &x3) // t4 := t4 - X3 + fp.Add(&x3, &e.x, &e.z) // X3 := X1 + Z1 + fp.Add(&y3, &element.x, &element.z) // Y3 := X2 + Z2 + + fp.Mul(&x3, &x3, &y3) // X3 := X3 * Y3 + fp.Add(&y3, &t0, &t2) // Y3 := t0 + t2 + fp.Sub(&y3, &x3, &y3) // Y3 := X3 - Y3 + + fp.Add(&x3, &t0, &t0) // X3 := t0 + t0 + fp.Add(&t0, &x3, &t0) // t0 := X3 + t0 + fp.Mul(&t2, b3, &t2) // t2 := b3 * t2 + + fp.Add(&z3, &t1, &t2) // Z3 := t1 + t2 + fp.Sub(&t1, &t1, &t2) // t1 := t1 - t2 + fp.Mul(&y3, b3, &y3) // Y3 := b3 * Y3 + + fp.Mul(&x3, &t4, &y3) // X3 := t4 * Y3 + fp.Mul(&t2, &t3, &t1) // t2 := t3 * t1 + fp.Sub(&x3, &t2, &x3) // X3 := t2 - X3 + + fp.Mul(&y3, &y3, &t0) // Y3 := Y3 * t0 + fp.Mul(&t1, &t1, &z3) // t1 := t1 * Z3 + fp.Add(&y3, &t1, &y3) // Y3 := t1 + Y3 + + fp.Mul(&t0, &t0, &t3) // t0 := t0 * t3 + fp.Mul(&z3, &z3, &t4) // Z3 := Z3 * t4 + fp.Add(&z3, &z3, &t0) // Z3 := Z3 + t0 + + e.x.Set(&x3) + e.y.Set(&y3) + e.z.Set(&z3) + + return e +} + +func (e *Element) add(element *Element) *Element { + if element.IsIdentity() { + return e + } + + if e.IsIdentity() { + e.set(element) + return e + } + + if e.isEqual(element) == 1 { + return e.double() + } + + switch formulaType { + case incomplete: + return e.addJacobianIncomplete(element) + case complete: + return e.addJacobianComplete(element) + } + + panic("invalid formula type") +} + +// Add sets the receiver to the sum of the input and the receiver, and returns the receiver. +func (e *Element) Add(element internal.Element) internal.Element { + q := assertElement(element) + return e.add(q) +} + +// Double sets the receiver to its double, and returns it. +// From http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l. +func (e *Element) doubleJacobianIncomplete() *Element { + var a, _b, c, d, e2, f, x3, y3, z3 big.Int + + fp.Square(&a, &e.x) + fp.Square(&_b, &e.y) + fp.Square(&c, &_b) + + fp.Add(&d, &e.x, &_b) + fp.Square(&d, &d) + fp.Sub(&d, &d, &a) + fp.Sub(&d, &d, &c) + fp.Lsh(&d, &d, 1) + + fp.Mul(&e2, &a, big.NewInt(3)) + fp.Square(&f, &e2) + + fp.Lsh(&x3, &d, 1) + fp.Sub(&x3, &f, &x3) + + fp.Sub(&y3, &d, &x3) + fp.Mul(&y3, &e2, &y3) + fp.Lsh(&c, &c, 3) + fp.Sub(&y3, &y3, &c) + + fp.Mul(&z3, &e.y, &e.z) + fp.Lsh(&z3, &z3, 1) + + e.x.Set(&x3) + e.y.Set(&y3) + e.z.Set(&z3) + + return e +} + +// https://eprint.iacr.org/2015/1060.pdf +func (e *Element) doubleJacobianComplete() *Element { + var t0, t1, t2, x3, y3, z3 big.Int + + fp.Square(&t0, &e.y) // t0 := Y ^2 + fp.Add(&z3, &t0, &t0) // Z3 := t0 + t0 + fp.Add(&z3, &z3, &z3) // Z3 := Z3 + Z3 + + fp.Add(&z3, &z3, &z3) // Z3 := Z3 + Z3 + fp.Mul(&t1, &e.y, &e.z) // t1 := Y * Z + fp.Square(&t2, &e.z) // t2 := Z ^2 + + fp.Mul(&t2, b3, &t2) // t2 := b3 * t2 + fp.Mul(&x3, &t2, &z3) // X3 := t2 * Z3 + fp.Add(&y3, &t0, &t2) // Y3 := t0 + t2 + + fp.Mul(&z3, &t1, &z3) // Z3 := t1 * Z3 + fp.Add(&t1, &t2, &t2) // t1 := t2 + t2 + fp.Add(&t2, &t1, &t2) // t2 := t1 + t2 + + fp.Sub(&t0, &t0, &t2) // t0 := t0 - t2 + fp.Mul(&y3, &t0, &y3) // Y3 := t0 * Y3 + fp.Add(&y3, &x3, &y3) // Y3 := X3 + Y3 + + fp.Mul(&t1, &e.x, &e.y) // t1 := X * Y + fp.Mul(&x3, &t0, &t1) // X3 := t0 * t1 + fp.Add(&x3, &x3, &x3) // X3 := X3 + X3 + + e.x.Set(&x3) + e.y.Set(&y3) + e.z.Set(&z3) + + return e +} + +func (e *Element) double() *Element { + switch formulaType { + case incomplete: + return e.doubleJacobianIncomplete() + case complete: + return e.doubleJacobianComplete() + } + + panic("invalid formula type") +} + +// Double sets the receiver to its double, and returns it. +// From https://www.microsoft.com/en-us/research/wp-content/uploads/2016/06/complete-2.pdf. +func (e *Element) Double() internal.Element { + return e.double() +} + +func (e *Element) negate() *Element { + e.y.Neg(&e.y) + return e +} + +// Negate sets the receiver to its negation, and returns it. +func (e *Element) Negate() internal.Element { + if e.IsIdentity() { + return e + } + + return e.negate() +} + +// Subtract subtracts the input from the receiver, and returns the receiver. +func (e *Element) Subtract(element internal.Element) internal.Element { + q := assertElement(element).negate() + return e.addJacobianIncomplete(q) +} + +// Multiply sets the receiver to the scalar multiplication of the receiver with the given Scalar, and returns it. +func (e *Element) Multiply(scalar internal.Scalar) internal.Element { + s := assert(scalar) + + if fp.AreEqual(&s.scalar, scOne) { + return e + } + + r0 := newElement() + r1 := e.copy() + + for i := s.scalar.BitLen() - 1; i >= 0; i-- { + if s.scalar.Bit(i) == 0 { + r1.add(r0) + r0.double() + } else { + r0.add(r1) + r1.double() + } + } + + return e.set(r0) +} + +// Equal returns 1 if the elements are equivalent, and 0 otherwise. +func (e *Element) isEqual(element *Element) int { + x1, y1 := e.affine() + x2, y2 := element.affine() + x := x1.Cmp(x2) + y := y1.Cmp(y2) + + if x == 0 && y == 0 { + return 1 + } + + return 0 +} + +// Equal returns 1 if the elements are equivalent, and 0 otherwise. +func (e *Element) Equal(element internal.Element) int { + q := assertElement(element) + return e.isEqual(q) +} + +// IsIdentity returns whether the Element is the point at infinity of the Group's underlying curve. +func (e *Element) IsIdentity() bool { + return e.z.Sign() == 0 || e.x.Sign() == 0 && e.y.Sign() == 0 +} + +func (e *Element) set(element *Element) *Element { + e.x.Set(&element.x) + e.y.Set(&element.y) + e.z.Set(&element.z) + + return e +} + +// Set sets the receiver to the value of the argument, and returns the receiver. +func (e *Element) Set(element internal.Element) internal.Element { + q := assertElement(element) + return e.set(q) +} + +func (e *Element) copy() *Element { + return &Element{ + x: *new(big.Int).Set(&e.x), + y: *new(big.Int).Set(&e.y), + z: *new(big.Int).Set(&e.z), + } +} + +// Copy returns a copy of the receiver. +func (e *Element) Copy() internal.Element { + return e.copy() +} + +// Encode returns the compressed byte encoding of the element. +func (e *Element) Encode() []byte { + var output [elementLength]byte + + if e.IsIdentity() { + return output[:] + } + + x, y := e.affine() + output[0] = byte(2 | y.Bit(0)&1) + x.FillBytes(output[1:]) + + return output[:] +} + +// XCoordinate returns the encoded x coordinate of the element, which is the same as Encode(). +func (e *Element) XCoordinate() []byte { + return e.Encode()[1:] +} + +// secp256Polynomial applies y^2=x^3+ax+b to recover y^2 from x. +func secp256Polynomial(y, x *big.Int) { + fp.Mul(y, x, x) + fp.Mul(y, y, x) + fp.Add(y, y, b) +} + +// Decode sets the receiver to a decoding of the input data, and returns an error on failure. +func (e *Element) Decode(data []byte) error { + /* + - check coordinates are in the correct range + - check point is on the curve + - point is not infinity + - point order validation is not necessary since the cofactor is 1 + */ + if len(data) != elementLength { + return internal.ErrParamInvalidPointEncoding + } + + if data[0] != 2 && data[0] != 3 { + return internal.ErrParamInvalidPointEncoding + } + + x := new(big.Int).SetBytes(data[1:]) + if x.Cmp(fp.Order()) != -1 { + return internal.ErrParamInvalidPointEncoding + } + + var y big.Int + secp256Polynomial(&y, x) + + if !fp.IsSquare(&y) { + return internal.ErrParamInvalidPointEncoding + } + + fp.SquareRoot(&y, &y) + + cond := int(y.Bytes()[0]&1) ^ int(data[0]&1) + fp.CondNeg(&y, &y, cond) + + // Identity Check + if x.Cmp(scZero) == 0 && y.Cmp(scZero) == 0 { + return internal.ErrIdentity + } + + e.x.Set(x) + e.y.Set(&y) + + return nil +} + +// MarshalBinary returns the compressed byte encoding of the element. +func (e *Element) MarshalBinary() (data []byte, err error) { + return e.Encode(), nil +} + +// UnmarshalBinary sets e to the decoding of the byte encoded element. +func (e *Element) UnmarshalBinary(data []byte) error { + return e.Decode(data) +} diff --git a/internal/secp256k1/group.go b/internal/secp256k1/group.go new file mode 100644 index 0000000..6d750a9 --- /dev/null +++ b/internal/secp256k1/group.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C)2020-2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +// Package secp256k1 allows simple and abstracted operations in the Secp256k1 group. +package secp256k1 + +import ( + "github.com/bytemare/crypto/internal" +) + +const ( + // H2CSECP256K1 represents the hash-to-curve string identifier for Secp256k1. + H2CSECP256K1 = "secp256k1_XMD:SHA-256_SSWU_RO_" + + // E2CSECP256K1 represents the encode-to-curve string identifier for Secp256k1. + E2CSECP256K1 = "secp256k1_XMD:SHA-256_SSWU_NU_" +) + +// Group represents the Secp256k1 group. It exposes a prime-order group API with hash-to-curve operations. +type Group struct{} + +// New returns a new instantiation of the Secp256k1 Group. +func New() internal.Group { + return Group{} +} + +// NewScalar returns a new scalar set to 0. +func (g Group) NewScalar() internal.Scalar { + return newScalar() +} + +// NewElement returns the identity element (point at infinity). +func (g Group) NewElement() internal.Element { + return newElement() +} + +// Base returns the group's base point a.k.a. canonical generator. +func (g Group) Base() internal.Element { + return newElement().Base() +} + +// HashToScalar returns a safe mapping of the arbitrary input to a Scalar. +// The DST must not be empty or nil, and is recommended to be longer than 16 bytes. +func (g Group) HashToScalar(input, dst []byte) internal.Scalar { + return hashToScalar(input, dst) +} + +// HashToGroup returns a safe mapping of the arbitrary input to an Element in the Group. +// The DST must not be empty or nil, and is recommended to be longer than 16 bytes. +func (g Group) HashToGroup(input, dst []byte) internal.Element { + return hashToCurve(input, dst) +} + +// EncodeToGroup returns a non-uniform mapping of the arbitrary input to an Element in the Group. +// The DST must not be empty or nil, and is recommended to be longer than 16 bytes. +func (g Group) EncodeToGroup(input, dst []byte) internal.Element { + return encodeToCurve(input, dst) +} + +// Ciphersuite returns the hash-to-curve ciphersuite identifier. +func (g Group) Ciphersuite() string { + return H2CSECP256K1 +} + +// ScalarLength returns the byte size of an encoded scalar. +func (g Group) ScalarLength() int { + return scalarLength +} + +// ElementLength returns the byte size of an encoded element. +func (g Group) ElementLength() int { + return elementLength +} + +// Order returns the order of the canonical group of scalars. +func (g Group) Order() string { + return groupOrder +} diff --git a/internal/secp256k1/scalar.go b/internal/secp256k1/scalar.go new file mode 100644 index 0000000..8fa3a31 --- /dev/null +++ b/internal/secp256k1/scalar.go @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C)2020-2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +package secp256k1 + +import ( + "crypto/subtle" + "math/big" + + "github.com/bytemare/crypto/internal" +) + +// Scalar implements the Scalar interface for Edwards25519 group scalars. +type Scalar struct { + scalar big.Int +} + +var ( + scZero = big.NewInt(0) + scOne = big.NewInt(1) +) + +func newScalar() *Scalar { + return &Scalar{scalar: big.Int{}} +} + +func assert(scalar internal.Scalar) *Scalar { + sc, ok := scalar.(*Scalar) + if !ok { + panic(internal.ErrCastScalar) + } + + return sc +} + +// Zero sets the scalar to 0, and returns it. +func (s *Scalar) Zero() internal.Scalar { + s.scalar.Set(scZero) + return s +} + +// One sets the scalar to 1, and returns it. +func (s *Scalar) One() internal.Scalar { + s.scalar.Set(scOne) + return s +} + +// Random sets the current scalar to a new random scalar and returns it. +// The random source is crypto/rand, and this functions is guaranteed to return a non-zero scalar. +func (s *Scalar) Random() internal.Scalar { + for { + fn.Random(&s.scalar) + + if !s.IsZero() { + return s + } + } +} + +// Add sets the receiver to the sum of the input and the receiver, and returns the receiver. +func (s *Scalar) Add(scalar internal.Scalar) internal.Scalar { + if scalar == nil { + return s + } + + sc := assert(scalar) + fn.Add(&s.scalar, &s.scalar, &sc.scalar) + + return s +} + +// Subtract subtracts the input from the receiver, and returns the receiver. +func (s *Scalar) Subtract(scalar internal.Scalar) internal.Scalar { + if scalar == nil { + return s + } + + sc := assert(scalar) + fn.Sub(&s.scalar, &s.scalar, &sc.scalar) + + return s +} + +// Multiply multiplies the receiver with the input, and returns the receiver. +func (s *Scalar) Multiply(scalar internal.Scalar) internal.Scalar { + if scalar == nil { + return s.Zero() + } + + sc := assert(scalar) + fn.Mul(&s.scalar, &s.scalar, &sc.scalar) + + return s +} + +// Pow sets s to s**scalar modulo the group order, and returns s. If scalar is nil, it returns 1. +func (s *Scalar) Pow(scalar internal.Scalar) internal.Scalar { + if scalar == nil || scalar.IsZero() { + return s.One() + } + + if scalar.Equal(scalar.Copy().One()) == 1 { + return s + } + + sc := assert(scalar) + fn.Exponent(&s.scalar, &s.scalar, &sc.scalar) + + return s +} + +// Invert sets the receiver to its modular inverse ( 1 / s ), and returns it. +func (s *Scalar) Invert() internal.Scalar { + fn.Inv(&s.scalar, &s.scalar) + return s +} + +// Equal returns 1 if the scalars are equal, and 0 otherwise. +func (s *Scalar) Equal(scalar internal.Scalar) int { + if scalar == nil { + return 0 + } + + sc := assert(scalar) + + return subtle.ConstantTimeCompare(s.scalar.Bytes(), sc.scalar.Bytes()) +} + +// LessOrEqual returns 1 if s <= scalar and 0 otherwise. +func (s *Scalar) LessOrEqual(scalar internal.Scalar) int { + sc := assert(scalar) + + ienc := s.Encode() + jenc := sc.Encode() + + leni := len(ienc) + if leni != len(jenc) { + panic(internal.ErrParamScalarLength) + } + + var res bool + + for i := 0; i < leni; i++ { + res = res || (ienc[i] > jenc[i]) + } + + if res { + return 0 + } + + return 1 +} + +// IsZero returns whether the scalar is 0. +func (s *Scalar) IsZero() bool { + return fn.AreEqual(&s.scalar, scZero) +} + +func (s *Scalar) set(scalar *Scalar) *Scalar { + *s = *scalar + return s +} + +// Set sets the receiver to the value of the argument scalar, and returns the receiver. +func (s *Scalar) Set(scalar internal.Scalar) internal.Scalar { + if scalar == nil { + return s.set(nil) + } + + ec := assert(scalar) + s.scalar.Set(&ec.scalar) + + return s +} + +// SetInt sets s to i modulo the field order, and returns an error if one occurs. +func (s *Scalar) SetInt(i *big.Int) error { + s.scalar.Set(i) + fn.Mod(&s.scalar) + + return nil +} + +// Copy returns a copy of the receiver. +func (s *Scalar) Copy() internal.Scalar { + cpy := newScalar() + cpy.scalar.Set(&s.scalar) + + return cpy +} + +// Encode returns the compressed byte encoding of the scalar. +func (s *Scalar) Encode() []byte { + byteLen := (fn.BitLen() + 7) / 8 + scalar := make([]byte, byteLen) + + return s.scalar.FillBytes(scalar) +} + +// Decode sets the receiver to a decoding of the input data, and returns an error on failure. +func (s *Scalar) Decode(in []byte) error { + if len(in) == 0 { + return internal.ErrParamNilScalar + } + + // warning - SetBytes interprets the input as a non-signed integer, so this will always be false + tmp := new(big.Int).SetBytes(in) + if tmp.Sign() < 0 { + return internal.ErrParamNegScalar + } + + if fn.Order().Cmp(tmp) <= 0 { + return internal.ErrParamScalarTooBig + } + + s.scalar.Set(tmp) + + return nil +} + +// MarshalBinary returns the compressed byte encoding of the scalar. +func (s *Scalar) MarshalBinary() (data []byte, err error) { + return s.Encode(), nil +} + +// UnmarshalBinary sets e to the decoding of the byte encoded scalar. +func (s *Scalar) UnmarshalBinary(data []byte) error { + return s.Decode(data) +} diff --git a/scalar.go b/scalar.go index f85b01b..8fc4af1 100644 --- a/scalar.go +++ b/scalar.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at diff --git a/tests/bench_test.go b/tests/bench_test.go index daf9d57..e41816f 100644 --- a/tests/bench_test.go +++ b/tests/bench_test.go @@ -1,6 +1,6 @@ // SPDX-License-Group: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -14,7 +14,7 @@ import ( ) func benchAll(b *testing.B, f func(*testing.B, *testGroup)) { - for _, group := range testGroups() { + for _, group := range testTable { b.Run(group.name, func(t *testing.B) { f(t, group) }) @@ -26,8 +26,8 @@ func BenchmarkPow(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - base := group.id.NewScalar().Random() - exp := group.id.NewScalar().Random() + base := group.group.NewScalar().Random() + exp := group.group.NewScalar().Random() res := base.Pow(exp) res.Equal(base) } @@ -42,7 +42,7 @@ func BenchmarkHashToGroup(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - group.id.HashToGroup(msg, dst) + group.group.HashToGroup(msg, dst) } }) } @@ -52,7 +52,7 @@ func BenchmarkSubtraction(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - base := group.id.Base() + base := group.group.Base() base.Subtract(base) } }) @@ -60,11 +60,11 @@ func BenchmarkSubtraction(b *testing.B) { func BenchmarkScalarBaseMult(b *testing.B) { benchAll(b, func(b *testing.B, group *testGroup) { - priv := group.id.NewScalar().Random() + priv := group.group.NewScalar().Random() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - _ = group.id.Base().Multiply(priv) + _ = group.group.Base().Multiply(priv) // to do : Prevent the compiler from optimizing out the operation. } }) @@ -72,8 +72,8 @@ func BenchmarkScalarBaseMult(b *testing.B) { func BenchmarkScalarMult(b *testing.B) { benchAll(b, func(b *testing.B, group *testGroup) { - priv := group.id.NewScalar().Random() - pub := group.id.Base().Multiply(group.id.NewScalar().Random()) + priv := group.group.NewScalar().Random() + pub := group.group.Base().Multiply(group.group.NewScalar().Random()) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -84,11 +84,11 @@ func BenchmarkScalarMult(b *testing.B) { func BenchmarkMarshalUnmarshal(b *testing.B) { benchAll(b, func(b *testing.B, group *testGroup) { - pub := group.id.Base().Multiply(group.id.NewScalar().Random()) + pub := group.group.Base().Multiply(group.group.NewScalar().Random()) b.ReportAllocs() for i := 0; i < b.N; i++ { buf := pub.Encode() - pk := group.id.NewElement() + pk := group.group.NewElement() if err := pk.Decode(buf); err != nil { b.Fatal(err) } diff --git a/tests/element_test.go b/tests/element_test.go index 13848c3..765abaa 100644 --- a/tests/element_test.go +++ b/tests/element_test.go @@ -1,6 +1,6 @@ // SPDX-License-Group: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -17,9 +17,9 @@ import ( ) const ( - expectedEquality = "expected equality" - expectedIdentity = "expected identity" - wrongGroup = "wrong group" + errExpectedEquality = "expected equality" + errExpectedIdentity = "expected identity" + errWrongGroup = "wrong group" ) func testElementCopySet(t *testing.T, element, other *crypto.Element) { @@ -47,7 +47,7 @@ func testElementCopySet(t *testing.T, element, other *crypto.Element) { func TestElementCopy(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - base := group.id.Base() + base := group.group.Base() cpy := base.Copy() testElementCopySet(t, base, cpy) }) @@ -55,8 +55,8 @@ func TestElementCopy(t *testing.T) { func TestElementSet(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - base := group.id.Base() - other := group.id.NewElement() + base := group.group.Base() + other := group.group.NewElement() other.Set(base) testElementCopySet(t, base, other) }) @@ -82,45 +82,45 @@ func TestElement_WrongInput(t *testing.T) { } testAll(t, func(t2 *testing.T, group *testGroup) { - element := group.id.NewElement() + element := group.group.NewElement() var alternativeGroup crypto.Group - switch group.id { + switch group.group { // The following is arbitrary, and simply aims at confusing identifiers case crypto.Ristretto255Sha512, crypto.Edwards25519Sha512: alternativeGroup = crypto.P256Sha256 - case crypto.P256Sha256, crypto.P384Sha384, crypto.P521Sha512: + case crypto.P256Sha256, crypto.P384Sha384, crypto.P521Sha512, crypto.Secp256k1: alternativeGroup = crypto.Ristretto255Sha512 default: - t.Fatalf("Invalid group id %d", group.id) + t.Fatalf("Invalid group id %d", group.group) } - if err := testPanic(wrongGroup, internal.ErrCastElement, exec(element.Add, alternativeGroup.NewElement())); err != nil { + if err := testPanic(errWrongGroup, internal.ErrCastElement, exec(element.Add, alternativeGroup.NewElement())); err != nil { t.Fatal(err) } - if err := testPanic(wrongGroup, internal.ErrCastElement, exec(element.Subtract, alternativeGroup.NewElement())); err != nil { + if err := testPanic(errWrongGroup, internal.ErrCastElement, exec(element.Subtract, alternativeGroup.NewElement())); err != nil { t.Fatal(err) } - if err := testPanic(wrongGroup, internal.ErrCastElement, exec(element.Set, alternativeGroup.NewElement())); err != nil { + if err := testPanic(errWrongGroup, internal.ErrCastElement, exec(element.Set, alternativeGroup.NewElement())); err != nil { t.Fatal(err) } - if err := testPanic(wrongGroup, internal.ErrCastElement, equal(element.Equal, alternativeGroup.NewElement())); err != nil { + if err := testPanic(errWrongGroup, internal.ErrCastElement, equal(element.Equal, alternativeGroup.NewElement())); err != nil { t.Fatal(err) } }) // Specifically test Ristretto - if err := testPanic(wrongGroup, internal.ErrCastScalar, mult(crypto.Ristretto255Sha512.NewElement().Multiply, crypto.P384Sha384.NewScalar())); err != nil { + if err := testPanic(errWrongGroup, internal.ErrCastScalar, mult(crypto.Ristretto255Sha512.NewElement().Multiply, crypto.P384Sha384.NewScalar())); err != nil { t.Fatal(err) } } func TestElement_EncodedLength(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - id := group.id.NewElement().Identity().Encode() + id := group.group.NewElement().Identity().Encode() if len(id) != group.elementLength { t.Fatalf("Encode() of the identity element is expected to return %d bytes, but returned %d bytes", group.elementLength, len(id)) } @@ -130,7 +130,7 @@ func TestElement_EncodedLength(t *testing.T) { t.Fatalf("Encode() of the identity element is unexpected.\n\twant: %v\n\tgot : %v", group.identity, encodedID) } - encodedElement := group.id.NewElement().Base().Multiply(group.id.NewScalar().Random()).Encode() + encodedElement := group.group.NewElement().Base().Multiply(group.group.NewScalar().Random()).Encode() if len(encodedElement) != group.elementLength { t.Fatalf("Encode() is expected to return %d bytes, but returned %d bytes", group.elementLength, encodedElement) } @@ -139,14 +139,13 @@ func TestElement_EncodedLength(t *testing.T) { func TestElement_Arithmetic(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - elementTestEqual(t, group.id) - elementTestAdd(t, group.id) - elementTestDouble(t, group.id) - elementTestNegate(t, group.id) - elementTestSubstract(t, group.id) - elementTestMultiply(t, group.id) - elementTestInversion(t, group.id) - elementTestIdentity(t, group.id) + elementTestEqual(t, group.group) + elementTestAdd(t, group.group) + elementTestDouble(t, group.group) + elementTestNegate(t, group.group) + elementTestSubstract(t, group.group) + elementTestMultiply(t, group.group) + elementTestIdentity(t, group.group) }) } @@ -155,7 +154,7 @@ func elementTestEqual(t *testing.T, g crypto.Group) { base2 := g.Base() if base.Equal(base2) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } random := g.NewElement().Multiply(g.NewScalar().Random()) @@ -170,7 +169,48 @@ func elementTestAdd(t *testing.T, g crypto.Group) { base := g.Base() cpy := base.Copy() if cpy.Add(nil).Equal(base) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) + } + + // Verify whether add yields the same element when given identity + base = g.Base() + cpy = base.Copy() + if cpy.Add(g.NewElement()).Equal(base) != 1 { + t.Fatal(errExpectedEquality) + } + + // Verify whether add yields the identity given the negative + base = g.Base() + negative := g.Base().Negate() + identity := g.NewElement() + if base.Add(negative).Equal(identity) != 1 { + t.Fatal(errExpectedEquality) + } + + // Verify whether add yields the same when adding to identity + base = g.Base() + identity = g.NewElement() + if identity.Add(base).Equal(base) != 1 { + t.Fatal(errExpectedEquality) + } + + // Verify whether add yields the double when adding to itself + base = g.Base() + double := g.Base().Double() + if base.Add(base).Equal(double) != 1 { + t.Fatal(errExpectedEquality) + } + + // Verify whether 3*base = base + base + base + three := g.NewScalar().One() + three.Add(three) + three.Add(g.NewScalar().One()) + + mult := g.Base().Multiply(three) + e := g.Base().Add(g.Base()).Add(g.Base()) + + if e.Equal(mult) != 1 { + t.Fatal(errExpectedEquality) } } @@ -204,10 +244,15 @@ func elementTestNegate(t *testing.T, g crypto.Group) { func elementTestDouble(t *testing.T, g crypto.Group) { // Verify whether double works like adding base := g.Base() - - double := g.Base().Add(base) + double := g.Base().Add(g.Base()) if double.Equal(base.Double()) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) + } + + two := g.NewScalar().One().Add(g.NewScalar().One()) + mult := g.Base().Multiply(two) + if mult.Equal(double) != 1 { + t.Fatal(errExpectedEquality) } } @@ -216,82 +261,82 @@ func elementTestSubstract(t *testing.T, g crypto.Group) { // Verify whether subtrating yields the same element when given nil. if base.Subtract(nil).Equal(base) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } // Verify whether subtracting and then adding yields the same element. base2 := base.Add(base).Subtract(base) if base.Equal(base2) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } } func elementTestMultiply(t *testing.T, g crypto.Group) { - scalar := g.NewScalar().Random() + scalar := g.NewScalar() + + // base = base * 1 + base := g.Base() + mult := g.Base().Multiply(scalar.One()) + if base.Equal(mult) != 1 { + t.Fatal(errExpectedEquality) + } // Random scalar mult must not yield identity + scalar = g.NewScalar().Random() m := g.Base().Multiply(scalar) if m.IsIdentity() { t.Fatal("random scalar multiplication is identity") } - // base = base * 1 - base := g.Base() - if base.Equal(g.Base().Multiply(scalar.One())) != 1 { - t.Fatal(expectedEquality) + // 2 * base = base + base + twoG := g.Base().Add(g.Base()) + two := g.NewScalar().One().Add(g.NewScalar().One()) + mult = g.Base().Multiply(two) + + if mult.Equal(twoG) != 1 { + t.Fatal(errExpectedEquality) } // base * 0 = id if !g.Base().Multiply(scalar.Zero()).IsIdentity() { - t.Fatal(expectedIdentity) + t.Fatal(errExpectedIdentity) } // base * nil = id if !g.Base().Multiply(nil).IsIdentity() { - t.Fatal(expectedIdentity) - } -} - -func elementTestInversion(t *testing.T, g crypto.Group) { - scalar := g.NewScalar().Random() - base := g.Base() - m := g.Base().Multiply(scalar) - inv := m.Multiply(scalar.Invert()) - - if inv.Equal(base) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedIdentity) } } func elementTestIdentity(t *testing.T, g crypto.Group) { id := g.NewElement() if !id.IsIdentity() { - t.Fatal(expectedIdentity) + t.Fatal(errExpectedIdentity) } base := g.Base() if id.Equal(base.Subtract(base)) != 1 { - t.Fatal(expectedIdentity) + t.Fatal(errExpectedIdentity) } sub1 := g.Base().Double().Negate().Add(g.Base().Double()) sub2 := g.Base().Subtract(g.Base()) if sub1.Equal(sub2) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } if id.Equal(base.Multiply(nil)) != 1 { - t.Fatal(expectedIdentity) + t.Fatal(errExpectedIdentity) } if id.Equal(base.Multiply(g.NewScalar().Zero())) != 1 { - t.Fatal(expectedIdentity) + t.Fatal(errExpectedIdentity) } base = g.Base() neg := base.Copy().Negate() base.Add(neg) if id.Equal(base) != 1 { - t.Fatal(expectedIdentity) + t.Fatal(errExpectedIdentity) } } diff --git a/tests/groups_test.go b/tests/groups_test.go index 1913d85..0322d48 100644 --- a/tests/groups_test.go +++ b/tests/groups_test.go @@ -1,6 +1,6 @@ // SPDX-License-Group: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -19,8 +19,8 @@ const consideredAvailableFmt = "%v is considered available when it must not" func TestAvailability(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - if !group.id.Available() { - t.Errorf("'%s' is not available, but should be", group.id.String()) + if !group.group.Available() { + t.Errorf("'%s' is not available, but should be", group.group.String()) } }) } @@ -36,7 +36,7 @@ func TestNonAvailability(t *testing.T) { t.Errorf(consideredAvailableFmt, d) } - oob = crypto.Edwards25519Sha512 + 1 + oob = crypto.Secp256k1 + 1 if oob.Available() { t.Errorf(consideredAvailableFmt, oob) } @@ -44,8 +44,10 @@ func TestNonAvailability(t *testing.T) { func TestGroup_Base(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - if hex.EncodeToString(group.id.Base().Encode()) != group.basePoint { - t.Fatalf("Got wrong base element %s", hex.EncodeToString(group.id.Base().Encode())) + if hex.EncodeToString(group.group.Base().Encode()) != group.basePoint { + t.Fatalf("Got wrong base element\n\tgot : %s\n\twant: %s", + hex.EncodeToString(group.group.Base().Encode()), + group.basePoint) } }) } @@ -59,11 +61,12 @@ func TestDST(t *testing.T) { crypto.P384Sha384: app + "-V01-CS04-", crypto.P521Sha512: app + "-V01-CS05-", crypto.Edwards25519Sha512: app + "-V01-CS06-", + crypto.Secp256k1: app + "-V01-CS07-", } testAll(t, func(t2 *testing.T, group *testGroup) { - res := string(group.id.MakeDST(app, version)) - test := tests[group.id] + group.h2c + res := string(group.group.MakeDST(app, version)) + test := tests[group.group] + group.h2c if res != test { t.Errorf("Wrong DST. want %q, got %q", res, test) } @@ -72,7 +75,7 @@ func TestDST(t *testing.T) { func TestGroup_String(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - res := group.id.String() + res := group.group.String() ref := group.h2c if res != ref { t.Errorf("Wrong DST. want %q, got %q", ref, res) @@ -82,7 +85,7 @@ func TestGroup_String(t *testing.T) { func TestGroup_NewScalar(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - s := group.id.NewScalar().Encode() + s := group.group.NewScalar().Encode() for _, b := range s { if b != 0 { t.Fatalf("expected zero scalar, but got %v", hex.EncodeToString(s)) @@ -93,7 +96,7 @@ func TestGroup_NewScalar(t *testing.T) { func TestGroup_NewElement(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - e := hex.EncodeToString(group.id.NewElement().Encode()) + e := hex.EncodeToString(group.group.NewElement().Encode()) ref := group.identity if e != ref { @@ -104,16 +107,16 @@ func TestGroup_NewElement(t *testing.T) { func TestGroup_ScalarLength(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - if int(group.id.ScalarLength()) != group.scalarLength { - t.Fatalf("expected encoded scalar length %d, but got %d", group.scalarLength, group.id.ScalarLength()) + if int(group.group.ScalarLength()) != group.scalarLength { + t.Fatalf("expected encoded scalar length %d, but got %d", group.scalarLength, group.group.ScalarLength()) } }) } func TestGroup_ElementLength(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - if int(group.id.ElementLength()) != group.elementLength { - t.Fatalf("expected encoded element length %d, but got %d", group.elementLength, group.id.ElementLength()) + if int(group.group.ElementLength()) != group.elementLength { + t.Fatalf("expected encoded element length %d, but got %d", group.elementLength, group.group.ElementLength()) } }) } diff --git a/tests/vectors/P256_XMD-SHA-256_SSWU_NU_.json b/tests/h2c/P256_XMD-SHA-256_SSWU_NU_.json similarity index 100% rename from tests/vectors/P256_XMD-SHA-256_SSWU_NU_.json rename to tests/h2c/P256_XMD-SHA-256_SSWU_NU_.json diff --git a/tests/vectors/P256_XMD-SHA-256_SSWU_RO_.json b/tests/h2c/P256_XMD-SHA-256_SSWU_RO_.json similarity index 100% rename from tests/vectors/P256_XMD-SHA-256_SSWU_RO_.json rename to tests/h2c/P256_XMD-SHA-256_SSWU_RO_.json diff --git a/tests/vectors/P384_XMD-SHA-384_SSWU_NU_.json b/tests/h2c/P384_XMD-SHA-384_SSWU_NU_.json similarity index 100% rename from tests/vectors/P384_XMD-SHA-384_SSWU_NU_.json rename to tests/h2c/P384_XMD-SHA-384_SSWU_NU_.json diff --git a/tests/vectors/P384_XMD-SHA-384_SSWU_RO_.json b/tests/h2c/P384_XMD-SHA-384_SSWU_RO_.json similarity index 100% rename from tests/vectors/P384_XMD-SHA-384_SSWU_RO_.json rename to tests/h2c/P384_XMD-SHA-384_SSWU_RO_.json diff --git a/tests/vectors/P521_XMD-SHA-512_SSWU_NU_.json b/tests/h2c/P521_XMD-SHA-512_SSWU_NU_.json similarity index 100% rename from tests/vectors/P521_XMD-SHA-512_SSWU_NU_.json rename to tests/h2c/P521_XMD-SHA-512_SSWU_NU_.json diff --git a/tests/vectors/P521_XMD-SHA-512_SSWU_RO_.json b/tests/h2c/P521_XMD-SHA-512_SSWU_RO_.json similarity index 100% rename from tests/vectors/P521_XMD-SHA-512_SSWU_RO_.json rename to tests/h2c/P521_XMD-SHA-512_SSWU_RO_.json diff --git a/tests/vectors/edwards25519_XMD-SHA-512_ELL2_NU_.json b/tests/h2c/edwards25519_XMD-SHA-512_ELL2_NU_.json similarity index 100% rename from tests/vectors/edwards25519_XMD-SHA-512_ELL2_NU_.json rename to tests/h2c/edwards25519_XMD-SHA-512_ELL2_NU_.json diff --git a/tests/vectors/edwards25519_XMD-SHA-512_ELL2_RO_.json b/tests/h2c/edwards25519_XMD-SHA-512_ELL2_RO_.json similarity index 100% rename from tests/vectors/edwards25519_XMD-SHA-512_ELL2_RO_.json rename to tests/h2c/edwards25519_XMD-SHA-512_ELL2_RO_.json diff --git a/tests/h2c/secp256k1_XMD-SHA-256_SSWU_NU_.json b/tests/h2c/secp256k1_XMD-SHA-256_SSWU_NU_.json new file mode 100644 index 0000000..27039d8 --- /dev/null +++ b/tests/h2c/secp256k1_XMD-SHA-256_SSWU_NU_.json @@ -0,0 +1,90 @@ +{ + "L": "0x30", + "Z": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc24", + "ciphersuite": "secp256k1_XMD:SHA-256_SSWU_NU_", + "curve": "secp256k1", + "dst": "QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_NU_", + "expand": "XMD", + "field": { + "m": "0x1", + "p": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" + }, + "hash": "sha256", + "k": "0x80", + "map": { + "name": "SSWU" + }, + "randomOracle": false, + "vectors": [ + { + "P": { + "x": "0xa4792346075feae77ac3b30026f99c1441b4ecf666ded19b7522cf65c4c55c5b", + "y": "0x62c59e2a6aeed1b23be5883e833912b08ba06be7f57c0e9cdc663f31639ff3a7" + }, + "Q": { + "x": "0xa4792346075feae77ac3b30026f99c1441b4ecf666ded19b7522cf65c4c55c5b", + "y": "0x62c59e2a6aeed1b23be5883e833912b08ba06be7f57c0e9cdc663f31639ff3a7" + }, + "msg": "", + "u": [ + "0x0137fcd23bc3da962e8808f97474d097a6c8aa2881fceef4514173635872cf3b" + ] + }, + { + "P": { + "x": "0x3f3b5842033fff837d504bb4ce2a372bfeadbdbd84a1d2b678b6e1d7ee426b9d", + "y": "0x902910d1fef15d8ae2006fc84f2a5a7bda0e0407dc913062c3a493c4f5d876a5" + }, + "Q": { + "x": "0x3f3b5842033fff837d504bb4ce2a372bfeadbdbd84a1d2b678b6e1d7ee426b9d", + "y": "0x902910d1fef15d8ae2006fc84f2a5a7bda0e0407dc913062c3a493c4f5d876a5" + }, + "msg": "abc", + "u": [ + "0xe03f894b4d7caf1a50d6aa45cac27412c8867a25489e32c5ddeb503229f63a2e" + ] + }, + { + "P": { + "x": "0x07644fa6281c694709f53bdd21bed94dab995671e4a8cd1904ec4aa50c59bfdf", + "y": "0xc79f8d1dad79b6540426922f7fbc9579c3018dafeffcd4552b1626b506c21e7b" + }, + "Q": { + "x": "0x07644fa6281c694709f53bdd21bed94dab995671e4a8cd1904ec4aa50c59bfdf", + "y": "0xc79f8d1dad79b6540426922f7fbc9579c3018dafeffcd4552b1626b506c21e7b" + }, + "msg": "abcdef0123456789", + "u": [ + "0xe7a6525ae7069ff43498f7f508b41c57f80563c1fe4283510b322446f32af41b" + ] + }, + { + "P": { + "x": "0xb734f05e9b9709ab631d960fa26d669c4aeaea64ae62004b9d34f483aa9acc33", + "y": "0x03fc8a4a5a78632e2eb4d8460d69ff33c1d72574b79a35e402e801f2d0b1d6ee" + }, + "Q": { + "x": "0xb734f05e9b9709ab631d960fa26d669c4aeaea64ae62004b9d34f483aa9acc33", + "y": "0x03fc8a4a5a78632e2eb4d8460d69ff33c1d72574b79a35e402e801f2d0b1d6ee" + }, + "msg": "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "u": [ + "0xd97cf3d176a2f26b9614a704d7d434739d194226a706c886c5c3c39806bc323c" + ] + }, + { + "P": { + "x": "0x17d22b867658977b5002dbe8d0ee70a8cfddec3eec50fb93f36136070fd9fa6c", + "y": "0xe9178ff02f4dab73480f8dd590328aea99856a7b6cc8e5a6cdf289ecc2a51718" + }, + "Q": { + "x": "0x17d22b867658977b5002dbe8d0ee70a8cfddec3eec50fb93f36136070fd9fa6c", + "y": "0xe9178ff02f4dab73480f8dd590328aea99856a7b6cc8e5a6cdf289ecc2a51718" + }, + "msg": "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "u": [ + "0xa9ffbeee1d6e41ac33c248fb3364612ff591b502386c1bf6ac4aaf1ea51f8c3b" + ] + } + ] +} diff --git a/tests/h2c/secp256k1_XMD-SHA-256_SSWU_RO_.json b/tests/h2c/secp256k1_XMD-SHA-256_SSWU_RO_.json new file mode 100644 index 0000000..8a052d1 --- /dev/null +++ b/tests/h2c/secp256k1_XMD-SHA-256_SSWU_RO_.json @@ -0,0 +1,115 @@ +{ + "L": "0x30", + "Z": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc24", + "ciphersuite": "secp256k1_XMD:SHA-256_SSWU_RO_", + "curve": "secp256k1", + "dst": "QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_", + "expand": "XMD", + "field": { + "m": "0x1", + "p": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" + }, + "hash": "sha256", + "k": "0x80", + "map": { + "name": "SSWU" + }, + "randomOracle": true, + "vectors": [ + { + "P": { + "x": "0xc1cae290e291aee617ebaef1be6d73861479c48b841eaba9b7b5852ddfeb1346", + "y": "0x64fa678e07ae116126f08b022a94af6de15985c996c3a91b64c406a960e51067" + }, + "Q0": { + "x": "0x74519ef88b32b425a095e4ebcc84d81b64e9e2c2675340a720bb1a1857b99f1e", + "y": "0xc174fa322ab7c192e11748beed45b508e9fdb1ce046dee9c2cd3a2a86b410936" + }, + "Q1": { + "x": "0x44548adb1b399263ded3510554d28b4bead34b8cf9a37b4bd0bd2ba4db87ae63", + "y": "0x96eb8e2faf05e368efe5957c6167001760233e6dd2487516b46ae725c4cce0c6" + }, + "msg": "", + "u": [ + "0x6b0f9910dd2ba71c78f2ee9f04d73b5f4c5f7fc773a701abea1e573cab002fb3", + "0x1ae6c212e08fe1a5937f6202f929a2cc8ef4ee5b9782db68b0d5799fd8f09e16" + ] + }, + { + "P": { + "x": "0x3377e01eab42db296b512293120c6cee72b6ecf9f9205760bd9ff11fb3cb2c4b", + "y": "0x7f95890f33efebd1044d382a01b1bee0900fb6116f94688d487c6c7b9c8371f6" + }, + "Q0": { + "x": "0x07dd9432d426845fb19857d1b3a91722436604ccbbbadad8523b8fc38a5322d7", + "y": "0x604588ef5138cffe3277bbd590b8550bcbe0e523bbaf1bed4014a467122eb33f" + }, + "Q1": { + "x": "0xe9ef9794d15d4e77dde751e06c182782046b8dac05f8491eb88764fc65321f78", + "y": "0xcb07ce53670d5314bf236ee2c871455c562dd76314aa41f012919fe8e7f717b3" + }, + "msg": "abc", + "u": [ + "0x128aab5d3679a1f7601e3bdf94ced1f43e491f544767e18a4873f397b08a2b61", + "0x5897b65da3b595a813d0fdcc75c895dc531be76a03518b044daaa0f2e4689e00" + ] + }, + { + "P": { + "x": "0xbac54083f293f1fe08e4a70137260aa90783a5cb84d3f35848b324d0674b0e3a", + "y": "0x4436476085d4c3c4508b60fcf4389c40176adce756b398bdee27bca19758d828" + }, + "Q0": { + "x": "0x576d43ab0260275adf11af990d130a5752704f79478628761720808862544b5d", + "y": "0x643c4a7fb68ae6cff55edd66b809087434bbaff0c07f3f9ec4d49bb3c16623c3" + }, + "Q1": { + "x": "0xf89d6d261a5e00fe5cf45e827b507643e67c2a947a20fd9ad71039f8b0e29ff8", + "y": "0xb33855e0cc34a9176ead91c6c3acb1aacb1ce936d563bc1cee1dcffc806caf57" + }, + "msg": "abcdef0123456789", + "u": [ + "0xea67a7c02f2cd5d8b87715c169d055a22520f74daeb080e6180958380e2f98b9", + "0x7434d0d1a500d38380d1f9615c021857ac8d546925f5f2355319d823a478da18" + ] + }, + { + "P": { + "x": "0xe2167bc785333a37aa562f021f1e881defb853839babf52a7f72b102e41890e9", + "y": "0xf2401dd95cc35867ffed4f367cd564763719fbc6a53e969fb8496a1e6685d873" + }, + "Q0": { + "x": "0x9c91513ccfe9520c9c645588dff5f9b4e92eaf6ad4ab6f1cd720d192eb58247a", + "y": "0xc7371dcd0134412f221e386f8d68f49e7fa36f9037676e163d4a063fbf8a1fb8" + }, + "Q1": { + "x": "0x10fee3284d7be6bd5912503b972fc52bf4761f47141a0015f1c6ae36848d869b", + "y": "0x0b163d9b4bf21887364332be3eff3c870fa053cf508732900fc69a6eb0e1b672" + }, + "msg": "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "u": [ + "0xeda89a5024fac0a8207a87e8cc4e85aa3bce10745d501a30deb87341b05bcdf5", + "0xdfe78cd116818fc2c16f3837fedbe2639fab012c407eac9dfe9245bf650ac51d" + ] + }, + { + "P": { + "x": "0xe3c8d35aaaf0b9b647e88a0a0a7ee5d5bed5ad38238152e4e6fd8c1f8cb7c998", + "y": "0x8446eeb6181bf12f56a9d24e262221cc2f0c4725c7e3803024b5888ee5823aa6" + }, + "Q0": { + "x": "0xb32b0ab55977b936f1e93fdc68cec775e13245e161dbfe556bbb1f72799b4181", + "y": "0x2f5317098360b722f132d7156a94822641b615c91f8663be69169870a12af9e8" + }, + "Q1": { + "x": "0x148f98780f19388b9fa93e7dc567b5a673e5fca7079cd9cdafd71982ec4c5e12", + "y": "0x3989645d83a433bc0c001f3dac29af861f33a6fd1e04f4b36873f5bff497298a" + }, + "msg": "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "u": [ + "0x8d862e7e7e23d7843fe16d811d46d7e6480127a6b78838c277bca17df6900e9f", + "0x68071d2530f040f081ba818d3c7188a94c900586761e9115efa47ae9bd847938" + ] + } + ] +} diff --git a/tests/h2c_test.go b/tests/h2c_test.go index 221dbf0..51b925a 100644 --- a/tests/h2c_test.go +++ b/tests/h2c_test.go @@ -1,6 +1,6 @@ // SPDX-License-Group: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -25,20 +25,31 @@ import ( edwards255192 "github.com/bytemare/crypto/internal/edwards25519" ) -type vectors struct { - Ciphersuite string `json:"ciphersuite"` - Dst string `json:"dst"` - Vectors []vector `json:"vectors"` +const hashToCurveVectorsFileLocation = "h2c" + +type h2cVectors struct { + Ciphersuite string `json:"ciphersuite"` + Dst string `json:"dst"` + Vectors []h2cVector `json:"vectors"` group crypto.Group } -type vector struct { - *vectors +type h2cVector struct { + *h2cVectors P struct { X string `json:"x"` Y string `json:"y"` } `json:"P"` - Msg string `json:"msg"` + Q0 struct { + X string `json:"x"` + Y string `json:"y"` + } `json:"Q0"` + Q1 struct { + X string `json:"x"` + Y string `json:"y"` + } `json:"Q1"` + Msg string `json:"msg"` + U []string `json:"u"` } func ecFromGroup(g crypto.Group) elliptic.Curve { @@ -54,7 +65,7 @@ func ecFromGroup(g crypto.Group) elliptic.Curve { } } -func vectorToNistBig(x, y string) (*big.Int, *big.Int) { +func vectorToBig(x, y string) (*big.Int, *big.Int) { xb, ok := new(big.Int).SetString(x, 0) if !ok { panic("invalid x") @@ -93,17 +104,32 @@ func vectorToEdwards25519(t *testing.T, x, y string) *edwards25519.Point { return edwards255192.AffineToEdwards(u, v) } -func (v *vector) run(t *testing.T) { +func vectorToSecp256k1(x, y string) []byte { + var output [33]byte + + yb, _ := hex.DecodeString(y[2:]) + yint := new(big.Int).SetBytes(yb) + output[0] = byte(2 | yint.Bit(0)&1) + + xb, _ := hex.DecodeString(x[2:]) + copy(output[1:], xb) + + return output[:] +} + +func (v *h2cVector) run(t *testing.T) { var expected string switch v.group { case crypto.P256Sha256, crypto.P384Sha384, crypto.P521Sha512: e := ecFromGroup(v.group) - x, y := vectorToNistBig(v.P.X, v.P.Y) + x, y := vectorToBig(v.P.X, v.P.Y) expected = hex.EncodeToString(elliptic.MarshalCompressed(e, x, y)) case crypto.Edwards25519Sha512: p := vectorToEdwards25519(t, v.P.X, v.P.Y) expected = hex.EncodeToString(p.Bytes()) + case crypto.Secp256k1: + expected = hex.EncodeToString(vectorToSecp256k1(v.P.X, v.P.Y)) } switch v.Ciphersuite[len(v.Ciphersuite)-3:] { @@ -111,7 +137,7 @@ func (v *vector) run(t *testing.T) { p := v.group.HashToGroup([]byte(v.Msg), []byte(v.Dst)) if hex.EncodeToString(p.Encode()) != expected { - t.Fatalf("Unexpected HashToGroup output.\n\tExpected %q\n\tgot %q", expected, hex.EncodeToString(p.Encode())) + t.Fatalf("Unexpected HashToGroup output.\n\tExpected %q\n\tgot \t%q", expected, hex.EncodeToString(p.Encode())) } case "NU_": p := v.group.EncodeToGroup([]byte(v.Msg), []byte(v.Dst)) @@ -124,24 +150,24 @@ func (v *vector) run(t *testing.T) { } } -func (v *vectors) runCiphersuite(t *testing.T) { +func (v *h2cVectors) runCiphersuite(t *testing.T) { for _, vector := range v.Vectors { - vector.vectors = v + vector.h2cVectors = v t.Run(v.Ciphersuite, vector.run) } } func TestHashToGroupVectors(t *testing.T) { - groups := testGroups() getGroup := func(ciphersuite string) (crypto.Group, bool) { - for _, group := range groups { + for _, group := range testTable { if group.h2c == ciphersuite || group.e2c == ciphersuite { - return group.id, true + return group.group, true } } return 0, false } - if err := filepath.Walk("vectors", + + if err := filepath.Walk(hashToCurveVectorsFileLocation, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -167,7 +193,7 @@ func TestHashToGroupVectors(t *testing.T) { t.Fatal(errRead) } - var v vectors + var v h2cVectors errJSON := json.Unmarshal(val, &v) if errJSON != nil { t.Fatal(errJSON) diff --git a/tests/nist_test.go b/tests/nist_test.go index 036bd87..7a1f185 100644 --- a/tests/nist_test.go +++ b/tests/nist_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -16,6 +16,8 @@ import ( "testing" ) +var errParamNotOnCurve = errors.New("point is not on curve") + // TestNistInvalidCoordinates tests big.Int values that are not valid field elements // (negative or bigger than P). They are expected to return false from // IsOnCurve, all other behavior is undefined. @@ -78,8 +80,6 @@ func solveNist(x, b, order *big.Int) *big.Int { return x3 } -var errParamNotOnCurve = errors.New("point is not on curve") - type solver func(x *big.Int) *big.Int func isOnCurve(x, y, order *big.Int, solve solver) error { diff --git a/tests/ristretto_hash_test.go b/tests/ristretto_hash_test.go index 7dfffb3..37220e9 100644 --- a/tests/ristretto_hash_test.go +++ b/tests/ristretto_hash_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -17,21 +17,21 @@ import ( "github.com/bytemare/crypto/internal/ristretto" ) -type h2gTestBytes struct { - x, dst, p []byte +type ristrettoH2gTestBytes struct { + input, dst, encodedElement []byte } -type h2gTest struct { - x string - dst string - p string +type ristrettoH2gTest struct { + input string + dst string + encodedElement string } -func (h *h2gTest) decode() (*h2gTestBytes, error) { - b := &h2gTestBytes{} +func (h *ristrettoH2gTest) decode() (*ristrettoH2gTestBytes, error) { + b := &ristrettoH2gTestBytes{} var err error - b.x, err = hex.DecodeString(h.x) + b.input, err = hex.DecodeString(h.input) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func (h *h2gTest) decode() (*h2gTestBytes, error) { return nil, err } - b.p, err = hex.DecodeString(h.p) + b.encodedElement, err = hex.DecodeString(h.encodedElement) if err != nil { return nil, err } @@ -49,33 +49,33 @@ func (h *h2gTest) decode() (*h2gTestBytes, error) { return b, err } -var h2gTests = []h2gTest{ +var ristrettoH2gTests = []ristrettoH2gTest{ { - x: "68656c6c6f", - dst: "564f50524630362d48617368546f47726f75702d000001", - p: "723c88cc59988d39889aa607b6696d423e7718a36d4825e0f940b3c3a534396a", + input: "68656c6c6f", + dst: "564f50524630362d48617368546f47726f75702d000001", + encodedElement: "723c88cc59988d39889aa607b6696d423e7718a36d4825e0f940b3c3a534396a", }, { - x: "776f726c64", - dst: "564f50524630362d48617368546f47726f75702d000001", - p: "a47c0a13c42a26ab06e60d2e251ba591334a289f4fdfe3b17ed3321a9527f44c", + input: "776f726c64", + dst: "564f50524630362d48617368546f47726f75702d000001", + encodedElement: "a47c0a13c42a26ab06e60d2e251ba591334a289f4fdfe3b17ed3321a9527f44c", }, } func TestRistretto_HashToGroup(t *testing.T) { - for i, test := range h2gTests { + for i, test := range ristrettoH2gTests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { v, err := test.decode() if err != nil { t.Fatalf("%d : %v", i, err) } - e := ristretto.Group{}.HashToGroup(v.x, v.dst) + e := ristretto.Group{}.HashToGroup(v.input, v.dst) - if !bytes.Equal(e.Encode(), v.p) { + if !bytes.Equal(e.Encode(), v.encodedElement) { t.Fatalf( "Mappings do not match.\n\tExpected: %v\n\tActual: %v\n", - hex.EncodeToString(v.p), + hex.EncodeToString(v.encodedElement), hex.EncodeToString(e.Encode()), ) } diff --git a/tests/ristretto_test.go b/tests/ristretto_test.go index 7bc5c8c..ba33d34 100644 --- a/tests/ristretto_test.go +++ b/tests/ristretto_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -18,8 +18,8 @@ import ( ) const ( - goodScalar = "243170e83a77812893c234314116e1c007671adfe23325011e3827c1b2ff8d0a" - basePoint = "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + goodRistrettoScalar = "243170e83a77812893c234314116e1c007671adfe23325011e3827c1b2ff8d0a" + ristrettoBasePoint = "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" testApp = "testRistretto255" testVersion = "0.0" @@ -36,34 +36,34 @@ type ristrettoTest struct { elem bool // says whether the element is supposed to be valid } -var tests = []ristrettoTest{ +var ristrettoTests = []ristrettoTest{ { name: "Valid element (base point), valid scalar", hashID: crypto.SHA512, app: testApp, version: testVersion, - scalar: goodScalar, - element: basePoint, + scalar: goodRistrettoScalar, + element: ristrettoBasePoint, scal: true, elem: true, }, { - name: "Valid element (base point), wrong scalar (size)", + name: "Valid element (base point), bad scalar (size)", hashID: crypto.SHA512, app: testApp, version: testVersion, scalar: "243170e83a77812893c234314116e1c007671adfe23325011e3827c1b2ff8d", - element: basePoint, + element: ristrettoBasePoint, scal: false, elem: true, }, { - name: "Valid element (base point), wrong scalar (encoding)", + name: "Valid element (base point), bad scalar (encoding)", hashID: crypto.SHA512, app: testApp, version: testVersion, scalar: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - element: basePoint, + element: ristrettoBasePoint, scal: false, elem: true, }, @@ -72,7 +72,7 @@ var tests = []ristrettoTest{ hashID: crypto.SHA512, app: testApp, version: testVersion, - scalar: goodScalar, + scalar: goodRistrettoScalar, element: "2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08", scal: true, elem: false, @@ -100,7 +100,7 @@ var tests = []ristrettoTest{ } func TestRistrettoScalar(t *testing.T) { - for _, tt := range tests { + for _, tt := range ristrettoTests { t.Run(tt.name, func(t *testing.T) { // Grab the bytes of the encoding encoding, err := hex.DecodeString(tt.scalar) @@ -149,16 +149,16 @@ func TestRistrettoElement(t *testing.T) { bp := ristretto.Group{}.NewElement().(*ristretto.Element).Base() // Grab the bytes of the encoding - encoding, err := hex.DecodeString(tests[0].element) + encoding, err := hex.DecodeString(ristrettoTests[0].element) if err != nil { - t.Fatalf("%s: bad hex encoding in test vector: %v", tests[0].name, err) + t.Fatalf("%s: bad hex encoding in test vector: %v", ristrettoTests[0].name, err) } if !bytes.Equal(bp.Encode(), encoding) { - t.Fatalf("%s: element doesn't decode to basepoint", tests[0].name) + t.Fatalf("%s: element doesn't decode to basepoint", ristrettoTests[0].name) } - for _, tt := range tests { + for _, tt := range ristrettoTests { t.Run(tt.name, func(t *testing.T) { // Grab the bytes of the encoding encoding, err := hex.DecodeString(tt.element) diff --git a/tests/scalar_test.go b/tests/scalar_test.go index b3872c7..3bdb9f1 100644 --- a/tests/scalar_test.go +++ b/tests/scalar_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -31,27 +31,27 @@ func TestScalar_WrongInput(t *testing.T) { } testAll(t, func(t2 *testing.T, group *testGroup) { - scalar := group.id.NewScalar() + scalar := group.group.NewScalar() methods := []func(arg *crypto.Scalar) *crypto.Scalar{ scalar.Add, scalar.Subtract, scalar.Multiply, scalar.Set, } var wrongGroup crypto.Group - switch group.id { + switch group.group { // The following is arbitrary, and simply aims at confusing identifiers - case crypto.Ristretto255Sha512, crypto.Edwards25519Sha512: + case crypto.Ristretto255Sha512, crypto.Edwards25519Sha512, crypto.Secp256k1: wrongGroup = crypto.P256Sha256 case crypto.P256Sha256, crypto.P384Sha384, crypto.P521Sha512: wrongGroup = crypto.Ristretto255Sha512 // Add a special test for nist groups, using a different field - wrongfield := ((group.id + 1) % 3) + 3 + wrongfield := ((group.group + 1) % 3) + 3 if err := testPanic("wrong field", internal.ErrWrongField, exec(scalar.Add, wrongfield.NewScalar())); err != nil { t.Fatal(err) } default: - t.Fatalf("Invalid group id %d", group.id) + t.Fatalf("Invalid group id %d", group.group) } for _, f := range methods { @@ -91,7 +91,7 @@ func testScalarCopySet(t *testing.T, scalar, other *crypto.Scalar) { func TestScalarCopy(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - random := group.id.NewScalar().Random() + random := group.group.NewScalar().Random() cpy := random.Copy() testScalarCopySet(t, random, cpy) }) @@ -99,8 +99,8 @@ func TestScalarCopy(t *testing.T) { func TestScalarSet(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - random := group.id.NewScalar().Random() - other := group.id.NewScalar() + random := group.group.NewScalar().Random() + other := group.group.NewScalar() other.Set(random) testScalarCopySet(t, random, other) }) @@ -110,7 +110,7 @@ func TestScalarSetInt(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { i := big.NewInt(0) - s := group.id.NewScalar() + s := group.group.NewScalar() if err := s.SetInt(i); err != nil { t.Fatal(err) } @@ -124,11 +124,11 @@ func TestScalarSetInt(t *testing.T) { t.Fatal(err) } - if s.Equal(group.id.NewScalar().One()) != 1 { + if s.Equal(group.group.NewScalar().One()) != 1 { t.Fatal("expected 1") } - order, ok := new(big.Int).SetString(group.id.Order(), 10) + order, ok := new(big.Int).SetString(group.group.Order(), 10) if !ok { t.Fatal("conversion error") } @@ -138,14 +138,14 @@ func TestScalarSetInt(t *testing.T) { } if !s.IsZero() { - t.Fatal("expected 0") + t.Fatalf("expected 0, got %v\n%v", s.Encode(), order) } }) } func TestScalar_EncodedLength(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - encodedScalar := group.id.NewScalar().Random().Encode() + encodedScalar := group.group.NewScalar().Random().Encode() if len(encodedScalar) != group.scalarLength { t.Fatalf("Encode() is expected to return %d bytes, but returned %d bytes", group.scalarLength, encodedScalar) } @@ -154,16 +154,16 @@ func TestScalar_EncodedLength(t *testing.T) { func TestScalar_Arithmetic(t *testing.T) { testAll(t, func(t2 *testing.T, group *testGroup) { - scalarTestZero(t, group.id) - scalarTestOne(t, group.id) - scalarTestEqual(t, group.id) - scalarTestLessOrEqual(t, group.id) - scalarTestRandom(t, group.id) - scalarTestAdd(t, group.id) - scalarTestSubtract(t, group.id) - scalarTestMultiply(t, group.id) - scalarTestPow(t, group.id) - scalarTestInvert(t, group.id) + scalarTestZero(t, group.group) + scalarTestOne(t, group.group) + scalarTestEqual(t, group.group) + scalarTestLessOrEqual(t, group.group) + scalarTestRandom(t, group.group) + scalarTestAdd(t, group.group) + scalarTestSubtract(t, group.group) + scalarTestMultiply(t, group.group) + scalarTestPow(t, group.group) + scalarTestInvert(t, group.group) }) } @@ -193,7 +193,7 @@ func scalarTestOne(t *testing.T, g crypto.Group) { one := g.NewScalar().One() m := one.Copy() if one.Equal(m.Multiply(m)) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } } @@ -209,13 +209,13 @@ func scalarTestEqual(t *testing.T, g crypto.Group) { zero2 := g.NewScalar().Zero() if zero.Equal(zero2) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } random := g.NewScalar().Random() cpy := random.Copy() if random.Equal(cpy) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } random2 := g.NewScalar().Random() @@ -248,13 +248,20 @@ func scalarTestLessOrEqual(t *testing.T, g crypto.Group) { if two.LessOrEqual(two) != 1 { t.Fatal("expected 2 == 2") } + + s := g.NewScalar().Random() + r := s.Copy().Add(g.NewScalar().One()) + + if s.LessOrEqual(r) != 1 { + t.Fatal("expected s < s + 1") + } } func scalarTestAdd(t *testing.T, g crypto.Group) { r := g.NewScalar().Random() cpy := r.Copy() if r.Add(nil).Equal(cpy) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } } @@ -262,7 +269,7 @@ func scalarTestSubtract(t *testing.T, g crypto.Group) { r := g.NewScalar().Random() cpy := r.Copy() if r.Subtract(nil).Equal(cpy) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } } @@ -390,6 +397,7 @@ func scalarTestPow(t *testing.T, g crypto.Group) { exp.Random() switch g { + // These are in little-endian case crypto.Ristretto255Sha512, crypto.Edwards25519Sha512: e := s.Encode() for i, j := 0, len(e)-1; i < j; i++ { @@ -427,13 +435,13 @@ func scalarTestInvert(t *testing.T, g crypto.Group) { i := s.Copy().Invert().Multiply(sqr) if i.Equal(s) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } s = g.NewScalar().Random() square := s.Copy().Multiply(s) inv := square.Copy().Invert() if s.One().Equal(square.Multiply(inv)) != 1 { - t.Fatal(expectedEquality) + t.Fatal(errExpectedEquality) } } diff --git a/tests/table_test.go b/tests/table_test.go new file mode 100644 index 0000000..96fca72 --- /dev/null +++ b/tests/table_test.go @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C)2020-2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +package group_test + +import ( + "testing" + + "github.com/bytemare/crypto" +) + +func testAll(t *testing.T, f func(*testing.T, *testGroup)) { + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + f(t, test) + }) + } +} + +// a testGroup references some parameters of a Group. +type testGroup struct { + name string + h2c string + e2c string + basePoint string + identity string + elementLength int + scalarLength int + group crypto.Group +} + +var testTable = []*testGroup{ + { + "Ristretto255", + "ristretto255_XMD:SHA-512_R255MAP_RO_", + "ristretto255_XMD:SHA-512_R255MAP_RO_", + ristrettoBasePoint, + "0000000000000000000000000000000000000000000000000000000000000000", + 32, + 32, + 1, + }, + { + "P256", + "P256_XMD:SHA-256_SSWU_RO_", + "P256_XMD:SHA-256_SSWU_NU_", + "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "000000000000000000000000000000000000000000000000000000000000000000", + 33, + 32, + 3, + }, + { + "P384", + "P384_XMD:SHA-384_SSWU_RO_", + "P384_XMD:SHA-384_SSWU_NU_", + "03aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + 49, + 48, + 4, + }, + { + "P521", + "P521_XMD:SHA-512_SSWU_RO_", + "P521_XMD:SHA-512_SSWU_NU_", + "0200c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66", + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + 67, + 66, + 5, + }, + { + "Edwards25519", + "edwards25519_XMD:SHA-512_ELL2_RO_", + "edwards25519_XMD:SHA-512_ELL2_NU_", + "5866666666666666666666666666666666666666666666666666666666666666", + "0100000000000000000000000000000000000000000000000000000000000000", + 32, + 32, + 6, + }, + { + "Secp256k1", + "secp256k1_XMD:SHA-256_SSWU_RO_", + "secp256k1_XMD:SHA-256_SSWU_NU_", + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "000000000000000000000000000000000000000000000000000000000000000000", + 33, + 32, + 7, + }, +} diff --git a/tests/utils_test.go b/tests/utils_test.go index 88a5c66..218b74a 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2020-2023 Daniel Bourdrez. All Rights Reserved. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree or at @@ -11,88 +11,12 @@ package group_test import ( "bytes" "encoding" + "encoding/hex" "errors" "fmt" "testing" - - "github.com/bytemare/crypto" ) -// a testGroup references some components of a Group. -type testGroup struct { - name string - h2c string - e2c string - basePoint string - identity string - elementLength int - scalarLength int - id crypto.Group -} - -func testGroups() []*testGroup { - return []*testGroup{ - { - "Ristretto255", - "ristretto255_XMD:SHA-512_R255MAP_RO_", - "ristretto255_XMD:SHA-512_R255MAP_RO_", - "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", - "0000000000000000000000000000000000000000000000000000000000000000", - 32, - 32, - 1, - }, - { - "P256", - "P256_XMD:SHA-256_SSWU_RO_", - "P256_XMD:SHA-256_SSWU_NU_", - "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", - "000000000000000000000000000000000000000000000000000000000000000000", - 33, - 32, - 3, - }, - { - "P384", - "P384_XMD:SHA-384_SSWU_RO_", - "P384_XMD:SHA-384_SSWU_NU_", - "03aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", - "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - 49, - 48, - 4, - }, - { - "P521", - "P521_XMD:SHA-512_SSWU_RO_", - "P521_XMD:SHA-512_SSWU_NU_", - "0200c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66", - "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - 67, - 66, - 5, - }, - { - "Edwards25519", - "edwards25519_XMD:SHA-512_ELL2_RO_", - "edwards25519_XMD:SHA-512_ELL2_NU_", - "5866666666666666666666666666666666666666666666666666666666666666", - "0100000000000000000000000000000000000000000000000000000000000000", - 32, - 32, - 6, - }, - } -} - -func testAll(t *testing.T, f func(*testing.T, *testGroup)) { - for _, test := range testGroups() { - t.Run(test.name, func(t *testing.T) { - f(t, test) - }) - } -} - var ( errNoPanic = errors.New("no panic") errNoPanicMessage = errors.New("panic but no message") @@ -152,7 +76,8 @@ func testEncoding(t *testing.T, thing1, thing2 serde) { marshalled, _ := thing1.MarshalBinary() if !bytes.Equal(encoded, marshalled) { - t.Fatalf("Encode() and MarshalBinary() are expected to have the same output.\twant: %v\tgot : %v", encoded, marshalled) + t.Fatalf("Encode() and MarshalBinary() are expected to have the same output."+ + "\twant: %v\tgot : %v", encoded, marshalled) } if err := thing2.Decode(nil); err == nil { @@ -160,7 +85,7 @@ func testEncoding(t *testing.T, thing1, thing2 serde) { } if err := thing2.Decode(encoded); err != nil { - t.Fatalf("Decode() failed on a valid encoding: %v", err) + t.Fatalf("Decode() failed on a valid encoding: %v. Value: %v", err, hex.EncodeToString(encoded)) } if err := thing2.UnmarshalBinary(encoded); err != nil { @@ -170,11 +95,12 @@ func testEncoding(t *testing.T, thing1, thing2 serde) { func TestEncoding(t *testing.T) { testAll(t, func(t *testing.T, group *testGroup) { - scalar := group.id.NewScalar().Random() - testEncoding(t, scalar, group.id.NewScalar()) + g := group.group + scalar := g.NewScalar().Random() + testEncoding(t, scalar, g.NewScalar()) - scalar = group.id.NewScalar().Random() - element := group.id.Base().Multiply(scalar) - testEncoding(t, element, group.id.NewElement()) + scalar = g.NewScalar().Random() + element := g.Base().Multiply(scalar) + testEncoding(t, element, g.NewElement()) }) }