Skip to content

Add ECIES encryption module for sr25519 keys#116

Closed
hitchho wants to merge 3 commits into
paritytech:masterfrom
hitchho:ecies
Closed

Add ECIES encryption module for sr25519 keys#116
hitchho wants to merge 3 commits into
paritytech:masterfrom
hitchho:ecies

Conversation

@hitchho

@hitchho hitchho commented Mar 13, 2026

Copy link
Copy Markdown

Summary

  • Adds ECIES (Elliptic Curve Integrated Encryption Scheme) over Ristretto255
  • Uses ChaCha20-Poly1305 for authenticated encryption and Merlin transcripts for key derivation
  • New ecies feature flag with encrypt / decrypt public API
  • Wire format: [version: 1B] [ephemeral_pk: 32B] [nonce: 12B] [ciphertext + tag: N+16B] (61 bytes overhead)

Test plan

  • Unit tests: round-trip, deterministic encryption, empty plaintext, wrong context, truncated ciphertext, bad version, tampered ciphertext, large plaintext
  • Review by maintainers

Implements Elliptic Curve Integrated Encryption Scheme (ECIES) over
Ristretto255 using ChaCha20-Poly1305 and Merlin transcript key derivation.

Signed-off-by: Tommi Niemi <[email protected]>
hitchho added a commit to hitchho/subxt that referenced this pull request Mar 13, 2026
Adds encrypt/decrypt methods to sr25519::Keypair using schnorrkel's
new ECIES module. Gated behind the `ecies` feature flag.

Depends on: paritytech/schnorrkel#116
hitchho added a commit to hitchho/subxt that referenced this pull request Mar 13, 2026
Adds encrypt/decrypt methods to sr25519::Keypair using schnorrkel's
new ECIES module. Gated behind the `ecies` feature flag.

Depends on: paritytech/schnorrkel#116
penumbra-style key hierarchy separating incoming decryption,
outgoing decryption, and signing authority. all derivations are
one-way via domain-separated merlin transcripts.

- IncomingViewingKey: decrypt messages sent TO you
- OutgoingViewingKey: decrypt messages sent BY you
- FullViewingKey: bundles ivk + ovk + signing public key
- ecies wire format always includes ovk-wrapped ephemeral secret
- ovk blob cryptographically bound to main ciphertext
- identity point rejection on encrypt and decrypt
- zeroize on all secret material, constant-time equality
@hitchho hitchho marked this pull request as draft March 14, 2026 05:58
- known-answer test vector (pins wire format against silent KDF changes)
- ovk blob swap between ciphertexts (verifies main-ciphertext binding)
- key-type confusion (ivk vs signing key domain isolation)
- self-encryption round-trip
- empty context string
- partial ovk blob truncation
- main vs ovk nonce independence
- tampered ephemeral public key
@Polkadot-Forum

Copy link
Copy Markdown

This pull request has been mentioned on Polkadot Forum. There might be relevant details there:

https://forum.polkadot.network/t/dystopia-awaits-lets-encrypt/17345/1

@burdges

burdges commented Mar 27, 2026

Copy link
Copy Markdown
Collaborator

We’ve had the schnorrkel::aead module forever, but I never developed it much or encouraged its usage for several reasons, many of which fall under: Do not mix encryption and signing keys!

There are three-ish big categories under that banner: (1) legal scenarios in different countries, (2) user key management, lifetime and rotation, and (3) security assurances. I'll remark on (3) first:

  • provable security: We almost never write security proofs for both encryption and signing done together.
  • practical security: We already mix too many different protocols in the blockchain world. And soft key derivations wind up usually being an especially bad idea. Also schnorrkel is a good crate for doing OPRFs, a cheap limited alternative to blind signatures, see PrivacyPass, but OPRFs create a decryption oracle.

Anyways, it's good this derives a new secret key, which avoids (3) but not (2) and not (1) in practice, and also.. why?

I'd think schnorrkel keys would usually live on ledger, vault, etc devices, so how could you get the secret from the device to the decryption device? Or would you want decryption in vault? Or would untrusted payloads there be a larger vulnerability?

We could simply define export age secret key functions, which both take the schnorrkel secret key and a derivation path, and outputs a [u8; 32] or age's string version. After which, everything continues using age itself. If we worked from the MiniSecretKey then we could derive age's PQ scheme (Xwing?) except rage lacks support so far.

Also, there is a forth category here: We've added many more features to encryption, like all the forward secure ratchets. All this depends upon a full ecosystem, but maybe one should not push everything here.

Again if we need something then that's a different matter, but what is the reasoning here?

Also: Is this vibe coded? At least some seems non-idiomatic, although my idea of idiomatic maybe flawed there.

@hitchho

hitchho commented Apr 1, 2026

Copy link
Copy Markdown
Author

hey jeff, thanks for taking the time

you're right that my adaptation is flawed. it doesn't properly separate spending and viewing authority. i was trying to model after penumbra's key hierarchy (spec): dk for detection, ivk/ovk for viewing, ak/ask for spend auth, none of these touch each other and everything can be derived from spend keys. i clearly failed to adapt that here, deriving everything from the most sensitive key.

for a long time i've hoped the people pallet would come with a field for age. okay, rotation wouldn't be great since users would need to reverify each identity hash after rotation, and i think there's a lot of value in being able to send unsolicited encrypted messages by default (e.g. no one uses telegram's solicited encryption).

and yes, partially vibed, adapting penumbra repos to learn and understand enough to even have a conversation about the subject matter. closing pr here, let's continue on the forums.

@hitchho hitchho closed this Apr 1, 2026
@burdges

burdges commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator

I do think it separates spending and decryption authority from a security perspective, but not from a legal or key management one.

A "viewing key" in zcash, and presumably penumbra, is not for general message decryption, but for trial decryption of possible payments. It'd likely have an extra layer of KDF so that the air gaped or hardware wallet could share the viewing key with the user's chain scanning node. Importantly, this extra KDF layer fixes the key management perspective and half the legal perspective too, aka if the courts demand your viewing key then software was designed to be able to hand it over, without giving the spending key.

We'd need something like this if we port zcash or penumbra to run on a parachain. If fact, we'd like want some flagging key so that helper nodes could flag the tx that maybe for you. And you might even want something like fuzzytags here.

At the moment, our only prospective major customer who wants anonymous payments wants fixed denomination anonymous payments, which we can do much more simply than zcash or penumbra. We do still think it'd rock to port zcash or penumbra to run on polakdot, but imho one should first aim for the minimal changes that allow one of those code bases to run as a parachain, even without any interaction via messages, etc.

Anyways the question remains: What do we need here? I think probably two stage KDFs from the MiniSecretKey could make sense here, although not sure for what really.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants