main
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}