Initial import
This commit is contained in:
372
protocol/meshcore/crypto/ed25519.go
Normal file
372
protocol/meshcore/crypto/ed25519.go
Normal file
@@ -0,0 +1,372 @@
|
||||
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
|
||||
}
|
||||
15
protocol/meshcore/crypto/ed25519_test.go
Normal file
15
protocol/meshcore/crypto/ed25519_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKey(t *testing.T) {
|
||||
pub, key, err := GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("key: %s", key.HexString())
|
||||
t.Logf("pub: %s", pub)
|
||||
}
|
||||
Reference in New Issue
Block a user