How it works
Somewhere a committee decided that withdrawing your own money needs a signed confession saying which coins are yours. Exchanges comply by collecting signatures, xpubs and test payments, then storing them in the same databases that keep ending up on hacker forums. This service exists so you can satisfy the form without feeding the panopticon. The rest of this page explains how it works and what you're trusting.
In short: you get a hop address that can sign the exchange's message. You withdraw to it, and it forwards everything to the destination address you chose, usually your cold wallet, whose key never signs anything for the exchange.
The hop address
It is a taproot address with two ways to spend:
- The normal path is a key shared between your browser and the server. Each holds half: your browser builds its key, B, from your emergency kit's rescue key, and the server keeps its own, S. They combine with MuSig2 (BIP-327), so moving the funds this way needs both.
- The safety net is a hidden script,
<D> OP_CHECKSIG, where D is the key inside the destination's bc1p address. The destination's wallet can spend this path on its own, at any time.
normal path · shared key
both needed · only ever pays the destination
safety net · hidden script
the destination, any time, without the server
■ your browser and kit■ the server■ the destination
The server sends its key, and your browser builds the address itself from the destination address, its own key and the server's key. If the server's address differs, the page refuses to use it. A server can't hand you an address without the safety net.
Signing the exchange's message
The page and the server sign together through the normal path. The result is an ordinary BIP-322 signature in the "simple" format: a base64 witness, the same kind a single-key taproot wallet produces. The page only signs the exact message you pasted in.
The forward
While the page is open, it asks the server about the address every few seconds. Once a deposit has one confirmation, the server proposes a transaction, and your browser checks it before signing:
- it spends exactly one deposit;
- its only output pays the destination address;
- the network fee is at most 2,000 sats.
These checks only stop a forward that pays someone other than the destination, or burns the deposit on fees. The server always proposes one that passes, and it can't move the funds without the page's signature. The server looks up the address, not a transaction id, because exchanges often fee-bump withdrawals, which changes the id. You can reuse the address for later withdrawals.
The safety net
If you lose this browser, or the server goes away, the destination's wallet can claim the funds through the hidden script. Whoever holds the destination's key holds this path: usually that's you, but if the destination is someone else's address, the safety net is theirs. It needs two things:
- Your emergency kit from step 2. Spending through the script requires the address's shared key, which the address alone doesn't reveal. The kit rebuilds it, along with the spending rules, on the recovery page.
- A wallet that can do the spend. A bc1p address holds a tweaked key (BIP-86), so the wallet has to sign the script with the address's tweaked private key. Bitcoin Core can do this if you give it that key, and custom software can derive it from the seed.
Taproot addresses that are script-only multisig have no usable key inside them, so the safety net can't work for them. Older bc1q addresses aren't accepted at all.
Emergency kit
Each browser gets one rescue key, 16 random bytes plus a 2-byte checksum, written as nine groups of four hex digits. The kit is a text file holding it and the server's public key. The page builds the browser key for each destination address from the rescue key, so one kit covers every hop address the browser makes with that server. The rescue key never changes, but switching server means saving a new kit. The page shows you a hop address once you've downloaded or copied the kit, and you can check the saved file at step 2 afterwards. Restoring the kit at step 2, in a browser that hasn't saved a kit of its own, brings back the hop addresses it covers, so you can forward from another device or after your browser clears its storage.
Keep it private. With it and the destination address, someone can find your hop addresses and rebuild your browser's half of the shared key. That half can't move funds without the server's.
Anyone can rebuild a hop address from the kit without the server:
R = the rescue key's first 16 bytes
b = TaggedHash("unravel/browser-key/v1", R || scriptPubKey(destination address)) mod n
B = b·G
hop address = tr(MuSig2(B, S), pk(D))
TaggedHash is the one from BIP-340. MuSig2 is BIP-327 key aggregation of B then S, in that order, not sorted. S is the server's key and D is the key inside the destination address. The recovery page does this for you, and is one self-contained file you can save and open offline, with the server gone.
Who you trust, and for what
- The server alone can't move your funds. It can refuse to forward them, and then you fall back to the safety net.
- Your browser alone can't move your funds either. Someone who copies its storage still needs the server.
- The code this site serves is what you really trust. Your browser's key is made by this site's code. If that code were malicious, whoever runs the site would hold both halves and could take a deposit as soon as it arrived. Checking the spending rules can't catch this, because the normal path shows up as a single key with no record of who holds it. The fix is to run a copy of the page you've checked yourself, such as a self-hosted build that matches the published source. That copy can still use this site's server: pick a server with the button at the top right.
No address design removes that last point on Bitcoin today. Without covenants (opcodes that restrict where coins can go), some key other than the destination's has to be able to spend, and whoever holds it could take the funds.
Checking the address yourself
Step 3's "verify" shows the address's spending rules as a descriptor:
tr(<shared key>,pk(<the destination's key>))#checksum
Give it to Bitcoin Core with bitcoin-cli deriveaddresses "<descriptor>", and it should print your hop address. The key in pk(…) should match the destination address: it is the 32 bytes encoded inside it.
Privacy
The exchange sees one ordinary taproot address that later pays the destination, which is no more than it would learn if you had withdrawn straight there. It never sees your xpub. The hidden script stays hidden unless the safety net is used. The server does learn which hop address belongs to which destination address, the exchange's message you sign, which can name your account, and your deposits. Like any website, it also sees your IP address. Use a fresh receive address as the destination, and a leak shows only that one payment.
Fees
There's no service fee. The destination receives the deposit minus the forward's network fee. The server pays a little over the mempool's going rate for the next block, but never less than 1 sat/vB or more than 10, which keeps a forward at or under 1,110 sats. If it can't get a fee estimate, it waits rather than guess. When fees are higher, the forward still goes out and takes longer to confirm, and step 5 warns you before you withdraw. Step 5 also shows the current fee and the smallest deposit that can be forwarded. Some exchanges also take their withdrawal fee out of the amount, so if you're paying someone an exact sum, send a little extra.
Status
This is extremely experimental and runs on Bitcoin mainnet with real bitcoin. Use it at your own risk.