Skip to content

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), while tweetnacl is 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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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.

Released under the MIT License. (13757e3d)