paulmillr / noble-ed25519

Fastest 4KB JS implementation of ed25519 signatures
https://paulmillr.com/noble
MIT License
420 stars 51 forks source link

Support key generation from seed as described in RFC 8032 section 5.1.5 #64

Closed zamicol closed 2 years ago

zamicol commented 2 years ago

Looking through the project, I did not see key generation from seed as describe in RFC 8032 section 5.1.5.

I would suggest ignoring the RFC's term "private key" and follow Go's term "seed". It makes the functions more readable while differentiating private key and seed.

Go's NewKeyFromSeed: https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/ed25519/ed25519.go;l=116

And Go's SetBytesWithClamping https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/internal/edwards25519/scalar.go;l=138;drc=7846e25418a087ca15122b88fc179405e26bf768;bpv=1;bpt=1

I would also point out Filippo's comment:

[I]t is lost to history why RFC 8032 adopted the irrelevant RFC 7748 clamping, but it is now required for compatibility.

Edit: The JavaScript TweetNaCl also is doing this like the Go implementation except they've dubbed it "secretKey" whereas the Go library has dubbed it "private key" (with the public 32 byte component appended). The first 32 bytes of secretKey is the secret component.
https://github.com/dchest/tweetnacl-js#naclsignkeypairfromsecretkeysecretkey

paulmillr commented 2 years ago

I don't understand. There's getPublicKey?

paulmillr commented 2 years ago

getPublicKey and getExtendedPublicKey

zamicol commented 2 years ago

Thank you, but I don't think "private key" (not "seed") is being exported. It looks like seed (which is named PrivKey) is given, public key, named pointBytes is returned, but the private key is not exposed anywhere.

getPublicKey only returns the public key and getExtendedPublicKey itself isn't exported.

In function "getExtendedPublicKey", I need scalar.

Either:

For comparison with the Go package,

This package refers to the RFC 8032 private key as the “seed”.

https://pkg.go.dev/crypto/ed25519

Edit: And another example from TweetNaCl Javascript: https://github.com/dchest/tweetnacl-js#naclsignkeypair

Where they are appending the public key to the private key (that's why it's 64 bytes). Seed is distinct from private key.

paulmillr commented 2 years ago

utils.getExtendedPublicKey is exported and described in readme in getPublicKey docs.

Use utils.getExtendedPublicKey if you need full SHA512 hash of seed

zamicol commented 2 years ago

For anyone in the future sharing my confusion:

What TweetNaCL , Go, and other packages call the "private key" is simply the seed concatenated with the public key.

Also, NaCL used to return the the private key as the "secret scalar s" with the "prefix" (the second half of the digest of the seed). Instead of doing that most libraries simply regenerate both secrete scalar s and prefix from seed, and optionally cache public key.

Thank you Paul for you work on this fantastic library. 👍

paulmillr commented 2 years ago

It's a shame they use 64-byte private keys:

  1. 64 > 32, it's harder to store
  2. The second (public) part of the 64-byte key can be corrupted e.g. by replacing one letter with another. This way, the private key would correspond to slightly different pubkey, and it could create all kinds of issues
  3. All other curves specify private key = private key, w/o pub key
  4. Spec says nothing about 64 byte keys