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 ed448lib "github.com/cloudflare/circl/sign/ed448"
11)
12
13type ed448 struct{}
14
15func NewEd448() *ed448 {
16 return &ed448{}
17}
18
19func (c *ed448) GetCurveName() string {
20 return "ed448"
21}
22
23// MarshalBytePoint encodes the public point from native format, adding the prefix.
24// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
25func (c *ed448) MarshalBytePoint(x []byte) []byte {
26 // Return prefixed
27 return append([]byte{0x40}, x...)
28}
29
30// UnmarshalBytePoint decodes a point from prefixed format to native.
31// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
32func (c *ed448) UnmarshalBytePoint(point []byte) (x []byte) {
33 if len(point) != ed448lib.PublicKeySize+1 {
34 return nil
35 }
36
37 // Strip prefix
38 return point[1:]
39}
40
41// MarshalByteSecret encoded a scalar from native format to prefixed.
42// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
43func (c *ed448) MarshalByteSecret(d []byte) []byte {
44 // Return prefixed
45 return append([]byte{0x40}, d...)
46}
47
48// UnmarshalByteSecret decodes a scalar from prefixed format to native.
49// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
50func (c *ed448) UnmarshalByteSecret(s []byte) (d []byte) {
51 // Check prefixed size
52 if len(s) != ed448lib.SeedSize+1 {
53 return nil
54 }
55
56 // Strip prefix
57 return s[1:]
58}
59
60// MarshalSignature splits a signature in R and S, where R is in prefixed native format and
61// S is an MPI with value zero.
62// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2
63func (c *ed448) MarshalSignature(sig []byte) (r, s []byte) {
64 return append([]byte{0x40}, sig...), []byte{}
65}
66
67// UnmarshalSignature decodes R and S in the native format. Only R is used, in prefixed native format.
68// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2
69func (c *ed448) UnmarshalSignature(r, s []byte) (sig []byte) {
70 if len(r) != ed448lib.SignatureSize+1 {
71 return nil
72 }
73
74 return r[1:]
75}
76
77func (c *ed448) GenerateEdDSA(rand io.Reader) (pub, priv []byte, err error) {
78 pk, sk, err := ed448lib.GenerateKey(rand)
79
80 if err != nil {
81 return nil, nil, err
82 }
83
84 return pk, sk[:ed448lib.SeedSize], nil
85}
86
87func getEd448Sk(publicKey, privateKey []byte) ed448lib.PrivateKey {
88 privateKeyCap, privateKeyLen, publicKeyLen := cap(privateKey), len(privateKey), len(publicKey)
89
90 if privateKeyCap >= privateKeyLen+publicKeyLen &&
91 bytes.Equal(privateKey[privateKeyLen:privateKeyLen+publicKeyLen], publicKey) {
92 return privateKey[:privateKeyLen+publicKeyLen]
93 }
94
95 return append(privateKey[:privateKeyLen:privateKeyLen], publicKey...)
96}
97
98func (c *ed448) Sign(publicKey, privateKey, message []byte) (sig []byte, err error) {
99 // Ed448 is used with the empty string as a context string.
100 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-13.7
101 sig = ed448lib.Sign(getEd448Sk(publicKey, privateKey), message, "")
102
103 return sig, nil
104}
105
106func (c *ed448) Verify(publicKey, message, sig []byte) bool {
107 // Ed448 is used with the empty string as a context string.
108 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-13.7
109 return ed448lib.Verify(publicKey, message, sig, "")
110}
111
112func (c *ed448) ValidateEdDSA(publicKey, privateKey []byte) (err error) {
113 priv := getEd448Sk(publicKey, privateKey)
114 expectedPriv := ed448lib.NewKeyFromSeed(priv.Seed())
115 if subtle.ConstantTimeCompare(priv, expectedPriv) == 0 {
116 return errors.KeyInvalidError("ecc: invalid ed448 secret")
117 }
118 return nil
119}