Would something like this be a reasonable way to implement the double ratchet with libsodium? The sodium port that I'm using is quite limited and does not offer a kdf.

Here is a link to the libsodium documentation page regarding the scalar multiplication.

What crypto_scalarmult_base / crypto_scalarmult does is: "compute a shared secret given a user's secret key and another user's public key".

// random key shared between parties during the initial handshake
const preKey = "yez6GrvuVl3zihPwHf9wiXUv93CSkXHBQhPs6e0YLxg="

// compute the first chain key based on the shared pre key
const chain1 = sodium.crypto_scalarmult_base(preKey)

// compute the actual message key, since chain has just started, 
// use the pre key as the "public key" and chain1 key as 
// the "secret key" for the multiplication
const key1 = sodium.crypto_scalarmult(chain1, preKey)

const cipher1 = encrypt_message(key1)
// compute the next chain2 key based on the previous chain1 key
const chain2 = sodium.crypto_scalarmult_base(chain1)

// compute the next message key based on the chain2 key, 
// used as the "secret key" and the chain1 key used as 
// the "public key" for the multiplication
const key2 = sodium.crypto_scalarmult(chain2, chain1)

const cipher2 = encrypt_message(key2)
const chain3 = sodium.crypto_scalarmult_base(chain2)
const key3 = sodium.crypto_scalarmult(chain3, chain2)

const cipher3 = encrypt_message(key3)

// and so on ....

From what I understand, scalarmult is not backtrackable and breaking key2 would not allow you to calculate key1 nor key3.

So my question is - is an implementation like this reasonable or a total disaster?

  • 1
    $\begingroup$ @fgrieu Thank you for your comment, I've tried to clarify the code a bit. $\endgroup$
    – M K
    Commented Jul 19, 2022 at 9:30
  • $\begingroup$ The question is expressed with code, and it would be best to at least sum up what sodium.crypto_scalarmult performs, both with one and two arguments. As an alternative, link to a relevant documentation. $\endgroup$
    – fgrieu
    Commented Jul 19, 2022 at 9:50
  • 1
    $\begingroup$ _base computes a public key from a private key. scalarmult also allows several different public keys to compute the same shared secret, which can be a vulnerability in some protocols. And you shouldn't use a shared secret as a key directly. You should at least hash the shared secret concatenated with both public keys. About all I can say on mobile. $\endgroup$ Commented Jul 19, 2022 at 10:38

1 Answer 1


I would strongly encourage you to carefully read the entire Point*scalar multiplication page and Signal's Double Ratchet documentation. You can also read about the Double Ratchet and Signal protocol in the book Real-World Cryptography.

Make sure you understand what the libsodium functions do, how the Double Ratchet algorithm works, and why the algorithm was designed that way.

So my question is - is an implementation like this reasonable or a total disaster?

It's unclear what the preKey is, but a key exchange involves exchanging two public keys, not a single key. If you exchange a single key, there's no need to use asymmetric cryptography like this at all. The tricky part is keeping that single key secure, which is why public keys (non-secrets) are normally exchanged.

crypto_scalarmult_base() computes a public key from the private key, not a shared secret.

crypto_scalarmult() computes a shared secret for a private key and someone else's public key. It looks like it's being used to compute a shared secret from what you believe to be another shared secret; I'm not entirely sure. There should be more than one key exchange going on.

You shouldn't use shared secrets directly because they're not uniformly random. They're meant to be passed through a KDF.

Also, for a given public key with X25519, there are several equivalent public keys that can be computed. This can lead to vulnerabilities, so it's recommended to hash/KDF the shared secret concatenated with both public keys. That's what the newer key exchange API does, but I'm guessing you don't have access to that.

The sodium port that I'm using is quite limited and does not offer a kdf.

This is a problem. If it supports keyed hashing, you can construct a KDF, but it sounds like you should just switch to a better libsodium binding if possible.

  • 1
    $\begingroup$ Thanks a lot for the detailed explanation. I fully agree that there is no way around a KDF for what I need. I've skipped a few things in my question, like the fact that the public key exchange has already happened and I was looking for a "less complex" way to have a new (symmetric) encryption key for every message, to ensure perfect secrecy. I need to fill my knowledge gaps and probably write my own set of bindings for libsodium. $\endgroup$
    – M K
    Commented Jul 20, 2022 at 9:42
  • 1
    $\begingroup$ Gotcha. Writing your own binding would be a good way to learn about libsodium, but I'd only publicly publish it if you're happy to maintain it in the future. A lot of bindings end up being unmaintained, which gets a bit messy. $\endgroup$ Commented Jul 20, 2022 at 18:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.