373 lines
9.9 KiB
Go
373 lines
9.9 KiB
Go
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/sha512"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
|
|
"filippo.io/edwards25519"
|
|
)
|
|
|
|
const (
|
|
seedSize = 32
|
|
publicKeySize = 32
|
|
privateKeySize = seedSize + publicKeySize
|
|
signatureSize = 64
|
|
sha512Size = 64
|
|
)
|
|
|
|
type PrivateKey struct {
|
|
seed [seedSize]byte
|
|
pub [publicKeySize]byte
|
|
s edwards25519.Scalar
|
|
prefix [sha512Size / 2]byte
|
|
}
|
|
|
|
func (priv *PrivateKey) Bytes() []byte {
|
|
k := make([]byte, 0, privateKeySize)
|
|
k = append(k, priv.seed[:]...)
|
|
k = append(k, priv.pub[:]...)
|
|
return k
|
|
}
|
|
|
|
func (priv *PrivateKey) HexString() string {
|
|
return hex.EncodeToString(priv.Bytes())
|
|
}
|
|
|
|
func (priv *PrivateKey) Seed() []byte {
|
|
seed := priv.seed
|
|
return seed[:]
|
|
}
|
|
|
|
func (priv *PrivateKey) PublicKey() []byte {
|
|
pub := priv.pub
|
|
return pub[:]
|
|
}
|
|
|
|
type PublicKey struct {
|
|
a edwards25519.Point
|
|
aBytes [32]byte
|
|
}
|
|
|
|
func (pub *PublicKey) Bytes() []byte {
|
|
a := pub.aBytes
|
|
return a[:]
|
|
}
|
|
|
|
func (pub *PublicKey) String() string {
|
|
return hex.EncodeToString(pub.Bytes())
|
|
}
|
|
|
|
func (pub *PublicKey) EqualTo(other *PublicKey) bool {
|
|
if pub == nil || other == nil {
|
|
return false
|
|
}
|
|
return bytes.Equal(pub.aBytes[:], other.aBytes[:])
|
|
}
|
|
|
|
func (pub *PublicKey) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(hex.EncodeToString(pub.Bytes()))
|
|
}
|
|
|
|
func (pub *PublicKey) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
b, err := hex.DecodeString(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
k, err := NewPublicKey(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pub.a = k.a
|
|
copy(pub.aBytes[:], k.aBytes[:])
|
|
return nil
|
|
}
|
|
|
|
// GenerateKey generates a new Ed25519 private key pair.
|
|
func GenerateKey() (*PublicKey, *PrivateKey, error) {
|
|
priv := &PrivateKey{}
|
|
key, err := generateKey(priv)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
pub, err := NewPublicKey(priv.PublicKey())
|
|
return pub, key, err
|
|
}
|
|
|
|
func generateKey(priv *PrivateKey) (*PrivateKey, error) {
|
|
rand.Read(priv.seed[:])
|
|
precomputePrivateKey(priv)
|
|
return priv, nil
|
|
}
|
|
|
|
func NewPrivateKeyFromSeed(seed []byte) (*PrivateKey, error) {
|
|
priv := &PrivateKey{}
|
|
return newPrivateKeyFromSeed(priv, seed)
|
|
}
|
|
|
|
func newPrivateKeyFromSeed(priv *PrivateKey, seed []byte) (*PrivateKey, error) {
|
|
if l := len(seed); l != seedSize {
|
|
return nil, errors.New("ed25519: bad seed length: " + strconv.Itoa(l))
|
|
}
|
|
copy(priv.seed[:], seed)
|
|
precomputePrivateKey(priv)
|
|
return priv, nil
|
|
}
|
|
|
|
func precomputePrivateKey(priv *PrivateKey) {
|
|
hs := sha512.New()
|
|
hs.Write(priv.seed[:])
|
|
h := hs.Sum(make([]byte, 0, sha512Size))
|
|
|
|
s, err := priv.s.SetBytesWithClamping(h[:32])
|
|
if err != nil {
|
|
panic("ed25519: internal error: setting scalar failed")
|
|
}
|
|
A := (&edwards25519.Point{}).ScalarBaseMult(s)
|
|
copy(priv.pub[:], A.Bytes())
|
|
|
|
copy(priv.prefix[:], h[32:])
|
|
}
|
|
|
|
func NewPrivateKey(priv []byte) (*PrivateKey, error) {
|
|
p := &PrivateKey{}
|
|
return newPrivateKey(p, priv)
|
|
}
|
|
|
|
func newPrivateKey(priv *PrivateKey, privBytes []byte) (*PrivateKey, error) {
|
|
if l := len(privBytes); l != privateKeySize {
|
|
return nil, errors.New("ed25519: bad private key length: " + strconv.Itoa(l))
|
|
}
|
|
|
|
copy(priv.seed[:], privBytes[:32])
|
|
|
|
hs := sha512.New()
|
|
hs.Write(priv.seed[:])
|
|
h := hs.Sum(make([]byte, 0, sha512Size))
|
|
|
|
if _, err := priv.s.SetBytesWithClamping(h[:32]); err != nil {
|
|
panic("ed25519: internal error: setting scalar failed")
|
|
}
|
|
// Note that we are not decompressing the public key point here,
|
|
// because it takes > 20% of the time of a signature generation.
|
|
// Signing doesn't use it as a point anyway.
|
|
copy(priv.pub[:], privBytes[32:])
|
|
|
|
copy(priv.prefix[:], h[32:])
|
|
|
|
return priv, nil
|
|
}
|
|
|
|
func DecodePublicKey(s string) (*PublicKey, error) {
|
|
var (
|
|
pub []byte
|
|
err error
|
|
)
|
|
switch len(s) {
|
|
case hex.EncodedLen(32):
|
|
pub, err = hex.DecodeString(s)
|
|
case base64.RawStdEncoding.EncodedLen(32):
|
|
pub, err = base64.RawStdEncoding.DecodeString(s)
|
|
case base64.StdEncoding.DecodedLen(32):
|
|
pub, err = base64.StdEncoding.DecodeString(s)
|
|
default:
|
|
return nil, errors.New("auth: invalid public key")
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewPublicKey(pub)
|
|
}
|
|
|
|
func NewPublicKey(pub []byte) (*PublicKey, error) {
|
|
p := &PublicKey{}
|
|
return newPublicKey(p, pub)
|
|
}
|
|
|
|
func newPublicKey(pub *PublicKey, pubBytes []byte) (*PublicKey, error) {
|
|
if l := len(pubBytes); l != publicKeySize {
|
|
return nil, errors.New("ed25519: bad public key length: " + strconv.Itoa(l))
|
|
}
|
|
// SetBytes checks that the point is on the curve.
|
|
if _, err := pub.a.SetBytes(pubBytes); err != nil {
|
|
return nil, errors.New("ed25519: bad public key")
|
|
}
|
|
copy(pub.aBytes[:], pubBytes)
|
|
return pub, nil
|
|
}
|
|
|
|
// Domain separation prefixes used to disambiguate Ed25519/Ed25519ph/Ed25519ctx.
|
|
// See RFC 8032, Section 2 and Section 5.1.
|
|
const (
|
|
// domPrefixPure is empty for pure Ed25519.
|
|
domPrefixPure = ""
|
|
// domPrefixPh is dom2(phflag=1) for Ed25519ph. It must be followed by the
|
|
// uint8-length prefixed context.
|
|
domPrefixPh = "SigEd25519 no Ed25519 collisions\x01"
|
|
// domPrefixCtx is dom2(phflag=0) for Ed25519ctx. It must be followed by the
|
|
// uint8-length prefixed context.
|
|
domPrefixCtx = "SigEd25519 no Ed25519 collisions\x00"
|
|
)
|
|
|
|
func Sign(priv *PrivateKey, message []byte) []byte {
|
|
// Outline the function body so that the returned signature can be
|
|
// stack-allocated.
|
|
signature := make([]byte, signatureSize)
|
|
return sign(signature, priv, message)
|
|
}
|
|
|
|
func sign(signature []byte, priv *PrivateKey, message []byte) []byte {
|
|
return signWithDom(signature, priv, message, domPrefixPure, "")
|
|
}
|
|
|
|
func SignPH(priv *PrivateKey, message []byte, context string) ([]byte, error) {
|
|
// Outline the function body so that the returned signature can be
|
|
// stack-allocated.
|
|
signature := make([]byte, signatureSize)
|
|
return signPH(signature, priv, message, context)
|
|
}
|
|
|
|
func signPH(signature []byte, priv *PrivateKey, message []byte, context string) ([]byte, error) {
|
|
if l := len(message); l != sha512Size {
|
|
return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
|
|
}
|
|
if l := len(context); l > 255 {
|
|
return nil, errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l))
|
|
}
|
|
return signWithDom(signature, priv, message, domPrefixPh, context), nil
|
|
}
|
|
|
|
func SignCtx(priv *PrivateKey, message []byte, context string) ([]byte, error) {
|
|
// Outline the function body so that the returned signature can be
|
|
// stack-allocated.
|
|
signature := make([]byte, signatureSize)
|
|
return signCtx(signature, priv, message, context)
|
|
}
|
|
|
|
func signCtx(signature []byte, priv *PrivateKey, message []byte, context string) ([]byte, error) {
|
|
// FIPS 186-5 specifies Ed25519 and Ed25519ph (with context), but not Ed25519ctx.
|
|
// Note that per RFC 8032, Section 5.1, the context SHOULD NOT be empty.
|
|
if l := len(context); l > 255 {
|
|
return nil, errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l))
|
|
}
|
|
return signWithDom(signature, priv, message, domPrefixCtx, context), nil
|
|
}
|
|
|
|
func signWithDom(signature []byte, priv *PrivateKey, message []byte, domPrefix, context string) []byte {
|
|
mh := sha512.New()
|
|
if domPrefix != domPrefixPure {
|
|
mh.Write([]byte(domPrefix))
|
|
mh.Write([]byte{byte(len(context))})
|
|
mh.Write([]byte(context))
|
|
}
|
|
mh.Write(priv.prefix[:])
|
|
mh.Write(message)
|
|
messageDigest := make([]byte, 0, sha512Size)
|
|
messageDigest = mh.Sum(messageDigest)
|
|
r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest)
|
|
if err != nil {
|
|
panic("ed25519: internal error: setting scalar failed")
|
|
}
|
|
|
|
R := (&edwards25519.Point{}).ScalarBaseMult(r)
|
|
|
|
kh := sha512.New()
|
|
if domPrefix != domPrefixPure {
|
|
kh.Write([]byte(domPrefix))
|
|
kh.Write([]byte{byte(len(context))})
|
|
kh.Write([]byte(context))
|
|
}
|
|
kh.Write(R.Bytes())
|
|
kh.Write(priv.pub[:])
|
|
kh.Write(message)
|
|
hramDigest := make([]byte, 0, sha512Size)
|
|
hramDigest = kh.Sum(hramDigest)
|
|
k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
|
|
if err != nil {
|
|
panic("ed25519: internal error: setting scalar failed")
|
|
}
|
|
|
|
S := edwards25519.NewScalar().MultiplyAdd(k, &priv.s, r)
|
|
|
|
copy(signature[:32], R.Bytes())
|
|
copy(signature[32:], S.Bytes())
|
|
|
|
return signature
|
|
}
|
|
|
|
func Verify(pub *PublicKey, message, sig []byte) error {
|
|
return verify(pub, message, sig)
|
|
}
|
|
|
|
func verify(pub *PublicKey, message, sig []byte) error {
|
|
return verifyWithDom(pub, message, sig, domPrefixPure, "")
|
|
}
|
|
|
|
func VerifyPH(pub *PublicKey, message []byte, sig []byte, context string) error {
|
|
if l := len(message); l != sha512Size {
|
|
return errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
|
|
}
|
|
if l := len(context); l > 255 {
|
|
return errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l))
|
|
}
|
|
return verifyWithDom(pub, message, sig, domPrefixPh, context)
|
|
}
|
|
|
|
func VerifyCtx(pub *PublicKey, message []byte, sig []byte, context string) error {
|
|
if l := len(context); l > 255 {
|
|
return errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l))
|
|
}
|
|
return verifyWithDom(pub, message, sig, domPrefixCtx, context)
|
|
}
|
|
|
|
func verifyWithDom(pub *PublicKey, message, sig []byte, domPrefix, context string) error {
|
|
if l := len(sig); l != signatureSize {
|
|
return errors.New("ed25519: bad signature length: " + strconv.Itoa(l))
|
|
}
|
|
|
|
if sig[63]&224 != 0 {
|
|
return errors.New("ed25519: invalid signature")
|
|
}
|
|
|
|
kh := sha512.New()
|
|
if domPrefix != domPrefixPure {
|
|
kh.Write([]byte(domPrefix))
|
|
kh.Write([]byte{byte(len(context))})
|
|
kh.Write([]byte(context))
|
|
}
|
|
kh.Write(sig[:32])
|
|
kh.Write(pub.aBytes[:])
|
|
kh.Write(message)
|
|
hramDigest := make([]byte, 0, sha512Size)
|
|
hramDigest = kh.Sum(hramDigest)
|
|
k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
|
|
if err != nil {
|
|
panic("ed25519: internal error: setting scalar failed")
|
|
}
|
|
|
|
S, err := edwards25519.NewScalar().SetCanonicalBytes(sig[32:])
|
|
if err != nil {
|
|
return errors.New("ed25519: invalid signature")
|
|
}
|
|
|
|
// [S]B = R + [k]A --> [k](-A) + [S]B = R
|
|
minusA := (&edwards25519.Point{}).Negate(&pub.a)
|
|
R := (&edwards25519.Point{}).VarTimeDoubleScalarBaseMult(k, minusA, S)
|
|
|
|
if !bytes.Equal(sig[:32], R.Bytes()) {
|
|
return errors.New("ed25519: invalid signature")
|
|
}
|
|
return nil
|
|
}
|