// Package chainkd This is an extension to ed25519.Sign that is compatible with NaCl `crypto_sign` // function taking 64-byte expanded private key (where the left part is a pre-multiplied // scalar and the right part is "prefix" used for generating a nonce). // // Invariants: // 1) Expanded(PrivateKey).Sign() == PrivateKey.Sign() // 2) InnerSign(Expanded(PrivateKey)) == Sign(PrivateKey) package chainkd import ( "crypto" "crypto/sha512" "errors" "io" "strconv" "github.com/vapor/crypto/ed25519" "github.com/vapor/crypto/ed25519/internal/edwards25519" ) const ( // ExpandedPrivateKeySize is the size, in bytes, of a "secret key" as defined in NaCl. ExpandedPrivateKeySize = 64 ) // ExpandedPrivateKey is the type of NaCl secret keys. It implements crypto.Signer. type ExpandedPrivateKey []byte // Public returns the PublicKey corresponding to secret key. func (priv ExpandedPrivateKey) Public() crypto.PublicKey { var A edwards25519.ExtendedGroupElement var scalar [32]byte copy(scalar[:], priv[:32]) edwards25519.GeScalarMultBase(&A, &scalar) var publicKeyBytes [32]byte A.ToBytes(&publicKeyBytes) return ed25519.PublicKey(publicKeyBytes[:]) } func expandEd25519PrivateKey(priv ed25519.PrivateKey) ExpandedPrivateKey { digest := sha512.Sum512(priv[:32]) digest[0] &= 248 digest[31] &= 127 digest[31] |= 64 return ExpandedPrivateKey(digest[:]) } // Sign signs the given message with expanded private key. // Ed25519 performs two passes over messages to be signed and therefore cannot // handle pre-hashed messages. Thus opts.HashFunc() must return zero to // indicate the message hasn't been hashed. This can be achieved by passing // crypto.Hash(0) as the value for opts. func (priv ExpandedPrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) { if opts.HashFunc() != crypto.Hash(0) { return nil, errors.New("ed25519: cannot sign hashed message") } return Ed25519InnerSign(priv, message), nil } // Ed25519InnerSign signs the message with expanded private key and returns a signature. // It will panic if len(privateKey) is not ExpandedPrivateKeySize. func Ed25519InnerSign(privateKey ExpandedPrivateKey, message []byte) []byte { if l := len(privateKey); l != ExpandedPrivateKeySize { panic("ed25519: bad private key length: " + strconv.Itoa(l)) } var messageDigest, hramDigest [64]byte h := sha512.New() h.Write(privateKey[32:]) h.Write(message) h.Sum(messageDigest[:0]) var messageDigestReduced [32]byte edwards25519.ScReduce(&messageDigestReduced, &messageDigest) var R edwards25519.ExtendedGroupElement edwards25519.GeScalarMultBase(&R, &messageDigestReduced) var encodedR [32]byte R.ToBytes(&encodedR) publicKey := privateKey.Public().(ed25519.PublicKey) h.Reset() h.Write(encodedR[:]) h.Write(publicKey[:]) h.Write(message) h.Sum(hramDigest[:0]) var hramDigestReduced [32]byte edwards25519.ScReduce(&hramDigestReduced, &hramDigest) var sk [32]byte copy(sk[:], privateKey[:32]) var s [32]byte edwards25519.ScMulAdd(&s, &hramDigestReduced, &sk, &messageDigestReduced) signature := make([]byte, ed25519.SignatureSize) copy(signature[:], encodedR[:]) copy(signature[32:], s[:]) return signature }