Skip to content

Add ECIES encryption module for sr25519 keys#116

Draft
hitchhooker wants to merge 3 commits intoparitytech:masterfrom
hitchhooker:ecies
Draft

Add ECIES encryption module for sr25519 keys#116
hitchhooker wants to merge 3 commits intoparitytech:masterfrom
hitchhooker:ecies

Conversation

@hitchhooker
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 <tommi@rotko.net>
hitchhooker added a commit to hitchhooker/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
hitchhooker added a commit to hitchhooker/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
@hitchhooker hitchhooker 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
Copy link
Copy Markdown
Collaborator

burdges commented Mar 27, 2026

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.

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