How it was made

The grown-up page. Each claim names its limits and a way to check it.

The short version

Your browser encrypts each file with AES-256-GCM before upload. We store ciphertext and never receive the key. Everything after # is the key. Browsers don't send it to any server, including ours.

Every exit goes on the public record: we add it to SealLog on the Ootle at the next checkpoint (within ). It proves we said we deleted it, publicly and permanently. It can't prove we kept no copy.

Who sees what

Our server

We see a padded size (rounded up so the exact size stays hidden: at least 1 KB, and at most 12% over for anything bigger), the hour it arrived, its exit rule and how often it's opened. Not the name, the type, the contents or the key.

Our server sees your IP address while you're connected. We don't write it to disk: rate limits use a keyed hash, kept in memory, with a key that changes every day.

The Tari™ chain

The chain shows an output with a hidden amount, a hidden owner and an encrypted memo. The fee is public. Sends from your browser spend a grant from our sponsor wallet, then each send's change, so anyone reading the Ootle can link them to each other and to our sponsor wallet, but not see to whom or how much.

Whoever has the link

The file, as many times as its rule says (once, by default). The link is the key: anyone who gets the full link can open it until it's gone.

A view key

A view key can't spend, but it can read everything sent to this address, including files. Give it only to someone allowed to see those.

For files sent to a locker, the view-key screen lists arrivals and opens no file. Files sent to another wallet open with its view key.

For space you rent from us we see the amount, because we're the seller. Nobody else does.

Every party, fact by fact
Our serverAnyone watching the OotleSomeone with the full linkThe person you send it to
File contentsno (ciphertext)noyesyes
Name and typeno (encrypted inside)noyesyes
Exact sizeno (a padded bucket)noyesyes
The keynonoyes (the link is the key)yes
That a file exists, its arrival hour, its rule, its opensyescounts only, in steps of 64: records (stored files plus exits) per checkpointpartly (state, opens left)does not apply
Your IP addresswhile you're connected; not written to diskno (Tari's indexer sees it only if you press “Check it on Tari's indexer”)nono
Who you send tosends from your browser: no; sends our relay posts (before your browser's wallet has its grant) and dead-drop deliveries: yes, while they're sentno (a one-time stealth address)does not applythey know it's them
That we sent a paymentyesyes for relay sends: one wallet, one fee; sends from your browser trace back to our sponsor wallet's grant, and to each other through their changedoes not applydoes not apply
Payment amountsfor space rented from us, because we're the sellerno (a Pedersen commitment)does not applydoes not apply
The network feedoes not applypublicdoes not applydoes not apply
SealLog (hashes, counts, times)yespublic by design, written by the same key that holds our public tTARIdoes not applydoes not apply

Our server, in detail

Sees and stores
Ciphertext and its padded size; the hour an item arrived and when it left; expiry and view counts, and the time of a link's first open (shown only to its owner); the exit rule (link or password); for lockers: a random-looking locker id, a hash of its login key, the first and last active day, how many distinct days it was active, quota and packs bought; invoices (pack, amount, status) and the chain outputs that paid them; dead drops: the drop's Tari address, its label, its tag and daily counters; abuse reports; notes about ended links (state and day).
Sees, never stores
Every uploader's and every opener's IP address, live (so at the moment it happens we could link who uploaded a link with who opened it); the login key at sign-in; access tokens (hashed on arrival); which drop a file was left in, and the submitter's IP; that your locker submitted a transaction, not to whom; for dead-drop deliveries, the drop's address while the delivery is sent.
Never sees
File names, types, contents and real sizes; file keys, link secrets, passwords, seeds or any private key; your locker's Tari address, unless you make a drop or a relay send goes to it; who you send a file to, when your browser's wallet sends it (until its grant arrives, our relay sends it and learns the address while it's sent); which chain outputs your browser can open (everyone downloads the same copy of the chain); who shares with whom (there is no sharing inside bye).

Everyone else

A network observer
That you talk to us, timing, and padded transfer sizes. Nothing inside TLS.
Someone with only the link's path (no #)
That a link exists, whether it needs a password, and its state; once it has ended, why and on what day, and its exit receipt: the file's id and exit hour, and through the log which checkpoint windows its arrival and exit fell in. Not the key, the contents, the name, the real size or the open count.
A view-key holder
Every delivery and payment to that address: amount, time and the memo's link text. Not the files locked to the locker, and it can't spend. The one exception: files a sender sealed with “The recipient uses another Ootle wallet” do open with the view key, and the send screen says so.
The public, through our live numbers
The free quota; our disk's size and free space, rounded; whether uploads or the Tari features are paused; how many lockers were active recently, once there are enough of them; SealLog's latest checkpoint; how many minutes ago someone last locked a file; and for up to 24 recent files, each at least an hour old, a coarse size and an exit rule, never whether one was opened, only when at least 8 exist in the last week. Nothing per person, but someone who knows a file's size and rule may recognise it there.
Our host (Hostinger)
Everything our server stores, plus the disk and any snapshots it keeps; in principle, live memory.

The locks

  • Files: AES-256-GCM in your browser, in chunks; size padded so the exact size stays hidden.
  • The key: in the link after #, or sealed to a Tari address inside a stealth payment's encrypted memo.
  • Photos: location and camera data removed before locking, unless you keep the original.
The cryptography, in full

Everything that touches a file or a key runs in your browser, with your browser's own WebCrypto. The server never receives a file key, a link's secret, a password or your seed. At sign-in it sees your login key and keeps only its SHA-256.

  • Files: a fresh random 32-byte key per file. The content is padded with Padmé (at least 1 KB, at most 12% over above that), cut into 4 MB chunks and encrypted with AES-256-GCM. Each chunk's nonce holds its index and a “last chunk” flag, and every chunk authenticates the file's header, so reordering, truncation and extension all fail.
  • Metadata: the name, type and real size are encrypted separately, also with AES-256-GCM, padded to a multiple of 256 bytes.
  • Keys from keys: HKDF-SHA256 with a separate label for every purpose, so one derived key reveals nothing about another.
  • Links: every link has its own 32-byte secret, after the #. It unwraps the file key and derives an access token; our server stores only the SHA-256 of that token.
  • Passwords: PBKDF2-SHA256, combined with the link's secret by default (link + password), so a copy of our database isn't enough to guess it.
  • Lockers: one 32-byte seed made in your browser, kept in its storage, and backed up as your recovery code or key file. Everything else is derived from it: the login key (we store its SHA-256), the locker's Tari keys, a separate wallet key, and the dead-drop tags. There is no email and no password with us.
  • Safety code: 12 emoji (96 bits) derived from a locker's public keys, so two people can compare a locker out loud.
  • Proof of work: SHA-256 with a number of leading zero bits, instead of a captcha. Your browser solves it in a background worker.
  • Photos: location and camera data are removed from JPEG, PNG and WebP files before encryption, unless you choose to keep the original.

Sealing a file to a Tari address

When you send a file to a Tari address, your browser seals the link's secret to that address's two public keys, the owner key and the view key, using the official Tari library for the key agreement. The sealed blob is 125 bytes, and it travels as the memo of a stealth payment.

  • Why a view key can't open your files: the seal needs both the owner key and the view key to open. A view key alone shows that a delivery arrived, its amount and its memo text, but not the file.
  • The exception, labelled: a sender can choose “The recipient uses another Ootle wallet”. That seal uses the view key only, so anyone with that view key can open the file, and the send screen says so.
  • Who posts the payment: your browser, once its wallet holds the testnet grant our sponsor wallet sends when a locker opens: it builds, signs and pays for it, and our server passes it to the network without learning who it's for. Until the grant arrives (a minute or two), our relay posts the send and learns the address while it's sent. A browser send spends that grant, so the chain links your sends to the sponsor wallet and to each other. Dead-drop deliveries still go through our relay, because a drop's address is public by design.
Who could attack a link, and what stops them
AdversaryCanWhat stops themWhat remains
Network observersee TLS metadataHTTPS only, HSTS, Padmé size bucketstiming and volume
A curious or compromised server (including our host)read the database and the stored files, change responses, watch IP addresses liveeverything is encrypted in the browser; tokens and verifiers are hashed; no public keys or locker addresses stored for logins; no sharing graph; each chunk is authenticated together with its headercan serve malicious JavaScript (see verify); can refuse service; sees the metadata listed on How it works; can ignore a deletion and keep ciphertext, which is useless without the key
Someone with only the link's pathask for its statusthe key lives only after the #; opening needs an access token derived from it; link + password adds a link prooflearns that a link exists, and its state
Password guesser, onlinetry passwordsthe access token comes from the password; growing pauses; proof of work; only holders of the full link can count as wrong passwordsslow guessing against weak password-only links
Password guesser, with a copy of our databaseguess password-only links offlinePBKDF2-SHA256; link + password is the default, and password-only links come with a warningweak password-only secrets
Chain observerread all public statestealth outputs, encrypted memos, one fee and one amount for every relay send, decoy sendsthe fee and the transaction graph are public; relay sends are recognisable as ours; sends from a browser spend a grant from our sponsor wallet and then their own change, so they can be linked to each other and to us
A view-key holderread arrivals, amounts and memo textfiles locked to a locker need its owner key toofiles sent with “The recipient uses another Ootle wallet” open with the view key (labelled at send time)
Phishing through our relaymake our wallet send a linkthe server writes every memo our relay sends, from its own address; a client supplies only a 125-byte sealed blob; sends need a locker; drop deliveries only go to the drop's own addresssomeone can send unwanted bye deliveries to an address: daily caps, closable drops, a separate inbox section, no automatic preview, a warning before programs. Sends from browsers carry a memo the browser wrote, so a modified client can put any text on chain to any address, within one sponsor grant a day per locker, and two per network; inboxes show a memo that isn't exactly a bye link as plain text, never as a link
Grieferfill storage, push proof of work up, drain daily budgets, lock linksreserved lanes for small and anonymous files, limits per network prefix, a first-day limit for new lockers, public and reserved budget slices with alertssomeone with many networks can use up the public slices for a day; the reserved slices keep the guided demo working
Someone hosting abuseupload and sharesmall and short-lived anonymous files, one open by default, proof of work, rate limits, downloads only, a warning before programs, no listing or search, noindex, the report form, removal by idwe can't scan encrypted content, by design
Script injection or a poisoned dependencyrun code in our originno third-party code; a strict content security policy with Trusted Types where supported; no HTML sinks in our code; the one vendored artifact is pinned by hash; SRI on each page's top-level scripts and stylesheets, and the hash manifest for the modules they importbrowser extensions (out of scope)
Draining our wallet through the APIspend the sponsor walletthe internet-facing API runs as a separate user that can't reach the wallet; jobs are validated; daily budget slices; a firewall rule per user on the wallet portat most the daily budgets

Where Tari comes in

The Ootle is Tari's layer for applications, where payments are private by default and can carry sealed messages. We use it to deliver keys and move money, and every use below is something you can check.

Hidden on the chain

Stealth outputs
Sending a file to a Tari address, dead-drop deliveries, and paying for more room.
Check: /verify/tx/<id> shows the amount hidden, the owner a one-time key, the memo encrypted and the fee public.
Encrypted memos (XChaCha20-Poly1305)
The file's key rides to one address inside the payment.
Check: only the receiving locker opens it; any other key fails to decrypt.
View keys, separate from spending
Your inbox scans the chain in your browser; /watch shows what a view key sees.
Check: for files sent to a locker, the view-key screen lists arrivals and opens no file. Files sent to another wallet open with its view key. More in who sees what.
Private transactions built in your browser
Sends signed and paid by your browser.
Check: the transaction's input is your browser wallet's output, not ours.

Checked on the chain

Payments
Packs of more room, paid in tTARI to our receiving address with the invoice's reference inside it; the reference travels in the encrypted memo, so nothing on chain shows which invoice was paid, or by whom.
Check: the official Tari library opens the commitment to the exact amount, then a Tari-run public indexer shows it committed.
SealLog, a template of our own
Public hashes of stored files and of every exit, on the Ootle.
Check: /verify checks an inclusion proof against the root on chain.

Keys, not accounts

Your locker
No email, no password: a key that lives in your browser, and its Tari address.
We store a random id and a hash of your login key. No email and no public key; a dead drop stores its own address, by design.

Why not an escrow template? On the Ootle, TARI held by a template reveals its amount. Native stealth outputs keep it hidden, so that's what we use.

How payments are checked

In September a wallet bug on the testnet showed deposits that never happened (forum thread). A wallet saying “sent” isn't enough, so we credit rented space only after our own checks.

We credit a payment only after the official Tari library opens its commitment to the exact amount and a Tari-run public indexer shows it committed on the chain. The check runs in a separate process that can only read; walletd never decides a credit.

Read the code that does it

The goodbye record

Every exit goes on the public record: we add it to SealLog on the Ootle at the next checkpoint (within ). It proves we said we deleted it, publicly and permanently. It can't prove we kept no copy.

Every stored file's ciphertext hash goes into SealLog on the Ootle. Your browser can check that your file is in it, unaltered. It proves what we held and when, not what we did with it later.

SealLog is a template on the Ootle. At each checkpoint we add one Merkle root over the ciphertext hash of every file we stored and a record of every exit. The log in use is the one /api/v1/config names.

  • What a proof shows: that the tree we committed to at that epoch contains your file's ciphertext hash, or the record of its exit. Not that we held the bytes, and not that we deleted them.
  • What nobody can change: the log has no owner, so nobody, us included, can change its rules or the code behind it. Anyone can check owner_rule: "None" in its header on an indexer. Checkpoints are only ever added.
  • Who writes it: one key, our service key, the same one that holds our public tTARI. It can add any root, stop, or start a new log. Nothing yet proves that each checkpoint extends the last one.
  • What it shows everyone: a rough upper bound on how many records (stored files plus exits) we've written, in steps of 64; for each checkpoint window, how many whole multiples of 64 records arrived (usually none); and the writer key.
  • The code it runs: anyone can check that the template on the chain is our published seal_log.wasm, byte for byte, with template/check.sh --onchain and the template's address.

See the goodbye records

Check us

SRI only pins files to this page; it can't protect you from us. What can: comparing what we serve with the published source, release hashes notarized on the Ootle, and running your own copy.

  1. Every build writes a manifest with the SHA-256 of every file this site serves and the git commit it came from.
  2. tools/verify-deploy.py rebuilds the site from a clean checkout of that commit, downloads every file in the live manifest, and prints any difference.
  3. Since SealLog went on, each release's manifest hash is also notarized through it; /releases.json lists them with their transactions. Anyone can notarize, so only events whose signer is the log's writer key are ours.
$ git clone https://github.com/c7b/mutis
$ cd mutis
$ python3 tools/verify-deploy.py https://srv2052606.hstgr.cloud 27e29462b1a6922aa3fcc4fa629793fdf4f07f87

It needs git and Python 3.11 or newer. This checks the site now. A browser can't pin it for you between visits.

Check it in your devtools

No analytics, no trackers, no third-party scripts or fonts. Your browser contacts a Tari-run indexer only when you press “Check it on Tari's indexer”. To see it for yourself:

  1. Open your browser's developer tools and choose the Network tab.
  2. Reload this page, then drop a file on the home page.
  3. Every request goes to this site's own address. Nothing else appears unless you press “Check it on Tari's indexer”.

Headers and content security policy

Every response carries HSTS, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy: same-origin, X-Frame-Options: DENY and a Permissions-Policy that turns off the camera, microphone, location, payment and USB APIs.

Pages allow scripts, styles, fonts and requests only from this origin, and nothing inline is allowed to run. The app pages add WebAssembly for the official Tari library, Trusted Types where the browser supports them, and the two Tari-run indexers for the “Check it on Tari's indexer” button.

Try to read one of our files

Every hour we lock a test file. Here it is, exactly as our disk holds it.

Download the stored bytes
curl -OJ https://srv2052606.hstgr.cloud/api/v1/canary/raw

It shows what our disk holds. It can't prove we can't read your files: we locked this one ourselves, and we publish its key a day later.

Live evidence

Our own test transactions, never a user's. They come from /evidence.json, refreshed every hour; older ones are archived in the repository

Report a vulnerability

Please tell us privately before you tell anyone else. We'll answer, fix what's real, and credit you if you want.

The full threat model, with the findings of our own review, is in docs/threat-model.md.

What we can't protect you from

Beyond what our server sees (who sees what):

  1. The person you send it to. They can save, screenshot or forward it.
  2. Anyone who gets the full link. The link is the key. Use Link + password (sent separately) or send it to one Tari address.
  3. Chat apps that preview links. Their servers see the full link. They can't use up a view (opening takes a click), but they could open it.
  4. Your own browser. Your browser history, and any history sync, can keep the full link, key included. We clear the key from the address bar once it's opened, but we can't reach your history. Extensions allowed to read your tabs can see the link. Clipboard history (and cloud clipboards) can keep a copied link.
  5. A compromised device or extension. Encryption in the browser can't beat malware in the browser.
  6. A malicious version of this website. It's the known weak spot of all web encryption: if our server were compromised, it could serve code that leaks keys. SRI only pins files to this page; it can't protect you from us. What can: our verify-deploy script and running your own copy.
  7. Metadata. We see a padded size (rounded up so the exact size stays hidden: at least 1 KB, and at most 12% over for anything bigger), the hour it arrived, when it leaves (rounded up to the hour when that's an hour or more away; “midnight” is your own local midnight, which shows your time zone), its exit rule and how often it's opened. Not the name, the type, the contents or the key. Our server sees your IP address while you're connected. We don't write it to disk: rate limits use a keyed hash, kept in memory, with a key that changes every day. A VPN or Tor hides your IP from us; links without a locker work in Tor Browser's Safer mode, and Tari features need WebAssembly.
  8. On-chain facts. The chain shows an output with a hidden amount, a hidden owner and an encrypted memo. The fee is public. Payments our relay sends all come from one wallet with the same fee, so someone watching could group them as “sent by bye”, but not see to whom. Sends from your browser spend a grant from our sponsor wallet, then each send's change, so anyone reading the Ootle can link them to each other and to our sponsor wallet, but not see to whom or how much. SealLog shows roughly how many records (stored files plus exits) we write, in steps of 64, and when.
  9. Deletion you can't see. We delete on cue, and nothing can prove we kept no copy. Snapshots and free disk blocks are described above.
  10. Testnet resets. Esmeralda can be reset. Payments and on-chain records from before a reset are archived in our repository; files on our server are not affected.
  11. Legal requests. If compelled, we can hand over what we have: ciphertext, the columns on the “What we know” page, invoices. Not contents. Not keys.

What never touches a disk

Our server and web server never write your IP address, your links or your files' ids to a log. The database holds file and link ids, never keys; Privacy lists every table.

  • Web server: no access log, and the error log drops addresses, paths, hosts and headers.
  • API: logs only the route's name, the status and how long it took.
  • Rate limits: a keyed hash of your network prefix, kept in memory, with a key that changes every day.
  • Background jobs: fixed codes, never addresses, ids or memos. Our own test transactions are the only ones we publish.
  • The firewall: logging is off. System journal: kept for seven days.

That's not the same as “nobody ever sees your IP”: our server sees it while you're connected, and so could our host.

Limits and space

Free

If the disk ever truly fills, new uploads pause for everyone, and nothing already stored is lost.

When the number goes down, nothing of yours is deleted. You can't add more until you're under it again.

Files without a locker have their own reserved space, and small files have a reserved lane of their own, so a busy day of big uploads can't crowd out a quick note.

Limits

No lockerFree lockerRented space
Kept files (only you)nountil you delete themuntil you delete them

More room, rented in tTARI

PackPrice (testnet)
Sold outnoneThe disk is finite. We'd rather say no than overbook rented space. Free space still works.

When rented space ends

Your limit goes back to the free share. Nothing is deleted. After that, if you're still over, your oldest kept files are removed until you fit.

How paying works

We credit a payment only after the official Tari library opens its commitment to the exact amount and a Tari-run public indexer shows it committed on the chain. You pay our receiving address with this invoice's reference inside it; the reference travels in the payment's encrypted memo, so the chain doesn't show which invoice was paid, or by whom. The network fee is public on the chain. The amount and who paid are not. For space you rent from us we see the amount, because we're the seller. Nobody else does.

Testnet. tTARI has no monetary value. These prices are real logic with play money.

Paid features stay on the testnet until our code has been reviewed.

How it was built

Built with AI coding agents under the author's direction. The design, threat model and crypto choices were reviewed by the author. Python's standard library on the server, plain JavaScript in the browser.

Every release runs the server, site, browser and end-to-end test suites first; the records are in the repository. This deployment runs commit 27e29462b1a6922aa3fcc4fa629793fdf4f07f87.

Run your own

File sharing needs Python's standard library and a web server. The Tari features also need a walletd on the Ootle testnet and Node for the payment checker. Self-host guide

Who's behind it

bye is built by an independent developer in Mexico, for the Tari community's October 2026 “Spooky Secrets” build contest, and it's meant to outlive it. The name says what it does: your friend sees the secret, and then it says bye.

The fine print (privacy, terms and how to report a link) is on one page: the fine print.