From 81196d06ff7ad70cdba2f92921bec5524af0b61e Mon Sep 17 00:00:00 2001
From: Paul Miller <paul@paulmillr.com>
Date: Thu, 27 Jun 2024 10:25:07 +0000
Subject: [PATCH] Refactor: use clean util

---
 README.md         |   2 +-
 package-lock.json |   4 +-
 src/_arx.ts       |   6 +--
 src/_micro.ts     |  71 ++++++++++++++++--------------
 src/_poly1305.ts  |  14 +++---
 src/_polyval.ts   |   8 ++--
 src/aes.ts        |  63 +++++++++++++-------------
 src/chacha.ts     | 110 +++++++++++++++++++++++-----------------------
 src/ff1.ts        |   7 ++-
 src/salsa.ts      |  63 +++++++++++++-------------
 src/utils.ts      |   6 ++-
 src/webcrypto.ts  |   2 +-
 12 files changed, 178 insertions(+), 178 deletions(-)

diff --git a/README.md b/README.md
index 403452c..c2942f4 100644
--- a/README.md
+++ b/README.md
@@ -518,7 +518,7 @@ encrypt (8KB)
 ├─chacha20poly1305 x 22,691 ops/sec @ 44μs/op
 ├─xchacha20poly1305 x 22,463 ops/sec @ 44μs/op
 ├─aes-256-gcm x 8,082 ops/sec @ 123μs/op
-└─aes-256-gcm-siv x 2,376 ops/sec @ 420μs/op
+└─aes-256-gcm-siv x 7,907 ops/sec @ 126μs/op
 encrypt (1MB)
 ├─xsalsa20poly1305 x 171 ops/sec @ 5ms/op
 ├─chacha20poly1305 x 186 ops/sec @ 5ms/op
diff --git a/package-lock.json b/package-lock.json
index f7d8681..5f879b0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@noble/ciphers",
-  "version": "0.5.3",
+  "version": "0.6.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "@noble/ciphers",
-      "version": "0.5.3",
+      "version": "0.6.0",
       "license": "MIT",
       "devDependencies": {
         "@paulmillr/jsbt": "0.1.0",
diff --git a/src/_arx.ts b/src/_arx.ts
index 4b920bf..c6a653e 100644
--- a/src/_arx.ts
+++ b/src/_arx.ts
@@ -1,6 +1,6 @@
 // Basic utils for ARX (add-rotate-xor) salsa and chacha ciphers.
-import { number as anumber, bytes as abytes, bool as abool } from './_assert.js';
-import { XorStream, checkOpts, u32, copyBytes } from './utils.js';
+import { bool as abool, bytes as abytes, number as anumber } from './_assert.js';
+import { XorStream, checkOpts, clean, copyBytes, u32 } from './utils.js';
 
 /*
 RFC8439 requires multi-step cipher stream, where
@@ -207,7 +207,7 @@ export function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream {
     }
     const n32 = u32(nonce);
     runCipher(core, sigma, k32, n32, data, output, counter, rounds);
-    for (const i of toClean) i.fill(0);
+    clean(...toClean)
     return output;
   };
 }
diff --git a/src/_micro.ts b/src/_micro.ts
index e19b0d5..e33afc9 100644
--- a/src/_micro.ts
+++ b/src/_micro.ts
@@ -1,11 +1,14 @@
 /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
 // prettier-ignore
-import {
-  Cipher, XorStream, createView, setBigUint64, wrapCipher,
-  bytesToHex, concatBytes, equalBytes, hexToNumber, numberToBytesBE,
-} from './utils.js';
 import { createCipher, rotl } from './_arx.js';
 import { bytes as abytes } from './_assert.js';
+import {
+  Cipher, XorStream,
+  bytesToHex, concatBytes,
+  createView,
+  equalBytes, hexToNumber, numberToBytesBE,
+  setBigUint64, wrapCipher,
+} from './utils.js';
 
 /*
 noble-ciphers-micro: more auditable, but slower version of salsa20, chacha & poly1305.
@@ -73,7 +76,7 @@ function salsaCore(
   const y = new Uint32Array([
     s[0], k[0], k[1], k[2], // "expa" Key     Key     Key
     k[3], s[1], n[0], n[1], // Key    "nd 3"  Nonce   Nonce
-    cnt,  0   , s[2], k[4], // Pos.   Pos.    "2-by"  Key
+    cnt, 0, s[2], k[4], // Pos.   Pos.    "2-by"  Key
     k[5], k[6], k[7], s[3], // Key    Key     Key     "te k"
   ]);
   const x = y.slice();
@@ -91,10 +94,10 @@ export function hsalsa(s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint
   ]);
   salsaRound(x, 20);
   let oi = 0;
-  o32[oi++] = x[0];  o32[oi++] = x[5];
+  o32[oi++] = x[0]; o32[oi++] = x[5];
   o32[oi++] = x[10]; o32[oi++] = x[15];
-  o32[oi++] = x[6];  o32[oi++] = x[7];
-  o32[oi++] = x[8];  o32[oi++] = x[9];
+  o32[oi++] = x[6]; o32[oi++] = x[7];
+  o32[oi++] = x[8]; o32[oi++] = x[9];
 }
 
 function chachaCore(
@@ -110,7 +113,7 @@ function chachaCore(
     s[0], s[1], s[2], s[3], // "expa"   "nd 3"  "2-by"  "te k"
     k[0], k[1], k[2], k[3], // Key      Key     Key     Key
     k[4], k[5], k[6], k[7], // Key      Key     Key     Key
-    cnt,  n[0], n[1], n[2], // Counter  Counter Nonce   Nonce
+    cnt, n[0], n[1], n[2], // Counter  Counter Nonce   Nonce
   ]);
   const x = y.slice();
   chachaRound(x, rounds);
@@ -127,8 +130,8 @@ export function hchacha(s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uin
   ]);
   chachaRound(x, 20);
   let oi = 0;
-  o32[oi++] = x[0];  o32[oi++] = x[1];
-  o32[oi++] = x[2];  o32[oi++] = x[3];
+  o32[oi++] = x[0]; o32[oi++] = x[1];
+  o32[oi++] = x[2]; o32[oi++] = x[3];
   o32[oi++] = x[12]; o32[oi++] = x[13];
   o32[oi++] = x[14]; o32[oi++] = x[15];
 }
@@ -282,30 +285,30 @@ export function secretbox(key: Uint8Array, nonce: Uint8Array) {
 
 export const _poly1305_aead =
   (fn: XorStream) =>
-  (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array): Cipher => {
-    const tagLength = 16;
-    const keyLength = 32;
-    abytes(key, keyLength);
-    abytes(nonce);
-    return {
-      encrypt(plaintext: Uint8Array) {
-        abytes(plaintext);
-        const res = fn(key, nonce, plaintext, undefined, 1);
-        const tag = computeTag(fn, key, nonce, res, AAD);
-        return concatBytes(res, tag);
-      },
-      decrypt(ciphertext: Uint8Array) {
-        abytes(ciphertext);
-        if (ciphertext.length < tagLength)
-          throw new Error(`encrypted data must be at least ${tagLength} bytes`);
-        const passedTag = ciphertext.subarray(-tagLength);
-        const data = ciphertext.subarray(0, -tagLength);
-        const tag = computeTag(fn, key, nonce, data, AAD);
-        if (!equalBytes(passedTag, tag)) throw new Error('invalid poly1305 tag');
-        return fn(key, nonce, data, undefined, 1);
-      },
+    (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array): Cipher => {
+      const tagLength = 16;
+      const keyLength = 32;
+      abytes(key, keyLength);
+      abytes(nonce);
+      return {
+        encrypt(plaintext: Uint8Array) {
+          abytes(plaintext);
+          const res = fn(key, nonce, plaintext, undefined, 1);
+          const tag = computeTag(fn, key, nonce, res, AAD);
+          return concatBytes(res, tag);
+        },
+        decrypt(ciphertext: Uint8Array) {
+          abytes(ciphertext);
+          if (ciphertext.length < tagLength)
+            throw new Error(`encrypted data must be at least ${tagLength} bytes`);
+          const passedTag = ciphertext.subarray(-tagLength);
+          const data = ciphertext.subarray(0, -tagLength);
+          const tag = computeTag(fn, key, nonce, data, AAD);
+          if (!equalBytes(passedTag, tag)) throw new Error('invalid poly1305 tag');
+          return fn(key, nonce, data, undefined, 1);
+        },
+      };
     };
-  };
 
 /**
  * chacha20-poly1305 12-byte-nonce chacha.
diff --git a/src/_poly1305.ts b/src/_poly1305.ts
index 7005175..d45277b 100644
--- a/src/_poly1305.ts
+++ b/src/_poly1305.ts
@@ -1,5 +1,5 @@
-import { exists as aexists, bytes as abytes, output as aoutput } from './_assert.js';
-import { Input, toBytes, Hash } from './utils.js';
+import { bytes as abytes, exists as aexists, output as aoutput } from './_assert.js';
+import { Hash, Input, clean, toBytes } from './utils.js';
 
 // Poly1305 is a fast and parallel secret-key message-authentication code.
 // https://cr.yp.to/mac.html, https://cr.yp.to/mac/poly1305-20050329.pdf
@@ -214,7 +214,7 @@ class Poly1305 implements Hash<Poly1305> {
       f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
       h[i] = f & 0xffff;
     }
-    g.fill(0);
+    clean(g);
   }
   update(data: Input): this {
     aexists(this);
@@ -222,7 +222,7 @@ class Poly1305 implements Hash<Poly1305> {
     data = toBytes(data);
     const len = data.length;
 
-    for (let pos = 0; pos < len; ) {
+    for (let pos = 0; pos < len;) {
       const take = Math.min(blockLen - this.pos, len - pos);
       // Fast path: we have at least one block in input
       if (take === blockLen) {
@@ -240,10 +240,7 @@ class Poly1305 implements Hash<Poly1305> {
     return this;
   }
   destroy() {
-    this.h.fill(0);
-    this.r.fill(0);
-    this.buffer.fill(0);
-    this.pad.fill(0);
+    clean(this.h, this.r, this.buffer, this.pad);
   }
   digestInto(out: Uint8Array) {
     aexists(this);
@@ -253,7 +250,6 @@ class Poly1305 implements Hash<Poly1305> {
     let { pos } = this;
     if (pos) {
       buffer[pos++] = 1;
-      // buffer.subarray(pos).fill(0);
       for (; pos < 16; pos++) buffer[pos] = 0;
       this.process(buffer, 0, true);
     }
diff --git a/src/_polyval.ts b/src/_polyval.ts
index f8bd548..2cd89f0 100644
--- a/src/_polyval.ts
+++ b/src/_polyval.ts
@@ -1,5 +1,5 @@
-import { createView, toBytes, Input, Hash, u32, copyBytes } from './utils.js';
 import { bytes as abytes, exists as aexists, output as aoutput } from './_assert.js';
+import { clean, copyBytes, createView, Hash, Input, toBytes, u32 } from './utils.js';
 
 // GHash from AES-GCM and its little-endian "mirror image" Polyval from AES-SIV.
 // Implemented in terms of GHash with conversion function for keys
@@ -148,7 +148,7 @@ class GHASH implements Hash<GHASH> {
     if (left) {
       ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
       this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
-      ZEROS32.fill(0); // clean tmp buffer
+      clean(ZEROS32); // clean tmp buffer
     }
     return this;
   }
@@ -184,7 +184,7 @@ class Polyval extends GHASH {
     key = toBytes(key);
     const ghKey = _toGHASHKey(copyBytes(key));
     super(ghKey, expectedLength);
-    ghKey.fill(0);
+    clean(ghKey);
   }
   update(data: Input): this {
     data = toBytes(data);
@@ -208,7 +208,7 @@ class Polyval extends GHASH {
         swapLE(ZEROS32[1]),
         swapLE(ZEROS32[0])
       );
-      ZEROS32.fill(0); // clean tmp buffer
+      clean(ZEROS32);
     }
     return this;
   }
diff --git a/src/aes.ts b/src/aes.ts
index d37999f..e615085 100644
--- a/src/aes.ts
+++ b/src/aes.ts
@@ -1,18 +1,19 @@
 // prettier-ignore
+import { bytes as abytes } from './_assert.js';
+import { ghash, polyval } from './_polyval.js';
 import {
-  wrapCipher,
   Cipher,
   CipherWithOutput,
+  clean,
+  copyBytes,
   createView,
-  setBigUint64,
   equalBytes,
+  isAligned32,
+  setBigUint64,
   u32,
   u8,
-  isAligned32,
-  copyBytes,
+  wrapCipher,
 } from './utils.js';
-import { ghash, polyval } from './_polyval.js';
-import { bytes as abytes } from './_assert.js';
 
 /*
 AES (Advanced Encryption Standard) aka Rijndael block cipher.
@@ -60,7 +61,7 @@ const sbox = /* @__PURE__ */ (() => {
     x |= x << 8;
     box[t[i]] = (x ^ (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63) & 0xff;
   }
-  t.fill(0);
+  clean(t);
   return box;
 })();
 
@@ -130,7 +131,7 @@ export function expandKeyLE(key: Uint8Array): Uint32Array {
     else if (Nk > 6 && i % Nk === 4) t = subByte(t);
     xk[i] = xk[i - Nk] ^ t;
   }
-  for (const i of toClean) i.fill(0);
+  clean(...toClean)
   return xk;
 }
 
@@ -144,7 +145,7 @@ export function expandKeyDecLE(key: Uint8Array): Uint32Array {
   for (let i = 0; i < Nk; i += 4) {
     for (let j = 0; j < 4; j++) xk[i + j] = encKey[Nk - i - 4 + j];
   }
-  encKey.fill(0);
+  clean(encKey);
   // apply InvMixColumn except first & last round
   for (let i = 4; i < Nk - 4; i++) {
     const x = xk[i];
@@ -259,7 +260,7 @@ function ctrCounter(xk: Uint32Array, nonce: Uint8Array, src: Uint8Array, dst?: U
     const b32 = new Uint32Array([s0, s1, s2, s3]);
     const buf = u8(b32);
     for (let i = start, pos = 0; i < srcLen; i++, pos++) dst[i] = src[i] ^ buf[pos];
-    b32.fill(0);
+    clean(b32);
   }
   return dst;
 }
@@ -303,7 +304,7 @@ function ctr32(
     const b32 = new Uint32Array([s0, s1, s2, s3]);
     const buf = u8(b32);
     for (let i = start, pos = 0; i < srcLen; i++, pos++) dst[i] = src[i] ^ buf[pos];
-    b32.fill(0);
+    clean(b32);
   }
   return dst;
 }
@@ -328,7 +329,7 @@ export const ctr = wrapCipher(
       const toClean = [xk, n];
       if (!isAligned32(buf)) toClean.push((buf = copyBytes(buf)));
       const out = ctrCounter(xk, n, buf, dst);
-      for (const i of toClean) i.fill(0);
+      clean(...toClean)
       return out;
     }
     return {
@@ -402,7 +403,7 @@ export const ecb = wrapCipher(
         const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
         const xk = expandKeyLE(key);
         let i = 0;
-        for (; i + 4 <= b.length; ) {
+        for (; i + 4 <= b.length;) {
           const { s0, s1, s2, s3 } = encrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
           (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
         }
@@ -411,7 +412,7 @@ export const ecb = wrapCipher(
           const { s0, s1, s2, s3 } = encrypt(xk, tmp32[0], tmp32[1], tmp32[2], tmp32[3]);
           (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
         }
-        xk.fill(0);
+        clean(xk);
         return _out;
       },
       decrypt(ciphertext: Uint8Array, dst?: Uint8Array) {
@@ -422,11 +423,11 @@ export const ecb = wrapCipher(
         if (!isAligned32(ciphertext)) toClean.push((ciphertext = copyBytes(ciphertext)));
         const b = u32(ciphertext);
         const o = u32(out);
-        for (let i = 0; i + 4 <= b.length; ) {
+        for (let i = 0; i + 4 <= b.length;) {
           const { s0, s1, s2, s3 } = decrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
           (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
         }
-        for (const i of toClean) i.fill(0);
+        clean(...toClean)
         return validatePCKS(out, pcks5);
       },
     };
@@ -454,7 +455,7 @@ export const cbc = wrapCipher(
         // prettier-ignore
         let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
         let i = 0;
-        for (; i + 4 <= b.length; ) {
+        for (; i + 4 <= b.length;) {
           (s0 ^= b[i + 0]), (s1 ^= b[i + 1]), (s2 ^= b[i + 2]), (s3 ^= b[i + 3]);
           ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
           (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
@@ -465,7 +466,7 @@ export const cbc = wrapCipher(
           ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
           (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
         }
-        for (const i of toClean) i.fill(0);
+        clean(...toClean)
         return _out;
       },
       decrypt(ciphertext: Uint8Array, dst?: Uint8Array) {
@@ -481,14 +482,14 @@ export const cbc = wrapCipher(
         const o = u32(out);
         // prettier-ignore
         let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
-        for (let i = 0; i + 4 <= b.length; ) {
+        for (let i = 0; i + 4 <= b.length;) {
           // prettier-ignore
           const ps0 = s0, ps1 = s1, ps2 = s2, ps3 = s3;
           (s0 = b[i + 0]), (s1 = b[i + 1]), (s2 = b[i + 2]), (s3 = b[i + 3]);
           const { s0: o0, s1: o1, s2: o2, s3: o3 } = decrypt(xk, s0, s1, s2, s3);
           (o[i++] = o0 ^ ps0), (o[i++] = o1 ^ ps1), (o[i++] = o2 ^ ps2), (o[i++] = o3 ^ ps3);
         }
-        for (const i of toClean) i.fill(0);
+        clean(...toClean)
         return validatePCKS(out, pcks5);
       },
     };
@@ -519,7 +520,7 @@ export const cfb = wrapCipher(
       const n32 = u32(_iv);
       // prettier-ignore
       let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
-      for (let i = 0; i + 4 <= src32.length; ) {
+      for (let i = 0; i + 4 <= src32.length;) {
         const { s0: e0, s1: e1, s2: e2, s3: e3 } = encrypt(xk, s0, s1, s2, s3);
         dst32[i + 0] = src32[i + 0] ^ e0;
         dst32[i + 1] = src32[i + 1] ^ e1;
@@ -533,9 +534,9 @@ export const cfb = wrapCipher(
         ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
         const buf = u8(new Uint32Array([s0, s1, s2, s3]));
         for (let i = start, pos = 0; i < srcLen; i++, pos++) dst[i] = src[i] ^ buf[pos];
-        buf.fill(0);
+        clean(buf);
       }
-      for (const i of toClean) i.fill(0);
+      clean(...toClean)
       return dst;
     }
     return {
@@ -562,7 +563,7 @@ function computeTag(
   setBigUint64(view, 8, BigInt(data.length * 8), isLE);
   h.update(num);
   const res = h.digest();
-  num.fill(0);
+  clean(num);
   return res;
 }
 
@@ -617,7 +618,7 @@ export const gcm = wrapCipher(
         const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
         toClean.push(tag);
         out.set(tag, plaintext.length);
-        for (const i of toClean) i.fill(0);
+        clean(...toClean)
         return out;
       },
       decrypt(ciphertext: Uint8Array) {
@@ -633,7 +634,7 @@ export const gcm = wrapCipher(
         toClean.push(tag);
         if (!equalBytes(tag, passedTag)) throw new Error('aes/gcm: invalid ghash tag');
         const out = ctr32(xk, false, counter, data);
-        for (const i of toClean) i.fill(0);
+        clean(...toClean)
         return out;
       },
     };
@@ -690,7 +691,7 @@ export const siv = wrapCipher(
       }
       const res = { authKey, encKey: expandKeyLE(encKey) };
       // Cleanup
-      for (const i of toClean) i.fill(0);
+      clean(...toClean)
       return res;
     }
     function _computeTag(encKey: Uint32Array, authKey: Uint8Array, data: Uint8Array) {
@@ -714,7 +715,7 @@ export const siv = wrapCipher(
       block[15] |= 0x80; // Force highest bit
       const res = ctr32(encKey, true, block, input);
       // Cleanup
-      block.fill(0);
+      clean(block);
       return res;
     }
     return {
@@ -729,7 +730,7 @@ export const siv = wrapCipher(
         out.set(tag, plaintext.length);
         out.set(processSiv(encKey, tag, plaintext));
         // Cleanup
-        for (const i of toClean) i.fill(0);
+        clean(...toClean)
         return out;
       },
       decrypt(ciphertext: Uint8Array) {
@@ -743,11 +744,11 @@ export const siv = wrapCipher(
         const expectedTag = _computeTag(encKey, authKey, plaintext);
         toClean.push(expectedTag);
         if (!equalBytes(tag, expectedTag)) {
-          for (const i of toClean) i.fill(0);
+          clean(...toClean)
           throw new Error('invalid polyval tag');
         }
         // Cleanup
-        for (const i of toClean) i.fill(0);
+        clean(...toClean)
         return plaintext;
       },
     };
diff --git a/src/chacha.ts b/src/chacha.ts
index eebc5aa..18223c1 100644
--- a/src/chacha.ts
+++ b/src/chacha.ts
@@ -1,10 +1,13 @@
 // prettier-ignore
-import {
-  wrapCipher, CipherWithOutput, XorStream, createView, equalBytes, setBigUint64,
-} from './utils.js';
-import { poly1305 } from './_poly1305.js';
 import { createCipher, rotl } from './_arx.js';
 import { bytes as abytes } from './_assert.js';
+import { poly1305 } from './_poly1305.js';
+import {
+  CipherWithOutput, XorStream,
+  clean,
+  createView, equalBytes, setBigUint64,
+  wrapCipher,
+} from './utils.js';
 
 // ChaCha20 stream cipher was released in 2008. ChaCha aims to increase
 // the diffusion per round, but had slightly less cryptanalysis.
@@ -18,14 +21,14 @@ function chachaCore(
   s: Uint32Array, k: Uint32Array, n: Uint32Array, out: Uint32Array, cnt: number, rounds = 20
 ): void {
   let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], // "expa"   "nd 3"  "2-by"  "te k"
-      y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], // Key      Key     Key     Key
-      y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], // Key      Key     Key     Key
-      y12 = cnt,  y13 = n[0], y14 = n[1], y15 = n[2]; // Counter  Counter	Nonce   Nonce
+    y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], // Key      Key     Key     Key
+    y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], // Key      Key     Key     Key
+    y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter  Counter	Nonce   Nonce
   // Save state to temporary variables
   let x00 = y00, x01 = y01, x02 = y02, x03 = y03,
-      x04 = y04, x05 = y05, x06 = y06, x07 = y07,
-      x08 = y08, x09 = y09, x10 = y10, x11 = y11,
-      x12 = y12, x13 = y13, x14 = y14, x15 = y15;
+    x04 = y04, x05 = y05, x06 = y06, x07 = y07,
+    x08 = y08, x09 = y09, x10 = y10, x11 = y11,
+    x12 = y12, x13 = y13, x14 = y14, x15 = y15;
   for (let r = 0; r < rounds; r += 2) {
     x00 = (x00 + x04) | 0; x12 = rotl(x12 ^ x00, 16);
     x08 = (x08 + x12) | 0; x04 = rotl(x04 ^ x08, 12);
@@ -39,7 +42,7 @@ function chachaCore(
 
     x02 = (x02 + x06) | 0; x14 = rotl(x14 ^ x02, 16);
     x10 = (x10 + x14) | 0; x06 = rotl(x06 ^ x10, 12);
-    x02 = (x02 + x06) | 0; x14 = rotl(x14 ^x02, 8);
+    x02 = (x02 + x06) | 0; x14 = rotl(x14 ^ x02, 8);
     x10 = (x10 + x14) | 0; x06 = rotl(x06 ^ x10, 7);
 
     x03 = (x03 + x07) | 0; x15 = rotl(x15 ^ x03, 16);
@@ -89,9 +92,9 @@ export function hchacha(
   s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array
 ) {
   let x00 = s[0], x01 = s[1], x02 = s[2], x03 = s[3],
-      x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3],
-      x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7],
-      x12 = i[0], x13 = i[1], x14 = i[2], x15 = i[3];
+    x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3],
+    x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7],
+    x12 = i[0], x13 = i[1], x14 = i[2], x15 = i[3];
   for (let r = 0; r < 20; r += 2) {
     x00 = (x00 + x04) | 0; x12 = rotl(x12 ^ x00, 16);
     x08 = (x08 + x12) | 0; x04 = rotl(x04 ^ x08, 12);
@@ -213,8 +216,7 @@ function computeTag(
   setBigUint64(view, 8, BigInt(data.length), true);
   h.update(num);
   const res = h.digest();
-  authKey.fill(0);
-  num.fill(0);
+  clean(authKey, num);
   return res;
 }
 
@@ -229,45 +231,45 @@ function computeTag(
  */
 export const _poly1305_aead =
   (xorStream: XorStream) =>
-  (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array): CipherWithOutput => {
-    const tagLength = 16;
-    abytes(key, 32);
-    abytes(nonce);
-    return {
-      encrypt(plaintext: Uint8Array, output?: Uint8Array) {
-        const plength = plaintext.length;
-        const clength = plength + tagLength;
-        if (output) {
-          abytes(output, clength);
-        } else {
-          output = new Uint8Array(clength);
-        }
-        xorStream(key, nonce, plaintext, output, 1);
-        const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
-        output.set(tag, plength); // append tag
-        tag.fill(0);
-        return output;
-      },
-      decrypt(ciphertext: Uint8Array, output?: Uint8Array) {
-        const clength = ciphertext.length;
-        const plength = clength - tagLength;
-        if (clength < tagLength)
-          throw new Error(`encrypted data must be at least ${tagLength} bytes`);
-        if (output) {
-          abytes(output, plength);
-        } else {
-          output = new Uint8Array(plength);
-        }
-        const data = ciphertext.subarray(0, -tagLength);
-        const passedTag = ciphertext.subarray(-tagLength);
-        const tag = computeTag(xorStream, key, nonce, data, AAD);
-        if (!equalBytes(passedTag, tag)) throw new Error('invalid tag');
-        xorStream(key, nonce, data, output, 1);
-        tag.fill(0);
-        return output;
-      },
+    (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array): CipherWithOutput => {
+      const tagLength = 16;
+      abytes(key, 32);
+      abytes(nonce);
+      return {
+        encrypt(plaintext: Uint8Array, output?: Uint8Array) {
+          const plength = plaintext.length;
+          const clength = plength + tagLength;
+          if (output) {
+            abytes(output, clength);
+          } else {
+            output = new Uint8Array(clength);
+          }
+          xorStream(key, nonce, plaintext, output, 1);
+          const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
+          output.set(tag, plength); // append tag
+          clean(tag);
+          return output;
+        },
+        decrypt(ciphertext: Uint8Array, output?: Uint8Array) {
+          const clength = ciphertext.length;
+          const plength = clength - tagLength;
+          if (clength < tagLength)
+            throw new Error(`encrypted data must be at least ${tagLength} bytes`);
+          if (output) {
+            abytes(output, plength);
+          } else {
+            output = new Uint8Array(plength);
+          }
+          const data = ciphertext.subarray(0, -tagLength);
+          const passedTag = ciphertext.subarray(-tagLength);
+          const tag = computeTag(xorStream, key, nonce, data, AAD);
+          if (!equalBytes(passedTag, tag)) throw new Error('invalid tag');
+          xorStream(key, nonce, data, output, 1);
+          clean(tag);
+          return output;
+        },
+      };
     };
-  };
 
 /**
  * ChaCha20-Poly1305 from RFC 8439.
diff --git a/src/ff1.ts b/src/ff1.ts
index 1e3c579..788d595 100644
--- a/src/ff1.ts
+++ b/src/ff1.ts
@@ -1,5 +1,5 @@
-import { Cipher, bytesToNumberBE, numberToBytesBE } from './utils.js';
 import { unsafe } from './aes.js';
+import { Cipher, bytesToNumberBE, clean, numberToBytesBE } from './utils.js';
 // NOTE: no point in inlining encrypt instead of encryptBlock, since BigInt stuff will be slow
 const { expandKeyLE, encryptBlock } = unsafe;
 
@@ -45,7 +45,7 @@ function getRound(radix: number, key: Uint8Array, tweak: Uint8Array, x: number[]
   // Q = T || [0](−t−b−1) mod 16 || [i]1 || [NUMradix(B)]b.
   const PQ = new Uint8Array(P.length + tweak.length + padding + 1 + b);
   PQ.set(P);
-  P.fill(0);
+  clean(P);
   PQ.set(tweak, P.length);
   const xk = expandKeyLE(key);
   const round = (A: number[], B: number[], i: number, decrypt = false) => {
@@ -80,8 +80,7 @@ function getRound(radix: number, key: Uint8Array, tweak: Uint8Array, x: number[]
     return [A, B];
   };
   const destroy = () => {
-    xk.fill(0);
-    PQ.fill(0);
+    clean(xk, PQ);
   };
   return { u, round, destroy };
 }
diff --git a/src/salsa.ts b/src/salsa.ts
index 852d965..a9d5d63 100644
--- a/src/salsa.ts
+++ b/src/salsa.ts
@@ -1,7 +1,7 @@
-import { bytes as abytes } from './_assert.js';
 import { createCipher, rotl } from './_arx.js';
+import { bytes as abytes } from './_assert.js';
 import { poly1305 } from './_poly1305.js';
-import { wrapCipher, Cipher, equalBytes } from './utils.js';
+import { Cipher, clean, equalBytes, wrapCipher } from './utils.js';
 
 // Salsa20 stream cipher was released in 2005.
 // Salsa's goal was to implement AES replacement that does not rely on S-Boxes,
@@ -17,30 +17,30 @@ function salsaCore(
 ): void {
   // Based on https://cr.yp.to/salsa20.html
   let y00 = s[0], y01 = k[0], y02 = k[1], y03 = k[2], // "expa" Key     Key     Key
-      y04 = k[3], y05 = s[1], y06 = n[0], y07 = n[1], // Key    "nd 3"  Nonce   Nonce
-      y08 = cnt,  y09 = 0   , y10 = s[2], y11 = k[4], // Pos.   Pos.    "2-by"	Key
-      y12 = k[5], y13 = k[6], y14 = k[7], y15 = s[3]; // Key    Key     Key     "te k"
+    y04 = k[3], y05 = s[1], y06 = n[0], y07 = n[1], // Key    "nd 3"  Nonce   Nonce
+    y08 = cnt, y09 = 0, y10 = s[2], y11 = k[4], // Pos.   Pos.    "2-by"	Key
+    y12 = k[5], y13 = k[6], y14 = k[7], y15 = s[3]; // Key    Key     Key     "te k"
   // Save state to temporary variables
   let x00 = y00, x01 = y01, x02 = y02, x03 = y03,
-      x04 = y04, x05 = y05, x06 = y06, x07 = y07,
-      x08 = y08, x09 = y09, x10 = y10, x11 = y11,
-      x12 = y12, x13 = y13, x14 = y14, x15 = y15;
+    x04 = y04, x05 = y05, x06 = y06, x07 = y07,
+    x08 = y08, x09 = y09, x10 = y10, x11 = y11,
+    x12 = y12, x13 = y13, x14 = y14, x15 = y15;
   for (let r = 0; r < rounds; r += 2) {
-    x04 ^= rotl(x00 + x12 | 0,  7); x08 ^= rotl(x04 + x00 | 0,  9);
+    x04 ^= rotl(x00 + x12 | 0, 7); x08 ^= rotl(x04 + x00 | 0, 9);
     x12 ^= rotl(x08 + x04 | 0, 13); x00 ^= rotl(x12 + x08 | 0, 18);
-    x09 ^= rotl(x05 + x01 | 0,  7); x13 ^= rotl(x09 + x05 | 0,  9);
+    x09 ^= rotl(x05 + x01 | 0, 7); x13 ^= rotl(x09 + x05 | 0, 9);
     x01 ^= rotl(x13 + x09 | 0, 13); x05 ^= rotl(x01 + x13 | 0, 18);
-    x14 ^= rotl(x10 + x06 | 0,  7); x02 ^= rotl(x14 + x10 | 0,  9);
+    x14 ^= rotl(x10 + x06 | 0, 7); x02 ^= rotl(x14 + x10 | 0, 9);
     x06 ^= rotl(x02 + x14 | 0, 13); x10 ^= rotl(x06 + x02 | 0, 18);
-    x03 ^= rotl(x15 + x11 | 0,  7); x07 ^= rotl(x03 + x15 | 0,  9);
+    x03 ^= rotl(x15 + x11 | 0, 7); x07 ^= rotl(x03 + x15 | 0, 9);
     x11 ^= rotl(x07 + x03 | 0, 13); x15 ^= rotl(x11 + x07 | 0, 18);
-    x01 ^= rotl(x00 + x03 | 0,  7); x02 ^= rotl(x01 + x00 | 0,  9);
+    x01 ^= rotl(x00 + x03 | 0, 7); x02 ^= rotl(x01 + x00 | 0, 9);
     x03 ^= rotl(x02 + x01 | 0, 13); x00 ^= rotl(x03 + x02 | 0, 18);
-    x06 ^= rotl(x05 + x04 | 0,  7); x07 ^= rotl(x06 + x05 | 0,  9);
+    x06 ^= rotl(x05 + x04 | 0, 7); x07 ^= rotl(x06 + x05 | 0, 9);
     x04 ^= rotl(x07 + x06 | 0, 13); x05 ^= rotl(x04 + x07 | 0, 18);
-    x11 ^= rotl(x10 + x09 | 0,  7); x08 ^= rotl(x11 + x10 | 0,  9);
+    x11 ^= rotl(x10 + x09 | 0, 7); x08 ^= rotl(x11 + x10 | 0, 9);
     x09 ^= rotl(x08 + x11 | 0, 13); x10 ^= rotl(x09 + x08 | 0, 18);
-    x12 ^= rotl(x15 + x14 | 0,  7); x13 ^= rotl(x12 + x15 | 0,  9);
+    x12 ^= rotl(x15 + x14 | 0, 7); x13 ^= rotl(x12 + x15 | 0, 9);
     x14 ^= rotl(x13 + x12 | 0, 13); x15 ^= rotl(x14 + x13 | 0, 18);
   }
   // Write output
@@ -66,25 +66,25 @@ export function hsalsa(
   s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array
 ) {
   let x00 = s[0], x01 = k[0], x02 = k[1], x03 = k[2],
-      x04 = k[3], x05 = s[1], x06 = i[0], x07 = i[1],
-      x08 = i[2], x09 = i[3], x10 = s[2], x11 = k[4],
-      x12 = k[5], x13 = k[6], x14 = k[7], x15 = s[3];
+    x04 = k[3], x05 = s[1], x06 = i[0], x07 = i[1],
+    x08 = i[2], x09 = i[3], x10 = s[2], x11 = k[4],
+    x12 = k[5], x13 = k[6], x14 = k[7], x15 = s[3];
   for (let r = 0; r < 20; r += 2) {
-    x04 ^= rotl(x00 + x12 | 0,  7); x08 ^= rotl(x04 + x00 | 0,  9);
+    x04 ^= rotl(x00 + x12 | 0, 7); x08 ^= rotl(x04 + x00 | 0, 9);
     x12 ^= rotl(x08 + x04 | 0, 13); x00 ^= rotl(x12 + x08 | 0, 18);
-    x09 ^= rotl(x05 + x01 | 0,  7); x13 ^= rotl(x09 + x05 | 0,  9);
+    x09 ^= rotl(x05 + x01 | 0, 7); x13 ^= rotl(x09 + x05 | 0, 9);
     x01 ^= rotl(x13 + x09 | 0, 13); x05 ^= rotl(x01 + x13 | 0, 18);
-    x14 ^= rotl(x10 + x06 | 0,  7); x02 ^= rotl(x14 + x10 | 0,  9);
+    x14 ^= rotl(x10 + x06 | 0, 7); x02 ^= rotl(x14 + x10 | 0, 9);
     x06 ^= rotl(x02 + x14 | 0, 13); x10 ^= rotl(x06 + x02 | 0, 18);
-    x03 ^= rotl(x15 + x11 | 0,  7); x07 ^= rotl(x03 + x15 | 0,  9);
+    x03 ^= rotl(x15 + x11 | 0, 7); x07 ^= rotl(x03 + x15 | 0, 9);
     x11 ^= rotl(x07 + x03 | 0, 13); x15 ^= rotl(x11 + x07 | 0, 18);
-    x01 ^= rotl(x00 + x03 | 0,  7); x02 ^= rotl(x01 + x00 | 0,  9);
+    x01 ^= rotl(x00 + x03 | 0, 7); x02 ^= rotl(x01 + x00 | 0, 9);
     x03 ^= rotl(x02 + x01 | 0, 13); x00 ^= rotl(x03 + x02 | 0, 18);
-    x06 ^= rotl(x05 + x04 | 0,  7); x07 ^= rotl(x06 + x05 | 0,  9);
+    x06 ^= rotl(x05 + x04 | 0, 7); x07 ^= rotl(x06 + x05 | 0, 9);
     x04 ^= rotl(x07 + x06 | 0, 13); x05 ^= rotl(x04 + x07 | 0, 18);
-    x11 ^= rotl(x10 + x09 | 0,  7); x08 ^= rotl(x11 + x10 | 0,  9);
+    x11 ^= rotl(x10 + x09 | 0, 7); x08 ^= rotl(x11 + x10 | 0, 9);
     x09 ^= rotl(x08 + x11 | 0, 13); x10 ^= rotl(x09 + x08 | 0, 18);
-    x12 ^= rotl(x15 + x14 | 0,  7); x13 ^= rotl(x12 + x15 | 0,  9);
+    x12 ^= rotl(x15 + x14 | 0, 7); x13 ^= rotl(x12 + x15 | 0, 9);
     x14 ^= rotl(x13 + x12 | 0, 13); x15 ^= rotl(x14 + x13 | 0, 18);
   }
   let oi = 0;
@@ -140,9 +140,7 @@ export const xsalsa20poly1305 = /* @__PURE__ */ wrapCipher(
         const tag = poly1305(output.subarray(32), authKey);
         // Clean auth key, even though JS provides no guarantees about memory cleaning
         output.set(tag, tagLength);
-        output.subarray(0, tagLength).fill(0);
-        // Cleanup
-        tag.fill(0);
+        clean(output.subarray(0, tagLength), tag);
         return output.subarray(tagLength);
       },
       decrypt(ciphertext: Uint8Array, output?: Uint8Array) {
@@ -164,15 +162,14 @@ export const xsalsa20poly1305 = /* @__PURE__ */ wrapCipher(
         // Separate call to calculate authkey, since first bytes contains tag
         // Here we use first 32 bytes for authKey
         const authKeyBuf = output.subarray(0, 32);
-        authKeyBuf.fill(0);
+        clean(authKeyBuf);
         const authKey = xsalsa20(key, nonce, authKeyBuf, authKeyBuf);
         const tag = poly1305(output.subarray(32 + tagLength), authKey); // alloc
         if (!equalBytes(output.subarray(32, 48), tag)) throw new Error('invalid tag');
         // NOTE: first 32 bytes skipped (used for authKey)
         xsalsa20(key, nonce, output.subarray(16), output.subarray(16));
         // Cleanup
-        output.subarray(0, 32 + 16).fill(0);
-        tag.fill(0);
+        clean(output.subarray(0, 32 + 16), tag);
         return output.subarray(32 + 16);
       },
     };
diff --git a/src/utils.ts b/src/utils.ts
index b068d15..903970f 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -262,6 +262,8 @@ export function copyBytes(bytes: Uint8Array) {
   return Uint8Array.from(bytes);
 }
 
-export function clean(bytes: Uint8Array) {
-  bytes.fill(0);
+export function clean(...arrays: TypedArray[]) {
+  for (let i = 0; i < arrays.length; i++) {
+    arrays[i].fill(0);
+  }
 }
diff --git a/src/webcrypto.ts b/src/webcrypto.ts
index 96e0d7e..b7f9468 100644
--- a/src/webcrypto.ts
+++ b/src/webcrypto.ts
@@ -7,8 +7,8 @@
 //
 // Use full path so that Node.js can rewrite it to `cryptoNode.js`.
 import { crypto } from '@noble/ciphers/crypto';
+import { bytes as abytes, number } from './_assert.js';
 import { AsyncCipher, Cipher, concatBytes } from './utils.js';
-import { number, bytes as abytes } from './_assert.js';
 
 /**
  * Secure PRNG. Uses `crypto.getRandomValues`, which defers to OS.