main
Raw Download raw file
  1// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
  2package ecc
  3
  4import (
  5	"bytes"
  6	"crypto/subtle"
  7	"io"
  8
  9	"github.com/ProtonMail/go-crypto/openpgp/errors"
 10	ed25519lib "github.com/cloudflare/circl/sign/ed25519"
 11)
 12
 13const ed25519Size = 32
 14
 15type ed25519 struct{}
 16
 17func NewEd25519() *ed25519 {
 18	return &ed25519{}
 19}
 20
 21func (c *ed25519) GetCurveName() string {
 22	return "ed25519"
 23}
 24
 25// MarshalBytePoint encodes the public point from native format, adding the prefix.
 26// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
 27func (c *ed25519) MarshalBytePoint(x []byte) []byte {
 28	return append([]byte{0x40}, x...)
 29}
 30
 31// UnmarshalBytePoint decodes a point from prefixed format to native.
 32// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
 33func (c *ed25519) UnmarshalBytePoint(point []byte) (x []byte) {
 34	if len(point) != ed25519lib.PublicKeySize+1 {
 35		return nil
 36	}
 37
 38	// Return unprefixed
 39	return point[1:]
 40}
 41
 42// MarshalByteSecret encodes a scalar in native format.
 43// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
 44func (c *ed25519) MarshalByteSecret(d []byte) []byte {
 45	return d
 46}
 47
 48// UnmarshalByteSecret decodes a scalar in native format and re-adds the stripped leading zeroes
 49// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
 50func (c *ed25519) UnmarshalByteSecret(s []byte) (d []byte) {
 51	if len(s) > ed25519lib.SeedSize {
 52		return nil
 53	}
 54
 55	// Handle stripped leading zeroes
 56	d = make([]byte, ed25519lib.SeedSize)
 57	copy(d[ed25519lib.SeedSize-len(s):], s)
 58	return
 59}
 60
 61// MarshalSignature splits a signature in R and S.
 62// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.1
 63func (c *ed25519) MarshalSignature(sig []byte) (r, s []byte) {
 64	return sig[:ed25519Size], sig[ed25519Size:]
 65}
 66
 67// UnmarshalSignature decodes R and S in the native format, re-adding the stripped leading zeroes
 68// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.1
 69func (c *ed25519) UnmarshalSignature(r, s []byte) (sig []byte) {
 70	// Check size
 71	if len(r) > 32 || len(s) > 32 {
 72		return nil
 73	}
 74
 75	sig = make([]byte, ed25519lib.SignatureSize)
 76
 77	// Handle stripped leading zeroes
 78	copy(sig[ed25519Size-len(r):ed25519Size], r)
 79	copy(sig[ed25519lib.SignatureSize-len(s):], s)
 80	return sig
 81}
 82
 83func (c *ed25519) GenerateEdDSA(rand io.Reader) (pub, priv []byte, err error) {
 84	pk, sk, err := ed25519lib.GenerateKey(rand)
 85
 86	if err != nil {
 87		return nil, nil, err
 88	}
 89
 90	return pk, sk[:ed25519lib.SeedSize], nil
 91}
 92
 93func getEd25519Sk(publicKey, privateKey []byte) ed25519lib.PrivateKey {
 94	privateKeyCap, privateKeyLen, publicKeyLen := cap(privateKey), len(privateKey), len(publicKey)
 95
 96	if privateKeyCap >= privateKeyLen+publicKeyLen &&
 97		bytes.Equal(privateKey[privateKeyLen:privateKeyLen+publicKeyLen], publicKey) {
 98		return privateKey[:privateKeyLen+publicKeyLen]
 99	}
100
101	return append(privateKey[:privateKeyLen:privateKeyLen], publicKey...)
102}
103
104func (c *ed25519) Sign(publicKey, privateKey, message []byte) (sig []byte, err error) {
105	sig = ed25519lib.Sign(getEd25519Sk(publicKey, privateKey), message)
106	return sig, nil
107}
108
109func (c *ed25519) Verify(publicKey, message, sig []byte) bool {
110	return ed25519lib.Verify(publicKey, message, sig)
111}
112
113func (c *ed25519) ValidateEdDSA(publicKey, privateKey []byte) (err error) {
114	priv := getEd25519Sk(publicKey, privateKey)
115	expectedPriv := ed25519lib.NewKeyFromSeed(priv.Seed())
116	if subtle.ConstantTimeCompare(priv, expectedPriv) == 0 {
117		return errors.KeyInvalidError("ecc: invalid ed25519 secret")
118	}
119	return nil
120}