NymDrop, anonymous submission over the Nym mixnet (live on my blog)

Hi everybody,

wanted to share something I’ve been building on top of Nym.

A bit of background first:

I run a small blog called Nonlocality - The Observer Effect (archives.virebent.art), independent, self-hosted, no ads, no tracking, no CDN, no analytics, no logs.

The editorial mission is specific: collecting testimonies, analyses, and voices from parts of the world where press freedom and free speech are suppressed, censored, or simply absent, independent journalists working under censorship, researchers whose findings make powerful people uncomfortable, activists documenting abuses.

For that kind of contributor, anonymity isn’t a nice-to-have, it’s the difference between publishing and not being able to say anything at all.

That’s the actual reason I built NymDrop: an anonymous submission tool with the Nym mixnet as its only transport.

Some details on how it works:

  • Client-side encryption: everything is encrypted in the browser with X25519 + HKDF-SHA-256 + AES-GCM-256 via WebCrypto before it leaves the source’s machine. The server never sees plaintext.
  • No-log, blind relay: the public-facing server writes nothing to disk, no IPs, no timesorwards ciphertext into the mixnet and forgets it.
  • Separate reader, offline from the public server: decryption happens on a completely different machine, with an X25519 private key that never touches the internet-facing box. Even if the public server got seized, there’s nothing readable on it.
  • Self-contained proof-of-work anti-spam: the browser has to find a hashcash-style nonce before a submission is accepted, no external challenge server, no CAPTCHA, no third-party JS.
  • No CDN, no tracking, no NIST curves, only a small Go binary and some vanilla JS, nothing else.

Live instance bolted onto the blog as the actual submission form: https://submit.archives.virebent.art

If you want to look at it or tear it apart:

Still rough in places, but it’s tested end-to-end and works over the mixnet.

Mostly wanted to show a concrete use case where mixnet transport actually solves a real problem for real people, not just a tech demo.

Happy to hear thoughts, criticism, or better ideas on the design.

Gab

3 Likes

Hi Gabx,

Nice, however the text is not white and therefore very hard to read on the black background.

Best regards
Ch1ffr3punk

1 Like

Hi @Ch1ffr3punk

Yes you’re right, now there’s the day/night button :wink:

Best regards

Gab

3 Likes

Ah, ok. Thanks! But it would be IMHO still nice if in dark mode the grey text would be much brighter like white.

Best regards
Ch1ffr3punk

1 Like

Thanks for the suggestions color choice, it actually looks better :slight_smile: :+1:

Larger fonts?

1 Like

hey, thanks for sharing - this is very cool!

I am getting this error when trying to send a 1.5MB file. Is that beyond the limit?

Hi @sudonym, thanks for the report.

The code tried to convert your whole file into text in one single giant step, and the browser choked on anything bigger than about 1MB.

Fixed that so it processes the file in small pieces instead.

Tested with real files up to ~8.7MB over the mixnet, works fine, just takes a couple minutes to arrive.

That’s normal: Nym deliberately sends traffic slowly and steadily to protect anonymity, it’s not a glitch.

Try again whenever, should go through now.

Best regards

Gab

I am updating my NymTransfer program for the official nym-client and was nasty and tested large files, but when doing so the exit gateway??? on the receiving site gets sometimes “confused”. So very important is to find the right balance with chunking and rate limiting, which I now implemented in NymTransfer CLI. Not sure if I should make it publicity available, once people would figure out to use it without NymVPN, free of charge, for torrenting…

1 Like

$ ./NymTransfer-linux-amd64 -h

A file transfer tool for the Nym Mixnet and official nym-client

USAGE:
Send a file:
nymtransfer -t
nymtransfer -to
nymtransfer -a
nymtransfer -alias

Receive files:
nymtransfer -r
nymtransfer -receive

OPTIONS:
-R float
Chunks per second (default: 1) (default 1)
-a string
Alias name (e.g., bob)
-alias string
Alias name (e.g., bob)
-c int
Chunk size in KiB (default: 64) (default 64)
-chunk-size int
Chunk size in KiB (default: 64) (default 64)
-o string
Output directory for received files (default “./received”)
-out string
Output directory for received files (default “./received”)
-p string
Nym client WebSocket port (default: 1977) (default “1977”)
-port string
Nym client WebSocket port (default: 1977) (default “1977”)
-r Receive mode (listen for files)
-rate float
Chunks per second (default: 1) (default 1)
-receive
Receive mode (listen for files)
-t string
Transfer file to address
-to string
Transfer file to address
-v Show version information
-version
Show version information

ALIAS FILE:
Create NymTransfer.json in the current directory:
{
“aliases”: {
“bob”: “BobNymAddressHere”,
“alice”: “AliceNymAddressHere”
}
}

That’s a useful data point, thanks for sharing.

We hit the same territory today testing NymDrop: max 255 Sphinx fragments per set, client paces real packets at ~50/s by default. Pushed it to 8.7MB using just the stock nym-client fragmentation (no manual chunking on our end) and it arrived clean every time, few minutes delivery. Never saw gateway confusion at that size.

Curious where your threshold was, what file size started causing it, and are you seeing it as a gateway-side rate limit, or more a reassembly/state issue on their end?

Your 64KiB/1 chunk per sec defaults are way more conservative than the client’s native pacing, so if you’re hitting problems even with that, good to know before we ever think about raising our own cap past 10MB.

Regards

Gab Virebent

Hi Gabx,

NymTransfer itself works as it is a port of OnionTransfer. 100MB, for example is no problem, but I tried also with 1GB and around 300MB I got on the receiving side red errors from nym-client which where caused by the Nym gateway???. I will test this further. On the other side, the Nym Mixnet should be only used for small payloads, like in your case or for email/Usenet etc.

I just did a test again with 100MB with my very slow Internet connection, which I won’t upgrade in the near future.

The result is IMHO pretty good for the Nym Mixnet, with the official nym-client.

I leave NymTransfer as it is and later if journalists or whistle blowers need to transmit larger files they should split files by themselves and use a break of a couple of minutes between each session, to give the receiving side proper time between each sesssion, due to latency which means that the finishing process on the receiving side takes longer until it reaches 100%.

I attach the .go source code PGP symmetric encrypted and you and the Nym team can DM me here for the password.

Best regards
Ch1ffr3punk


-----BEGIN PGP MESSAGE-----

jA0ECQMCKmHMYSzOfd7/0uwB21f/Hr8jFpaJaPkGrAhlhU3Wy2/3K8rSGI/B4sq5
y6gk9GI19Lo3Sv7vgNr1ve6mB6w4KrVQ6rBo6QxY30uX/VDVICIGfsqK2F5ZxQYv
2HRBQeMYpFIYMn0TuDYb4NZDtLfHnRTIAPvXP+wo/ATfy3l/+3+Rj7bY/pkdlU3W
9kOfGEsJNInGhuqZC4G/uJP+qhoGC6cYLnaKcESBibczEcYEDg9bLXkPr0rjRh6p
ZNJC6MFJKVQl/25ZSjumZ5VV50n4u+leR1wZcglBzvS1DHL9iGcwQcOujnL4wzH5
mcMi5XYPZDi1OKvoXdc+44hwdHntuuwKuZZyz+7shzjZKfcxKx4Tsa+OXY53wg39
NVR01VbbM1ww4sz0slNKSdWEzsIWeM3e/wYtPaQaIHIrnvgmqT5pl4khnD5a9Lvw
gbNysM18xi1VLbSV8n9bmDVgZrBKBoP16gkE/YmJptgy83gOuVsCJ8fH+cZZQMz1
IJ3x5tEZLef3NnfW/GB46IzzZYjqOo7ixRYyJTBPWX07BVap0iJfS1nB2PN7mT7w
VTXVXRwTD+JIB/+SUQcD2p67Fz4QBWauSeUhCdPTGu1Kz2yWJlCldt3UC+ezsXWl
dz2Ri54WS+lOwAlNV25cD6hJwtoIY+p+QE897XiXLXRjo930uCKAju6VzDOIhu05
SquIKOyRH506toR3/BHOKX0eiObET5jcyzWevLyDuIzBPMvaAcTlm2Mjtale3keR
vek7uwL8QBc/L8gFB/RjZ9Fm7aHh1tJsyQQ2LlHi0wxBWFAzxpynggg3mGSkimvo
idpqI6nVFyx2rjmOIpwFB6wjlAqu9CW8HBLCRsR99c3tQ7dZfzIcMd+/wkBDNMMd
FA+edMaoHcBFfku4f+WLcHgFu6xUMD7VaQbp0Gy6kCn0P7pE3r1ogxsDiXepKNWn
sf8cTvalVUCo2nJJIYIMlif4eOn4CYg/Y8o+NdefJThJ/B3/BEfwhnkpI0h49ps4
cMDPe1wyOotKHkWcmiOx00Rt1mgwHwa519GnjwrMjHnVsIUtEnR43Bptn3kf8qfT
I7EVVy8t21cAmo2Bobzz9okEymqLiP3vloDa5fasCXeKtb94kKjDzbKiho6q5D7P
e9JiqQN6bjPNhgrr/2frZQxWwB4P9FiqHKK7NAUMU0eWhSEgam9PNOB1Q8mC4GkA
EgYfYP40LMl2xYx0RNpEcfKC0Ut1VYcSc6XG1oxV7MmvKuVXtFWLXbYNvCHMZ8pz
7lpCwXgc5/uwLQqAtlbnuMxBbaluVvSUjaivw5fcx2rH6nBRi8daEPxGrOHWCzIL
pE1nRaG2XEsdsoCvHm+8OwXl1guFtnTWXKjccONrC5apetxiyVq9EHwy4tv3vKPL
Q0jkhm2F+3wTTSb86GC1s2gozMbtTrTv9con6h1Eta3M7UcbpXfae5L9gP22Db8g
b9YYQNqW8C1qP/EfXoVAUpS3gtCga2rl6hc17Gdzb+0lWDrk8zW+ys5FVEwtqZMw
TJPND4UwPGoULo3wpnrAoWIXWsY4oOz4Vlh6XCrMY/MOE9LTK/yau6gu7rMTSkoV
Z3IQd8xjIelLvVOzRnPnVGLFvbedSynpsjc9HgJE4WLopb2d6vBRty7onWMu0qmu
8YJxjB8YcxZh4KgK2BBYja55PoYUwipjXgXh2YNh//VDRDkzMYjIYt85bt8+Z56I
SmFCvB5QNKRdpubD0F6bjUiyjCAYZpYlM2hCRy8WIJCtVwz1CeOUhV1zz5XSWBlB
WIxy+TdibU0CNHZRd9BbztXQ+rUhQpdhThVdcfYZHGhgzgRQdJPd/8cHs6LyW3iu
Guj/trkCO8vsk4D1IpIe8dcueT7WivWiegomJZmOK8Ib6VTvyV8sx3xXlhQoMX/G
MNbLhMqNU9YCrqwL+B7v9njrdQC6z/+zM1wlc0ojraMsxfY5rYWfvPVYmCBTvbOi
IMFyhhWBYBloL+9v835HJv8iZVjMvUFAHYNAL6plpRtE9R47CyugiHE03eTJQHlm
3IR7XDay/nHJyY8i7ZpK+KLc+KhEXmWDMihjs3o5tabKZzDaTfkC7wRBNVKLEY6o
rf2/Lo0DTPO+kbEky6EA/G65Re+EwJaQYBnbn6B2ZiWhbcyMQfHKMABxP0h99AiQ
7fcfWmXPiKqFfQRVbtBRBe0kAbDAHFoMJ/xbZ7a5x/YJrG/PlhbmpvAKvXd6FSYS
AidSvx8UqeH1XxfnUPELRGPhYHH4I+6r1arbWiaxYTF4qDELgRrdt25HPYNsbETM
/1T0wn6UYbcTQZSYdFXtJOAAs3DM+7tEyDd+3Ovgd5OtQus40z9y4yd+flK70keW
M5qg2JU9jNadyLG7Qha8862oOKIowhke55RRnNYn5hr1Hntd/4NczOkaJBB9i+qA
SuCFP9b65i1vkMxG5VkzmT6//zcW7m3KYfCmqwhuWogzhutv6jyaU9PMVsJlevSC
3zJi92xs5ZYvp1fIaSyFO03/t35Gkbm4K57Xc/Mo/BsFc5LM0yonQaapNeG2YEmD
8PkYDYO0zXw/jPq2NL2R3saGJhFpTf9Rrei4o1hK9BT9df36nK80n0NX0O0lX85Y
BKW1LKsx51HYjSReF4qDkifr5Xd92LrU2ACEM9J1Vc2QGYiQzjDL7iP/73heplHk
8XDCSprwFb99vGq02DOwYDyT2I9EYNXX5egQrcdGizBg5iw94ZC99TOFiRtf0Tki
7JK9pMFyRt0GIqdrA+y9HudME7075uIOIeTo1QyvbZaYzFuZvUGLaKcfk7A1wPH4
bD9qqxi+ppi2cZSxfB+Fg/9/UKs+Ed2O/5DSTw8Fo88cl4AJLwaz9ybxJoevibxe
6ro7MT3oqWyVRTxWqOapegiU2Mi49rFNITe6N4xrmFjZAtF/bNLA5vT3TP2Bs1fC
UXQqS+iwdjI9ZEISeUSjXabghrnX/2aAeY9EOhDt5t6TNdab5M0l5yoSJzlL/uix
BxHZcSLBU0WuhvTCRgWgdHPA9a5/ttGAWyfLmhGn3LqBhX/HwUk6jpaksFSACxlg
L0prprwrKnnQ9Hp5svuufAljXfIxNSyedgA7NM+9tNlzIws5kO3mVHGGl9xd1opU
J8jNLY9p3ph5lOxtiOykMewX/bSFp1v4n/Ra57SBGM7/chj9GmjdQcAaP85Oec42
ely1NcGott5EVVtB46ukunqWE5U7MEkNW2vfovo020LUS+I43GxWOCkw7xkojfqQ
GJMu9K6XcEneAERtf125/j+g4PoNZVVD2FfBBX9pqi/bwl4Fl0+mGsFzpDLuxIfM
yW+MbOmBcYgL1r94lAcmbLRHDi9VveCkdA2P2BsP83rmOu+Cd/rOX5U0w9CaYNVj
Gq7Biec4pO3j4PbHWQSrN3efO4YfgraiqhMwGgqX4aVGU5Wmw8Mjz7o1t3vPk3jc
D/5EFWibUyQn0aaIJ51MQovsWpQPS9bFGqO2fHSvvC4wnBMKilV02RZaPHbjsDz9
Wn6SSFg2XpOdoZk8BLIcWxBJ25Ew2IPt4NXFrhhZYBk3NgvWvNKvUvarvwMRtykL
jLt+YpYX3973a8GN8vP2JEhWHocBuq7ctxaXMGI/TculHX+nAC0giXO/Mh57W1TX
J4k4Phd5CJkh49WdImzoKowy1V/bTCU7aVFl//9vJhYP/p4sy+APT6/HGUOAs2yW
EdfdaEJArow3akQdcu57r3QvP4CLx8HtyBohNZahYKIwV6s0Kc0oCKtHMajspf71
fp0ARos/DmA5Vd7RKsr5CW2JpC/A+ScciXrjaMR4EMc6KD3b+jFZAOLYJbL+xkO9
0AR2C7znUMuiE2Xf7Q0RswBblgsm0IwwWjUsRF1sarmMp/8lWb07kiD/0ZyG434L
ZCIbirNXNs9LHEVFoINly+8MjDfgV8SLBA1BwBUSGJDVEf/8IgSIBNqHlx1AwHeq
tGkHfgDQwVFEBOHur8LfR21ODlDTqyEXCQawB9f3igBbY+GiCRKHlCTI2qufHF7x
FmvL1P9YFTKCMn8Y527J6VL2PQS0Iw63tBxavQOQ1Jz7BD9ydoQ7GLeGKhejwLSG
HlmuORRAZ3KNQDcVjaWbX1/Y6Be7vWa8mgyhqyNFigtFk2Kb9eHbPZgB/xulB4Ph
FtGIbdLkhO3Z8+UWlDebT8n+cmzUrdBx9ccXV7MHl4qG193HPG0g8muj2Jq42B8I
t6Rcf1y+FY4gKZ7MIakc18sLDMqEi3R0oiheH5SBF0v02JnBsdZhThjsGdhC0VHT
q/3AmyrJ1xaQldx3Zt0uK87iRE1Jmz7XSuHzMMfcK+4NVT7b6t3N1OOiHNZfAM0c
rncGwx8f4VAa3A26huVbfzJ3GXlIOMxaB5lsicwDkaIEFcZyAxAN23aL41UnX1vL
iPbRKraPAWUEbN0bLII6Q/HIZwEE5Rb83WnQLd3x9IbN3txPFCyMWYoLpO+MziRe
P2t/GBg2xwlwifCedzjs9pa6T3+agO5cEenRsrNSqopdNlisdZCTol1bg7XePTT/
CzQ+UZpDXTRDDSWPlnT9o2MEdL6OExedyRpYGnwtojRBcc8zdCaEhPJ+YFAc1otq
IjA/PMXGfGmBu/eQtf94RSwel0mSFR0SuoZOiN72odEUP5HLDuRSjRgEWX/A8E9r
ge6/324cLDptGS9O7/0ne7zZs7ArVY9/q9FQRXjs3bqbQCg4jQtK/j+68gJBwt+E
Wp/dgoyQQiVOVKy6yJy3PiUQ5xEZ0dzT7sbYZqEHCbii8Z+yhUTCKlxfAtPALH7t
xbCO/naJM7ZXyvPN3+VQWOTbetFuKY/ZAMsfxANVRnP1YxqDDhzRYmVj2swA2m2M
SVPtMC7SfkRQpvnbH8Sme1tjZYsweAdqQRFslVW/QWVy32pqSWfmljD9hd9bxwjy
OY+GeI/DpJBMDsgIyhkqpyAnzZBMqO4yIShzddSxEOcoKwSzVbYiHgf85RZqBJQF
l7SDFbkXjYMkWGTBytTau59YD+03SP89h61XIs3IQR+m4sUsLiXSmelIemlzxfPF
KDUmWW7TBZ9BEcV419sYLKWedAbBuqWlthsP7a6IVk4BH4H/foGh6a5fTHH9zFiX
Z4Q0SEcjFUtq5y3rccLRNrcnzBW+bC2vhttJDuNxFo4f53fx16iHGV4pS+3M4ZOB
5gZzAojPRJw6+0ymfMUbi/7d6sTl+F6cCmKKPbvaI/9UCkSD4NgjndfVPHXCJTf5
yNE3JUD+u2RElNUipFvc4fs1YZcwdglzWsUkLH3qdDVOZNIYqsroC+LJf+9Hhc/0
LVJcKyyrAHd4eVxs0JtarChiZGSm9g+24Wt/+ar94ZPSFw6rRMXCOsdP3g6Ck0uw
sH7lEdsWb+GxFvOF9sIbLE8adIB25ajmU6z0Lxs0HBSKBFKnSXg7JlwX9jV4I9Gi
W/fh1wXPvS+4kKccvjA1Rbhf7h8mvP/qg9XcKikwskRpGzMt2P0zvLw6yAlb8upl
uaeWtiwIOJSX9G2Ryw==
=lDTb
-----END PGP MESSAGE-----

That answers it then, sounds like it’s gateway-side rather than a client pacing issue, since your defaults are already more conservative than the native fragmentation and you’re still seeing it around 300MB.

Manual split with a break between chunks makes sense as the practical fix for now, better than chasing a gateway bug you can’t control from the client. Good to have that confirmed before anyone assumes 1GB is fine just because 100MB works.

I’ll take you up on the source password, thanks.

Regards

Gab

1 Like

Let’s see what the Nym team says, once they are testing. I could enhance NymTransfer with automatic splitting very large files, prior sending and automatic joining on the receiving side, but for now I leave it as it is, because such enhancements would probably invite people for torrenting over the Nym Mixnet, which I do not support, when reading sometimes such things from NymVPN users.

Makes sense to hold off given the torrenting risk. On my side, still curious if anyone here wants to give NymDrop a spin and report back.

1 Like

I will test NymDrop later today. I tried also in the past Wormhole-Nym (Web version) from Alexis, but It is a bit confusing to use and I archived no result.

1 Like

Hi Gabx,

I tried NymDrop with exactly 10MB but it gives an error.

$ openssl rand 10485760 > ch1ffr3punk_random_binary_data

$ shasum -a 256 ch1ffr3punk_random_binary_data
f0906bd53981c35e3c233287ad92e35c4b4bd60dbfe7aab1d1ae6c4f00e4c770 ch1ffr3punk_random_binary_data

Found it, thanks for the exact repro.

Turns out there are two base64 layers stacked between the raw file and the mixnet, not one: the browser base64s the file before encrypting it, then our server does a second base64 to hand the ciphertext to the local nym-client over its own WebSocket control API.

The two combine to about 1.78x, and the real wall we hit was nym-client’s own hardcoded 16MB WebSocket message limit, not anything in our code.

A 10MB file was pushing an 18.6MB internal message past that limit.

Fixed by lowering our cap with real margin under that 16MB ceiling.

Honest update: the real safe raw file size is about 8MB, not 10MB like we said before.

Tested it live with your exact scenario, a 10MB file now fails clean and fast instead of the silent “Delivery failed” you saw.

Thanks again for catching this, wouldn’t have found the second base64 layer without a real test at that exact size.

Best regards

Gab

1 Like

You’re welcome!

1 Like

Hi Gabx,

I have added -split and -pause when a file is larger than 100MB. I send you a DM via TG.