As with other digital signature schemes, Ed25519 consists of three protocols: key generation, signing and verification. They are similar, but distinct, from the generic Schnorr scheme.
Key Generation
Ed25519 does not match secret keys to scalars. Instead, a secret scalar is generated from a seed, a 32-byte string, which should be filled at random from a cryptographically secure RNG.
xseed = Hash(seed) = "AJlLvhZBLRewj30Ty1V2Av5Y9IN5aVSEkRheeCqb+/Xxsqv0ARAy0wzzAwuWvjlIVdn7Vh25smpZf56jmPU6Rw=="
The seed is expanded to 64 bytes with the help of a hash function. Most Ed25519 implementations use SHA-512, but any cryptographic hash function with 64-byte output can suffice.
a = Sc(clamp(xseed[..32])) = Sc("hc2SM16LrK7TRbietIBdcP1Y9IN5aVSEkRheeCqb+wU=")
The first 32 bytes are “clamped” by setting the lower 3 bits and the highest bit (in the LSB interpretation) to 0, and the second-highest bit to 1. The resulting byte sequence is interpreted as a scalar a
.
nonce = xseed[32..] = "8bKr9AEQMtMM8wMLlr45SFXZ+1YdubJqWX+eo5j1Okc="
The upper 32 bytes of the expanded seed are used as a nonce during signing.
A = [a]B = Pt("KDvfEbkxtpm8/cDKSCKmem9xoyKYV8cpqiGUyErua08=")
The public key is still (the encoding of) a point on the elliptic curve, obtained by multiplying the basepoint B
by the secret scalar.
If you want to know why the secret scalar is clamped in this way, refer to this explanation.
Signing
Signing in Ed25519 is deterministic: it doesn't require an RNG during signing. A faulty RNG during signing can leak the secret key, so this is an understandable design choice.
r = Sc(Hash(nonce ‖ M)) = Sc("M+gQycFyNDBj8l1I9pfruzOBySNRuO8ADou7fMs+uAA=")
Like in RFC 6979, the “random” scalar r
is chosen based on the secret key and the message M
.
h = Sc(Hash(R ‖ A ‖ M)) = Sc("D7i1hnfes0rucGeQIG3FK3dpnWEn4efOTeDlDVIl0gE=")
Unlike vanilla Schnorr, we include the public key A
to values being hashed.
Verification
Verification uses the equation following from Schnorr and the modified signing procedure:
[s]B == R + [H(R ‖ A ‖ M)]A.
h = Sc(Hash(R ‖ A ‖ M)) = Sc("D7i1hnfes0rucGeQIG3FK3dpnWEn4efOTeDlDVIl0gE=")
Hash scalar can be readily recreated from public information.