dchest / tweetnacl-js

Port of TweetNaCl cryptographic library to JavaScript
https://tweetnacl.js.org
The Unlicense
1.75k stars 292 forks source link

nacl.sign.keyPair & nacl.box.keyPair have inconsistent pubkey #228

Closed 1000i100 closed 2 years ago

1000i100 commented 2 years ago

Here is some pseudo-code (without b64 to Uint8Array conversion) to show an unespected difference.

seed = "BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU=";
nacl.sign.keyPair.fromSeed(seed).pubkey === "bnoc3Smwt4/ROvTFWY/v9O8qlxZuPKby5Pv8zYBQW/E="
nacl.box.keyPair.fromSecretKey(seed).pubkey === "UKYUCbHd0DJemxa3AOcZ6XcsBwALG9d4bpB8ZT0gSV0="

Why the pubkey isn't the same ?

Have I any way to encrypt a message to a distant user knowing only his signPubKey (and my seed/secretKey and pubkey) ?

CMEONE commented 2 years ago

They aren't supposed to be the same keys, encrypting and signing use different curves. Encryption uses Curve25519, while signing uses Ed25519. This Cryptography StackExchange post might help explain this design choice a bit.

You can try using ed2curve.js to convert Ed25519 keys (signing keys) to Curve25519 keys (encryption keys), but there is currently no proof that this is safe to do. It is safer to share both Ed25519 and Curve25519 public keys (their concatenation is 64 bytes long).

1000i100 commented 2 years ago

Thank you ! And is there a way to test if a given pubKey is an ed25519 or a curve25519 ? with tweetnacl ? or mathematically (and an other lib ?)

CMEONE commented 2 years ago

From https://cr.yp.to/ecdh.html:

How do I validate Curve25519 public keys?

Don't. The Curve25519 function was carefully designed to allow all 32-byte strings as Diffie-Hellman public keys. Relevant lower-level facts: the number of points of this elliptic curve over the base field is 8 times the prime 2^252 + 27742317777372353535851937790883648493; the number of points of the twist is 4 times the prime 2^253 - 55484635554744707071703875581767296995. This is discussed in more detail in the curve25519 paper.

There are some unusual non-Diffie-Hellman elliptic-curve protocols that need to ensure ``contributory'' behavior. In those protocols, you should reject the 32-byte strings that, in little-endian form, represent 0, 1, 325606250916557431795983626356110631294008115727848805560023387167927233504 (which has order 8), 39382357235489614581723060781553021112529911719440698176882885853963445705823 (which also has order 8), 2^255 - 19 - 1, 2^255 - 19, 2^255 - 19 + 1, 2^255 - 19 + 325606250916557431795983626356110631294008115727848805560023387167927233504, 2^255 - 19 + 39382357235489614581723060781553021112529911719440698176882885853963445705823, 2(2^255 - 19) - 1, 2(2^255 - 19), and 2(2^255 - 19) + 1. But these exclusions are unnecessary for Diffie-Hellman.

CMEONE commented 2 years ago

If you set up your public key storage system properly, you shouldn't need to check whether a given public key is Ed25519 or Curve25519, you should just simply label each public key appropriately wherever you are storing/transmitting them or come up with a consistent system if you want to store them together (example: you can store the concatenation of the Curve25519 and Ed25519 keys, so you know that bytes 1-32 are the Curve25519 key and bytes 33-64 are the Ed25519 key because that's how you've combined them together).

1000i100 commented 2 years ago

If i was able to choose what user use, i certainly choose what you suggest 32bytes curve + 32 bytes ed. Actually, i'm developping a lib to be used by an existing community and what's allready spreaded is 32bytes ed pubkey only. So i will have to deal with it. What i plan to do :

  1. check if the given pubkey is 32 or 64 bytes. If 64 bytes, i will pick the curve part for encryption
  2. if I only have 32 bytes, i will try to convert from ed to curve with ed2curve.js if it fails (i hope it will fail, but your previous message make me think it will not) i will assume it's already a curve pubkey, if it succed, i have a fresh curve pubkey

this way, thoses using my lib can use any pubkey and it will do the job. Does it look valid to you (assuming it's not proven to be safe using the same seed for sign and encryption) ?

CMEONE commented 2 years ago

My apologies, I missed this comment for some reason! Your plan seems to rely on some assumptions (like hoping that ed2curve will throw an error), which may not always be true in all circumstances. In general (and especially in the field of cryptography), you want to make sure that there are no circumstances where your algorithm can fail or produce unintended results. Perhaps you can add separate functions or a separate parameter to your library to differentiate between what types of keys are being supplied? I don't think it's a good idea to try and guess what type of public key is being supplied to the library, it's far better to include an extra piece of data along with the key to differentiate the two types of keys (you said that Ed25519 is the one already being used so maybe you can have that as the default parameter if no key type details are supplied and have the optional parameter for the key type which should be used always in the future for any new generated keys).