Replacements for tweetnacl
The Web Crypto API natively supports most of the primitives implemented by tweetnacl: Ed25519 signatures (nacl.sign.detached), X25519 key exchange (nacl.scalarMult), SHA-512 hashing (nacl.hash), and random bytes (nacl.randomBytes).
Two differences to keep in mind when migrating:
- Web Crypto is asynchronous (
await), whiletweetnaclis synchronous. - Ed25519 and X25519 support in Web Crypto is a recent addition across browsers, so older engines may need a fallback.
Signatures (Ed25519)
nacl.sign.detached and nacl.sign.detached.verify map to crypto.subtle.sign and crypto.subtle.verify with the Ed25519 algorithm. Raw public keys can be imported directly with crypto.subtle.importKey.
Verifying a signature:
import nacl from 'tweetnacl'
const isVerified = nacl.sign.detached.verify(message, sig, pubKey)
const publicKey = await crypto.subtle.importKey(
'raw',
pubKey,
{ name: 'Ed25519' },
false,
['verify']
)
const isVerified = await crypto.subtle.verify(
{ name: 'Ed25519' },
publicKey,
sig,
message
) Signing a message:
import nacl from 'tweetnacl'
const keyPair = nacl.sign.keyPair()
const sig = nacl.sign.detached(message, keyPair.secretKey)
const keyPair = await crypto.subtle.generateKey(
{ name: 'Ed25519' },
false,
['sign', 'verify']
)
const sig = new Uint8Array(
await crypto.subtle.sign({ name: 'Ed25519' }, keyPair.privateKey, message)
) Note that nacl.sign (which prepends the signature to the message) has no direct equivalent; use detached signatures and concatenate manually if needed.
Key exchange (X25519)
nacl.scalarMult maps to crypto.subtle.deriveBits with the X25519 algorithm:
import nacl from 'tweetnacl'
const sharedSecret = nacl.scalarMult(mySecretKey, theirPublicKey)
const publicKey = await crypto.subtle.importKey(
'raw',
theirPublicKey,
{ name: 'X25519' },
false,
[]
)
const sharedSecret = new Uint8Array(
await crypto.subtle.deriveBits(
{ name: 'X25519', public: publicKey },
myPrivateKey,
256
)
) Hashing (SHA-512)
nacl.hash maps to crypto.subtle.digest:
import nacl from 'tweetnacl'
const hash = nacl.hash(data)
const hash = new Uint8Array(await crypto.subtle.digest('SHA-512', data)) Random bytes
nacl.randomBytes maps to crypto.getRandomValues:
import nacl from 'tweetnacl'
const bytes = nacl.randomBytes(32)
const bytes = crypto.getRandomValues(new Uint8Array(32)) Not covered: nacl.secretbox and nacl.box
XSalsa20-Poly1305 authenticated encryption (nacl.secretbox, nacl.box) has no Web Crypto equivalent. Code using these primitives cannot migrate to native APIs and should keep using tweetnacl or a maintained alternative implementing the same construction.