While fully relying on ETH-JSON-RPC node-side private keys has the advantage of offloading private key (PK) security to the ethereum node, on the most common usecases (e.g. Metamask, Parity) it has the downside of making for a poor UX, as every signature requires an interactive prompt and user action. On current implementation, at least 2 signatures are required at first startup time for Matrix login and up to 6 are required for a single payment sending. This even worse when using a hardware wallet (e.g. Metamask's Trezor support), as each signature will need multiple approval clicks on UI and on the physical device.
As most of the time these are signatures of opaque byte strings or even hashes, there's no point in requiring users to see it anyway.

Here we define a subkey (child key) as a new private key deterministically and secretly derived from the main/parent user key/account. An ETH private key is 256bits/32 bytes number/string. A possible approach is to use the last 32 bytes the keccak or sha256 hash (32 bytes) of the signature (65 bytes) of a constant (possibly per-network) string as private key.
Pseudo-code Example:
message = `raiden_${network.name || network.chainId}`;
subkey = web3.sha3(web3.personal.sign(message, web3.eth.accounts[0]));
// or eth_sign equivalent, depending on provider's implementation of EIP191
subwallet = new Wallet(subkey, provider);
Optionally, the to-be-signed constant string could contain BIG WARNINGS (which will be invariably shown to the user in Metamask sign prompt), alerting users to NOT sign this message anywhere outside a trusted Raiden client, to prevent key steal by phishing:

EDIT: updated to the current proposal of using the hash of the signature
At startup/load time, the user may be given the choice to use some eth-provider key (e.g. Metamask always expose a single key, the selected one) directly (as is today) or a generated subkey.
On the subkey case:
localStorage object, but also no evidence of relationship between main and subkey), and the user will be given the initial prompt again on every startup.This approach is already used in other dapps which need a big number of signatures.
This proposal is fully compatible with Raiden protocol, as from the other node's perspective, it's just a normal account interacting with the network, with additional perk of being able to keep the bulk of token stash/ether on a separate, not often used main account.
Additionally, it makes possible to use Raiden Light Client with hardware wallets like Trezor, as a single startup signing prompt, plus eventual on-chain deposit tx prompts are the only intervention needed from the main account.
As the key is not persisted anywhere, but derived from the main key on each session's startup and kept only on memory, there's very little risk of steal. If the application isn't trusted or its runtime was compromised somehow, user OS/browser/environment or the account itself was already compromised and there's little value on the interactive prompts anyway, while positively making the main user account use so rare it'd raise red flags if any unrequested prompt is shown.
Surprisingly very little, as the SDK already has support for different key/signers (like ether's JSONRPCSigner and local Wallet). It would be just a matter of having the subkey generation code on Raiden.create (to be used as Raiden signer) and keeping the provider's main_signer around and implement giving the alternative of using it on behalf of subkey as deposit source account.
I have two comments about the key generation:
I like the warning as a signed message.
@pirapira For the 2nd point, I agree, but that was just a suggestion. How about using the keccak or sha256 hash of the signature of that warning message as PK? That would be determinist and have the same secrecy as the signature itself.
subkey = web3.sha3(web3.personal.sign(message, web3.eth.accounts[0]));
For the first point, it would be extremely improbable to get a signature or hash outside the range of valid keys, but if desired to be 100% safe, we could e.g. add a loop appending some constant byte to the end of the signature before hashing, until we get a valid PK as hash. But getting a hash starting with 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFF is so improbable I wouldn't care for this edge case, as the worse can happen is a client using a key which triggers it to fail startup on this case, and being asked to use the main key directly or a different account.
Thank you very much for your thoughts, good insights on this.
Deriving a local key sounds like a very nice usability improvement!
But I'd strongly suggest that someone with a solid background in cryptography should give an opinion on the security of this scheme.
From my very limited knowledge in that area these are questions that come to mind:
sha3 sufficient? The well known key derivation schemes (e.g. pbkdf2, scrypt, argon2) all have a tuneable rounds parameters. The recommended values for those depend very much on the algorithm (but e.g. for pbkdf2 100k - 200k(!) rounds aren't uncommon).@ulope great questions! I also would like a specialist view on this approach
My initial thoughts would be that it'd be enough: ec-signatures are 65bytes/520bits long, and although we lose some bytes on beginning and end, I'm quite sure we can get at least 256bits of entropy out of it.
About the the single sha3-pass, it'd be great if we can get that to be verified as well, but if we consider an ETH-address is only the last 20bytes of a keccak hash as well, from a shorter string (33 bytes on the used compressed pubkey format), and a colision there would allow any funds to be stollen, I have some hope the hash of something secret and entropic enough (like the proposed signature, if so) would also give enough security for this kind of usage. If not, we can always go to some common derivation lib/standard like the ones mentioned by you, as long as it's fast enough to be run on startup of a dApp. The only requirement is it's deterministic from the main key.
@christianbrb can we get someone to verify this proposal?
@andrevmatos I can have somebody take a look at it.
What about this proposal? https://programtheblockchain.com/posts/2018/07/18/state-channels-with-signing-keys/
@christianbrb that would require changes to the smart contract, and would tie the user to the temporary key by contract. My proposal is fully compatible with current or past contracts, as it actually effectivelly uses the new keys as defacto Ethereum account for signing, with the difference that most on-chain transactions, specially those requiring ETH or token funds to be available, may be performed by the parent account on behalf of the subkey (but it's optional, the user can use either or actually any available account whenever needed), as it's just standard keys, the user can eject at any point and use their subkey wherever he pleases, on Raiden and out of it, without requiring informing the blockchain or the partners of that, as it's transparent.
Outcome of the external review
When the Raiden Light Client uses the secret key managed by Metamask or Parity a user has to confirm each signing request via a pop-up shown by these applications. For carrying out a single transaction of the Raiden network this results into up to 6 signatures (i.e., confirmations), and hence leads into poor user experience.
The Raiden team suggested to generate a session key to improve user experience while still being compatible with secret key management offered by external wallets. The session key is generated as follows:
Sig = ECDSA_SIGN(sk,”Raiden subkey generation message”)
Session-key = H(Sig)
In the above ECDSA_SIGN denotes the ECDSA signing algorithm according to RFC 6979 (deterministic usage of ECDSA) as is used by Metamask. H is a cryptographic hash function (e.g., SHA-3) and sk is the ECDSA secret key stored in the wallet.
The suggested solution is secure as long as the message “Raiden subkey generation message” is never signed for other purposes and revealed. In this case the security follows from the security of ECDSA signatures and the security of the hash function. More precisely, from the existential unforgeability property of the signature scheme it follows that signatures on fresh messages are unpredictable, and assuming that H is modeled as a random oracle (which is a standard assumption in cryptography), we can argue that the Session-key is uniform.
We emphasize that the security of the proposed solution highly depends on ensuring that the “Raiden subkey generation message” is never signed in any other context then as for the Raiden Light Client, otherwise it can lead to the loss of all funds that are managed by the Session-key derived in the above protocol. We recommend that the Session-Key is not stored but instead derived for each transaction. Otherwise if the “Session-key” is permanently stored the above approach does not benefit much from the “secure storage” offered by Metamask. There are some extensions, that can offer better security but they result into drawbacks from a user experience point of view.
These are:
Overall, we recommend the above proposed solution as a method to improve user experience. The Raiden team shall investigate if one of the two proposed ideas can be used to improve security without significantly degrading user experience.
Smart-contract work tracked in: https://github.com/raiden-network/raiden-contracts/issues/1123
Ok, SDK-wise, an approach for the implementation of this feature could look something in this direction:
Raiden.create async factory still receives the main account as the current account parameter, but also a subkey: boolean which defaults to false (current behavior)subkey is false, everything happens as is now, with Raiden's instance effective account being the account passed to create, and it's also used for signing messages and transactions and as signer on contracts factoriessubkey=true:Signer instance in create, it becomes main: { address: Address; signer: Signer } of the instance, and before proceeding, a sub private key/account is created as signer = new Wallet(sha256(await main.signer.signMessage(verif_message)) as PrivateKey, provider), and this signer becomes the effective signer & address: Address of the instanceverif_message above is a string containing network's name and a warning this message should never be signed outside of trusted Raiden dAppnetloc/current serverName, and warn the user to NEVER sign this message outside of this domainmain is stored in deps as the optional main?: { address: Address; signer: Signer } property, which iff !== undefined indicates that the subkey case is in usesignMessages still uses deps.signer, as this is the subkey/effective key for signing messages; filters also use current deps.address (subkey's) where needed, since this is the effective instance addressmain.signer instead of signer, as this is the default case for on-chain transactionssubkey: boolean optional param, which tells them to try to use the subkey for this specific on-chain operation. e.g. if Raiden.openChannel method receives subkey: true, forward it to channelOpen action, which will cause channelOpenEpic to use a tokenNetwork contract connected to the subkey's signer for the on-chain call instead of the default contract connected to main.signer;main.signer for on-chain tx on behalf of the subkey, but both cases always target subkey's address as self, never main.addresssubkey=false (default), this effectively uses main account's funds (tokens, ETH) to pay for anything for the subkey, allowing funds to be kept on safer main account (behind MM/eth node, etc)subkey=true to all on-chain methods is effectively the same to extracting the subkey and using it as main key for instance, as subkey's signer and address will be user for everything thenmain.address as beneficiary, so tokens can go back directly to main accountMain usecase for dApp would be:
Connect buttons are shown in homescreen: Connect to Main account & Sign & Connect to Derived accountsubkey is also passed as true, but only to create; all other methods should use defaultsRaiden.address which is subkey's. Some indicator/tooltip that this is a derived session, and possibly allowing to see main account address may be useful but not essential@andrevmatos Thanks a lot for writing down the design :) It helps me to understand the whole process.
Let's prepare this for the next refinement to decide how it looks like from a UX perspective and if it might get splitted.
CC @taleldayekh
2 Connect buttons are shown in homescreen: Connect to Main account & Sign & Connect to Derived account
Could be done differently as well
As an additional solution to background signing we might want to take a look how trustlines is doing it: https://github.com/trustlines-protocol/clientlib
This is how it looks like after experimenting in the PoC:

Edit: fixed data flow arrows
@andrevmatos Thanks for the update
Whiteboard shot from our refinement session

We would also need to decide how this is going to be presented to the end user as the address would be the subkey, but the ETH would be from the main key

Yes, this UX is the "hardest" part of this issue. Ideally, the user would understand and be able to see, select and operate over ETH & tokens from both main & subkey. All of this is already possible on the SDK PoC, but designing it in a comprehensible manner to the user reamains to be done.
Done in #650 for the SDK, dApp's side will be tracked in #745
Most helpful comment
This is how it looks like after experimenting in the PoC:
Edit: fixed data flow arrows