Extensible crypto for wallets

security

#1

Extensible crypto for wallets

Problem

SNARKs and STARKs require signatures algorithms that differ from Ethereums default one.

EIP-1024 proposes encryption functions to be added to wallets, forcing all wallets to implement new crypto, which is complex and risky.

DApps have anticipated future needs for new cryptographic algorithms, for example diffie-hellman, off-the-record messaging, etc. Going through an EIP process each time is a bottleneck.

As discussed in the ETH1x workshop, we should focus on solving classes of problems, not point features. This proposal aims to solve this class of problems.

Proposal

DApp developers can submit webassembly bytecode for their custom crypto using a register_algorithm API. It can then be called using a user_algo API.

register_algo(algo_wasm_blob) -> code_hash
use_algo(code_hash, [...algo_input]) -> algo_output

They are defined as follows:

def register_algo(algo_wasm_blob):
    code_hash = sha3(algo_wasm_blob)
    cached_algos[code_hash] = algo_wasm_blob
    return code_hash
def use_algo(code_hash, [...input]):
    wasm_blob = cached_algos[code_hash]
    derived_key = sha3(private_key, code_hash)
    output = ewasm_engine(wasm_blob, [derived_key, ...input])
    return output

This API allows implementing new signature schemes, but also more complex protocols involving

A well designed API also allows adding other primitives like symmetric encryption/decryption, public key encryption, diffie-helman, off-the-record messaging, etc.

In particular it allows implementing
If the algorithm needs random numbers, it can use hash(derived_key, input_nonce) with a DApp supplied nonce in input.

Rationale

User supplied crypto. A DApp is allowed to supply it’s own algorithms. This is the most flexible approach as wallet developers are not involved in adding new cryptographic methods. An alternative would be to have wallet maintainers approve a set of algorithms, much like precompiles in Ethereum.

Key derivation. Especially when arbitrary code is involved, it should not be possible to output any information on the private key. In the proposed API, the worst that could happen is that the derived_key is leaked (i.e. when the algo_wasm_blob implements the identity function). This does not compromise the private key or the derived keys of other algorithms.

Choice of WebAssembly. Nodes will have eWASM implementations available already to support eWASM precompiles which can be re-used here. Browser integrated wallets like Metamask/Brave/Opera have WebAssembly available through the host browser.

Questions

Q1. How hard is it for native/mobile wallets to process eWASM?

Q2. Are there attack vectors for browser integrated wallets to run untrusted WebAssembly? Most of this would be covered by random webpages using WebAssembly, so the attacks would have to come from the integration with extension.


The UX of EIP 1024: Encrypt/Decrypt
#2

Interesting proposal!

A question that comes to mind is how in practise will this be dealt in term of UX.

Is the reason behind splitting registration and call an optimization perspective or do you think such registration require user confirmation, hence the need to separate the two.

If the latter, how do you think wallet will explain to the user what it means? Also do you expect wallet to save such registration across devices or will they require user to approve again when switching device posing a UX annoyance?

If the former would we not run the risk of dapps implementing broken algo in purpose and wait for user putting enough funds in their application to screw them up?

Similarly how do you think wallet will be able to provide meaningful UX when an algo call is requested if every algo can have very different meanings.

While I understand the wish to bypass the EIP process that I feel is quite slow too and might itself lead to api fragmentation as seen in the so called browser war of the old web. The process when complete allow wallets to provide a meaningfully UX to the users by being able to display the meaning of each operations to the users and guarantee the security of it.

You mentioned the use of “precompiled” algo though and this could well be a middle ground where the EIP process could be sped up by simply needing to agree to a specific algo implementation and its meaning. In this case the proposal here would be the base on which further algo EIP are built.

Of course we could also have both options available where non “precompiled” algo would require pre-registration and are displayed as unknown algo while the EIP process in in progress to be accepted. This would lead to the issue mentioned above for these cases though.


#3

Super interesting. I think that we definitely more crypto algorithms in Ethereum like EIP-1024, however I would be very skeptical to have Dapp-provided algorithms run on the Wallet side.

A user will most definitely try 1000s of Dapps but it will likely only use 1 or 2 Wallets. The user will trust the Wallet software to securely manage and store its private keys. I think it’s crucial that Wallets handle its own crypto always. It’s up to the Wallet providers to keep it up-to-date with the latest standards.

Thus I would suggest that we keep adding new crypto algorithms as new JSON RPC methods like EIP-1024. What we could standardize instead is how the Dapp and the Wallet communicate to share what algorithms are available.


#4

Optimization, I expect these WASM blobs to be up to several tens of kilobytes, so it may make sense not to pass them along for each usage API call.

The wallet should not be forced to store contracts indefinitely, so the DApp would need to re-register them when the use_algo fails with ERROR_UNKNOWN_HASH. User Acceptance is a separate issue.

I’m starting to think it is better to just pass them with each request, tens of kB is not that large for a request.

Another alternative is to pass a URL to the WASM blob instead, but this has its own set of security considerations.


#5

@wighawag UX is something I have not looked into yet for this proposal. I first want to make sure the API would actually be able to solve the class of problems in a cryptographically secure way before addressing the UX concerns. But let me go into your concerns:

This is possible, if users put funds in a malicious contract they run the risk of loosing them. Nothing really changes here. Contracts with backdoors, webapps with backdoors, signing algos with back doors. Same thing. Don’t put money in DApps you don’t trust. Demand audits.

The important thing is that a mallicious DApp can not affect a benevolent DApps. If you submit a backdoored version of a known algo, the code_hash will differ and you will get a different derived_key. You might be able to steal this key, but it won’t be the key that is used for the non-backdoored algo.

Security problems in a particular wasm blob are always contained to that specific wasm blob.

This is the same problem as with signing messages, which can also mean anything to any contract. I partially addressed that with EIP-712. Something similar works here, the derivation_key is used as a domain separator.

Fully addressing this requires some way to trustlessly explain users what a particular operation means, which seams nearly impossible. This is no better or worse from the problems with the current signing UX. Signing messages can also have very different meanings.

You could then say: “…but at least you know your are signing something! With this you don’t know if you are signing, decrypting or deity-knows-what!”.

True. There is a bit more explaining to do on the end of the DApp on what is currently happening. This explaining can be EIP712 style domain-separated-in in a secure way.

For example, let’s say we require the WebAssembly blob to also implement explain() -> string and name() -> string etc.

Consider a benevolent blob A. The wallet can query it all the context it needs to explain the user what is going on.

Now consider a malicious blob B that pretends it is A. Let say it returns the same context strings. To the user it could look like the user is about to do A, but really it is doing B. While very confusing, this has no effect on the security of A because derivation_key is incompatible (it can only be the same if B == A, but then there can be no attack because A is benevolent).


#6

There are different gradations we can apply here:

A. Require each new algo to be formally approved by EIP (whatever that means).
B. Let wallet developers decide which algos they want to whitelist.
C. Require wallets to accept all algos.

I feel C might be a bit much, at least until we have solved to UX issues. My preference is to go for A or B initially. This is the same approach as Ethereum is taking with eWASM contracts: start with hand-picked ewasm precompiles and later allow ewasm contracts.

Whatever we pick, I want to provide wallet developers a simple and standardized mechanism to add new cryptographic methods. In particular I want to avoid wallet developers having to implement the same crypto algos for every wallet, which is time consuming and risky, and also avoid bloating the APIs with an ever growing list of algorithms.


#7

Just want to throw another number in here: The user will likely only use 2 to 20 crypto algorithms (Transaction, Eth message signing, encrypt/decrypt, SNARK signing, STARK signing, Off-the-record protocol, etc).

I expect that a dozen crypto algos will cover the needs of these 1000s of DApps.

I agree with you here! The user should always be able to trust that their wallets keep their private key safe.

The derived_key mechanism guarantees that the private key is always safe, no matter what sort of nasty wasm algo the DApp throws at the wallet. eWasm is safe to run in nodes, so it is also safe to run inside the wallet.

I’m currently leaning to a whitelisting approach from wallets anyway, not so much for security reasons, but for the UX reasons mentioned above. Given that likely only a dozen algos are required, whitelisting is feasible.

If a wallet really wants to, they can always substitute their own implementation of a given webassembly blob. This might make sense for performance, but it should not matter for security.


#8

That makes sense and I agree with you but given that only a dozen algorithms will be necessary, then it should be feasible for Wallets to implement themselves


#9

UX is something I have not looked into yet for this proposal. I first want to make sure the API would actually be able to solve the class of problems in a cryptographically secure way before addressing the UX concerns

I do not want to hijack the purpose of this thread but in my opinion, UX concerns should be at the core of such proposal where user action is required

Nothing really changes here. Contracts with backdoors, webapps with backdoors, signing algos with back doors. Same thing. Don’t put money in DApps you don’t trust. Demand audits.

Sure except that the point was made in the context of comparison with a normal EIP approval. By bypassing it, we introduce a new vector of attack that was not there before.

The important thing is that a mallicious DApp can not affect a benevolent DApps. If you submit a backdoored version of a known algo, the code_hash will differ and you will get a different derived_key . You might be able to steal this key, but it won’t be the key that is used for the non-backdoored algo.

I understood the mechanism by which derived_key separate each algorithm but I am concerned with a different issue: If the wallet can’t meaningfully explain to the user what its action will do, a malicious app could make a user sign / decrypt… data meant for another app and this without the user being able to easily know. After all, if the benevolent app can’t relies on the wallet to explain to the user what the action will do, a malicious app can easily request the user to do the same set of action without the user being able to discriminate.

This is the same problem as with signing messages, which can also mean anything to any contract. I partially addressed that with EIP-712. Something similar works here, the derivation_key is used as a domain separator.

Again the issue I am concerned with is different and while EIP712 solve it partially by displaying all the information to the user it is not ideal because the user need to ensure the application is not trying to get them sign something targeted at another context. I have explained this on the EIP712 issues comments and that’s why I have proposed the use of automated origin checks there which remove the need for the users to verify the data on non-audited apps. Nonetheless I agree that what EIP712 does is far better than having nothing to verify meaningfully, which this proposal in its current form is doing.

If the goal is indeed to make [....input] into something akin to EIP712, then we can indeed make this proposal safer. As you said, we will have to define a way to explain to the user what the algorithm is supposed to do in some way.

Fully addressing this requires some way to trustlessly explain users what a particular operation means, which seams nearly impossible. This is no better or worse from the problems with the current signing UX. Signing messages can also have very different meanings.

I think my proposal on origin checks allow to solve this issue by proposing that the UI is moved into the app as much as possible. In that context, your proposal could be made a lot safer by protecting users from apps that would try to make them sign/decrypt data meant to be used in a different context.


#10

I think this is a great idea but I am not sure it requires to have such api exposed this way (unless we can solve the UI in a general way).

After all, if we can come up with such wasm blob in the first place, then already wallets do not need to reimplement it since they can execute such blob. After such blob is proposed the rest of the EIP process would be to define the set of input and how we can meaningfully use them for the users.

This does not preclude us to force wallet to having derived_key for such algorithms to remove the risk of having a bad implementation leak the main key.


#11

I would like to believe this is true. So far, wallets have been able to use off-the-shelve cryptographic libraries (libsecp256k1, libkeccak). This is also the case for EIP1024 encrypt/decrypt, where something like NaCl or libsodium can be used.

This no longer holds once dapps start using zero knowledge proofs (SN/TARKs). For the signatures required in these dapps, there are no off-the-shelve libraries that implement them. While some coordination and algorithm re-use among dapps is likely, there will be no support from the mainstream crypto libraries. (Yes, I know this breaks the “don’t roll your own crypto” mantra, but this is why we have expert cryptographers like Starkware working on it.)

This means that the wallet developers need to implement cryptography from scratch. For example, for starkdex signatures they need to implement a prime field and an elliptic curve on top of it. These are large non-trivial pieces of security-critical code! General wisdom in crypto-engineering is that you avoid this if you can.

We should not force wallet developers to implement cryptography. And this proposal achieves that, while still allowing dapps to use custom cryptography for which no libraries are available.

Now, coming back to you point. A wallet is still allowed to implement its own cryptography. Under the proposal this is as simple as recognizing the code_hash and instead of executing the wasm, execute your own implementation instead. Reasons for this could be 1) not having a wasm engine 2) increased performance. This makes sense for embedded devices for example, or specialist high throughput nodes.


#12

I would love to narrow the scope of the proposal to only make it easier for wallets to implement new crypto, and leave API and UX issues out of it.

However, if we go down this route it will be harder to extend derived_key with further domain separation like EIP712, or extend the wasm blob with methods like name() -> string and description(input) -> string.

I propose the following:

  1. First we reach rough consensus on wasm crypto extensions and derived_key, leaving the API and UX out of the porposal.
  2. We try to solve the API and UX generically by extending the proposal.
  3. If we can not adequately solve solve these, we give up and revert to 1 and instead solve it in a case-by-case way.