git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/crypto/ed25519.go (about)

     1  package crypto
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ed25519"
     6  	"crypto/sha512"
     7  	"errors"
     8  	"io"
     9  	"math/big"
    10  )
    11  
    12  const (
    13  	// Ed25519PublicKeySize is the size, in bytes, of public keys as used in this package.
    14  	Ed25519PublicKeySize = ed25519.PublicKeySize
    15  
    16  	// Ed25519PrivateKeySize is the size, in bytes, of private keys as used in this package.
    17  	Ed25519PrivateKeySize = ed25519.PrivateKeySize
    18  
    19  	// Ed25519SignatureSize is the size, in bytes, of signatures generated and verified by this package.
    20  	Ed25519SignatureSize = ed25519.SignatureSize
    21  
    22  	// Ed25519SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
    23  	Ed25519SeedSize = ed25519.SeedSize
    24  
    25  	// Ed25519SignerOpts must be used for `PrivateKey.Sign`
    26  	Ed25519SignerOpts = crypto.Hash(0)
    27  )
    28  
    29  // Ed25519PublicKey is the type of Ed25519 public keys.
    30  type Ed25519PublicKey ed25519.PublicKey
    31  
    32  // Verify reports whether sig is a valid signature of message by publicKey.
    33  // returns true if signature is valid. false otherwise.
    34  func (publicKey Ed25519PublicKey) Verify(message, signature []byte) (bool, error) {
    35  	if len(publicKey) != Ed25519PublicKeySize {
    36  		return false, errors.New("crypto: Invalid Ed25519 public key size")
    37  	}
    38  
    39  	return ed25519.Verify(ed25519.PublicKey(publicKey), message, signature), nil
    40  }
    41  
    42  var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
    43  
    44  // ToCurve25519PublicKey returns the corresponding Curve25519 public key.
    45  //
    46  // See here for more details: https://blog.filippo.io/using-ed25519-keys-for-encryption
    47  func (publicKey Ed25519PublicKey) ToCurve25519PublicKey() Curve25519PublicKey {
    48  	// taken from https://github.com/FiloSottile/age/blob/master/internal/agessh/agessh.go#L179
    49  
    50  	// ed25519.PublicKey is a little endian representation of the y-coordinate,
    51  	// with the most significant bit set based on the sign of the x-coordinate.
    52  	bigEndianY := make([]byte, Ed25519PublicKeySize)
    53  	for i, b := range publicKey {
    54  		bigEndianY[Ed25519PublicKeySize-i-1] = b
    55  	}
    56  	bigEndianY[0] &= 0b0111_1111
    57  
    58  	// The Montgomery u-coordinate is derived through the bilinear map
    59  	//
    60  	//     u = (1 + y) / (1 - y)
    61  	//
    62  	// See https://blog.filippo.io/using-ed25519-keys-for-encryption.
    63  	y := new(big.Int).SetBytes(bigEndianY)
    64  	denom := big.NewInt(1)
    65  	denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y)
    66  	u := y.Mul(y.Add(y, big.NewInt(1)), denom)
    67  	u.Mod(u, curve25519P)
    68  
    69  	out := make([]byte, Curve25519PublicKeySize)
    70  	uBytes := u.Bytes()
    71  	for i, b := range uBytes {
    72  		out[len(uBytes)-i-1] = b
    73  	}
    74  
    75  	return out
    76  }
    77  
    78  func (publicKey Ed25519PublicKey) Bytes() []byte {
    79  	return publicKey[:]
    80  }
    81  
    82  func NewEd25519PublicKeyFromBytes(key []byte) (publicKey Ed25519PublicKey, err error) {
    83  	if len(key) != Ed25519PublicKeySize {
    84  		err = errors.New("crypto: Invalid Ed25519 public key size")
    85  		return
    86  	}
    87  	publicKey = Ed25519PublicKey(key)
    88  	return
    89  }
    90  
    91  // Ed25519PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
    92  type Ed25519PrivateKey ed25519.PrivateKey
    93  
    94  // Sign signs the given message with priv.
    95  // Ed25519 performs two passes over messages to be signed and therefore cannot
    96  // handle pre-hashed messages. Thus opts.HashFunc() must return zero to
    97  // indicate the message hasn't been hashed. This can be achieved by passing
    98  // crypto.Ed25519SignerOpts as the value for opts.
    99  func (privateKey Ed25519PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
   100  	if len(privateKey) != Ed25519PrivateKeySize {
   101  		return nil, errors.New("crpyto: Invalid Ed25519 private key size")
   102  	}
   103  
   104  	return ed25519.Sign(ed25519.PrivateKey(privateKey), message), nil
   105  }
   106  
   107  // ToCurve25519PrivateKey returns a corresponding Curve25519 private key.
   108  //
   109  // See here for more details: https://blog.filippo.io/using-ed25519-keys-for-encryption
   110  func (privateKey Ed25519PrivateKey) ToCurve25519PrivateKey() Curve25519PrivateKey {
   111  	// taken from https://github.com/FiloSottile/age/blob/292c3aaeea0695dbba356dfe18a70f10efb17d75/internal/agessh/agessh.go#L294
   112  	h := sha512.New()
   113  	h.Write(privateKey.Seed())
   114  	out := h.Sum(nil)
   115  	return out[:Curve25519PrivateKeySize]
   116  }
   117  
   118  // Public returns the Ed25519PublicKey corresponding to priv.
   119  func (privateKey Ed25519PrivateKey) Public() Ed25519PublicKey {
   120  	return Ed25519PublicKey(ed25519.PrivateKey(privateKey).Public().(ed25519.PublicKey))
   121  }
   122  
   123  // Seed returns the private key seed corresponding to priv. It is provided for interoperability
   124  // with RFC 8032. RFC 8032's private keys correspond to seeds in this package.
   125  func (privateKey Ed25519PrivateKey) Seed() []byte {
   126  	return ed25519.PrivateKey(privateKey).Seed()
   127  }
   128  
   129  func (privateKey Ed25519PrivateKey) Bytes() []byte {
   130  	return privateKey[:]
   131  }
   132  
   133  func NewEd25519PrivateKeyFromBytes(key []byte) (privateKey Ed25519PrivateKey, err error) {
   134  	if len(key) != Ed25519PrivateKeySize {
   135  		err = errors.New("crypto: Invalid Ed25519 private key size")
   136  		return
   137  	}
   138  	privateKey = Ed25519PrivateKey(key)
   139  	return
   140  }
   141  
   142  // GenerateEd25519KeyPair generates a public/private Ed25519 key pair
   143  func GenerateEd25519KeyPair() (Ed25519PublicKey, Ed25519PrivateKey, error) {
   144  	public, private, err := ed25519.GenerateKey(nil)
   145  	return Ed25519PublicKey(public), Ed25519PrivateKey(private), err
   146  }
   147  
   148  // NewEd25519PrivateKeyFromSeed calculates a private key from a seed. It will panic if
   149  // len(seed) is not SeedSize. This function is provided for interoperability
   150  // with RFC 8032. RFC 8032's private keys correspond to seeds in this
   151  // package.
   152  func NewEd25519PrivateKeyFromSeed(seed []byte) (Ed25519PrivateKey, error) {
   153  	if len(seed) != Ed25519SeedSize {
   154  		return nil, errors.New("crypto: Invalid Ed25519 seed size")
   155  	}
   156  
   157  	private := ed25519.NewKeyFromSeed(seed)
   158  	return Ed25519PrivateKey(private), nil
   159  }