DCIPs/EIPS/eip-5630.md

206 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
eip: 5630
title: New approach for encryption / decryption
description: defines a specification for encryption and decryption using Ethereum wallets.
author: Firn Protocol (@firnprotocol), Fried L. Trout, Weiji Guo (@weijiguo)
discussions-to: https://ethereum-magicians.org/t/eip-5630-encryption-and-decryption/10761
status: Draft
type: Standards Track
category: ERC
created: 2022-09-07
---
## Abstract
This EIP proposes a new way to encrypt and decrypt using Ethereum keys. This EIP uses _only_ the `secp256k1` curve, and proposes two new RPC methods: `eth_getEncryptionPublicKey` and `eth_performECDH`. These two methods, in conjunction, allow users to receive encryptions and perform decryptions (respectively). We require that the wallet _only_ perform the core ECDH operation, leaving the ECIES operations up to implementers (we do suggest a standardized version of ECIES, however). In contrast, a previous EIPs used the same secret key, in both signing and encryption, on two _different_ curves (namely, `secp256k1` and `ec25519`), and hardcoded a particular version of ECIES.
## Motivation
We discuss a few motivating examples. One key motivation is direct-to-address encryption on Ethereum. Using our EIP, one can directly send encrypted messages to some desired recipient on-chain, without having a prior direct channel to that recipient. (Note that in this EIP, we standardize _only_ the encryption procedure—that is, the generation of the ciphertext—and _not_ how exactly the on-chain message should be sent. In practice, ideally, smart-contract infrastructure will be set up for this purpose; barring this, encryptors could make use of the raw `data` field available in each standard transfer.)
We discuss a second sort of example. In a certain common design pattern, a dApp generates a fresh secret on behalf of a user. It is of interest if, instead of forcing this user to independently store, safeguard, and back up this latter secret, the dApp may instead encrypt this secret to a public key which the user controls—and whose secret key, crucially, resides within the user's HD wallet hierarchy—and then post the resulting ciphertext to secure storage (e.g., on-chain). This design pattern allows the dApp/user to bootstrap the security of the _fresh_ secret onto the security of the user's existing HD wallet seed phrase, which the user has already gone through the trouble of safeguarding and storing. This represents a far lower UX burden than forcing the user to store and manage fresh keys directly (which can, and often does, lead to loss of funds). We note that this design pattern described above is used today by, various dApps (e.g., Tornado Cash).
## Specification
We describe our approach here; we compare our approach to prior EIPs in the **Rationale** section below. Throughout, we make reference to SEC 1: Elliptic Curve Cryptography, by Daniel R. L. Brown.
We use the `secp256k1` curve for both signing and encryption.
For encryption, we use ECIES. We specify that the wallet _only_ perform the sensitive ECDH operation. This lets implementers select their own ECIES variants at will.
We propose that all binary data be serialized to and from `0x`-prefixed hex strings. We moreover use `0x`-prefixed hex strings to specify private keys and public keys, and represent public keys in compressed form. We represent Ethereum accounts in the usual way (`0x`-prefixed, 20-byte hex strings). Specifically, to serialize and deserialize elliptic curve points, implementers MUST use the following standard:
- to serialize a point: use [SEC 1, §2.3.3], with point compression.
- to deserialize a point: use [SEC 1, §2.3.3], while _requiring_ point compression; that is:
- the input byte string MUST have length ⌈log₂q / 8⌉ + 1 = `33`.
- the first byte MUST be `0x02` or `0x03`.
- the integer represented by the remaining 32 bytes (as in [SEC 1, §2.3.8]) MUST reside in {0, ..., _p_ - 1}, and moreover MUST yield a quadratic residue modulo _p_ under the Weierstrass expression X^3 + 7 (modulo _p_).
For application-level implementers actually implementing ECIES, we propose the following variant. Unless they have a reason to do otherwise, implementers SHOULD use the following standardized choices:
- the KDF `ANSI-X9.63-KDF`, where the hash function `SHA-512` is used,
- the HMAC `HMACSHA-256256 with 32 octet or 256 bit keys`,
- the symmetric encryption scheme `AES256 in CBC mode`.
We propose that the binary, _concatenated_ serialization mode for ECIES ciphertexts be used, both for encryption and decryption, where moreover elliptic curve points are _compressed_.
Thus, on the request:
```javascript
request({
method: 'eth_getEncryptionPublicKey',
params: [account]
})
```
where `account` is a standard 20-byte, `0x`-prefixed, hex-encoded Ethereum account, the client should operate as follows:
- find the secret signing key `sk` corresponding to the Ethereum account `account`, or else return an error if none exists.
- compute the `secp256k1` public key corresponding to `sk`.
- return this public key in compressed, `0x`-prefixed, hex-encoded form, following [SEC 1, §2.3.3].
On the request
```javascript
request({
method: 'eth_performECDH',
params: [account, ephemeralKey]
})
```
where `account` is as above, and `ephemeralKey` is an elliptic curve point encoded as above:
- find the secret key `sk` corresponding to the Ethereum account `account`, or else return an error if none exists.
- deserialize `ephemeralKey` to an elliptic curve point using [SEC 1, §2.3.3] (where compression is required), throwing an error if deserialization fails.
- compute the elliptic curve DiffieHellman secret, following [SEC 1, §3.3.1].
- return the resulting field element as an 0x-prefixed, hex-encoded, 32-byte string, using [SEC 1, §2.3.5].
Test vectors are given below.
### Encrypting to a smart contract
In light of account abstraction, [EIP-4337](eip-4337.md), and the advent of smart-contract wallets, we moreover specify a way to encrypt to a contract.
More precisely, we specify a way for a contract to _advertise_ how it would like encryptions to it to be constructed. This should be viewed as an analogue of [EIP-1271](eip-1271.md), but for encryption, as opposed to signing.
Our specification is as follows.
```solidity
pragma solidity ^0.8.0;
contract ERC5630 {
/**
* @dev Should return an encryption of the provided plaintext, using the provided randomness.
* @param plaintext Plaintext to be encrypted
* @param randomness Entropy to be used during encryption
*/
function encryptTo(bytes memory plaintext, bytes32 randomness)
public
view
returns (bytes memory ciphertext);
}
```
Each contract MAY implement `encryptTo` as it desires. Unless it has a good reason to do otherwise, it SHOULD use the ECIES variant we propose above.
## Rationale
There is _no security proof_ for a scheme which simultaneously invokes signing on the `secp256k1` curve and encryption on the `ec25519` curve, and where _the same secret key is moreover used in both cases_. Though no attacks are known, it is not desirable to use a scheme which lacks a proof in this way.
We, instead, propose the reuse of the same key in signing and encryption, but where _the same curve is used in both_. This very setting has been studied in prior work; see, e.g., Degabriele, Lehmann, Paterson, Smart and Strefler, _On the Joint Security of Encryption and Signature in EMV_, 2011. That work found this joint scheme to be secure in the generic group model.
We note that this very joint scheme (i.e., using ECDSA and ECIES on the same curve) is used live in production in EMV payments.
We now discuss a few further aspects of our approach.
**On-chain public key discovery.** Our proposal has an important feature whereby an encryption _to_ some account can be constructed whenever that account has signed at least one transaction.
Indeed, it is possible to recover an account's `secp256k1` public key directly from any signature on behalf of that account.
**ECDH vs. ECIES.** We specify that the wallet _only_ perform the sensitive ECDH operation, and let application-level implementers perform the remaining steps of ECIES. This has two distinct advantages:
- **Flexibility.** It allows implementers to select arbitrary variants of ECIES, without having to update what the wallet does.
- **Bandwidth.** Our approach requires that only small messages (on the order of 32 bytes) be exchanged between the client and the wallet. This could be material in settings in which the plaintexts and ciphertexts at play are large, and when the client and the wallet are separated by an internet connection.
**Twist attacks.** A certain GitHub post by Christian Lundkvist warns against "twist attacks" on the `secp256k1` curve. These attacks are not applicable to this EIP, for multiple _distinct_ reasons, which we itemize:
- **Only applies to classical ECDH, not ECIES.** This attack only applies to classical ECDH (i.e., in which both parties use persistent, authenticated public keys), and not to ECIES (in which one party, the encryptor, uses an ephemeral key). Indeed, it only applies to a scenario in which an attacker can induce a victim to exponentiate an attacker-supplied point by a sensitive scalar, and then moreover send the result back to the attacker. But this pattern only happens in classical DiffieHellman, and never in ECIES. Indeed, in ECIES, we recall that the only sensitive DiffieHellman operation happens during decryption, but in this case, the victim (who would be the decryptor) never sends the resulting DH point back to the attacker (rather, the victim merely uses it locally to attempt an AES decryption). During _encryption_, the exponentiation is done by the encryptor, who has no secret at all (sure enough, the exponentiation is by an ephemeral scalar), so here there would be nothing for the attacker to learn.
- **Only applies to uncompressed points.** Indeed, we use compressed points in this EIP. When compressed points are used, each 33-byte string _necessarily_ either resolves to a point on the correct curve, or else has no reasonable interpretation. There is no such thing as "a point not on the curve" (which, in particular, can pass undetectedly as such).
- **Only applies when you fail to check a point is on the curve.** But this is inapplicable for us anyway, since we use compressed points (see above). We also require that all validations be performed.
## Backwards Compatibility
Our `eth_performECDH` method is new, and so doesn't raise any backwards compatibility issues.
A previous proposal proposed an `eth_getEncryptionPublicKey` method (together with an `eth_decrypt` method unrelated to this EIP). Our proposal overwrites the previous behavior of `eth_getEncryptionPublicKey`.
It is unlikely that this will be an issue, since encryption keys need be newly retrieved _only_ upon the time of encryption; on the other hand, _new_ ciphertexts will be generated using our new approach.
(In particular, our modification will not affect the ability of ciphertexts generated using the old EIP to be `eth_decrypt`ed.)
In any case, the previous EIP was never standardized, and is _not_ (to our knowledge) implemented in a non-deprecated manner in _any_ production code today.
### Test Cases
The secret _signing key_
```
0x439047a312c8502d7dd276540e89fe6639d39da1d8466f79be390579d7eaa3b2
```
with Ethereum address `0x72682F2A3c160947696ac3c9CC48d290aa89549c`, has `secp256k1` public key
```
0x03ff5763a2d3113229f2eda8305fae5cc1729e89037532a42df357437532770010
```
Thus, the request:
```javascript
request({
method: 'eth_getEncryptionPublicKey',
params: ["0x72682F2A3c160947696ac3c9CC48d290aa89549c"]
})
```
should return:
```javascript
"0x03ff5763a2d3113229f2eda8305fae5cc1729e89037532a42df357437532770010"
```
If an encryptor were to encrypt a message—say, `I use Firn Protocol to gain privacy on Ethereum.`—under the above public key, using the above ECIES variant, he could obtain, for example:
```javascript
"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf850e6c2af8fb38e3e31d679deac82bd12148332fa0e34aecb31981bd4fe8f7ac1b74866ce65cbe848ee7a9d39093e0de0bd8523a615af8d6a83bbd8541bf174f47b1ea2bd57396b4a950a0a2eb77af09e36bd5832b8841848a8b302bd816c41ce"
```
Upon obtaining this ciphertext, the decryptor would extract the relevant ephemeral public key, namely:
```javascript
"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf8"
```
And submit the request:
```javascript
request({
method: 'eth_performECDH',
params: [
"0x72682F2A3c160947696ac3c9CC48d290aa89549c",
"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf8"
]
})
```
which in turn would return the DiffieHellman secret:
```javascript
"0x4ad782e7409702101abe6d0279f242a2c545c46dd50a6704a4b9e3ae2730522e"
```
Upon proceeding with the above ECIES variant, the decryptor would then obtain the string `I use Firn Protocol to gain privacy on Ethereum.`.
## Security Considerations
Our proposal uses heavily standardized algorithms and follows all best practices.
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).