/* eslint-disable camelcase */ const assert = require('nanoassert') const { randombytes_buf } = require('./randombytes') const { crypto_stream_chacha20_ietf, crypto_stream_chacha20_ietf_xor, crypto_stream_chacha20_ietf_xor_ic, crypto_stream_chacha20_ietf_KEYBYTES } = require('./crypto_stream_chacha20') const { crypto_core_hchacha20, crypto_core_hchacha20_INPUTBYTES } = require('./internal/hchacha20') const Poly1305 = require('./internal/poly1305') const { sodium_increment, sodium_is_zero, sodium_memcmp } = require('./helpers') const crypto_onetimeauth_poly1305_BYTES = 16 const crypto_secretstream_xchacha20poly1305_COUNTERBYTES = 4 const crypto_secretstream_xchacha20poly1305_INONCEBYTES = 8 const crypto_aead_xchacha20poly1305_ietf_KEYBYTES = 32 const crypto_secretstream_xchacha20poly1305_KEYBYTES = crypto_aead_xchacha20poly1305_ietf_KEYBYTES const crypto_aead_xchacha20poly1305_ietf_NPUBBYTES = 24 const crypto_secretstream_xchacha20poly1305_HEADERBYTES = crypto_aead_xchacha20poly1305_ietf_NPUBBYTES const crypto_aead_xchacha20poly1305_ietf_ABYTES = 16 const crypto_secretstream_xchacha20poly1305_ABYTES = 1 + crypto_aead_xchacha20poly1305_ietf_ABYTES const crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX = Number.MAX_SAFE_INTEGER const crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX = Number.MAX_SAFE_INTEGER const crypto_secretstream_xchacha20poly1305_TAGBYTES = 1 const crypto_secretstream_xchacha20poly1305_TAG_MESSAGE = new Uint8Array([0]) const crypto_secretstream_xchacha20poly1305_TAG_PUSH = new Uint8Array([1]) const crypto_secretstream_xchacha20poly1305_TAG_REKEY = new Uint8Array([2]) const crypto_secretstream_xchacha20poly1305_TAG_FINAL = new Uint8Array([crypto_secretstream_xchacha20poly1305_TAG_PUSH | crypto_secretstream_xchacha20poly1305_TAG_REKEY]) const crypto_secretstream_xchacha20poly1305_STATEBYTES = crypto_secretstream_xchacha20poly1305_KEYBYTES + crypto_secretstream_xchacha20poly1305_INONCEBYTES + crypto_secretstream_xchacha20poly1305_COUNTERBYTES + 8 const KEY_OFFSET = 0 const NONCE_OFFSET = crypto_secretstream_xchacha20poly1305_KEYBYTES const PAD_OFFSET = NONCE_OFFSET + crypto_secretstream_xchacha20poly1305_INONCEBYTES + crypto_secretstream_xchacha20poly1305_COUNTERBYTES const _pad0 = new Uint8Array(16) function STORE64_LE (dest, int) { let mul = 1 let i = 0 dest[0] = int & 0xFF while (++i < 8 && (mul *= 0x100)) { dest[i] = (int / mul) & 0xFF } } function crypto_secretstream_xchacha20poly1305_counter_reset (state) { assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES, 'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long') const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET) for (let i = 0; i < crypto_secretstream_xchacha20poly1305_COUNTERBYTES; i++) { nonce[i] = 0 } nonce[0] = 1 } function crypto_secretstream_xchacha20poly1305_keygen (k) { assert(k.length === crypto_secretstream_xchacha20poly1305_KEYBYTES) randombytes_buf(k) } function crypto_secretstream_xchacha20poly1305_init_push (state, out, key) { assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES, 'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long') assert(out instanceof Uint8Array && out.length === crypto_secretstream_xchacha20poly1305_HEADERBYTES, 'out not byte array of length crypto_secretstream_xchacha20poly1305_HEADERBYTES') assert(key instanceof Uint8Array && key.length === crypto_secretstream_xchacha20poly1305_KEYBYTES, 'key not byte array of length crypto_secretstream_xchacha20poly1305_KEYBYTES') const k = state.subarray(KEY_OFFSET, NONCE_OFFSET) const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET) const pad = state.subarray(PAD_OFFSET) randombytes_buf(out, crypto_secretstream_xchacha20poly1305_HEADERBYTES) crypto_core_hchacha20(k, out, key, null) crypto_secretstream_xchacha20poly1305_counter_reset(state) for (let i = 0; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) { nonce[i + crypto_secretstream_xchacha20poly1305_COUNTERBYTES] = out[i + crypto_core_hchacha20_INPUTBYTES] } pad.fill(0) } function crypto_secretstream_xchacha20poly1305_init_pull (state, _in, key) { assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES, 'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long') assert(_in instanceof Uint8Array && _in.length === crypto_secretstream_xchacha20poly1305_HEADERBYTES, '_in not byte array of length crypto_secretstream_xchacha20poly1305_HEADERBYTES') assert(key instanceof Uint8Array && key.length === crypto_secretstream_xchacha20poly1305_KEYBYTES, 'key not byte array of length crypto_secretstream_xchacha20poly1305_KEYBYTES') const k = state.subarray(KEY_OFFSET, NONCE_OFFSET) const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET) const pad = state.subarray(PAD_OFFSET) crypto_core_hchacha20(k, _in, key, null) crypto_secretstream_xchacha20poly1305_counter_reset(state) for (let i = 0; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) { nonce[i + crypto_secretstream_xchacha20poly1305_COUNTERBYTES] = _in[i + crypto_core_hchacha20_INPUTBYTES] } pad.fill(0) } function crypto_secretstream_xchacha20poly1305_rekey (state) { assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES, 'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long') const k = state.subarray(KEY_OFFSET, NONCE_OFFSET) const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET) const new_key_and_inonce = new Uint8Array( crypto_stream_chacha20_ietf_KEYBYTES + crypto_secretstream_xchacha20poly1305_INONCEBYTES) let i for (i = 0; i < crypto_stream_chacha20_ietf_KEYBYTES; i++) { new_key_and_inonce[i] = k[i] } for (i = 0; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) { new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + i] = nonce[crypto_secretstream_xchacha20poly1305_COUNTERBYTES + i] } crypto_stream_chacha20_ietf_xor(new_key_and_inonce, new_key_and_inonce, nonce, k) for (i = 0; i < crypto_stream_chacha20_ietf_KEYBYTES; i++) { k[i] = new_key_and_inonce[i] } for (i = 0; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) { nonce[crypto_secretstream_xchacha20poly1305_COUNTERBYTES + i] = new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + i] } crypto_secretstream_xchacha20poly1305_counter_reset(state) } function crypto_secretstream_xchacha20poly1305_push (state, out, m, ad, tag) { assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES, 'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long') if (!ad) ad = new Uint8Array(0) const k = state.subarray(KEY_OFFSET, NONCE_OFFSET) const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET) const block = new Uint8Array(64) const slen = new Uint8Array(8) assert(crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX <= crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX) crypto_stream_chacha20_ietf(block, nonce, k) const poly = new Poly1305(block) block.fill(0) poly.update(ad, 0, ad.byteLength) poly.update(_pad0, 0, (0x10 - ad.byteLength) & 0xf) block[0] = tag[0] crypto_stream_chacha20_ietf_xor_ic(block, block, nonce, 1, k) poly.update(block, 0, block.byteLength) out[0] = block[0] const c = out.subarray(1, out.byteLength) crypto_stream_chacha20_ietf_xor_ic(c, m, nonce, 2, k) poly.update(c, 0, m.byteLength) poly.update(_pad0, 0, (0x10 - block.byteLength + m.byteLength) & 0xf) STORE64_LE(slen, ad.byteLength) poly.update(slen, 0, slen.byteLength) STORE64_LE(slen, block.byteLength + m.byteLength) poly.update(slen, 0, slen.byteLength) const mac = out.subarray(1 + m.byteLength, out.byteLength) poly.finish(mac, 0) assert(crypto_onetimeauth_poly1305_BYTES >= crypto_secretstream_xchacha20poly1305_INONCEBYTES) xor_buf(nonce.subarray(crypto_secretstream_xchacha20poly1305_COUNTERBYTES, nonce.length), mac, crypto_secretstream_xchacha20poly1305_INONCEBYTES) sodium_increment(nonce) if ((tag[0] & crypto_secretstream_xchacha20poly1305_TAG_REKEY) !== 0 || sodium_is_zero(nonce.subarray(0, crypto_secretstream_xchacha20poly1305_COUNTERBYTES))) { crypto_secretstream_xchacha20poly1305_rekey(state) } return crypto_secretstream_xchacha20poly1305_ABYTES + m.byteLength } function crypto_secretstream_xchacha20poly1305_pull (state, m, tag, _in, ad) { assert(state.byteLength === crypto_secretstream_xchacha20poly1305_STATEBYTES, 'state is should be crypto_secretstream_xchacha20poly1305_STATEBYTES long') if (!ad) ad = new Uint8Array(0) const k = state.subarray(KEY_OFFSET, NONCE_OFFSET) const nonce = state.subarray(NONCE_OFFSET, PAD_OFFSET) const block = new Uint8Array(64) const slen = new Uint8Array(8) const mac = new Uint8Array(crypto_onetimeauth_poly1305_BYTES) assert(_in.byteLength >= crypto_secretstream_xchacha20poly1305_ABYTES, 'ciphertext is too short.') const mlen = _in.byteLength - crypto_secretstream_xchacha20poly1305_ABYTES crypto_stream_chacha20_ietf(block, nonce, k) const poly = new Poly1305(block) block.fill(0) // sodium_memzero(block, sizeof block); poly.update(ad, 0, ad.byteLength) poly.update(_pad0, 0, (0x10 - ad.byteLength) & 0xf) block.fill(0) // memset(block, 0, sizeof block); block[0] = _in[0] crypto_stream_chacha20_ietf_xor_ic(block, block, nonce, 1, k) tag[0] = block[0] block[0] = _in[0] poly.update(block, 0, block.byteLength) const c = _in.subarray(1, _in.length) poly.update(c, 0, mlen) poly.update(_pad0, 0, (0x10 - block.byteLength + mlen) & 0xf) STORE64_LE(slen, ad.byteLength) poly.update(slen, 0, slen.byteLength) STORE64_LE(slen, block.byteLength + m.byteLength) poly.update(slen, 0, slen.byteLength) poly.finish(mac, 0) const stored_mac = _in.subarray(1 + mlen, _in.length) if (!sodium_memcmp(mac, stored_mac)) { mac.fill(0) throw new Error('MAC could not be verified.') } crypto_stream_chacha20_ietf_xor_ic(m, c.subarray(0, m.length), nonce, 2, k) xor_buf(nonce.subarray(crypto_secretstream_xchacha20poly1305_COUNTERBYTES, nonce.length), mac, crypto_secretstream_xchacha20poly1305_INONCEBYTES) sodium_increment(nonce) if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) !== 0 || sodium_is_zero(nonce.subarray(0, crypto_secretstream_xchacha20poly1305_COUNTERBYTES))) { crypto_secretstream_xchacha20poly1305_rekey(state) } return mlen } function xor_buf (out, _in, n) { for (let i = 0; i < n; i++) { out[i] ^= _in[i] } } module.exports = { crypto_secretstream_xchacha20poly1305_keygen, crypto_secretstream_xchacha20poly1305_init_push, crypto_secretstream_xchacha20poly1305_init_pull, crypto_secretstream_xchacha20poly1305_rekey, crypto_secretstream_xchacha20poly1305_push, crypto_secretstream_xchacha20poly1305_pull, crypto_secretstream_xchacha20poly1305_STATEBYTES, crypto_secretstream_xchacha20poly1305_ABYTES, crypto_secretstream_xchacha20poly1305_HEADERBYTES, crypto_secretstream_xchacha20poly1305_KEYBYTES, crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX, crypto_secretstream_xchacha20poly1305_TAGBYTES, crypto_secretstream_xchacha20poly1305_TAG_MESSAGE, crypto_secretstream_xchacha20poly1305_TAG_PUSH, crypto_secretstream_xchacha20poly1305_TAG_REKEY, crypto_secretstream_xchacha20poly1305_TAG_FINAL }