ERC-5630 - New approach for encryption / decryption

Created 2022-09-07
Status Draft
Category ERC
Type Standards Track
Authors

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:

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:

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:

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:

On the request

request({
  method: 'eth_performECDH',
  params: [account, ephemeralKey]
})

where account is as above, and ephemeralKey is an elliptic curve point encoded as above:

Test vectors are given below.

Encrypting to a smart contract

In light of account abstraction, EIP-4337, 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, but for encryption, as opposed to signing.

Our specification is as follows.

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:

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:

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_decrypted.)

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:

request({
  method: 'eth_getEncryptionPublicKey',
  params: ["0x72682F2A3c160947696ac3c9CC48d290aa89549c"]
})

should return:

"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:

"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf850e6c2af8fb38e3e31d679deac82bd12148332fa0e34aecb31981bd4fe8f7ac1b74866ce65cbe848ee7a9d39093e0de0bd8523a615af8d6a83bbd8541bf174f47b1ea2bd57396b4a950a0a2eb77af09e36bd5832b8841848a8b302bd816c41ce"

Upon obtaining this ciphertext, the decryptor would extract the relevant ephemeral public key, namely:

"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf8"

And submit the request:

request({
  method: 'eth_performECDH',
  params: [
    "0x72682F2A3c160947696ac3c9CC48d290aa89549c",
    "0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf8"
  ]
})

which in turn would return the Diffie–Hellman secret:

"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.