Skip to content

proposal: x/crypto/curve25519: new X25519 API #32670

@FiloSottile

Description

@FiloSottile

Current API

func ScalarBaseMult(dst, in *[32]byte)
func ScalarMult(dst, in, base *[32]byte)

The names are the same as the elliptic.Curve methods, but with different function signatures.

The main issue with the current API is that there is no way to return an error from ScalarMult for low order inputs, which are a serious problem in certain protocols.

See #31846 and the linked paper, where it was only the Go implementations that were actually exploitable, due to our lack of safety checks, which is the opposite of what we want to achieve.

Proposed API

package curve25519

// Basepoint is the canonical Curve25519 generator.
var Basepoint []byte

// X25519 returns the result of the scalar multiplication (scalar * point),
// according to RFC 7748, Section 5. scalar, point and the return value
// are slices of 32 bytes.
//
// If point is Basepoint (but not if it's a different slice with the same contents)
// a precomputed implementation might be used to speed up the computation.
func X25519(scalar, point []byte) ([]byte, error)

X25519 is the name of the function as specified in RFC 7748.

Basepoint is identified by slice equality (as opposed to bytes.Equal) to avoid a timing side-channel when the point might or might not be the basepoint.

The implementation of X25519 would just check if point == Basepoint, and invoke the ScalarBaseMult or ScalarMult implementation (the latter followed by the missing check).

Why slices

There is a split in Go crypto APIs between slices (like ed25519) and array pointers (like curve25519). Both have their advantages, but over time I decided I prefer slices: arrays require a new variable and a copy at most uses, and I often saw bugs due to making short copies into the array.

var dst, scalar [32]byte
copy(scalar[:], inputShorterThan32)
curve25519.ScalarBaseMult(&dst, &scalar)

So while type safety is nice, the failure mode of arrays in practice is worse. Slices cause an error at runtime, but if the code is ever tested, that condition should be caught more easily than the example above.

Open questions

Should we have Scalar []byte and Point []byte types to prevent mixing up scalar and point arguments?

Does this function always allocate, or will inliner and escape analysis be able to figure out that if the return value is quickly disposed of it can live on the stack?

Should we deprecate the unsafe ScalarMult in favor of X25519? For most uses of ScalarMult the unsafe corner-case doesn't really matter, but on the other hand most common uses will be in other standard or x/crypto packages. If an application uses it directly, they might be doing something complex where low-order points matter.

Considered alternatives

Adding an error

This package lives in curve25519, so we could add an error return value to ScalarMult. Since it currently doesn't have a return value, it would be a fairly low impact.

The problem is indeed that it would probably go unnoticed for existing code.

Adding a check function

The unsafe inputs generate recognizable results, so we could add a CheckOutput(p []byte) error function.

This is the opposite of secure by default.

Replacing only ScalarMult

We could just deprecate ScalarMult and replace it with an identical function with an error return value.

I could honestly not think of a good name, and it seemed like a good opportunity to switch to slices and the naming of the RFC.

/cc @agl @rsc

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions