EIP-5630: New approach for encryption / decryption
defines a specification for encryption and decryption using deterministically derived, pseudorandom keys.
作者 | Firn Protocol, Fried L. Trout |
---|---|
讨论-To | https://ethereum-magicians.org/t/eip-5630-encryption-and-decryption/10761 |
状态 | Draft |
类型 | Standards Track |
分类 | ERC |
创建日期 | 2022-09-07 |
英文版 | https://eips.ethereum.org/EIPS/eip-5630 |
摘要
This EIP proposes a new way to encrypt and decrypt using Ethereum keys. This EIP uses separate, unlinkable, pseudorandom keys for signing and encryption; it uses only the secp256k1
curve, and it uses a standardized version of ECIES. In contrast, other EIPs reused secret keys across both signing and encryption, and moreover reused the same secret key across both the secp256k1
and ec25519
curves.
动机
We discuss a few 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.
规范
We describe our approach here; we compare our approach to other EIPs 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. Specifically, we propose the choices:
- the KDF
ANSI-X9.63-KDF
, where the hash functionSHA-512
is used, - the HMAC
HMAC–SHA-256–256 with 32 octet or 256 bit keys
, - the symmetric encryption scheme
AES–256 in CBC mode
.
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). Indeed, we propose that, given a signing private key sk ∈ 𝔽_q—which is naturally represented as a 32-byte big-endian byte string—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, 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:
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 accountaccount
, or else return an error if none exists. - compute the 32-byte secret
dk := ANSI-X9.63-KDF(sk)
, where thekeccak256
hash is used in the KDF. - compute the
secp256k1
public key corresponding todk
. - return this public key in compressed,
0x
-prefixed, hex-encoded form.
On the request
request({
method: 'eth_decrypt',
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 valueencryptedMessage.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 accountaccount
, or else return an error if none exists. - compute the 32-byte secret
dk := ANSI-X9.63-KDF(sk)
, where thekeccak256
hash is used in the KDF. - using
dk
, perform an ECIES decryption ofencryptedMessage.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.
基本原理
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. Certain papers have studied the reuse of the same key in signing and encryption, but where the same curve is used in both (e.g., in the context of EMV payments). Those papers have found 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.
向后兼容性
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 the previous EIP 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.
测试用例
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:
request({
method: 'eth_getEncryptionPublicKey',
params: ["0x72682F2A3c160947696ac3c9CC48d290aa89549c"],
})
should return:
"0x023e5feced05739d8aad239b037787ba763706fb603e3e92ff0a629e8b4ec2f9be"
Encrypting the message "My name is Satoshi Buterin"
under the above public key could yield, for example:
{
version: 'secp256k1-sha512kdf-aes256cbc-hmacsha256',
ciphertext: '0x03ab54b1b866c5231787fddc2b4dfe9813b6222646b811a2a395040e24e098ae93e39ceedec5516dbf04dbd7b8f5f5030cde786f6aeed187b1d10965714f8d383c2240b4014809077248ddb66cc8bd86eb815dff0e42b0613bbdd3024532c19d0a',
}
Therefore, the request
request({
method: 'eth_decrypt',
params: [{
version: 'secp256k1-sha512kdf-aes256cbc-hmacsha256',
ciphertext: '0x03ab54b1b866c5231787fddc2b4dfe9813b6222646b811a2a395040e24e098ae93e39ceedec5516dbf04dbd7b8f5f5030cde786f6aeed187b1d10965714f8d383c2240b4014809077248ddb66cc8bd86eb815dff0e42b0613bbdd3024532c19d0a',
}, "0x72682F2A3c160947696ac3c9CC48d290aa89549c"],
})
should return the string "My name is Satoshi Buterin"
.
Security Considerations
Our proposal uses heavily standardized algorithms and follows all best practices.
版权声明
Copyright and related rights waived via CC0.
参考文献
Please cite this document as:
Firn Protocol, Fried L. Trout, "EIP-5630: New approach for encryption / decryption [DRAFT]," Ethereum Improvement Proposals, no. 5630, September 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5630.