Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP-5630: Encryption and Decryption #5630

Merged
merged 23 commits into from
Sep 16, 2022
Merged
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a93c554
first commit for encryption
firnprotocol Sep 8, 2022
f950601
add category
firnprotocol Sep 8, 2022
954f5dd
add EIP number
firnprotocol Sep 8, 2022
124032b
try to remove all (non-relative) links
firnprotocol Sep 8, 2022
982cadd
more attempts to comply with scan
firnprotocol Sep 8, 2022
8346692
more adjustments.
firnprotocol Sep 8, 2022
f4fefc3
make links verbatim; try to satisfy HTML Proofer
firnprotocol Sep 8, 2022
a1bb2ff
create ethereum magicians page
firnprotocol Sep 8, 2022
50c8b0c
remove offending links
firnprotocol Sep 8, 2022
cafc4b6
another attempt at HTML proofer
firnprotocol Sep 9, 2022
2a1c1bd
some kind of bug with HTML proofer
firnprotocol Sep 9, 2022
0f96433
minor stuff
firnprotocol Sep 9, 2022
603a59a
just remove the two references
firnprotocol Sep 9, 2022
0dc82d4
rename file to match PR number
firnprotocol Sep 9, 2022
e7cfd68
also adjust table
firnprotocol Sep 9, 2022
5683b4f
Merge remote-tracking branch 'ethereum/master' into encryption
firnprotocol Sep 9, 2022
9a6c26b
Merge remote-tracking branch 'ethereum/master' into encryption
firnprotocol Sep 12, 2022
344f1af
Merge remote-tracking branch 'ethereum/master' into encryption
firnprotocol Sep 13, 2022
9a2e457
Update eip-5630.md
Pandapip1 Sep 15, 2022
950489f
remove spurious parenthesis
firnprotocol Sep 15, 2022
3861024
Merge remote-tracking branch 'ethereum/master' into encryption
firnprotocol Sep 15, 2022
fe8504f
grammar
firnprotocol Sep 15, 2022
5fb65cf
a few more grammar changes post-edits
firnprotocol Sep 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions EIPS/eip-5605.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
eip: 5605
title: New approach for encryption / decryption
description: defines a specification for encryption and decryption using deterministically derived, pseudorandom keys.
author: Firn Protocol (@firnprotocol), Fried L. Trout
discussions-to: https://ethereum-magicians.org/t/eip-5605-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, which remedies the shortcomings of the now-stagnant PR [#1098].
This proposal uses separate, unlinkable, pseudorandom keys for signing and encryption; it uses _only_ the `secp256k1` curve, and it uses a standardized version of ECIES (see e.g. Brown [SEC 1, § 5.1] and [ANSI X9.63, § 5.8]).
In contrast, [#1098] reused secret keys across both signing and encryption, and moreover reused the same secret key across both the `secp256k1` and `ec25519` curves.

## Motivation
Some motivation for introducing encryption to Ethereum has already been discussed in [#1098].

To that discussion, we add a few further motivating examples. 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, can be derived deterministically from 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 _exact_ design pattern described above is used today by, e.g., Tornado Cash.

As a separate motivation, we mention the possibility of dApps which facilitate end-to-end encrypted messaging.

## Specification
We describe our approach here; we compare our approach to [#1098]'s in the **Rationale** section below.

We use the `secp256k1` curve for both signing and encryption (with different keys, see below).
In the latter case, we use ECIES; specifically, we use a standardized variant, which appears e.g. in [SEC 1, § 5.1].
Specifically, we propose the choices:
- the KDF `ANSI-X9.63-KDF`, where the hash function `SHA-512` is used (see [SEC 1, § 3.6]),
- the HMAC `HMAC–SHA-256–256 with 32 octet or 256 bit keys` (see [SEC 1, § 3.7]),
- the symmetric encryption scheme `AES–256 in CBC mode` (see [SEC 1, § 3.8]).

We finally describe a method to derive encryption secret keys deterministically—but pseudorandomly—from signing keys, in such a way that a natural one-to-one relationship obtains between these keys (this latter property is essential, since it allows Ethereum accounts to be used as handles onto encryption/decryption keys, as both the former and current API interfaces do).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This turns the Ethereum account into a global identifier in a way that makes it much harder to establish a pairwise identifier relationship. I'd rather see these detached personally since there's not a necessity to achieve the goals of encrypting the messages.

Indeed, we propose that, given a signing private key _sk_ ∈ 𝔽_q—which is naturally represented as a 32-byte big-endian byte string (see [SEC 1, § 2.3.6–2.3.7])—the corresponding decryption key _dk_ ∈ 𝔽_q be generated as the 32-byte secret:

dk := ANSI-X9.63-KDF(sk),
where moreover the _Ethereum `keccak256`_ hash is used for this KDF. This latter decision is essentially for implementation convenience; indeed, MetaMask's `eth-simple-keyring` already has something close to this functionality built in, and it requires only a minimal code change (see our implementation below).
We set _SharedInfo_ to be empty here.

We propose that the binary, _concatenated_ serialization mode for ECIES ciphertexts be used (see [SEC 1, § 5.1.3, 8.], both for encryption and decryption, where moreover elliptic curve points are _compressed_. This approach is considerably more space-efficient than the prior approach, which outputted a stringified JSON object (itself containing base64-encoded fields).
We moreover propose that 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).

Thus, on the request:
```javascript
request({
method: 'eth_getEncryptionPublicKey',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not going to be a good idea to overlap the method names here. Furthermore, I'd suggest the namespace used here is wallet_* rather than eth_* as this is more likely to be used by wallets than nodes.

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 32-byte secret `dk := ANSI-X9.63-KDF(sk)`, where the `keccak256` hash is used in the KDF.
- compute the `secp256k1` public key corresponding to `dk`.
- return this public key in compressed, `0x`-prefixed, hex-encoded form.

On the request
```javascript
request({
method: 'eth_decrypt',
Copy link
Contributor

@kdenhartog kdenhartog Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I don't think this is a great API design any longer. The original purpose of this API design seemed motivated by the need for P2P messaging. However, the more likely case in which this is used is to encrypt data passed by dApps to wallets. I'd rather see the design move more towards an approach of a storage call here as this design only allows one way communication from the dApp to the client. The client is then forced to rely on TLS for any sort of confidentiality guarantees in the response because there's no way for them to use the message layer to encrypt. In other words TLS is already having to do half the work here if this is meant to be a synchronous session based messaging protocol. Given TLS is pervasively deployed across dApps and provides stronger cryptographic guarantees then this design I think it's more useful for us to focus on storage based encryption for dApps (e.g. data at rest) rather than session based encryption (e.g. data in transit) which would be more useful for a P2P communication system.

params: [encryptedMessage, account],
})
```
where `account` is as above, and `encryptedMessage` is a JSON object with the properties `version` (an arbitrary string) and `ciphertext` (a `0x`-prefixed, hex-encoded, bytes-like string), the client should operate as follows:
- perform a `switch` on the value `encryptedMessage.version`. if it equals:
- `x25519-xsalsa20-poly1305`, then use [#1098]'s specification;
- `secp256k1-sha512kdf-aes256cbc-hmacsha256`, then proceed as described below;
- if it equals neither, throw an error.
- find the secret key `sk` corresponding to the Ethereum account `account`, or else return an error if none exists.
- compute the 32-byte secret `dk := ANSI-X9.63-KDF(sk)`, where the `keccak256` hash is used in the KDF.
- using `dk`, perform an ECIES decryption of `encryptedMessage.ciphertext`, where the above choices of parameters are used.
- decode the resulting binary plaintext as a `utf-8` string, and return it.

Test vectors are given below.
## Rationale

Our proposal should be viewed as a successor to [#1098], which has stagnated. That EIP received some support. It was implemented, and subsequently deprecated, by MetaMask; it was also implemented by Ledger (see links below).

The community ultimately declined to ratify that EIP, however, for a few reasons:
- It used the _same_ key both for signing/verifying and for encryption/decryption, which is generally considered bad practice (though cf. [DLG+11] and further discussion below);
- That EIP moreover used the same secret key on _two different curves_.

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 note that the paper [DLG+11] studies the reuse of the same key in signing and encryption, but where _the same curve is used in both_ (in the context of EMV payments). That paper finds the joint scheme to be secure in the generic group model.
Though this result provides _some level of_ assurance of security of this joint scheme (where, we stress, _only one_ curve is used), it is at least as secure to use different, pseudorandomly unlinkable keys for signing and encryption. Indeed, we note that if the hash function is modeled as a random oracle, then each decryption key `dk` is completely random, and in particular uncorrelated with its corresponding signing key.

## Backwards Compatibility
The previous proposal stipulated that encryption and decryption requests contain a `version` string. Our proposal merely adds a case for this string; encryption and decryption requests under the existing scheme will be handled identically. Unfortunately, the previous proposal did _not_ include a version string in `encryptionPublicKey`, and merely returned the `ec25519` public key directly as a string. We thus propose to immediately return the `secp256k1` public key, overwriting the previous behavior. The old behavior can be kept via a legacy method.

We note that [#1098] is _not_ (to our knowledge) implemented in a non-deprecated manner in _any_ production code today, and the EIP stagnated. We thus have a lot of flexibility here; we only need enough backwards compatibility to allow dApps to migrate.

### Test Cases

Starting from the secret _signing key_

0x439047a312c8502d7dd276540e89fe6639d39da1d8466f79be390579d7eaa3b2

with Ethereum address `0x72682F2A3c160947696ac3c9CC48d290aa89549c`, the `keccak256`-based KDF described above yields the secret _decryption key_

0xecb4fbc91b48954259469d13d2e69c6fe4b57b73dd9dd277085b2d5e764a4023

with `secp256k1` public key

0x023e5feced05739d8aad239b037787ba763706fb603e3e92ff0a629e8b4ec2f9be

Thus, the request:

```javascript
request({
method: 'eth_getEncryptionPublicKey',
params: ["0x72682F2A3c160947696ac3c9CC48d290aa89549c"],
})
```
should return:
```javascript
"0x023e5feced05739d8aad239b037787ba763706fb603e3e92ff0a629e8b4ec2f9be"
```

Encrypting the message `"My name is Satoshi Buterin"` under the above public key could yield, for example:
```javascript
{
version: 'secp256k1-sha512kdf-aes256cbc-hmacsha256',
ciphertext: '0x03ab54b1b866c5231787fddc2b4dfe9813b6222646b811a2a395040e24e098ae93e39ceedec5516dbf04dbd7b8f5f5030cde786f6aeed187b1d10965714f8d383c2240b4014809077248ddb66cc8bd86eb815dff0e42b0613bbdd3024532c19d0a',
}
```
Therefore, the request
```javascript
request({
method: 'eth_decrypt',
params: [{
version: 'secp256k1-sha512kdf-aes256cbc-hmacsha256',
ciphertext: '0x03ab54b1b866c5231787fddc2b4dfe9813b6222646b811a2a395040e24e098ae93e39ceedec5516dbf04dbd7b8f5f5030cde786f6aeed187b1d10965714f8d383c2240b4014809077248ddb66cc8bd86eb815dff0e42b0613bbdd3024532c19d0a',
}, "0x72682F2A3c160947696ac3c9CC48d290aa89549c"],
})
```
should return the string `"My name is Satoshi Buterin"`.

### Implementation
We have implemented this functionality in two forks of MetaMask repositories, namely:
- `firnprotocol/eth-sig-util`: https://github.com/firnprotocol/eth-sig-util/tree/encryption, and
- `firnprotocol/eth-simple-keyring`: https://github.com/firnprotocol/eth-simple-keyring/tree/encryption.

The vast majority of the code changes reside in the former repository.
Our reference implementation leverages the `standard-ecies` library of **@bin-y**, whom we thank; see https://github.com/bin-y/standard-ecies.

## Security Considerations
Our proposal uses heavily standardized algorithms and follows all best practices.

## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).

###References:

- [#1098]: Pull Request: "Add web3.eth.encrypt and web3.eth.decrypt functions to JSON-RPC". https://github.com/ethereum/EIPs/pull/1098
- [SEC 1]: SEC 1: Elliptic Curve Cryptography. Daniel R. L. Brown. https://www.secg.org/sec1-v2.pdf
- [ANSI X9.63]: ANSI X9.63-2011 (R2017): Public Key Cryptography For The Financial Services Industry. https://webstore.ansi.org/Standards/ASCX9/ANSIX9632011R2017
- [DGL+11]: Jean Paul Degabriele, Anja Lehmann, Kenneth G. Paterson, Nigel P. Smart, and Mario Strefler. On the Joint Security of Encryption and Signature in EMV. https://eprint.iacr.org/2011/615.pdf