EIP-5630: Encryption and Decryption

sure, I am happy to do this. the name is too long. good idea.

my opinion is that for simplicity, we should choose one approach for this EIP. i am not sure yet which it should be, but I think I have a preference against doing both. so I will have to vote “no” in the poll I think.

actually, personally, if I had to choose, I’d prefer not doing the KDF (as you suggested). it’s a question of whether the community will also agree—the security is (very) slightly weaker, since the best known proofs require the GGM.

@firnprotocol how about a new poll with 3 options, that is: both, with-kdf, without?

sure. (i’d prefer we exclude the option “both”, but let’s let the poll decide that :slight_smile:) thanks for putting this together.

How do you think we should proceed? Please select one:

  • support only the original verision secp256k1(-X963)-sha512kdf-aes256(cbc-hmacsha256), a new key pair for encryption/decryption is derived from sk
  • support only the new verision secp256k1(-none)-sha512kdf-aes(256cbc-hmacsha256), just use sk and its corresponding public key
  • support both versions

0 voters

Besides the vote, I’d like to bring up another use issue here. That is, after considering how the APIs will be use by various application, I came to realize that maybe some encrypted message will be too large to be decrypted inside a hardware wallet, especially if the wallet is air-gaped and requires optical scanning to exchange data. @Lixin @Aaron

Say, if this EIP becomes popular some day and people use it to encrypt large messages, for example, in several MBs, decrypting the message within a hardware wallet may not be desirable.

So I am proposing that we add another API to recover the symmetric key (and IV) instead of decrypting the whole message:

request({
  method: 'eth_recoverSymKey',
  params: [encryptionHeader, account],
})

We do not need to pass in the entire cipher text. Instead, we only need sender’s ephemeral public key. encryptionHeader is much similar to encryptedMessage in that it contains version and senderEphemeralPublicKey:

{
  version: 'secp256k1-sha512kdf-aes256cbc-hmacsha256',
  senderEphemeralPublicKey: '0x03ab54...',
}

Then the wallet managing the private key could derive the shared secret, sha512-kdf the shared secret to derive AES-256 key and IV, which it returns to caller, the caller may decrypt cipher text outside of the wallet. @firnprotocol how do you think?

Edit: by preventing arbitrary message from being decrypted and written to memory inside a wallet managing users’ seed / private keys, this new request also protects wallets from potential buffer overflow attack etc.

There’s a solution that solves this problem and is a more directive way of providing the encryption public key, or even changing it later if the original is compromised, separating the signing (identity) keypair from the encryption keypair.

The EIP should be updated to include requiring an encoded message that is published in the input/data field of a transaction, which would be a prefixed text input to include the public key encoded as base64 to save space, such as:

EIP5630::BDewu3qCiNOO1JpSS13JjP8+tcqCTJ+dwN/bPZzWAPKZpheZErdFHAmJbECY7KfOay5YMwZyeV6EfE1q9E4CQjA=

I know everyone wants the public key to be “discoverable” from the Ether address, however you can only recover it if there’s a signed transaction. Might as well publish the public key in that transaction in a way that takes minimal gas and allows swapping keys.

If you agree @firnprotocol I can add a section to the EIP.

How would this work together with smart contract wallets?

IMO given that smart contract wallets are finally picking up, it’s important for something like this to have a path for encrypting to smart contract wallets.

The most natural approach that I can see is to have an EIP-1271 equivalent for signing, something like:

encryptToAccount(bytes memory plainText) public view returns (bytes cipherText)

This would be fully generic and support any algorithm that someone might want, including threshold encryption, post-quantum algorithms, etc.

Have people here thought about this?

1 Like

intriguing.

it seems plausible that an additional parameter—containing randomness/entropy—would need to be supplied to this method, no? since (public-key) encryption requires ingesting randomness.

a further note: this approach would essentially allow contracts to choose arbitrary encryption mechanisms—which, in general, may not be compatible with eth_decrypt (i.e., the RPC call we’re proposing here). this could be a bit confusing. but i guess that is a caveat emptor situation for the writer of the contract.

in general, i completely support this! very much appreciate your thoughts.

1 Like

Ah yes, adding an entropy parameter would definitely be a good idea.

As for eth_decrypt, that would be up to the javascript part of smart contract wallets to implement correctly, right? So eg. Soul Wallet would give everyone ERC4337 accounts by default, and their javascript interface would implement eth_decrypt their own way based on how they know that the encryption algorithms they choose work.

1 Like

very interesting.

the issue is that, above, we have prescribed / suggested a specific way for eth_decrypt to operate (i.e., which algorithm / variant of ECIES etc.).

maybe what we need to do is actually remove the prescription—and let wallets do it however they want. there could be a “standard” / default choice, but also arbitrary choices, made on a per-wallet basis.

@vbuterin I’ve given this a fair bit of thought for my project. Most immediate issue for me: have a revokable trust interface in the smart contract with assignable trust levels (like is used in PGP). That way you can set up trust rules (at least x of n keys trust this key as of block y).

For implementing something like a smart-wallet that does encryption (the results being unshareable/unknowable), having a queryable, stateful “reputation score” is essential.

Let’s pull back the discussion a little bit. Unlike on-chain signature verification, both encryption and decryption must happen off-chain because both the key and the plaintext must be kept confidential.

That being said, we had considered smart contract uses before, which is to provide a function to return the public key for encryption. If multiple curves are supported or consider post-quantum security, return also the supported and/or preferred version. It could also return multiple choices.

We could make such an API part of either 5630 or 4337. I think that should address smart contract / ERC-4337.

Would the smart contract accept an Ethereum address as an argument and then return a public key assigned to that address? If so, I imagine the contract would act like Active Directory. In your model then, is there a method that allows a user update / revoke a public key? Apologies if I’m misunderstanding your point.

I don’t see the need for an argument. And yes to the second question, the contract owner should be able to update the public key in any way that she or he sees fit. Decryption will be carried out off-chain. Note that I am talking about a ERC-4337 style contract that is owned by individual entity, not some DAO style that could be “owned” collectively.

Sounds reasonable, and of course all encryption would occur off-chain, I don’t think anyone is arguing otherwise, or at all.

The feature I’m interested in is a formalized SC EIP that can enable a smart contract to act as a trust oracle, where users can revokably signal their trust of a particular contract / address. To me this is necessary since the operation is done off-chain, and the results are private. If someone has reason to believe a decryption key is compromised, they should be able to signal it, like an Ethereum analog for OCSP.

This is separate from my suggestion above about publishing the public key in the data field of a transaction, which I still think is a reasonable approach per my comments.

hi all. happy to report that i’ve made some updates to the EIP. the new material includes:

  • a specification of how a contract should advertise—to the world—how encryptions to it should be constructed; this is important for account abstraction (cc. @vbuterin).
  • removed the HKDF between skdk; this allows on-chain public-key discovery essentially for free (cc. @Weiji).
  • added material on why twist attacks are not applicable to this EIP (cc. @rekmarks).

hugely appreciate the community effort on this! please let me know any thoughts and comments.

2 Likes

btw, i thought of a cool idea. what if you want to encrypt to someone—and also be anonymous in the process; i.e., the recipient shouldn’t know who the sender was (kind of like an anonymous drop-box)?

you can do this with Firn—i.e., you just send the ciphertext to the recipient as data, in the usual way, except make the transaction as a private & gasless withdrawal from Firn. viola.

1 Like

Thoughts on my suggestion above, sending a transaction that establishes the relationship between a public key and an address? Can be useful to associate multiple keys with an address, revoke a key, etc.

not sure i fully understand—in the quote

it sounds like you’re suggesting that the structure of the basic transaction object be changed, i.e., a field added for public key? i assume this isn’t what you mean, since it’d require changing the base ethereum protocol, which seems off the table. do you mean that a specific transaction (i.e., to some given contract) be published, which contains this info? this could definitely be done, though might not be necessary—see below.

indeed, the problem of public key discovery seems essentially solved now that we’ve removed the HKDF, since you can always grab someone’s key from a transaction they’ve signed.

not sure I follow—recovering someone’s key doesn’t require gas; you only need read-access to a node. let me know your thoughts.