-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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 web3.eth.encrypt and web3.eth.decrypt functions to JSON-RPC #1098
Conversation
an encryption/decryption api is definitely needed. Did you see the discussion happening at #130 As mentioned there an origin field would provide security against malicious app requesting decryption , see #130 (comment) |
+1 |
title: Add web3.eth.encrypt and web3.eth.decrypt functions | ||
author: Tope Alabi <alabitemitope@gmail.com> | ||
status: Draft | ||
type: Interface Track |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no 'interface track'. Please use a type and category from EIP 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still not fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type: Interface Track | |
type: Standards Track | |
category: Interface |
Parity wallet already implements a compatible [encrypt/decrypt] https://wiki.parity.io/JSONRPC-parity-module#parity_decryptmessage method and the MetaMask version is on the way. Having a cross-client standard will enable a whole new wave of decentralized applications that will allow users to securely store their private data in public databases such as IPFS. | ||
|
||
### Motivation | ||
Imagine an illegal immigrant named Martha. Martha moved to the United States illegally but then had 2 children there, so her children are citizens. One day Martha gets arrested and deported but her children get to stay. How will Martha pass power of Attorney, bank account info, identification docs, and other sensitive information to her children? Storing that data in a centralized database can be incriminating for Martha, so maybe decentralized databases like IPFS could help, but if the data is not encrypted anyone can see it, which kind of defeats the purpose. If Martha had access to a Dapp with end-to-end encryption connected to her identity, she could save her data in a decentralized, censor-proof database and still have confidence that only her children can access it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This motivation is bound to piss people off but it gets big ups from me. Nice imagination. Just wanted to leave that here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. But it's also a very legit scenario.
Isn't there something like this already available for Whisper? If not, this should be more finely integrated into Whisper, don't you think? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice! I had a question below:
/** | ||
* Encrypts plain data. | ||
* @param {string} encryptionPublicKey - The encryption public key of the reciever | ||
* @param {string} version - A unique string identifying the encryption strategy. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to define an enum, and use it here instead of a raw string?
pros:
- this would enable tools/IDEs to type-check any arguments here
- makes it more discoverable which encryption-strategies are available e.g. via autocomplete
cons:
- I don't know how easy or tough it would be to add new encryption-strategies i.e. new values to the enum, if the enum definition were to be included in the standard itself.
- Maybe there's an alternate way of defining it?
- e.g. if we could have a base-enum in the standard and implementations can "extend" it, but I'm not sure if the solidity language supports that. Oh, also this would only work for "string-enums", but solidity seems to only have "int-enums" https://solidity.readthedocs.io/en/develop/types.html#enums
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An enum would be a good idea when we have more than one option (as of now this is the only one).
Adding more options, and enumerating them, sound like a good thing for a future EIP that specifies this among its requires
.
|
||
/** | ||
* Returns user's public Encryption key derived from privateKey Ethereum key | ||
* @param {Account} reciever - The Ethereum account that will be recieving/decrypting the data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor nitpick: reciever, is usually spelt as "receiver"
@topealabi This interface
cannot work this way since the dapp has not access to the users private key, this is more the lower-level implementation. The right interface IMO would be
which would return the encryption public key corresponding to the private key associated to that address. This would require some sort of user interaction/UI to grant access to the private key, which would normally be encrypted/locked. This line
is a bit unclear, since we are saying that we can encrypt any object? How to do that for a nested object for instance? Maybe it's better to just put Also in the MetaMask PR I mentioned padding and the security implications thereof. I don't think padding should be built into the encryption function, but it might be a good idea to expose a simple padding interface to remind developers that this is important to think about. Not really sure about this though, |
@christianlundkvist re UI needed to get public key, It should not be necessary if the nonce of the account is greater than zero as the public key is already available on the network (using the private key here is only internal). Even if the nonce is still zero, for some signer (maybe metamask?) it could be assumed that the user is ready to reveal its public key in that context. (it will be hard to explain to the users what they are actually giving out and why does it requires their agreement) For all, I looked briefly at the PR and did not see discussion related to the issue I mentionned in #130 regarding malicious dapp that ask the decryption of data published by another dapp. I proposed a solution using an 'origin' field to solve it. See comment : #130 (comment) and the discussion that follows |
Not in this case. The public encryption key is on the curve Potentially you could also generate the public encryption key when a new account is created in the UI. |
Quick re-cap for anyone caching up: We’d like to achieve end-to-end encrypted communications between ETH addresses. But, encryption requires a keypair that is specifically designed for the purpose – an ETH keypair won't do. Users will need a dedicated encryption keypair. Whichever way that encryption keypair is generated, the public-key will differ from the ETH address and must be broadcast between users prior to encrypted communication. Some initial thoughts on this proposal so far: This standard proposes generating an encryption keypair using the ETH private key, so that both key pairs (Ethereum & Encryption) use a common private key. However, there are some risks perhaps in using the ETH private key for both the signing and the encryption of data:
Most of these risks could be mitigated by some higher level protocol. But why not avoid these difficulties altogether and use separate keys? Any number of encryption keys can be associated with an individual using a digital signature from their ETH key pair. This standard could include an interface for key association which encourages the use of multiple keys – rather than run the risk of normalising the use of a single key. |
Hi @geeogi , thanks for the recap. I Appreciate your concerns and I think this is exactly the kind of skepticism the ecosystem needs in order to make sure that we are doing good work. I’d be happy to address some of the points you’ve mentioned above. The main idea you seem to be teasing out is:
That is incorrect. I think the confusion may be coming from the fact that the encryption keypair is derived from the Ethereum privatekey but let me explain how that works:
Now if someone else gets a hold of your Ethereum private key, they can generate your encryption keys and control your assets but this concept is precisely what makes a decentralized world work. Please protect your private keys.
There are many benefits to having encryption keys as part of your digital identity, this EIP is a positive step towards a world where people can have private communications in public places without fear of censorship. |
@christianlundkvist thanks for the correction. Sorry for not reading the spec carefuly. But yes, we should allow signer to make it easier for users if the intention is to use such publick key with dapps. Since once it is used, it should be assumed to be public. |
@topealabi Thanks for your detailed response! I'm grateful to you for your work on this EIP so far. I absolutely see the benefit in having encryption keys associated with your digital identity. This can be achieved now by digitally signing an encryption key pair using your ETH key. As I understand from your explanation, using the ETH private key to generate the encryption key pair means that both key pairs will be vulnerable in the event that a single key (the ETH private key) gets compromised. Granted, we aim to protect private keys at all times and this should always be encouraged. But i'm wondering what’s the benefit of using the ETH private key to generate the encryption key pair over say generating an encryption key pair with an entirely different seed? |
Why not use ENS to store the user's public key? |
Hi @geeogi, thanks for the feedback. As we all know, the criteria for a keypair seed is that it is secret and unique, therefore Ethereum private keys are a good fit, with the added benefit that the user doesn’t have to memorize a new secret and the clients don’t have to drastically alter their architecture. It is true that if someone has your private key they could receive your packages but we could say the same for signing. As things stand today, if someone has your private key they can sign data packages as you, which is why we continue to emphasize that you protect your private key. We have structured this implementation in a way that actually makes it more difficult to guess your private key from an encrypted message than it would be to guess your private key from signed data. With that said, we understand that all things evolve, and so we have created a version interface that allows developers to make further improvements to this protocol in the future. |
Just as a point of reference, Parity already has pretty much exactly this support in the |
This is a great idea. This proposal tackles key-generation and allows each implementor to choose where they want to store their keys. I believe we can explore key storage as a future upgrade or a separate eip once we reach consensus on the key-generation bit. |
@geeogi What you are describing is known as the |
i see; upon looking further, it looks like they implemented the same thing MetaMask did (and just deprecated). it's strange to see MetaMask and Ledger going in opposite directions on this. I don't love the approach myself (any more than you do), but a low-impact route could be to just endorse it anyway (especially considering Ledger's recent adoption, etc.). though using the same key on different curves is obviously not great, i don't think there are any known attacks. i worry about developers at the application level; most of all the need is for something consistent which wallets will implement. your approach sounds good too. i'm not an expert on how HD wallets work; but, given the need for the ETH account to serve as an identifier for the encryption public / private key, maybe we could use a "parallel" HD derivation path to the one which MetaMask (say) already uses, so that there's a one-to-one correspondence between signing keys and encryption keys. but an issue arises: what about the case where an account is "imported" into MetaMask, and so lies outside of the HD derivation chain? how would we generate an encryption keypair for that account? unrelatedly: i don't see the point of using the ed25519 curve at all. couldn't we just use ECIES over secp256k1? let me know your thoughts. |
I agree on keeping it to one curve, per the reasons given for deprecating this capability. No address “lies outside” a derivation chain, the derivation is a one way function that can start at any private key. It’s not so much that two addresses are “unrelated” (since being able to prove they are actually unrelated would make the HD algorithm insecure), more that they are within a certain number of derivations. Also, importantly, If you give a counterparty an Therefore, if we created a new BIP / EIP and used that number in the derivation path, even without creating a new algorithm for that specific proposal (copying the derivation function from one of the current heavily used As part of the proposal we could define the “change” derivation address as “encryption”, since the concept of “change” does not apply to Ethereum. It would be interoperable with the current toolsets / libraries and require minimal changes to current applications. Thoughts? |
When building an app, following a defacto standard can be useful. When authoring a standard, you SHOULD try to design the best standard possible and try (whenever possible) to not be constrained by what people are doing today. The best standards survive for decades/centuries, and no one cares at all about how people did things at the time the standard was created 100 years later, they care that the standard is good. |
@TJKoury sure, in general i agree with your reasoning principles and will support this. it's a bit subtle what
i agree with this.
i'm not sure i was clear above. my thinking is that, in order to comply with the API, we need a way to associate, to each standard ETH account in MetaMask, a corresponding encryption public/private keypair. short of using the exact same keypair used for signing (which may be less secure—though I'm not personally convinced of this), the easiest way I see would be to use a "alternate" derivation path running in parallel to the main one which MetaMask implicitly uses when generating accounts. so if you request the encryption public/private key corresponding to the ith account in your MetaMask derivation chain, then it would return the public / private key of the ith element in the "parallel" derivation chain. i'm not sure how this latter path would relate to the xpubs exported for the purposes of receiving repeated communications. presumably it would be separate from those. |
The xpub (and ypub, zpub) allows you to derive the public keys for a derivation path without the private key. If you give an xpub to another party, they can generate all public keys for all keypairs derived from the starting keypair. One of the original use cases was to put the xpub on a server for an online store, and let the customers get a new address to send payments to for every checkout, so as to not reuse addresses.
We could, thus the new BIP/EIP I proposed, however the "easiest" way we could do it currently is as I mentioned, simply use the "change" address as "encryption". This would be against convention for UTXO-based cryptos but Ethereum has no need for a "change" address. So, for HD derivation path "m/44'/60'/0'/0'/0" the encryption keypair could be generated by using the derivation path "m/44'/60'/0'/1'/0". We could write this convention into an EIP and create a very simple drop-in solution that works with all current HD wallet implementations. I have not seen any Ethereum wallet actually use the "change" address in HD derivation since it has no use, so this would not conflict with any known implementation. |
right. i think your proposal is compatible with what i was (attempting to) say. shall we proceed? :) |
i understand the process mechanically; the question is, as far as encryption is concerned, if i'm sending you multiple messages, then is encrypting them under different pubkeys (all within an xpub path) more secure than just using the same public key every time? it's not obvious why this would be. contrast this with the case of bitcoin, where the TXO addresses obviously need to be made public, so there is an (at least mild) privacy / linkability benefit to using different keys. whereas in the case of encryption, i can send encrypted messages to you in a public channel without ever having to publish the corresponding public keys, so the linkability benefit of xpub over and above reuse wouldn't exist. there could be a forward secrecy benefit to using xpub for encryption if you (the recipient) could somehow delete each early key in the derivation path after it's used to decrypt its corresponding message, including all relevant chaincodes. but this is getting fairly complicated. |
No, I don’t believe it would be more secure cryptographically, and obviously if you’re sharing the xpub over public channels, it negates any sort of fundamental operational benefit. The scheme you mention at the end is something that I have considered for a while; if you have derivation paths for signing and encryption keys, and share xpub keys, you could verify a signature of the encrypted data (since you can get recover the public key from the signature), then use the next public key in the derivation path for forward encryption, “burning” previous key paths. If both parties keep the xpubs protected after initial secure key exchange (say on a standalone system) and airgap the following sets of key pairs, you could have an extremely robust system that could survive even the compromise of operationally used private keys. It is fairly complicated as you state, and I would not recommend building this type of scheme into a standard or hardcode its use into a wallet. At the same time, being aware of the possibilities and engineering a solution that can take advantage of them is something to consider. |
And yes, we should proceed. I would be happy to collaborate. |
Just reading through the thread and responding to a few things:
This seems like a high risk chance to take for wallets that are storing and managing private keys that handle billions of dollars in conglomerate to leave it in now that we know it's not provably secure. I certainly don't want to be on the hook for accepting that risk when we can design something safer.
I believe the reason Ed25519 was chosen was because it was much easier to prevent issues across a variety of wallets because the
Wouldn't that allow the counterparty the ability to derive all of the encryption keys below the provided path? That seems like a solution that leads to fingerprinting privacy problems which are a major consideration for browsers now and I suspect will become a major consideration of wallets going forward in web3. I don't think this is a feature we want to support since it means a user can be tracked by their encryption key unless they generate a separate seed for each party they want to interact with since the counter parties could coordinate and brute force check if the user is the same person across dApps. In this case, key sharing being done by the wallet becomes a privacy enhancing feature rather than a bug in my opinion.
How would this be coordinated across parties?
There's an implicit assumption here that the wallet needs to be avoid managing state of signing and encryption keys and as such the signing public key (e.g. ETH account) should act as an identifier for the encryption key in a way that couples the pair together even though they don't need to be. What's the benefit in trying to keep these key pairs coupled when they don't need to be?
This runs into that privacy problem I was talking about. While it is a simple solution, I don't think we should be ignoring these fingerprinting concerns going forward.
Yes, this is a difference between weak perfect forward secrecy and strong perfect forward secrecy because compromise of the same public key on the recipient side means that the attacker is capable of decrypting all previous messages that they passively intercepted. It should be noted that the HD path mechanism isn't achieving strong PFS either because if the seed is compromised then the attacker can again decrypt all possible passively intercepted messages because they can regenerate all of the recipient private keys. A simple HD scheme can't get you post compromise security like Signal is able to achieve. Reading through this thread so far I think there's a lot of baked in requirements and assumptions that aren't clear to me why they exist. I think before we dive into any sort of solution design we should figure out what it is that we actually need going forward. As @MicahZoltu pointed out we should really be designing this for something that can last or we'll be back at this stage in a few years time. There's a lot we can learn from evaluating the usage of these APIs over the past 5 years, what limits dApps ran into, what limits wallets ran into and from there adjust our expectations of what this is trying to achieve accordingly. For example, I know one implicit assumption that everyone has made here from the outset of this EIP is that the key generation MUST rely on an HD scheme which is used to derive all keys from a seed phrase. Why is that? Historically speaking, it comes from the BTC motivation of "not your keys not your crypto" which has an implicit tradeoff around key recovery by making everything seed based. This isn't a necessary assumption though, it's just something that has stuck because that's how everyone else did it. If we're already revisiting assumptions here why not consider refactoring that as well? It greatly simplifies the security model if we can independently generate the private keys for each interaction which seems like a really good idea considering these wallets are collectively managing hundreds of billions of dollars worth of assets and one incorrect design choice can become incredibly costly here. On the flip side, the two downsides that comes to my mind with this design consideration are:
These may be reasons to not consider it as a solution, but until we decide on what the users needs are, what the wallet vendors needs are, what the dApp developers needs are, and what the implementation costs are I don't think it's a smart choice for us to just dive in and find a solution without considering what's actually useful. |
Yes, in some cases this is a feature, not a bug. There are many PKI implementations where attribution is required; in these cases the ability to attribute an address to an owner in as few steps as possible is a benefit. The fact that this is possible, but not required, is a positive for using HD derivation.
This is only an issue if the xpub is shared, which it does not have to be. Even knowing a wallet is using this relational derivation path is mathematically useless without knowing the chain code.
HD derivation does not rely on using a seed phrase, you are confusing BIP39 (seed phrase) with BIP32/44/84 (HD derivation). They are used in combination due to ease of use and compatibility. It’s the same reason almost all wallets use only 128 bits of entropy (12 words) rather than up to 24 words for the full 256 bits, which is exponentially more secure. If there is another option which works better I am sure everyone will consider it.
I don’t think anyone is arguing that it would by itself, however it does give a good starting point compatible with current wallet implementations, and does not preclude finding a scheme that would provide it. PFS using ephemeral DH key exchange in the Open Whisper protocol only works if you disable logging for example, which is an implementation detail. You could easily generate and then encrypt ephemeral keys with / for an HD derived key, which would provide PFS even if the HD private key was compromised. We need to keep in mind that the primary purpose of these wallets is maintaining access to value as you say:
If a seed phrase is compromised, the damage is chiefly the loss of funds. Trying to rewrite the whole playbook at this juncture for a single feature, ignoring prior art, seems much riskier than using current tools to accomplish the job. |
From who's perspective, a privacy conscious user or a dApp developer? Which persons considerations are more important? This is why I'm trying to start the discussion as a framing of requirements and priority tradeoffs rather than jumping strictly into a solution driven conversation. This is the tradeoff of writing standards. They simply take longer than just agile engineering development. And since this API is meant to be implemented across a variety of different wallets in an interoperable way it's wise of us to figure out what the goals are here first and follow more of a standards based approach for solving this problem rather just finding something that works.
The generate independent keys idea above is another option, but until we determine the goals it's too hard to determine what "better" is since that's a subjective view point. Your points raised are valid trade off considerations and are not something I'm attempting to write off as unimportant. That's why I wanted to call it out as a design tradeoff in my comment above.
Is it still if we're adding secure messaging platform APIs on top? Seems like they're becoming more than that with these APIs. EDIT: Also, I do think discussing these different solutions is useful because it grounds the discussion about what's needed with the discussion about what's possible. |
Yes, it is. If we attempt to create a solution that makes it more difficult to do the basics, or creates such an development upheaval that no one implements it, then we have failed in our objective. |
+1 you won't find me disagreeing with that at all. Equally so we could find dApp devs not willing to use it because it doesn't meet their needs (e.g. there's a lot of dApps out there which don't rely on this functionality. I don't know why that is), or users choosing not to use a particular wallet because each wallet is doing it differently and only the popular wallet supports the trendy dApp. |
In my mind here's the requirements worth highlighting so far:
open questions:
What ones can you come up with @TJKoury to add to that? |
imo, the following use cases might be low-hanging fruits and would be already great:
a bit more hard to achieve and probably requires an extra layer (discovery protocol):
if discovery is part of the solution then it would be great if the eth account would act as the root identifier:
|
the idea here is that (at least as things currently are) the eth account serves as a "handle" onto the encryption / decryption keys, as well as onto signing behavior. dissociating these could make things harder to keep track of, but could definitely be done.
in my view, this assumption is actually crucial. many applications—mine, as well as e.g. Tornado cash—use encryption / decryption precisely so that management of new, application-level secrets can be bootstrapped onto the same seed phrase people have already gone through the trouble of storing (by encrypting these new keys using MetaMask and storing them in reliable storage). if you break this assumption, then (in these particular applications) you lose the whole point of using encryption / decryption in the first place, since by the time you're making the user memorize / store / manage new decryption keys, you might as well just make them store / manage the new application-level keys directly, bypassing encryption entirely. edit this point is only relevant if the management of the decryption keys has to be handled by the user, as opposed to the wallet; i guess i should have clarified this. but even if the wallet does handle it, this makes export / transfer of wallets more complex.
right, my issue is not so much whether the keys are independent from the existing signing keys, as whether they can be derived from the same seed phrase. i think this latter assumption is what's important. |
as @TJKoury mentions, there are only very restricted circumstances in which someone would be able to derive subsequent keys. in short, it's possible only when an "xpub" is used and the counterparty is in possession of the relevant chaincode. in this circumstance, the ability to derive future keys is more likely a feature than a bug. i suppose the idea's that in practice, the receiver of messages will share the xpub (including chaincode) directly with the sender (i.e., through a private channel). so that only the sender will be able to do this derivation subsequently, but not anyone else. (a bit of a bootstrapping issue how this channel is to be come upon, but we can assume they're able to do a one-time advance exchange of the secret material.) |
Can you explain this a bit further because I'm not following this assumption? For example, password managers leverage a password plus a KDF in order to store any number of arbitrary secrets which are randomly generated such as a randomly generated password. They don't require the user to store or manage everything instead the password manager handles both of those things for the user. It could just as easily be used to generate an arbitrary key, store the key, and also help the user to supply it to thee web app when necessary and call it a wallet. So with that in mind I'm not following what additional benefits are gained by using the most common model for wallets rather than the most common model for password managers. The difference here is that wallets today require only the seed as managed state in order to regenerate everything again rather than requiring an import/export functionality like password managers have. However, as soon as you need to import and export messages to migrate wallets you lose the ability to regenerate everything from the seed, so it's none of the upsides with all the downsides in my eyes. Hence why I asked if that's necessary which from a UX perspective I would think it would be for both dApp devs and users.
Two things to clarify here - This xpub based public key derivation is only possible through secp256k1 derivation schemes right? Isn't there a downside to leveraging secp256k1 for key agreement schemes (aka deriving encryption keys) because this curve is susceptible to twist attacks that can reveal the private key with encryption? I've actually found that this was a vulnerability for a dependency that many wallets relied on. Why not pick a different curve here then in order to avoid this concern altogether? Also, just to clarify so that I'm not making a mistake in my understanding of the way this HD scheme works, who's sharing the chaincode and when? Is the user of the wallet because my concern here is around the user sharing the pub key+chaincode with dApp 1 and then sharing the child key of that pubkey+chaincode with dApp 2 and dApp 1 would be able to coordinate even if signing was never used. Obviously if transactions from the associated accounts are used then that data is public and it's possible to see which contracts are being interacted with to easily detect this usage. However, encryption should probably make slightly different assumptions since it's possible that a dApp may want to only use a encryption key without wanting to do signing/on-chain transactions. |
right, I'm not opposed to the password manager approach in principle—but then the burden would fall on us to define the import / export protocol. note also that password managers (at least LastPass) routinely sync all passwords with a remote server, so that import / export is less critical; this is obviously not a desirable design choice in the case of crypto wallets.
right; this was my mistake; see the "edit" at the end of my second paragraph above.
i'm not sure I follow this. why would you lose the ability? in my mental model of things, the relevant ciphertexts here are being posted somewhere durable (e.g., on-chain). so as long as you can recover the decryption keys from the seed, you can also recover the messages. even if they're not posted durably in the case of messaging, they definitely are in the case of application-key-bootstrap (e.g. Tornado), so there are still cases where you would indeed get the benefit of recovery.
was not aware of this; but if you read into it, it looks like this relies on the victim not checking whether the attacker's ephemeral point is on the curve:
this is only an issue when encrypting to others (and not in the encrypt-to-self paradigm Tornado uses), and is easy to prevent if you check that the incoming point is on the curve. moreover this is only an issue if the attacker sends an uncompressed point; most implementations these days expect compressed points. most importantly, it looks like this is only relevant if the attacker can convince the victim to use the same (sensitive) exponent across multiple encryptions. but this won't happen for ECIES; indeed, the encryptor (who would be the victim here) uses a fresh random ephemeral key in the DH exchange, not a persistent key, and the attacker gains nothing by learning about this ephemeral secret key. so for this to be an issue, a lot of things have to go wrong. in particular, if we define
this sort of thing will probably be possible for any Weierstrass curve (leaving basically ec25519, which was already ruled out above) and, in any case, the risk is so low with correctly implemented code that i wouldn't find that path justified.
as i understand it, if i want to receive a stream of messages from you, then I will generate a new "xpub" (i.e., a public key + chaincode), then send both directly to you (though some assumed existing private channel). i will never reuse that chaincode with anyone else. thus your scenario with dApp1 and dApp2 just wouldn't happen (at least under typical use patterns). now whether this is actually any better / more secure than me just sending you a single key is much less clear to me. i think that'd only be possible if i could generate an xpub whose chaincode was outside of my HD chain, and delete old elements of the xpub sequence after i use them to decrypt. in this way you could get some sort of forward secrecy. but i'm not sure whether this is in scope, because it would require xpubs whose chaincode lived outside of the HD chain. |
I disagree, that sounds like a great recovery and cross device scheme to me to be able to backup encrypted copies either to a service offered by the wallet or by downloading it and storing it yourself. This is the same direction webauthn is looking to head with passkey as well, so it's a scalable and secure design that's looking to be deployed to billions of people it seems like Additionally, you could build in a SSS scheme on top based on the KDF digest which allows for social key recovery without the backup service ever gaining access to keys. I do understand the scepticism here, but key backup via cloud or personal servers seems like a great way to solve the lost seed problems of today.
Ahh that sounds like a big no-no. On chain costs gas for storage and runs a risk of passive compromise attacks in the future. Same issue if they're published to IPFS with out an additional authorization layer above. Additionally in the case of "application-key-bootstrap" that's managed by the user (e.g. download the key) so the burden is on them to manage migration. This is similar to import and export of plaintext CSV files for password managers though so is certainly plausible as a simple import/export protocol. However as soon as you start factoring in being able to move messages from one wallet to another. Even just the URL of where they exist in a "durable location" you have to factor in storing those URLs as well. So why not just do the same thing for both messages and keys as soon as you've already built the protocol for messages?
This is true, but crypto is hard. (That vulnerability wasn't discovered in an open source wallet that's used by 8.2 million repos) So you can trivialize it, but it remains a problem that can be designed around by simply picking a different curve. Why risk it?
Also, I'm not finding in Tornado's codebase or the docs where they're doing this encrypt-to-self. Do you by chance know where the code is so I can take a look at their implementation to understand how it's being used? I presumed based on Metamask's medium article that this API was in use, but it wasn't clear to me which dApps were actually using it. From what I could tell the file being downloaded was for their Pedersen commitment scheme not for encryption.
It was ruled out to reuse the same key material across curves, not to rule out the curve all together I thought. The only other thing I saw on the topic is that @TJKoury would prefer to reuse the same curve, but that's where we encounter real design problems that have been discovered in the wild. Let's not trivialize implementing crypto here that's been shown to have mistakes and took a bit to catch for the sake of preference.
Thanks for the further clarification. And is the publicKey and chain code unique to each dApp or just one of these? From my read of things the public extended key isn't that way and so if it got reused across dApps this fingerprinting would be a concern even if different indexes within the path are used because the higher up path could correlate the lower path assuming they're coordinating.
Yeah this is why I was going down the route of |
Couldn't this be covered by the TLS channel setup when the user connects to the dApp? In DIDComm it's not assumed there's a client-server channel, but as far as I can tell dApps still assume this architecture. The one exception to this was Augur which operates as a stateless SPA that's hosted, but since it's stateless it couldn't generate a key to encrypt it to the user plus it's already being ran client side so I don't think that use case aligns.
This one doesn't seem like it would require the eth account to handle it. In fact, it's better to just use a symmetrical key in this case since the eth user is encrypting it for themselves.
These get to the heart of one of the questions I'm curious about as well. Is this platform API meant only for developers or also for p2p communication between ETH users? If this is the case, there's quite a few different assumptions we'd need to change from the original |
in the case of Ledger wallets, this might not even be possible, since the secret keys can never leave the device or be exported, by design. so it seems that this would rule out compatibility with Ledgers.
i was perhaps not clear. by "application-key-bootstrap", i mean "encrypt a new, application-level secret key under an encryption key whose decryption key derives from the wallet's seed phrase". so the point is precisely to not place any additional burden on the user to either download this application-level key or take any additional steps upon migration.
i beg to differ 😄 like it or not, this is exactly what Tornado cash—a very widely used and trusted app—does. they've apparently found that these "risks" are outweighed by the key-management benefits of not forcing users to download and maintain new sporadic keys.
i'm not familiar with their codebase, but see attached screenshot for proof. you can also experiment with their app.
i don't know if i was emphatic enough above in explaining why this isn't a risk to us. it's an attack on ECDH, not ECIES. this is only a risk if (1) the victim Bob doesn't check that the attacker Alice's public key is on the curve and (2) the victim Bob exponentiates some point supplied by the attacker Alice by the same sensitive secret scalar multiple times. but this will never happen with ECIES, by definition of the scheme, since Bob doesn't use any persistent secret key whatsoever, but rather only exponentiates Alice's points by random scalars. so Bob (the encryptor) has no secret key for Alice to crack. |
"connected" in this context means -> connect button, not didcomm. the eth user is just using a SPA and clicks on "connect". this just means, the SPA knows the eth user's address (account) and nothing else (also no authentication). the "connect" button is what almost all dapps have implemented. typically, web3modal or rainbowkit's modal would then be displayed to select the wallet. to store personal data about the end user, this data should be encrypted to the user's private key. the dapp (or the browser, perhaps through extensions) can provide the user with options how to store the data on the user's behalf, e.g., on ceramic, filecoin, kepler, dwn etc. Update:
this could be done by symmetrical encryption, but you don't want to send all your data through a walletconnect session for example. so, you would rather create a symmetric key in the SPA to encrypt the data and then encrypt the symmetric key with ECDH via the wallet.
@obstropolos ^^ |
Are there alternative solutions to dispense with the encryption and decryption of Metamask ? |
@ahmaddradii please see https://eips.ethereum.org/EIPS/eip-5630. |
Abstract
This EIP proposes a cross-client method for requesting encryption/decryption. This method will include a version parameter, so that different encryption methods can be added under the same name. Nacl is a cryptographically complete and well audited library that works well for this by implementers are free to choose their crypto. Ethereum keypairs should not be used directly for encryption, instead we should derive an encryption keypair from the account's private key for decryption and generate a random ephemeral keypair for encryption.
Parity wallet already implements a compatible [encrypt/decrypt] https://wiki.parity.io/JSONRPC-parity-module#parity_decryptmessage method and the MetaMask version is on the way. Having a cross-client standard will enable a whole new wave of decentralized applications that will allow users to securely store their private data in public databases such as IPFS.
Motivation
Imagine an illegal immigrant named Martha. Martha moved to the United States illegally but then had 2 children there, so her children are citizens. One day Martha gets arrested and deported but her children get to stay. How will Martha pass power of Attorney, bank account info, identification docs, and other sensitive information to her children? Storing that data in a centralized database can be incriminating for Martha, so maybe decentralized databases like IPFS could help, but if the data is not encrypted anyone can see it, which kind of defeats the purpose. If Martha had access to a Dapp with end-to-end encryption connected to her identity, she could save her data in a decentralized, censor-proof database and still have confidence that only her children can access it.
More casually, Martha can create a treasure hunt game, or a decentralized chat app etc.
Specification
To Encrypt:
To Decrypt:
Rationale
These methods should require user confirmation. We include the versioning to allow different encryption/decryption types to be added under the same method name. For example, it might make sense to have a few kinds of decrypt methods, for different kinds of consent:
Backwards Compatibility
Parity implements an encrypt/decrypt method with a different curve than the one which is intended in this proposal, but that it would be possible to add support for curves to this standard.
https://wiki.parity.io/JSONRPC-parity-module#parity_decryptmessage
Test Cases
getEncryptionPublicKey(7e5374ec2ef0d91761a6e72fdf8f6ac665519bfdf6da0a2329cf0d804514b816)
should return a public encryption key of the form"C5YMNdqE4kLgxQhJO1MfuQcHP5hjVSXzamzd/TxlR0U="
web3.eth.encrypt("C5YMNdqE4kLgxQhJO1MfuQcHP5hjVSXzamzd/TxlR0U=", 'x25519-xsalsa20-poly1305-v1', {data: 'My name is Satoshi Buterin'})
should return a blob of the form{ version: 'x25519-xsalsa20-poly1305', nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }
web3.eth.decrypt('7e5374ec2ef0d91761a6e72fdf8f6ac665519bfdf6da0a2329cf0d804514b816', { version: 'x25519-xsalsa20-poly1305', nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' })
should return plain text/file of the form{ data:'My name is Satoshi Buterin' }
Implementation
Parity wallet has already implemented a compatible encryption/decryption method. The Metamask version will be published soon.
https://github.com/topealabi/eth-sig-util/blob/master/index.js
Copyright
Copyright and related rights waived via CC0.