elliptic-curves diffie-hellman libsodium x25519
Updated Thu, 02 Jun 2022 20:29:50 GMT

Representations of secret keys on Curve25519 gives the following as a secret key / public key combo:

         S_a = 0x6A2CB91DA5FB77B12A99C0EB872F4CDF
         P_a = 85 20 F0 09 89 30 A7 54 74 8B 7D DC B4 3E F7 5A
               0D BF 3A 0D 26 38 1A F4 EB A4 A9 8E AA 9B 4E 6A gives another secret key / public key combo:

Alice's private key, a:
Alice's public key, X25519(a, 9):

Note how the public key is the same for both but the secret key is not.

sodium_crypto_box _publickey_from_secretkey seems to concur with the RFC but not the IETF Draft:

My question is... what is the RFC doing that the draft is not?

I ask because I have an implementation that is giving me the correct public key from the secret key in the IETF Draft and I'm wanting to make it so that I can use that same implementation to get the public key from the secret key in the IETF RFC. But, more than that, I'm just curious.



I'm pretty sure this is an endianness issue. Specifically, taking the S_a value from the Josefsson draft and reversing the order of the bytes (i.e. pairs of hex digits) in it gives:


which is almost the same as the value of a given in RFC 7748 6.1. In fact, XORing the values shows that they differ at only four bits:

= 0700000000000000000000000000000000000000000000000000000000000040

The remaining difference seems to come down to canonicalization; in particular, RFC 7748 5 says (emphasis mine):

For X25519, in order to decode 32 random bytes as an integer scalar, set the three least significant bits of the first byte and the most significant bit of the last to zero, set the second most significant bit of the last byte to 1 and, finally, decode as little-endian.

The S_a value given in the Josefsson draft appears to have this canonicalization correctly pre-applied (after accounting for the byte reversal, that is), whereas the a value in RFC 7748 6.1 has the three least significant bits of the first byte set to one, and second most significant bit of the last byte set to zero.

Alternatively, one could simply interpret this as a from RFC 7748 6.1 being the raw input sequence of 32 random bytes (written as 32 pairs of hex digits concatenated together), whereas S_a from the Josefsson draft is the corresponding 256-bit number (written as a hexadecimal integer literal) obtained after those random bytes have been canonicalized as described above and decoded in little-endian order.