Don't trust us. Verify.

The Verify pillar of the deniability infrastructure. 22 cryptographic tests that run in your browser using the same engine that powers deny.sh, plus reproducible-build hashes, signed releases, and SRI for every served asset. Don't trust us. Run them.

Run the tests yourself.

Every test executes in your browser using the same cryptographic engine that powers the API. Pick a seed phrase or use the random one.

0 / 22

Type your own seed phrase or use the random one. Every test runs live with your input. Nothing is pre-recorded.

running with:

Everything runs client-side. Nothing leaves your browser.

01

Statistical Indistinguishability

5 tests
Can anyone tell the difference between a real control file and a deniable one? We generate hundreds of samples and run every standard randomness test. The answer should be no.
Real vs deniable comparison entropy gap measurement

Byte value distribution: random data (blue) vs deniable control file (green). They should look the same.

Chi-squared test byte frequency uniformity
Shannon entropy information density per byte
Kolmogorov-Smirnov test cumulative distribution fit
Serial correlation adjacent byte patterns
02

Ciphertext Invariance

2 tests
The encrypted blob never changes. Creating a decoy is a pure mathematical operation on the control data, not on the ciphertext.
Ciphertext unchanged across deniable decryptions 5 variants, same blob
No bit mutation during deny operations 20 iterations, zero drift
03

Length Independence

2 tests
The ciphertext doesn't reveal the real message length. The 4-byte length prefix lives inside the encrypted zone.
Different length messages from same ciphertext short + medium decoys
Length prefix inside encrypted zone header + 4B + plaintext
04

Cross-implementation

1 test
The browser engine produces identical output across all input types, including unicode.
encrypt/decrypt round-trip incl. unicode + edge cases
05

Known-Answer Tests

3 tests
Key derivation is deterministic and sensitive to every input: password, salt, and ordering.
Key derivation is deterministic scrypt reproducibility
Different passwords produce different results 3 password combos
Password order matters pw1/pw2 not swappable
06

Fuzz Testing

2 tests
Random messages, random passwords, random sizes. Every round: encrypt, decrypt, deny, verify. Plus all 256 single-byte values.
Random input fuzz test random sizes + passwords
Single-byte edge cases (0x00-0xFF) boundary values
07

Security Properties

5 tests
Non-deterministic encryption, no error oracles, uncorrelated control files. The properties that make this secure, not just functional.
10 encryptions = 10 unique ciphertexts random salt each time
Wrong password produces garbage, not error no oracle leak
Wrong control file = different message no accidental reveal
Ciphertext entropy on repetitive plaintext 500 bytes of 'A'
Control files uncorrelated XOR entropy check
08

Multiple Deniable Messages

2 tests
One ciphertext. One pair of passwords. 20 different control files. 20 different decrypted messages. The original still works after all 20.
20 deniable messages from 1 ciphertext one blob, many truths
Original message still intact real secret survives
// supply-chain integrity

Reproducible build and integrity.

The 22 tests above run inside your browser. The bundle that runs them is delivered from this server. If a sophisticated attacker compromises the delivery path, the tests still pass, but they pass on whatever JavaScript the attacker injected, not on the published source. The countermeasure is: pin the bundle to known-good hashes, and reproduce the build from public source.

What's served right now.

Every cryptographic asset on deny.sh has a published Subresource Integrity (SRI) hash. These are the SHA-384 digests of the exact bytes our server is sending you for the v=74 / v=122 bundle. Recompute them locally, paste them into your integrity attribute, and your browser will refuse to execute any byte that doesn't match.

Asset Bytes SRI hash (sha384)
/js/scrypt.js?v=74 17,811 sha384-P50LwVtK4JkjiY/Jl2JTPauGn+CwsK/6GLLFhUqTpMSt5Kfn9lbLQAkiPTbHdCHe
/js/crypto.js?v=74 9,243 sha384-9gi8GmLkDOVNypfxTH/GMp77WsMke8PqPTrp4LP4PX2oVSgkIj/UvN++InYndOlk
/js/verify.js?v=74 50,007 sha384-9fE7TWv3+eKQoOobwHMlB7RSDbgjgepAgroJcAjb9lzaNhwg3T9NRG5nMGt0ucyK
/js/nav.js?v=74 1,652 sha384-BV81ynd3Lsr25LspYx1P9J+PcUjCi3PhBFYVUbUMY5VlqfHPlkIs9U36U3TCr/eL
/js/copy-code.js?v=75 799 sha384-IZXsXjvi0nsvh4Vygcmz4hkBee+SeonEp+3xqTei6VHi7ZzMmWVZ8OO5FhA6mwUI
/js/back-to-top.js?v=74 654 sha384-GbBBXsssarGIR/ST7jZSFpjfdOD0M/czgfNyxspI9OOrDc+oBScVBg7W4hz/cdRY
/css/style.css?v=164 121,834 sha384-uJrSPO83RzcZttCXQNFnfeNY4SrKVjb+WpwaMH5o2otBFGFbLiBODBT0tWHM6HAI

Recompute any of these locally with one command:

# pin verify.js by recomputing its hash and comparing
curl -sf https://deny.sh/js/verify.js?v=74 \
  | openssl dgst -sha384 -binary \
  | base64

# compare against the table above. mismatch = do not trust the bundle.

A live SRI manifest, machine-readable, is published at /.well-known/integrity.json and updated on every deploy. Pin against that if you're integrating deny.sh assets into another origin.

Reproduce the build.

The published artefact (the JS bundle that powers the in-browser verifier and the SDK on npm) is built from a tagged commit on deny-sh-crypto/deny-js with a single deterministic command. Same source + same Node version + same dependency lockfile = byte-identical output. If your build doesn't match the published SRI hash, either the source changed or someone tampered with the artefact.

# 1. clone the public source at the released tag
git clone https://github.com/deny-sh-crypto/deny-js
cd deny-js
git checkout v1.1.0

# 2. pin Node and reinstall from lockfile (no transitive surprises)
nvm use 22.22.2
npm ci

# 3. build the published bundle
npm run build:web

# 4. hash the bundled crypto.js the same way the server does
cat dist/web/crypto.js | openssl dgst -sha384 -binary | base64

# expect: 9gi8GmLkDOVNypfxTH/GMp77WsMke8PqPTrp4LP4PX2oVSgkIj/UvN++InYndOlk

Determinism details. The build pins Node v22.22.2 (LTS), uses npm ci against a committed package-lock.json, and disables timestamp-based dependency invalidation. The bundler (esbuild v0.25 in --minify --no-bundle-time-comment mode) writes byte-deterministic output for the same input. We don't ship sourcemap timestamps, build hostnames, or compile-time strings into the bundle.

How publishing and signing work.

Every release of the SDK lands on three independent surfaces, each with its own integrity story:

  • npm registry. npm publish from a workstation that holds the publishing token. npm computes its own SHA-512 integrity field and stores it in the package metadata. npm install deny-sh@1.1.0 --integrity verifies on install. The published package.json includes the repository.url pointing at the public source mirror, so you can diff what npm shipped against what GitHub holds.
  • GitHub release. Tagged commit + signed annotated tag. The release page on deny-sh-crypto/deny-js/releases publishes the source tarball + a SHA256SUMS file + the maintainer's GPG signature over that file. Verify with gpg --verify SHA256SUMS.sig SHA256SUMS.
  • This server. The web bundle is rebuilt from the tagged commit and uploaded to the deny.sh origin behind Cloudflare. The SRI manifest at /.well-known/integrity.json is regenerated on every deploy. If the bundle changes without a corresponding GitHub tag and signed release, that's a tamper signal.

Coordinated disclosure for any integrity issue (mismatched hash, unsigned release, source-vs-published divergence) goes to security@deny.sh per the disclosure policy.

Honest caveat. Reproducible builds protect against tampering between source and your browser. They don't protect against bugs in the source itself; that's what the in-browser test suite, the cross-language byte-compatibility KAT vectors in the four SDKs, the cryptographic construction write-up, and the external cryptographic audit on the roadmap are for. Different layers of defence; check all of them.

Convinced?

The maths works. Now try it with your own data.

Encrypt something Build with the API