De-anonymization in cashu
Link cashu tokens with wallets using keyset and Etag
I have used 2 things to link all cashu tokens and transactions associated with a wallet:
Keyset id
ETag
A keyset is a set of public keys that the mint
Bobgenerates and shares with its users. It refers to the set of public keys that each correspond to the amount values that the mint supports (e.g.1, 2, 4, 8, ...) respectively.Each keyset indicates its keyset
id, the currencyunit, whether the keyset isactive, and aninput_fee_ppkthat determines the fees for spending ecash from this keyset.A mint can have multiple keysets at the same time. For example, it could have one keyset for each currency
unitthat it supports. Wallets should support multiple keysets. They must respect theactiveand theinput_fee_ppkproperties of the keysets they use.
ETag: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag
Since ETags are used in browsers, I have tested it using cashu.me wallet. However, this could work for mobile wallets with okhttp or other caching practices.
Every wallet gets a unique seed and keyset ID pinned to their browser with ETags. When tokens are spent (swapped or melted), the mint inspects the keyset ID of each input token to identify the original wallet that minted it.
In this demo, I have used cashu.me wallet in chrome and firefox on the same machine:
Keyset Id
The mint recognizes Wallet A with the ETag and assigns a unique Keyset ID when asked for public keys. When the user sends tokens to Wallet B, they must send unique ID back to the mint to swap them.
const originDataRaw = await KV.get(`keyset:${kid}`);
if (originDataRaw) {
const { walletId: originWalletId } = JSON.parse(originDataRaw);
if (originWalletId !== walletId) {
console.log(`[MALICIOUS] Wallet ${walletId} is swapping tokens minted by ${originWalletId}`);
}
}
ETag
When the user first visits the mint, the server shares a walletId. Browser silently saves it and automatically sends it back on every future GET handshake.
c.header('ETag', `"${walletId}"`);
c.header('Cache-Control', 'private, max-age=31536000');IP address and User Agent fingerprint
Browsers strip the ETag header during POST requests. The IP + User-Agent fingerprint allows the mint to re-identify device signature in the absence of Etag.
const syncKey = `sync_v4:${await hash(ip + ua)}`;
if (etagId) {
await KV.put(syncKey, etagId, { expirationTtl: 3600 });
return etagId;
} (log) [MALICIOUS] Issued 3 tokens. Wallet: 9o3ztcs8nfcnbt59ejl0u
OPTIONS https://malicious-mint-worker.alicexbt.workers.dev/v1/checkstate
POST https://malicious-mint-worker.alicexbt.workers.dev/v1/checkstate
OPTIONS https://malicious-mint-worker.alicexbt.workers.dev/v1/swap
POST https://malicious-mint-worker.alicexbt.workers.dev/v1/swap
(log) [DEBUG] Found existing wallet session: wallet:6x10xf7xqvnsaohlctujd (Seed: u1btwe6849m)
(log) [MALICIOUS] SWAP
(log) [MALICIOUS] Wallet 6x10xf7xqvnsaohlctujd is swapping tokens minted by 9o3ztcs8nfcnbt59ejl0u (Keyset: 009d68d193ea3415)
(log) [MALICIOUS] cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vbWFsaWNpb3VzLW1pbnQtd29ya2VyLmFsaWNleGJ0LndvcmtlcnMuZGV2IiwicHJvb2ZzIjpbeyJzZWNyZXQiOiJiMDljZmExNDVhZmU4MjlkYWYwYmQ0MDc1OGQyYWQ5MjJhMzliNThkZjkzMmYyYjcxNjE4Y2U1Nzc1ZDRiOTQ1IiwiQyI6IjAzMjA1ZWI0MzJhZTI5Nzg2ZjFlYmE0M2VmM2IxOTU0OWE5N2MzMjgwNmU1NTQxMDVjOTMwMGI2MDg3MzM0MDY1MSIsImFtb3VudCI6NjQsImlkIjoiMDA5ZDY4ZDE5M2VhMzQxNSJ9LHsic2VjcmV0IjoiNWIyMDk4NjIzZGNkYTI4ODRjOTRkNGIxNmIxNjNmNTgyMTQ4NTVlZTQ3ODFiODljMWE1NDkwMmY3N2NhMWY3NCIsIkMiOiIwMzMyZDVkNzBiYjA1YTc2NWM4OTBjYjA4OTVjMjJiOTE1NDJjMDg5NmY5ZWM5MzgwZWU4ZDEwNzRlYTdkMTFiNmEiLCJhbW91bnQiOjMyLCJpZCI6IjAwOWQ2OGQxOTNlYTM0MTUifSx7InNlY3JldCI6IjA0YmM0YThjYzM2YTdlYzEyNjE1OWZjNGM1YjAxMmQ3ZjViODgxMWNjMDFiOTY1YjVkYjRkZjAyMDRkMzczYTIiLCJDIjoiMDM2MGVjNzAzY2Q1Yzc5Zjk3MDA1YzBjOWJjMjM0MDQ4NzZiNzk5MTJkYTFlMzE1YTBjZmVlMmE5NTVlMGNkMmQ4IiwiYW1vdW50Ijo0LCJpZCI6IjAwOWQ2OGQxOTNlYTM0MTUifV19XSwidW5pdCI6InNhdCJ9
OPTIONS https://malicious-mint-worker.alicexbt.workers.dev/v1/melt/quote/bolt11
POST https://malicious-mint-worker.alicexbt.workers.dev/v1/melt/quote/bolt11
OPTIONS https://malicious-mint-worker.alicexbt.workers.dev/v1/melt/bolt11
POST https://malicious-mint-worker.alicexbt.workers.dev/v1/melt/bolt11
(log) [DEBUG] Found existing wallet session: wallet:6x10xf7xqvnsaohlctujd (Seed: u1btwe6849m)
(log) [MALICIOUS] MELT
(log) [MALICIOUS] Wallet 6x10xf7xqvnsaohlctujd is spending its own tokens (Keyset: 005a5a68e0cc9da1)
(log) [MALICIOUS] cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vbWFsaWNpb3VzLW1pbnQtd29ya2VyLmFsaWNleGJ0LndvcmtlcnMuZGV2IiwicHJvb2ZzIjpbeyJhbW91bnQiOjY0LCJDIjoiMDI0OWVmNzI3ZGY5NDQ1OWRkN2JkMzI0MDFkNjlmOWQ2NTRmNGE2NjkwOTkxM2YwYmVlMzFjNWViYmY2OGQxNDRjIiwiaWQiOiIwMDVhNWE2OGUwY2M5ZGExIiwic2VjcmV0IjoiZjA4NmRkZDNmNzA2NzhlMzcxNjNmYTUzODhlYjQ5ZTQyNjRhN2EyMjk3MWZmYzFmMzQ3MzBjYjQyODE3MmY5ZSIsInJlc2VydmVkIjpmYWxzZX0seyJhbW91bnQiOjMyLCJDIjoiMDIzYTBiZjk0YzNhNzE3ZjhlYTZjN2FmMWE0MjM2OWU1NWQyOTM4MWQ0YjE2MjMyMzM0OWFiMGQxOGVhNmMxZmE1IiwiaWQiOiIwMDVhNWE2OGUwY2M5ZGExIiwic2VjcmV0IjoiYTdjMDRiMzRjYmViMDBjOGMwMDE2NmU4YzBiNmNkNzc1YjI2Yjc1NzdlNWRhNWUzNTdhZGUyZTA1MTViZmIyMSIsInJlc2VydmVkIjpmYWxzZX0seyJhbW91bnQiOjQsIkMiOiIwMzM5ODBhYWJkNmMwOGVjMzM2MjhkZmQxMTc2ZjE1YjVjNDhlNmQyYjhiNDQ2OWMxOTJhZjhlOTYwMDU1NTUxMWEiLCJpZCI6IjAwNWE1YTY4ZTBjYzlkYTEiLCJzZWNyZXQiOiI2Yzc0NzRmZDk2YmYwYmVjMjRmYTAzNWQwZjAzYzljZTgxZjkwYzFjMjUwOGU0MDRiYjMyOTAwYzU2NTdmNTEwIiwicmVzZXJ2ZWQiOmZhbHNlfV19XSwidW5pdCI6InNhdCJ9
This was tested with a cloudflare worker. Source code and deployment is added in this repository: https://gitlab.com/1440000bytes/keytag. It can be tested with a free cloudflare account.
Fix
Wallets should strip all common tracking headers (ETag, Last-Modified etc.) and route through Tor. Also, the keyset id could be published by the mint on nostr and validated by the wallets.
Note: Keyset Id isn’t the only thing that can be used for tagging. Mints can use unique denomination, expiry timestamp, fees, p2pk lock, timing correlation etc.



