Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update dependencies and docs, add tests, minor fixes #44

Merged
merged 2 commits into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/.golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ linters:
- errorlint
- execinquery
#- exhaustive
- exhaustruct
#- exhaustruct
- exportloopref
- forbidigo
- forcetypeassert
Expand Down
27 changes: 13 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,24 @@
```

This package exposes abstract operations over opaque prime-order elliptic curve groups and their scalars and elements,
and implements the latest [hash-to-curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve) specification
to date.
and support hash-to-curve as per [RFC 9380](https://datatracker.ietf.org/doc/rfc9380).

It is made so you can swap between primitives with no code change. The only changing parameter is the Group identifier.
The package serves as an interface to optimized and secured implementations that serve as backends, and to which you
It is made so you can swap between primitives with no code change and only the Group identifier.
The package serves as an interface to optimized and secure implementations that serve as backends, and to which you
don't need to adapt.

The following table indexes supported groups with hash-to-curve capability and links each one to the underlying implementations:

| ID | Name | Backend |
|-----|--------------|-------------------------------|
| 1 | Ristretto255 | github.com/gtank/ristretto255 |
| 2 | Decaf448 | not yet supported |
| 3 | P-256 | filippo.io/nistec |
| 4 | P-384 | filippo.io/nistec |
| 5 | P-521 | filippo.io/nistec |
| 6 | Edwards25519 | filippo.io/edwards25519 |
| 7 | Secp256k1 | github.com/bytemare/secp256k1 |
| 8 | Double-Odd | not yet supported |
| ID | Name | Backend |
|----|--------------|-------------------------------|
| 1 | Ristretto255 | github.com/gtank/ristretto255 |
| 2 | Decaf448 | not supported |
| 3 | P-256 | filippo.io/nistec |
| 4 | P-384 | filippo.io/nistec |
| 5 | P-521 | filippo.io/nistec |
| 6 | Edwards25519 | filippo.io/edwards25519 |
| 7 | Secp256k1 | github.com/bytemare/secp256k1 |
| 8 | Double-Odd | not yet supported |

## Prime-order group interface

Expand Down
18 changes: 13 additions & 5 deletions element.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,22 @@ import (

// Element represents an element on the curve of the prime-order group.
type Element struct {
_ disallowEqual
internal.Element
}

func newPoint(p internal.Element) *Element {
return &Element{p}
return &Element{Element: p}
}

// Base sets the element to the group's base point a.k.a. canonical generator.
func (e *Element) Base() *Element {
return &Element{e.Element.Base()}
return &Element{Element: e.Element.Base()}
}

// Identity sets the element to the point at infinity of the Group's underlying curve.
func (e *Element) Identity() *Element {
return &Element{e.Element.Identity()}
return &Element{Element: e.Element.Identity()}
}

// Add sets the receiver to the sum of the input and the receiver, and returns the receiver.
Expand Down Expand Up @@ -96,13 +97,20 @@ func (e *Element) IsIdentity() bool {

// Set sets the receiver to the argument, and returns the receiver.
func (e *Element) Set(element *Element) *Element {
if element == nil {
e.Element.Set(nil)

return e
}

e.Element.Set(element.Element)

return e
}

// Copy returns a copy of the receiver.
func (e *Element) Copy() *Element {
return &Element{e.Element.Copy()}
return &Element{Element: e.Element.Copy()}
}

// Encode returns the compressed byte encoding of the element.
Expand All @@ -118,7 +126,7 @@ func (e *Element) XCoordinate() []byte {
// Decode sets the receiver to a decoding of the input data, and returns an error on failure.
func (e *Element) Decode(data []byte) error {
if err := e.Element.Decode(data); err != nil {
return fmt.Errorf("element decoding: %w", err)
return fmt.Errorf("element Decode: %w", err)
}

return nil
Expand Down
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ module github.com/bytemare/crypto
go 1.21

require (
filippo.io/edwards25519 v1.0.0
filippo.io/edwards25519 v1.1.0
filippo.io/nistec v0.0.3
github.com/bytemare/hash2curve v0.2.2
github.com/bytemare/secp256k1 v0.1.0
github.com/bytemare/hash2curve v0.2.4
github.com/bytemare/secp256k1 v0.1.1
github.com/gtank/ristretto255 v0.1.2
)

require (
github.com/bytemare/hash v0.1.5 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
github.com/bytemare/hash v0.2.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/sys v0.15.0 // indirect
)
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/nistec v0.0.3 h1:h336Je2jRDZdBCLy2fLDUd9E2unG32JLwcJi0JQE9Cw=
filippo.io/nistec v0.0.3/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw=
github.com/bytemare/hash v0.1.5 h1:VW+X1YQ2b3chjRFHkRUnO42uclsQjXimdBCPOgIobR4=
github.com/bytemare/hash v0.1.5/go.mod h1:+QmWXTky/2b63ngqM5IYezGydn9UTFDhpX7mLYwYxCA=
github.com/bytemare/hash2curve v0.2.2 h1:zaGx6Z4/N4Pl9B7aGNtpbZ09vu1NNJGoJRRtHHl8oTw=
github.com/bytemare/hash2curve v0.2.2/go.mod h1:Wma3DmJdn8kqiK9j120hkWvC3tQVKS1PyA8ZzyG23BI=
github.com/bytemare/secp256k1 v0.1.0 h1:kjVJ06GAHSa+EJ7Rz1LdVgE0DQWdvUT77tmcGf7epXQ=
github.com/bytemare/secp256k1 v0.1.0/go.mod h1:hzquMsr3GXhVcqL9qFX7GGjmcT5dlQldKrArd7tcXHE=
github.com/bytemare/hash v0.2.0 h1:BVWJOz1IIaLmxSybx8WiMMAS6OlB23JLU9vkCLgrt+0=
github.com/bytemare/hash v0.2.0/go.mod h1:aAUXRjcoavq+IrTSZHPY9nEy8wHmWZk8y4Sbol4XkWU=
github.com/bytemare/hash2curve v0.2.4 h1:os6/FM43D7W/K0FkUbrGoGemp+nX4x/Sytv9N5tF+hU=
github.com/bytemare/hash2curve v0.2.4/go.mod h1:P9v9uVR5wOGlSwlPqvRYzbj28+pmw/Lxpn2FgRQ7hCE=
github.com/bytemare/secp256k1 v0.1.1 h1:gy594u/BhqXt77EA+c2nBuJbGl/XAFhsc+kXm+5O6oA=
github.com/bytemare/secp256k1 v0.1.1/go.mod h1:VGxliu7lu4ZKheaBvGwAATmbzdELTrQqiDusFkodOCM=
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.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
5 changes: 5 additions & 0 deletions groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
case Ristretto255Sha512:
g.initGroup(ristretto.New)
case decaf448Shake256:
panic("decaf is not yet supported")

Check warning on line 161 in groups.go

View check run for this annotation

Codecov / codecov/patch

groups.go#L161

Added line #L161 was not covered by tests
case P256Sha256:
g.initGroup(nist.P256)
case P384Sha384:
Expand All @@ -170,8 +170,13 @@
case Secp256k1:
g.initGroup(secp256k1.New)
case maxID:
panic("group not recognized")

Check warning on line 173 in groups.go

View check run for this annotation

Codecov / codecov/patch

groups.go#L173

Added line #L173 was not covered by tests
default:
panic("group not recognized")
}
}

// disallowEqual is an incomparable type.
// If you place it first in your struct, you prevent == from
// working on your struct without growing its size.
type disallowEqual [0]func()
7 changes: 4 additions & 3 deletions internal/edwards25519/element.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
// Set sets the receiver to the value of the argument, and returns the receiver.
func (e *Element) Set(element internal.Element) internal.Element {
if element == nil {
return e.set(nil)
return e.Identity()
}

ec, ok := element.(*Element)
Expand All @@ -127,7 +127,8 @@
return e.element.Bytes()
}

// XCoordinate returns the encoded x coordinate of the element, which is the same as Encode().
// XCoordinate returns the encoded u coordinate of the element. Note that there's no inverse function for this, and
// that decoding this output might result in another point.
func (e *Element) XCoordinate() []byte {
return e.element.BytesMontgomery()
}
Expand All @@ -139,7 +140,7 @@

e := ed.NewIdentityPoint()
if _, err := e.SetBytes(element); err != nil {
return nil, fmt.Errorf("edwards25519 element Decode: %w", err)
return nil, fmt.Errorf("%w", err)

Check warning on line 143 in internal/edwards25519/element.go

View check run for this annotation

Codecov / codecov/patch

internal/edwards25519/element.go#L143

Added line #L143 was not covered by tests
}

return e, nil
Expand Down
7 changes: 3 additions & 4 deletions internal/edwards25519/scalar.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@
return &Scalar{*ed.NewScalar().Set(&sc.scalar)}
}

func (s *Scalar) set(scalar *ed.Scalar) *Scalar {
func (s *Scalar) set(scalar *ed.Scalar) {
s.scalar = *scalar
return s
}

// Zero sets the scalar to 0, and returns it.
Expand Down Expand Up @@ -260,7 +259,7 @@
// 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)
return s.Zero()

Check warning on line 262 in internal/edwards25519/scalar.go

View check run for this annotation

Codecov / codecov/patch

internal/edwards25519/scalar.go#L262

Added line #L262 was not covered by tests
}

ec := assert(scalar)
Expand Down Expand Up @@ -308,7 +307,7 @@

s := ed.NewScalar()
if _, err := s.SetCanonicalBytes(scalar); err != nil {
return nil, fmt.Errorf("ristretto scalar Decode: %w", err)
return nil, fmt.Errorf("%w", err)
}

return s, nil
Expand Down
77 changes: 0 additions & 77 deletions internal/field/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
return *p
}

panic("invalid string to convert")

Check warning on line 29 in internal/field/field.go

View check run for this annotation

Codecov / codecov/patch

internal/field/field.go#L29

Added line #L29 was not covered by tests
}

// Field represents a Galois Field.
Expand Down Expand Up @@ -76,7 +76,7 @@
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))

Check warning on line 79 in internal/field/field.go

View check run for this annotation

Codecov / codecov/patch

internal/field/field.go#L79

Added line #L79 was not covered by tests
}

res.Set(tmp)
Expand Down Expand Up @@ -109,22 +109,11 @@
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
Expand All @@ -135,24 +124,6 @@
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))
Expand All @@ -163,55 +134,7 @@
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
}
3 changes: 3 additions & 0 deletions internal/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ var (

// ErrParamScalarTooBig reports an error when the input scalar is too big.
ErrParamScalarTooBig = errors.New("scalar too big")

// ErrParamScalarInvalidEncoding indicates an invalid scalar encoding has been provided, or that it's too big.
ErrParamScalarInvalidEncoding = errors.New("invalid scalar encoding")
)

// RandomBytes returns random bytes of length len (wrapper for crypto/rand).
Expand Down
7 changes: 1 addition & 6 deletions internal/nist/element.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@
func (e *Element[P]) Multiply(scalar internal.Scalar) internal.Element {
if e.isGenerator() {
if _, err := e.p.ScalarBaseMult(scalar.Encode()); err != nil {
panic(err)

Check warning on line 122 in internal/nist/element.go

View check run for this annotation

Codecov / codecov/patch

internal/nist/element.go#L122

Added line #L122 was not covered by tests
}
} else {
if _, err := e.p.ScalarMult(e.p, scalar.Encode()); err != nil {
panic(err)

Check warning on line 126 in internal/nist/element.go

View check run for this annotation

Codecov / codecov/patch

internal/nist/element.go#L126

Added line #L126 was not covered by tests
}
}

Expand All @@ -145,15 +145,10 @@
return subtle.ConstantTimeCompare(b, i) == 1
}

func (e *Element[P]) set(element *Element[P]) *Element[P] {
*e = *element
return e
}

// Set sets the receiver to the value of the argument, and returns the receiver.
func (e *Element[P]) Set(element internal.Element) internal.Element {
if element == nil {
return e.set(nil)
return e.Identity()
}

ec, ok := element.(*Element[P])
Expand Down Expand Up @@ -194,8 +189,8 @@
encodedLength = p384CompressedEncodingLength
case "P521":
encodedLength = p521CompressedEncodingLength
default:
panic("could not infer nist curve")

Check warning on line 193 in internal/nist/element.go

View check run for this annotation

Codecov / codecov/patch

internal/nist/element.go#L192-L193

Added lines #L192 - L193 were not covered by tests
}

return make([]byte, encodedLength)
Expand Down
Loading
Loading