Skip to content
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
20 changes: 11 additions & 9 deletions cryptobackend/ed25519/ed25519_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ package ed25519

import "github.com/microsoft/go-crypto-darwin/xcrypto"

type PrivateKey = xcrypto.PrivateKeyEd25519
type PublicKey = xcrypto.PublicKeyEd25519
type backendPrivateKey = xcrypto.PrivateKeyEd25519
type backendPublicKey = xcrypto.PublicKeyEd25519

func Supports() bool { return true }
func GenerateKey() (PrivateKey, error) { return xcrypto.GenerateKeyEd25519(), nil }
func NewPrivateKey(priv []byte) (PrivateKey, error) { return xcrypto.NewPrivateKeyEd25519(priv) }
func NewPublicKey(pub []byte) (PublicKey, error) { return xcrypto.NewPublicKeyEd25519(pub) }
func NewPrivateKeyFromSeed(seed []byte) (PrivateKey, error) {
func Supports() bool { return true }
func generateKey() (backendPrivateKey, error) { return xcrypto.GenerateKeyEd25519(), nil }
func newPrivateKey(priv []byte) (backendPrivateKey, error) { return xcrypto.NewPrivateKeyEd25519(priv) }
func newPublicKey(pub []byte) (backendPublicKey, error) { return xcrypto.NewPublicKeyEd25519(pub) }
func newPrivateKeyFromSeed(seed []byte) (backendPrivateKey, error) {
return xcrypto.NewPrivateKeyEd25519FromSeed(seed)
}
func Sign(priv PrivateKey, message []byte) ([]byte, error) { return xcrypto.SignEd25519(priv, message) }
func Verify(pub PublicKey, message, sig []byte) error {
func sign(priv backendPrivateKey, message []byte) ([]byte, error) {
return xcrypto.SignEd25519(priv, message)
}
func verify(pub backendPublicKey, message, sig []byte) error {
return xcrypto.VerifyEd25519(pub, message, sig)
}
239 changes: 239 additions & 0 deletions cryptobackend/ed25519/ed25519_msgostd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build goexperiment.systemcrypto && !windows && (msgostd || cmd_go_bootstrap)

package ed25519

import (
"crypto/internal/boring/bcache"
fallback "crypto/internal/fips140/ed25519"
"crypto/internal/fips140/edwards25519"
"crypto/internal/rand"
cryptorand "crypto/rand"
"crypto/subtle"
"errors"
"strconv"
)

type fipsPrivateKey = fallback.PrivateKey
type fipsPublicKey = fallback.PublicKey

func GenerateKey() (key *PrivateKey, err error) {
var k PrivateKey
err = generate(&k)
if err == nil {
key = &k
}
return
}

func generate(k *PrivateKey) error {
if supportsBackend() && rand.IsDefaultReader(cryptorand.Reader) {
key, err := generateKey()
if err != nil {
return err
}
return initBackendPrivateKey(k, key)
}
key, err := fallback.GenerateKey()
if err != nil {
return err
}
k.fips, k.encoding = key, [64]byte(key.Bytes())
return nil
}

func NewPrivateKeyFromSeed(seed []byte) (key *PrivateKey, err error) {
var k PrivateKey
err = newPrivateFromSeed(&k, seed)
if err == nil {
key = &k
}
return
}

func newPrivateFromSeed(k *PrivateKey, seed []byte) error {
if len(seed) != 32 {
return errors.New("ed25519: bad seed length: " + strconv.Itoa(len(seed)))
}
if supportsBackend() {
key, err := newPrivateKeyFromSeed(seed)
if err != nil {
return err
}
return initBackendPrivateKey(k, key)
}
key, err := fallback.NewPrivateKeyFromSeed(seed)
if err != nil {
return err
}
k.fips, k.encoding = key, [64]byte(key.Bytes())
return nil
}

func NewPrivateKey(encoding []byte) (*PrivateKey, error) {
if len(encoding) != 64 {
return nil, errors.New("ed25519: bad private key length: " + strconv.Itoa(len(encoding)))
}
if supportsBackend() {
key, err := newPrivateKey(encoding)
if err != nil {
return nil, err
}
k := new(PrivateKey)
if err := initBackendPrivateKey(k, key); err != nil {
return nil, err
}
// Native importers derive the public suffix from the seed. Go signing
// uses the supplied suffix, even when it does not match that seed.
if subtle.ConstantTimeCompare(encoding, k.encoding[:]) == 1 {
return k, nil
}
}
key, err := fallback.NewPrivateKey(encoding)
if err != nil {
return nil, err
}
return &PrivateKey{fips: key, encoding: [64]byte(encoding)}, nil
}

func initBackendPrivateKey(k *PrivateKey, key backendPrivateKey) error {
encoding, err := key.Bytes()
if err != nil {
return err
}
if len(encoding) != 64 {
return errors.New("ed25519: invalid backend private key length")
}
k.backend = key
copy(k.encoding[:], encoding)
return nil
}

// Preserve the native public-key cache's GC-based eviction. Each cached key
// holds its own immutable encoding, so input mutations invalidate the entry.
var publicKeyCache bcache.Cache[byte, PublicKey]

func init() {
if supportsBackend() {
publicKeyCache.Register()
}
}

func NewPublicKey(encoding []byte) (key *PublicKey, err error) {
var k PublicKey
err = importPublicKey(&k, encoding)
if err == nil {
key = &k
}
return
}

func importPublicKey(k *PublicKey, encoding []byte) error {
if len(encoding) != 32 {
return errors.New("ed25519: bad public key length: " + strconv.Itoa(len(encoding)))
}
if supportsBackend() && testMalleability() {
p := &encoding[0]
if cached := publicKeyCache.Get(p); cached != nil && subtle.ConstantTimeCompare(encoding, cached.backend.encoding[:]) == 1 {
*k = *cached
return nil
}
cached, err := importBackendPublicKey(encoding)
if err != nil {
return err
}
publicKeyCache.Put(p, cached)
*k = *cached
return nil
}
key, err := fallback.NewPublicKey(encoding)
if err != nil {
return err
}
k.fips = *key
return nil
}

func importBackendPublicKey(encoding []byte) (*PublicKey, error) {
// Preserve the Go point validation before accepting a native key.
var point edwards25519.Point
if _, err := point.SetBytes(encoding); err != nil {
return nil, errors.New("ed25519: bad public key")
}
key, err := newPublicKey(encoding)
if err != nil {
return nil, err
}
s := &struct {
PublicKey
state backendPublicKeyState
}{state: backendPublicKeyState{key: key, encoding: [32]byte(encoding)}}
s.backend = &s.state
return &s.PublicKey, nil
}

func newGoPrivateKey(encoding *[64]byte) *fipsPrivateKey {
key, err := fallback.NewPrivateKey(encoding[:])
if err != nil {
panic(err)
}
return key
}

func newGoPublicKey(encoding *[32]byte) *fipsPublicKey {
key, err := fallback.NewPublicKey(encoding[:])
if err != nil {
panic(err)
}
return key
}

func Sign(priv *PrivateKey, message []byte) []byte {
signature := make([]byte, 64)
signInto(signature, priv, message)
return signature
}

func signInto(signature []byte, priv *PrivateKey, message []byte) {
if priv.backend != nil {
sig, err := sign(priv.backend, message)
if err != nil {
panic(err)
}
copy(signature, sig)
return
}
copy(signature, fallback.Sign(priv.fips, message))
}

// SignDeterministic uses the Go implementation. PrivateKey.Sign uses this path
// because some native providers randomize otherwise valid Ed25519 signatures.
func SignDeterministic(priv *PrivateKey, message []byte) []byte {
return fallback.Sign(priv.goKey(), message)
}

func SignPH(priv *PrivateKey, message []byte, context string) ([]byte, error) {
return fallback.SignPH(priv.goKey(), message, context)
}

func SignCtx(priv *PrivateKey, message []byte, context string) ([]byte, error) {
return fallback.SignCtx(priv.goKey(), message, context)
}

func Verify(pub *PublicKey, message, signature []byte) error {
if pub.backend != nil {
return verify(pub.backend.key, message, signature)
}
return fallback.Verify(pub.goKey(), message, signature)
}

func VerifyPH(pub *PublicKey, message, signature []byte, context string) error {
return fallback.VerifyPH(pub.goKey(), message, signature, context)
}

func VerifyCtx(pub *PublicKey, message, signature []byte, context string) error {
return fallback.VerifyCtx(pub.goKey(), message, signature, context)
}
62 changes: 62 additions & 0 deletions cryptobackend/ed25519/ed25519_nobackend_msgostd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build (windows || !goexperiment.systemcrypto) && (msgostd || cmd_go_bootstrap)

package ed25519

import (
fallback "crypto/internal/fips140/ed25519"

_ "github.com/microsoft/go/cryptobackend"
)

// CNG does not implement Ed25519. Like Go-only builds, Windows uses the core
// types directly to avoid wrapping unsupported native operations.
type PrivateKey = fallback.PrivateKey
type PublicKey = fallback.PublicKey

// PrivateKeyBytes returns the key encoding for read-only cache comparisons.
// A static call keeps the core encoder inlineable through the Go-only type alias.
func PrivateKeyBytes(k *PrivateKey) []byte { return k.Bytes() }

// Keep these forwarding calls small enough to inline before the compiler
// expands the core constructors, so their returned keys can stay on the stack.
func generateGoKey(f func() (*PrivateKey, error)) (*PrivateKey, error) { return f() }
func newGoKey[K any](f func([]byte) (*K, error), encoding []byte) (*K, error) {
return f(encoding)
}
func signGoVariant(f func(*PrivateKey, []byte, string) ([]byte, error), key *PrivateKey, message []byte, context string) ([]byte, error) {
return f(key, message, context)
}

func GenerateKey() (*PrivateKey, error) { return generateGoKey(fallback.GenerateKey) }
func NewPrivateKey(encoding []byte) (*PrivateKey, error) {
return newGoKey(fallback.NewPrivateKey, encoding)
}
func NewPrivateKeyFromSeed(seed []byte) (*PrivateKey, error) {
return newGoKey(fallback.NewPrivateKeyFromSeed, seed)
}
func NewPublicKey(encoding []byte) (*PublicKey, error) {
return newGoKey(fallback.NewPublicKey, encoding)
}
func Sign(priv *PrivateKey, message []byte) []byte { return fallback.Sign(priv, message) }
func SignDeterministic(priv *PrivateKey, message []byte) []byte {
return fallback.Sign(priv, message)
}
func SignPH(priv *PrivateKey, message []byte, context string) ([]byte, error) {
return signGoVariant(fallback.SignPH, priv, message, context)
}
func SignCtx(priv *PrivateKey, message []byte, context string) ([]byte, error) {
return signGoVariant(fallback.SignCtx, priv, message, context)
}
func Verify(pub *PublicKey, message, signature []byte) error {
return fallback.Verify(pub, message, signature)
}
func VerifyPH(pub *PublicKey, message, signature []byte, context string) error {
return fallback.VerifyPH(pub, message, signature, context)
}
func VerifyCtx(pub *PublicKey, message, signature []byte, context string) error {
return fallback.VerifyCtx(pub, message, signature, context)
}
41 changes: 41 additions & 0 deletions cryptobackend/ed25519/ed25519_nomsgostd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !msgostd && !cmd_go_bootstrap

package ed25519

// Standalone builds and source importers cannot import standard-library internals.
type fipsPrivateKey struct{}
type fipsPublicKey struct{}

func (*fipsPublicKey) Bytes() []byte { panic("cryptobackend: not available") }

func GenerateKey() (*PrivateKey, error) { panic("cryptobackend: not available") }
func NewPrivateKey([]byte) (*PrivateKey, error) {
panic("cryptobackend: not available")
}
func NewPrivateKeyFromSeed([]byte) (*PrivateKey, error) {
panic("cryptobackend: not available")
}
func NewPublicKey([]byte) (*PublicKey, error) { panic("cryptobackend: not available") }
func Sign(*PrivateKey, []byte) []byte { panic("cryptobackend: not available") }
func SignDeterministic(*PrivateKey, []byte) []byte {
panic("cryptobackend: not available")
}
func SignPH(*PrivateKey, []byte, string) ([]byte, error) {
panic("cryptobackend: not available")
}
func SignCtx(*PrivateKey, []byte, string) ([]byte, error) {
panic("cryptobackend: not available")
}
func Verify(*PublicKey, []byte, []byte) error { panic("cryptobackend: not available") }
func VerifyPH(*PublicKey, []byte, []byte, string) error {
panic("cryptobackend: not available")
}
func VerifyCtx(*PublicKey, []byte, []byte, string) error {
panic("cryptobackend: not available")
}
func newGoPrivateKey(*[64]byte) *fipsPrivateKey { panic("cryptobackend: not available") }
func newGoPublicKey(*[32]byte) *fipsPublicKey { panic("cryptobackend: not available") }
Loading
Loading