sodium-friends / sodium-javascript

Pure Javascript version of sodium-native
MIT License
92 stars 24 forks source link

Support for noise-protocol #17

Open bcomnes opened 4 years ago

bcomnes commented 4 years ago

It would be great if the noise-protocol module would work with sodium universal.

From what I can gather the following parts of sodium need to be ported:

The key exchange looks relatively straight forward, the crypto_aead_xchacha20poly1305_ietf end of things looks a bit more involved (e.g. reimplementing https://github.com/jedisct1/libsodium/blob/927dfe8e2eaa86160d3ba12a7e3258fbc322909c/src/libsodium/crypto_core/hchacha20/core_hchacha20.c or doing some kind of WAT rewrite like xsalsa20 ?

@emilbayes do you have any pointers on how you would go about doing that?

bcomnes commented 4 years ago

I started some work on the easy parts here: https://github.com/bcomnes/sodium-javascript/tree/noise-support

emilbayes commented 4 years ago

Those primitives will be good regardless, but I will change the primitives in noise-protocol soon so they conform with most other implementations out there. This means I will do chacha20-poly1305 soon

emilbayes commented 4 years ago

Since these are brand new primitives, maybe you want to split them into a separate file like we started doing on some of the newly implemented ones?

bcomnes commented 4 years ago

maybe you want to split them into a separate file like we started doing on some of the newly implemented ones?

Yeah agreed, I was sort of just feeling my way around. I'll split those out.

I will change the primitives in noise-protocol soon so they conform with most other implementations out there. This means I will do chacha20-poly1305 soon

You mean instead of the crypto_kx_* primitives, switch to the chacha20-poly1305 primitives?

I'm taking a stab at a hchacha20 .wat implementation. I'll post if I can get it working.

Thank you for the input!

bcomnes commented 4 years ago

I started a WAT implementation in https://github.com/little-core-labs/hchacha20/blob/master/hchacha20.wat

Has anyone spotted a good way to port macro blocks?

https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_core/hchacha20/core_hchacha20.c#L8-L14

I presume just type it out is the most straight forward option.

bcomnes commented 4 years ago

Completed hchacha20: https://github.com/little-core-labs/hchacha20/blob/master/hchacha20.wat

Time to see if it works.

cblgh commented 4 years ago

@bcomnes did it work? :)

bcomnes commented 4 years ago

@cblgh had to step away from the project. 2 observations:

tinchoz49 commented 4 years ago

https://github.com/tinchoz49/workaround-hypercore8-browser

Hey everyone! this is a workaround example that I did using sodium-javascript and adding the missing crypto functions from libsodium.js.

It's just an example to check what we need to get hypercore 8 working on the browser.

libsodium.js has everything what we need but:

  1. The API is different from the sodium-friends approach.
  2. I did a benchmark comparing libsodium.js vs sodium-javascript and libsodium.js is like 6x slower than the javascript implementation.

So I think updating sodium-javascript could be the first option.

emilbayes commented 4 years ago

Nice demo @tinchoz49! Even with handrolled sodium-javascript I don't know what kind of performance we can expect, but probably 2 - 4 times slower than sodium-native (just from intuition, no benchmarks to base this on)

tinchoz49 commented 4 years ago

Thank you @emilbayes ! My benchmark was very poor but it was more for investigation purpose, since I cannot test the xchacha20 functions in sodium-javascript i just run a common crypto_secretbox:

const bench = require('nanobench')
const sodium = require('sodium-native')
const sodiumjs = require('sodium-javascript')
const libsodium = require('libsodium-wrappers')

libsodium.ready.then(() => {
  bench('sodium-native', function (b) {
    b.start()

    for (let i = 0; i < 5000; i++) {
      var nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
      var key = sodium.sodium_malloc(sodium.crypto_secretbox_KEYBYTES) // secure buffer
      var message = Buffer.from('Hello, World!')
      var ciphertext = Buffer.alloc(message.length + sodium.crypto_secretbox_MACBYTES)

      sodium.randombytes_buf(nonce) // insert random data into nonce
      sodium.randombytes_buf(key) // insert random data into key

      // encrypted message is stored in ciphertext.
      sodium.crypto_secretbox_easy(ciphertext, message, nonce, key)

      var plainText = Buffer.alloc(ciphertext.length - sodium.crypto_secretbox_MACBYTES)

      sodium.crypto_secretbox_open_easy(plainText, ciphertext, nonce, key)
    }

    b.end()
  })

  bench('sodium-javascript', function (b) {
    b.start()

    const sodium = sodiumjs

    for (let i = 0; i < 5000; i++) {
      var nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
      var key = sodium.sodium_malloc(sodium.crypto_secretbox_KEYBYTES) // secure buffer
      var message = Buffer.from('Hello, World!')
      var ciphertext = Buffer.alloc(message.length + sodium.crypto_secretbox_MACBYTES)

      sodium.randombytes_buf(nonce) // insert random data into nonce
      sodium.randombytes_buf(key) // insert random data into key

      // encrypted message is stored in ciphertext.
      sodium.crypto_secretbox_easy(ciphertext, message, nonce, key)

      var plainText = Buffer.alloc(ciphertext.length - sodium.crypto_secretbox_MACBYTES)

      sodium.crypto_secretbox_open_easy(plainText, ciphertext, nonce, key)
    }

    b.end()
  })

  bench('libsodium.js', function (b) {
    b.start()

    const sodium = libsodium

    for (let i = 0; i < 5000; i++) {
      var key = Buffer.alloc(sodium.crypto_secretbox_KEYBYTES) // secure buffer
      var message = Buffer.from('Hello, World!')
      var nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES)
      var ciphertext = sodium.crypto_secretbox_easy(message, nonce, key)
      var plainText = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key)
    }

    b.end()
  })
})

sodium-native

ok ~162 ms (0 s + 161726505 ns)

sodium-javascript

ok ~101 ms (0 s + 100917442 ns)

libsodium.js

ok ~372 ms (0 s + 372270050 ns)

tinchoz49 commented 4 years ago

If we choose to use libsodium.js we can do a wrapper to provide the same API than sodium-native have. My only concern is about the async nature of loading a wasm library, that breaks everything.

We can compile libsodium.js using the emscripten flag BINARYEN_ASYNC_COMPILATION=0 ?

If we use libsodium.js we would have full support of sodium for the browser, that's a good point I guess.

emilbayes commented 4 years ago

We have plans to to the remaining ciphers in wasm :) The async'ness is unfortunately a question of binary size

tinchoz49 commented 4 years ago

The async'ness is unfortunately a question of binary size

That's true.

I'm working on a minimal version of sodium in wasm based on libsodium.js that we can load sync in the browser (thinking about the 4kb limitation of chrome)

Initially, it will have only the necessary to run the missing operations that we need for noise-protocol:

But my idea is to build something that we can extend in the future to add the rest of the operations to have everything in wasm.

I will try to publish next week :crossed_fingers:

tinchoz49 commented 4 years ago

https://github.com/geut/sodium-javascript-plus

Experimental support of xchacha20 and kx for sodium-javascript.

And updated the example: https://github.com/tinchoz49/workaround-hypercore8-browser

Performance is about 3x slow than sodium-native but faster than libsodium.js

cblgh commented 4 years ago

👏 👏 👏 👏 👏 👏

nice job @tinchoz49!

bcomnes commented 4 years ago

Great work @tinchoz49 !