one of the major disappointments in the ecosystem for me has been that after all these years we still do not have proper primitives for end-to-end encryption. every wallet extension can sign messages, but none hold primitives for e2ee. the entire toolchain stops at signatures.
the root cause as far as i can tell is that sr25519 never got a proper encryption scheme implemented around it. the underlying curve is fully capable - ristretto255 supports ECDH key agreement, and from there it’s standard symmetric crypto (chacha20-poly1305 or xchacha20-poly1305). so afaik the math is not the problem, but the implementation just never got done.
the evidence is scattered across years of dead github issues:
- polkadot-js/extension#691 - asymmetric encryption feature request, closed with “there is no exposed asymmetric encryption/decryption in sr25519”
- polkadot-js/common#1124 - encrypt/decrypt attempted with sr25519 keypairs, doesn’t work because ed2curve conversion fails on ristretto-encoded pubkeys
- polkadot-js/common#1314 - follow-up confirming sr25519 encryption is broken, maintainer response: “have not seen it out in the wild, if it doesn’t work it will need to be removed”
- polkadot-js/extension#928 - another attempt, same wall
there is a golang implementation (hodo.dev) that does ECDH over sr25519 by expanding the secret key and doing scalar multiplication on the ristretto point. a python ECIES implementation exists using libsodium’s crypto_scalarmult_ristretto255. so it’s clearly possible. it’s just that nobody shipped it as a standard primitive in the JS/wasm toolchain that wallet extensions actually use.
i have now built a few applications where i always hit this rock wall that i do not want to hold or relay unencrypted user data. for example building whodb.org i lost motivation trying to figure out how to do encryption with PGP. it fucking sucks. anyone thinking that PGP would see mass adaption is just fucking delusional at this point. encryption has to happen under the hood without end user having to think one second about it or it fails.
now im building an application(1337) for text-to-speech, speech-to-text and end-to-end translation with the idea of an open competitive marketplace for inference providers, handling finances through state channels and smart contracts on asset hub. the main lacking feature is encrypting and attesting inference between parties. latest nvidia data center GPUs support inference in TEEs with just about 10% overhead, yet no frontier lab provides this because the game is all about data mining your way to the stars.
having accounts be polkadot sr25519 accounts just won’t cater for this. one option is that i write my own extension with ed25519 keys that have broad support for encryption - but that means not catering the existing user base and slightly removing the purpose of using polkadot at all.
as an application developer the options today are:
- ask users to derive a separate ed25519 keypair from the same mnemonic and use nacl box. works but terrible UX - users now manage two key types, extensions don’t expose this, and it’s not standardized. all extensions can only do signing and wont support ed25519 so its not really viable either.
- do ECDH directly on sr25519 ristretto points ideally with ovk and ivk keys. works in theory but no audited JS/wasm library ships this. and this would require all extensions to adapt it to be viable. something we should really
- ignore the wallet entirely and use a separate key exchange protocol. but that defeats the purpose of having an identity system.
i think mission statement was something along the lines of building post snowden web. snowden revelations were all about government having a permanent record of citizens through private companies. right now tools we offer is basicly getting to have keys to our own slavery by having permanent public record of all our actions. what the fuck is that for post snowden web? i do really hope that im wrong and have missed something crucial here.
so that said as a ecosystem we should probably think of a way how to salvage the sitution. do we double down on sr25519 working encryption on it or should we rotate to use more standardized x25519 by having wallet implementations to derive an x25519 keypair from the same mnemonic at a different derivation path. publish the x25519 public key somewhere discoverable - could be an on-chain identity field, a well-known derivation that anyone can verify, or just exchanged out-of-band. then ECDH + chacha20-poly1305 for the encrypted channel. afaik this is essentially what signal does.
doubling down on sr25519 would mean implementing ECIES natively on the ristretto255 group that sr25519 already uses.
so ive been sparring this issue with a clanker and opened a draft PR on schnorrkel (paritytech/schnorrkel#116) that implements full ECIES over ristretto255 with a penumbra-inspired viewing key hierarchy:
- IncomingViewingKey (IVK) - a separate ECDH scalar derived one-way from the signing secret via merlin transcript. senders encrypt to the IVK public key. the signing key never touches the encryption path.
- OutgoingViewingKey (OVK) - a 32-byte symmetric key derived from the same secret. the ephemeral secret gets wrapped under the OVK and appended to every ciphertext so the sender can re-read their own outgoing messages.
- FullViewingKey (FVK) - bundles IVK + OVK + signing pubkey. can decrypt everything but cannot sign.
the ECIES scheme: ephemeral keypair per message, ECDH on ristretto255, merlin-derived chacha20-poly1305 with domain separation, AAD binding both public keys. 120 bytes overhead per message. the OVK wrap uses a separate domain and binds to the main ciphertext so blobs can’t be swapped between messages.
what still needs to happen if this path is chosen:
- probably some actual cryptographer should take a look on it
- schnorrkel wasm bindings need to expose encrypt/decrypt/derive_ivk/derive_ovk
- polkadot-js or papi? needs to surface these in the keyring API
- wallet extensions need an injected API method - something like keypair.encryptMessage(plaintext, recipientIvkPublic) and keypair.decryptMessage(ciphertext)
- a convention for publishing IVK public keys - on-chain identity field, or a well-known derivation that anyone can verify from the signing pubkey. would not be forward secure but simple
with european union going full facist mode with their 2027 ban i feel that this issue is more pressing more pressing than now than ever. well is always been on the very top of my list i did expect w3f to deliver for polkadot to be relevant place to build respectful agentic(clientside) applications. slightly frustrating that nothing has come into fruition in a decade.