TweetNaCl.js: Cryptographic Library
TweetNaCl.js is a JavaScript port of the TweetNaCl/NaCl cryptographic library, designed for modern browsers and Node.js. It provides a thin layer of idiomatic high-level API over a faithful translation of the original C implementation, prioritizing security and correctness. The library ships with two versions: `nacl.js` (a direct port) and `nacl-fast.js` (which includes faster, optimized functions and is used by default when installed via npm). Currently at version 1.0.3, it maintains a stable release cadence with updates typically addressing security fixes or minor improvements. A key differentiator is its public domain license and a comprehensive audit by Cure53 in 2017, which found no security problems, cementing its reputation as a robust and secure cryptographic tool. All API functions operate on `Uint8Array` for byte handling.
Common errors
-
Error: Argument 1 must be of type Uint8Array
cause TweetNaCl.js functions strictly expect `Uint8Array` instances for all byte operations. Passing strings, Arrays, or Node.js Buffers incorrectly will lead to type errors.fixAlways convert input data to `Uint8Array` before passing it to TweetNaCl.js functions. For Node.js Buffers, use `Buffer.from(myBuffer)` for copying, or `Buffer.isBuffer(myVar) ? Uint8Array.from(myVar) : myVar` for robust handling. For strings, use `new TextEncoder().encode(myString)` or `tweetnacl-util-js`. -
TypeError: nacl.box is not a function
cause This usually indicates that the `nacl` object was not imported correctly, or the import resulted in an undefined or partial object.fixEnsure you are using the correct import statement: `import nacl from 'tweetnacl';` for ESM, or `const nacl = require('tweetnacl');` for CommonJS. Verify that your environment correctly handles module resolution, especially in bundler configurations.
Warnings
- breaking In versions 1.0.0 and later, functions like `nacl.secretbox.open`, `nacl.box.open`, and `nacl.box.after` now return `null` instead of `false` when decryption fails (e.g., due to incorrect key, nonce, or tampered input). While `if (!result)` checks generally remain compatible, direct comparison `if (result == false)` will behave differently.
- breaking Version 1.0.3 includes an important security fix. A bug in the modulo reduction calculation for integers larger than 32 bits could lead to `nacl.sign` or `nacl.sign.detached` creating *incorrect signatures*. This issue specifically affected signature generation, not verification. Users of previous versions are strongly urged to upgrade.
- breaking Versions 0.14.2 through 0.14.3 experienced a critical bug in the 'fast' version of Poly1305. This bug could occasionally produce incorrect results, potentially affecting the authenticity of `secretbox`, `secretbox.open`, `box`, and `box.open` functions. Although the chance of real-world impact was considered low, it was a significant vulnerability.
Install
-
npm install tweetnacl -
yarn add tweetnacl -
pnpm add tweetnacl
Imports
- nacl
import { nacl } from 'tweetnacl';import nacl from 'tweetnacl';
- nacl
const nacl = require('tweetnacl'); - BoxKeyPair
import type { BoxKeyPair } from 'tweetnacl';
Quickstart
import nacl from 'tweetnacl';
// Utility for converting strings to Uint8Array and vice-versa (not part of tweetnacl itself)
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function utf8ToUint8Array(str) {
return encoder.encode(str);
}
function uint8ArrayToUtf8(arr) {
return decoder.decode(arr);
}
// Generate a key pair for the sender and receiver
const senderKeyPair = nacl.box.keyPair();
const receiverKeyPair = nacl.box.keyPair();
// A message to encrypt
const message = 'Hello, secure world! This is a secret message.';
const messageUint8 = utf8ToUint8Array(message);
// Generate a random nonce for the encryption
const nonce = nacl.randomBytes(nacl.box.nonceLength);
// Encrypt the message
const encryptedMessage = nacl.box(
messageUint8,
nonce,
receiverKeyPair.publicKey,
senderKeyPair.secretKey
);
console.log('Original message:', message);
console.log('Encrypted message (Uint8Array):', encryptedMessage);
// Decrypt the message
const decryptedMessageUint8 = nacl.box.open(
encryptedMessage,
nonce,
senderKeyPair.publicKey,
receiverKeyPair.secretKey
);
if (decryptedMessageUint8) {
const decryptedMessage = uint8ArrayToUtf8(decryptedMessageUint8);
console.log('Decrypted message:', decryptedMessage);
} else {
console.error('Decryption failed!');
}