diff --git a/.github/.golangci.yml b/.github/.golangci.yml index dbaa240..40e4e8a 100644 --- a/.github/.golangci.yml +++ b/.github/.golangci.yml @@ -99,7 +99,7 @@ linters-settings: threshold: 100 errcheck: check-type-assertions: true - check-blank: true + check-blank: false exclude-functions: - (*crypto/Element).MarshalBinary - (*crypto/Scalar).MarshalBinary diff --git a/go.mod b/go.mod index d76314f..ea1bab6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( filippo.io/edwards25519 v1.1.0 filippo.io/nistec v0.0.3 github.com/bytemare/hash2curve v0.3.0 - github.com/bytemare/secp256k1 v0.1.4 + github.com/bytemare/secp256k1 v0.1.6 github.com/gtank/ristretto255 v0.1.2 ) diff --git a/go.sum b/go.sum index dce1810..6146add 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/bytemare/hash v0.3.0 h1:RqFMt3mqpF7UxLdjBrsOZm/2cz0cQiAOnYc9gDLopWE= github.com/bytemare/hash v0.3.0/go.mod h1:YKOBchL0l8hRLFinVCL8YUKokGNIMhrWEHPHo3EV7/M= github.com/bytemare/hash2curve v0.3.0 h1:41Npcbc+u/E252A5aCMtxDcz7JPkkX1QzShneTFm4eg= github.com/bytemare/hash2curve v0.3.0/go.mod h1:itj45U8uqvCtWC0eCswIHVHswXcEHkpFui7gfJdPSfQ= -github.com/bytemare/secp256k1 v0.1.4 h1:6F1yP6RiUiWwH7AsGHsHktmHm24QcetdDcc39roBd2M= -github.com/bytemare/secp256k1 v0.1.4/go.mod h1:Pxb9miDs8PTt5mOktvvXiRflvLxI1wdxbXrc6IYsaho= +github.com/bytemare/secp256k1 v0.1.6 h1:5pOA84UBBTPTUmCkjtH6jHrbvZSh2kyxG0mW/OjSih0= +github.com/bytemare/secp256k1 v0.1.6/go.mod h1:Zr7o3YCog5jKx5JwgYbj984gRIqVioTDZMSDo1y0zgE= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= diff --git a/groups.go b/groups.go index 462f86e..c84ce33 100644 --- a/groups.go +++ b/groups.go @@ -151,7 +151,7 @@ func (g Group) ElementLength() int { } // Order returns the order of the canonical group of scalars. -func (g Group) Order() string { +func (g Group) Order() []byte { return g.get().Order() } diff --git a/internal/edwards25519/group.go b/internal/edwards25519/group.go index 51bb6ea..0ab5fb3 100644 --- a/internal/edwards25519/group.go +++ b/internal/edwards25519/group.go @@ -11,6 +11,7 @@ package edwards25519 import ( "crypto" + "slices" ed "filippo.io/edwards25519" @@ -87,6 +88,6 @@ func (g Group) ElementLength() int { } // Order returns the order of the canonical group of scalars. -func (g Group) Order() string { - return orderPrime +func (g Group) Order() []byte { + return slices.Clone(orderBytes) } diff --git a/internal/edwards25519/scalar.go b/internal/edwards25519/scalar.go index c6a3e41..06f8bab 100644 --- a/internal/edwards25519/scalar.go +++ b/internal/edwards25519/scalar.go @@ -22,9 +22,17 @@ import ( const inputLength = 64 var ( - scZero Scalar - scOne Scalar - order big.Int + scZero Scalar + scOne Scalar + order big.Int + scMinusOne = []byte{ + 236, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, + } + orderBytes = []byte{ + 237, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, + } ) func init() { @@ -84,6 +92,12 @@ func (s *Scalar) One() internal.Scalar { return s } +// MinusOne sets the scalar to order-1, and returns it. +func (s *Scalar) MinusOne() internal.Scalar { + _ = s.decodeScalar(scMinusOne) + 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 { @@ -278,14 +292,11 @@ func (s *Scalar) SetUInt64(i uint64) internal.Scalar { encoded := make([]byte, canonicalEncodingLength) binary.LittleEndian.PutUint64(encoded, i) - sc, err := decodeScalar(encoded) - if err != nil { + if err := s.decodeScalar(encoded); err != nil { // This cannot happen, since any uint64 is smaller than the order. panic(fmt.Sprintf("unexpected decoding of uint64 scalar: %s", err)) } - s.set(sc) - return s } @@ -320,33 +331,25 @@ func (s *Scalar) Encode() []byte { return s.scalar.Bytes() } -func decodeScalar(scalar []byte) (*ed.Scalar, error) { +func (s *Scalar) decodeScalar(scalar []byte) error { if len(scalar) == 0 { - return nil, internal.ErrParamNilScalar + return internal.ErrParamNilScalar } if len(scalar) != canonicalEncodingLength { - return nil, internal.ErrParamScalarLength + return internal.ErrParamScalarLength } - s := ed.NewScalar() - if _, err := s.SetCanonicalBytes(scalar); err != nil { - return nil, fmt.Errorf("%w", err) + if _, err := s.scalar.SetCanonicalBytes(scalar); err != nil { + return fmt.Errorf("%w", err) } - return s, nil + return nil } // Decode sets the receiver to a decoding of the input data, and returns an error on failure. func (s *Scalar) Decode(in []byte) error { - sc, err := decodeScalar(in) - if err != nil { - return err - } - - s.scalar = *sc - - return nil + return s.decodeScalar(in) } // Hex returns the fixed-sized hexadecimal encoding of s. diff --git a/internal/field/field.go b/internal/field/field.go index 74f596e..cc5dd9f 100644 --- a/internal/field/field.go +++ b/internal/field/field.go @@ -76,6 +76,11 @@ func (f Field) Order() *big.Int { return f.order } +// PMinusOne returns p-1, the greatest integer below the order. +func (f Field) PMinusOne() *big.Int { + return new(big.Int).Sub(f.order, big.NewInt(1)) +} + // ByteLen returns the length of the field order in bytes. func (f Field) ByteLen() int { return f.byteLen diff --git a/internal/group.go b/internal/group.go index a0949ff..e00e531 100644 --- a/internal/group.go +++ b/internal/group.go @@ -47,5 +47,5 @@ type Group interface { ElementLength() int // Order returns the order of the canonical group of scalars. - Order() string + Order() []byte } diff --git a/internal/nist/group.go b/internal/nist/group.go index 4f515b6..710cd28 100644 --- a/internal/nist/group.go +++ b/internal/nist/group.go @@ -159,8 +159,9 @@ func (g Group[P]) ElementLength() int { } // Order returns the order of the canonical group of scalars. -func (g Group[P]) Order() string { - return g.scalarField.Order().String() +func (g Group[P]) Order() []byte { + out := make([]byte, g.scalarField.ByteLen()) + return g.scalarField.Order().FillBytes(out) } var ( diff --git a/internal/nist/scalar.go b/internal/nist/scalar.go index dc536a4..806563e 100644 --- a/internal/nist/scalar.go +++ b/internal/nist/scalar.go @@ -73,6 +73,12 @@ func (s *Scalar) One() internal.Scalar { return s } +// MinusOne sets the scalar to order-1, and returns it. +func (s *Scalar) MinusOne() internal.Scalar { + s.scalar.Set(s.field.PMinusOne()) + return s +} + // Random sets s 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 { diff --git a/internal/ristretto/ristretto.go b/internal/ristretto/ristretto.go index 0955dd1..828ac3a 100644 --- a/internal/ristretto/ristretto.go +++ b/internal/ristretto/ristretto.go @@ -11,6 +11,7 @@ package ristretto import ( "crypto" + "slices" "github.com/bytemare/hash2curve" "github.com/gtank/ristretto255" @@ -26,12 +27,6 @@ const ( // H2C represents the hash-to-curve string identifier. H2C = "ristretto255_XMD:SHA-512_R255MAP_RO_" - - // orderPrime represents curve25519's subgroup prime-order - // = 2^252 + 27742317777372353535851937790883648493 - // = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed - // cofactor h = 8. - orderPrime = "7237005577332262213973186563042994240857116359379907606001950938285454250989" ) // Group represents the Ristretto255 group. It exposes a prime-order group API with hash-to-curve operations. @@ -99,6 +94,6 @@ func (g Group) ElementLength() int { } // Order returns the order of the canonical group of scalars. -func (g Group) Order() string { - return orderPrime +func (g Group) Order() []byte { + return slices.Clone(orderBytes) } diff --git a/internal/ristretto/scalar.go b/internal/ristretto/scalar.go index 72fb629..fea20ff 100644 --- a/internal/ristretto/scalar.go +++ b/internal/ristretto/scalar.go @@ -13,7 +13,6 @@ import ( "encoding/binary" "encoding/hex" "fmt" - "math/big" "github.com/gtank/ristretto255" @@ -23,9 +22,21 @@ import ( const canonicalEncodingLength = 32 var ( - scZero = &Scalar{*ristretto255.NewScalar()} - scOne Scalar - order big.Int + scZero = &Scalar{*ristretto255.NewScalar()} + scOne Scalar + scMinusOne = []byte{ + 236, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, + } + // orderBytes represents curve25519's subgroup prime-order + // = 2^252 + 27742317777372353535851937790883648493 + // = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed + // = 7237005577332262213973186563042994240857116359379907606001950938285454250989 + // cofactor h = 8. + orderBytes = []byte{ + 237, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, + } ) func init() { @@ -36,10 +47,6 @@ func init() { }); err != nil { panic(err) } - - if _, ok := order.SetString(orderPrime, 10); !ok { - panic(internal.ErrBigIntConversion) - } } // Scalar implements the Scalar interface for Ristretto255 group scalars. @@ -77,6 +84,12 @@ func (s *Scalar) One() internal.Scalar { return s } +// MinusOne sets the scalar to order-1, and returns it. +func (s *Scalar) MinusOne() internal.Scalar { + _ = s.decodeScalar(scMinusOne) + 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 { @@ -270,14 +283,11 @@ func (s *Scalar) SetUInt64(i uint64) internal.Scalar { encoded := make([]byte, canonicalEncodingLength) binary.LittleEndian.PutUint64(encoded, i) - sc, err := decodeScalar(encoded) - if err != nil { + if err := s.decodeScalar(encoded); err != nil { // This cannot happen, since any uint64 is smaller than the order. panic(fmt.Sprintf("unexpected decoding of uint64 scalar: %s", err)) } - s.set(sc) - return s } @@ -312,33 +322,25 @@ func (s *Scalar) Encode() []byte { return s.scalar.Encode(nil) } -func decodeScalar(scalar []byte) (*ristretto255.Scalar, error) { +func (s *Scalar) decodeScalar(scalar []byte) error { if len(scalar) == 0 { - return nil, internal.ErrParamNilScalar + return internal.ErrParamNilScalar } if len(scalar) != canonicalEncodingLength { - return nil, internal.ErrParamScalarLength + return internal.ErrParamScalarLength } - s := ristretto255.NewScalar() - if err := s.Decode(scalar); err != nil { - return nil, fmt.Errorf("%w", err) + if err := s.scalar.Decode(scalar); err != nil { + return fmt.Errorf("%w", err) } - return s, nil + return nil } // Decode sets the receiver to a decoding of the input data, and returns an error on failure. func (s *Scalar) Decode(in []byte) error { - sc, err := decodeScalar(in) - if err != nil { - return err - } - - s.scalar = *sc - - return nil + return s.decodeScalar(in) } // Hex returns the fixed-sized hexadecimal encoding of s. diff --git a/internal/scalar.go b/internal/scalar.go index a345512..ea665f7 100644 --- a/internal/scalar.go +++ b/internal/scalar.go @@ -20,6 +20,9 @@ type Scalar interface { // One sets the scalar to 1, and returns it. One() Scalar + // MinusOne sets the scalar to order-1, and returns it. + MinusOne() Scalar + // 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. Random() Scalar diff --git a/internal/secp256k1/group.go b/internal/secp256k1/group.go index 081f51a..410a967 100644 --- a/internal/secp256k1/group.go +++ b/internal/secp256k1/group.go @@ -27,15 +27,13 @@ const ( // E2CSECP256K1 represents the encode-to-curve string identifier for Secp256k1. E2CSECP256K1 = "secp256k1_XMD:SHA-256_SSWU_NU_" - groupOrder = "115792089237316195423570985008687907852837564279074904382605163141518161494337" - scalarLength = 32 - elementLength = 33 + scalarLength = 32 ) -// Group represents the Secp256k1 group. It exposes a prime-order group API with hash-to-curve operations. +// 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. +// New returns a new instantiation of the SECp256k1 Group. func New() internal.Group { return Group{} } @@ -85,15 +83,15 @@ func (g Group) Ciphersuite() string { // ScalarLength returns the byte size of an encoded scalar. func (g Group) ScalarLength() int { - return scalarLength + return secp256k1.ScalarLength() } // ElementLength returns the byte size of an encoded element. func (g Group) ElementLength() int { - return elementLength + return secp256k1.ElementLength() } // Order returns the order of the canonical group of scalars. -func (g Group) Order() string { - return groupOrder +func (g Group) Order() []byte { + return secp256k1.Order() } diff --git a/internal/secp256k1/scalar.go b/internal/secp256k1/scalar.go index 149c5d9..760b8c3 100644 --- a/internal/secp256k1/scalar.go +++ b/internal/secp256k1/scalar.go @@ -52,6 +52,12 @@ func (s *Scalar) One() internal.Scalar { return s } +// MinusOne sets the scalar to order-1, and returns it. +func (s *Scalar) MinusOne() internal.Scalar { + s.scalar.MinusOne() + 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 { diff --git a/scalar.go b/scalar.go index 52cf2c7..72d2949 100644 --- a/scalar.go +++ b/scalar.go @@ -43,6 +43,12 @@ func (s *Scalar) One() *Scalar { return s } +// MinusOne sets the scalar to order-1, and returns it. +func (s *Scalar) MinusOne() *Scalar { + s.Scalar.MinusOne() + 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() *Scalar { diff --git a/tests/groups_test.go b/tests/groups_test.go index 9e83d01..a58a9b9 100644 --- a/tests/groups_test.go +++ b/tests/groups_test.go @@ -210,3 +210,12 @@ func TestHashToGroup_NoDST(t *testing.T) { } }) } + +func TestGroup_Order(t *testing.T) { + testAllGroups(t, func(group *testGroup) { + h := hex.EncodeToString(group.group.Order()) + if h != group.groupOrder { + t.Error(errExpectedEquality) + } + }) +} diff --git a/tests/scalar_test.go b/tests/scalar_test.go index 6b74a01..d6dd2c3 100644 --- a/tests/scalar_test.go +++ b/tests/scalar_test.go @@ -242,12 +242,7 @@ func TestScalar_Decode_OutOfBounds(t *testing.T) { // Decode a scalar higher than order errMessage = "invalid scalar encoding" encoded = make([]byte, group.group.ScalarLength()) - - order, ok := new(big.Int).SetString(group.group.Order(), 0) - if !ok { - t.Errorf("setting int in base %d failed: %v", 0, group.group.Order()) - } - + order := new(big.Int).SetBytes(group.group.Order()) order.Add(order, big.NewInt(1)) order.FillBytes(encoded) @@ -267,6 +262,7 @@ func TestScalar_Arithmetic(t *testing.T) { testAllGroups(t, func(group *testGroup) { scalarTestZero(t, group.group) scalarTestOne(t, group.group) + scalarTestMinusOne(t, group.group) scalarTestEqual(t, group.group) scalarTestLessOrEqual(t, group.group) scalarTestRandom(t, group.group) @@ -308,6 +304,14 @@ func scalarTestOne(t *testing.T, g crypto.Group) { } } +func scalarTestMinusOne(t *testing.T, g crypto.Group) { + m1 := g.NewScalar().MinusOne() + one := g.NewScalar().One() + if !m1.Add(one).IsZero() { + t.Fatal(errExpectedEquality) + } +} + func scalarTestRandom(t *testing.T, g crypto.Group) { r := g.NewScalar().Random() if r.Equal(g.NewScalar().Zero()) { @@ -519,11 +523,13 @@ func scalarTestPow(t *testing.T, g crypto.Group) { } func bigIntExp(t *testing.T, g crypto.Group, base, exp *big.Int) *crypto.Scalar { - order, ok := new(big.Int).SetString(g.Order(), 0) - if !ok { - t.Fatal(ok) + orderBytes := g.Order() + + if g == crypto.Ristretto255Sha512 || g == crypto.Edwards25519Sha512 { + slices.Reverse(orderBytes) } + order := new(big.Int).SetBytes(orderBytes) r := new(big.Int).Exp(base, exp, order) b := make([]byte, g.ScalarLength()) diff --git a/tests/table_test.go b/tests/table_test.go index f46f3d7..aea9300 100644 --- a/tests/table_test.go +++ b/tests/table_test.go @@ -45,6 +45,7 @@ type testGroup struct { basePointX string identity string fieldOrder string + groupOrder string hashToCurve testHashToCurve elementLength int scalarLength int @@ -78,6 +79,7 @@ var testTable = []*testGroup{ basePointX: ristrettoBasePoint, identity: "0000000000000000000000000000000000000000000000000000000000000000", fieldOrder: "57896044618658097711785492504343953926634992332820282019728792003956564819949", + groupOrder: "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", hashToCurve: testHashToCurve{ input: testHashToGroupInput, dst: testHashToGroupDST, @@ -114,6 +116,7 @@ var testTable = []*testGroup{ basePointX: "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", identity: "000000000000000000000000000000000000000000000000000000000000000000", fieldOrder: "115792089210356248762697446949407573530086143415290314195533631308867097853951", + groupOrder: "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", hashToCurve: testHashToCurve{ input: testHashToGroupInput, dst: testHashToGroupDST, @@ -150,6 +153,7 @@ var testTable = []*testGroup{ basePointX: "aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", identity: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", fieldOrder: "39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319", + groupOrder: "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", hashToCurve: testHashToCurve{ input: testHashToGroupInput, dst: testHashToGroupDST, @@ -186,6 +190,7 @@ var testTable = []*testGroup{ basePointX: "00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66", identity: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", fieldOrder: "6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151", + groupOrder: "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409", hashToCurve: testHashToCurve{ input: testHashToGroupInput, dst: testHashToGroupDST, @@ -222,6 +227,7 @@ var testTable = []*testGroup{ basePointX: "0900000000000000000000000000000000000000000000000000000000000000", identity: "0100000000000000000000000000000000000000000000000000000000000000", fieldOrder: "57896044618658097711785492504343953926634992332820282019728792003956564819949", + groupOrder: "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", hashToCurve: testHashToCurve{ input: testHashToGroupInput, dst: testHashToGroupDST, @@ -258,6 +264,7 @@ var testTable = []*testGroup{ basePointX: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", identity: "000000000000000000000000000000000000000000000000000000000000000000", fieldOrder: "115792089237316195423570985008687907853269984665640564039457584007908834671663", + groupOrder: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", hashToCurve: testHashToCurve{ input: testHashToGroupInput, dst: testHashToGroupDST,