Current implementation of exchangeKey is insecure. I first designed it as a pragmatic approach to recycle persisted sessions on browser's localstorage. However you can't securely exchange symmetric keys using previously compromised keys. This is something that I've been wanting to revisit and change for months but haven't prioritized in the whole scope of things that needed to be tackled.
A couple of weeks ago, me and @ligi brainstormed a better selection to ensure that the key exchange was secure and guaranteed to only be known by two parties. WalletConnect protocol is intended to only be used 1-on-1 therefore anyone else with access to the symmetry key exposed through URI (displayed by either a QR Code or deep link) shouldn't be able to decrypt these communications between these parties.
The first proposed change is that exchangeKey should only be triggered after the connection is established, therefore no unnecessary computation is used unless the session is approved by the counter-party (which currently is exchange prior approval).
The second proposed change is that the exchangeKey flow should be initiated by a signing challenge through a JSON-RPC request with the method wc_signingChallenge
. For example, after connection approval the Dapp will request a signing challenge (which should be specific for WalletConnect to be auto-signed). This is very analogous to a similar proposal that I made on ETH Magicians here. The Wallet would receive this challenge and trigger an event for the signing challenge which can be handled with a callback as follows.
walletConnector.on("signing_challenge", (error, payload) => {
if (error) {
throw error;
}
wallet.sign(payload.params[1])
.then((signature) => {
walletConnector.approveSigningChallenge(signature)
})
.catch((error) => {
walletConnector.rejectSigningChallenge(error.message)
});
});
The reason we included this signing challenge is to be used by the Dapp to recover the public key to be used to encrypted the contents of the following exchangKey request. Hence after the Wallet has successfully signed the challenged, the Dapp will follow with a new request that will contain an encrypted payload with the next symmetric key to be used in the communications. We won't reuse the wc_exchangeKey
JSON RPC method in order to keep backwards compatibility with previous v1.0.0-beta versions. The JSON-RPC request should have a method of wc_updateKey
and should include a single parameter that includes a hexadecimal encoded string of the next key encrypted with the public key recovered from the signing challenge. This key can be decrypted by subscribing to an event for the key update which can be handled with a callback as follows.
walletConnector.on("key_update", (error, payload) => {
if (error) {
throw error;
}
wallet.decrypt(payload.params[1])
.then((nextKey) => {
walletConnector.approveKeyUpdate(nextKey)
})
.catch((error) => {
walletConnector.rejectKeyUpdate(error.message)
});
});
Additionally, we could provide an easier integration within the SDK configuration options that include a field for a private key to be used for the new exchangeKey flow. This will make it easier for Wallet integrations instead of subscribing to both events individually, yet both options should be available.
Finally, the private/public key pair doesn't need to be the same as the wallet keys which hold funds, meaning that can be generated separately and potentially could be generated within the SDK instead of passing this responsibility to the Wallet developer