AmbireTech / adex-protocol

AdEx Protocol: docs
45 stars 21 forks source link

Identity #10

Closed Ivshti closed 5 years ago

Ivshti commented 5 years ago

Basic Gas Abstractions

Identity contract

Each user is represented by one instance of the Identity contract.

The Identity contract, by itself, is a simple 1/M multisig, constructed with an array of owners (addresses allowed to control it).

It's interface is as follows:

This contract, by itself, is self-sufficient and trustless. Even in the case where a relayer does not exist or is not present, the user may still invoke execute directly, paying the gas themselves.

But a relayer may invoke execute, spending the needed gas, but taking tx.feeTokenAmount

Each tx should have a tx.nonce counted by the contract, to eliminate the possibility of double-spends.

Since the msg.sender of any transaction done through execute will be the contract, no changes need to be made in your protocol/dApp contracts: it's just like your user is being represented by a msig contract.

Single relayer

Because of the trustless Identity contract, a single centralized relayer can be used without imposing any risks to the user and without assuming any trust.

This is possible because:

  1. The Identity smart contract only sends transactions if there's a valid signed message from the user
  2. It allows anyone to pay for the gas; should the relayer become unavailable, it can be replaced, or the user can invoke the contract directly themselves

Getting paid in tokens

Each signed message that represents a transaction also contains feeTokenAddr and feeTokenAmount, that will be paid out to the msg.sender

Counterfactual deployment

The process of deploying the contract, withour requiring the user to hold any ETH, is as follows:

  1. The user generates a new empty ethereum wallet; at this stage, it can be computed, using the address of this wallet, and nonce 0, what the Identity contract address would be
  2. The user signs a TX that would deploy their Identity contract from their new ethereum wallet and sends it to the relayer; since the contract code is included in the TX, the relayer can now verify that the Identity contract would allocate a deploy reward for them
  3. The user sends tokens to the Identity contract address; at this stage the relayer is certain that if the TX is broadcasted and mined, they will receive their reward
  4. The relayer, having this signed TX, can now fund the user's deploy wallet and broadcast the SC deploy TX; the relayer has to check that (1) the user signed a TX that deploys specific SC code that the relayer has approved, (2) the user sent a certain minimum amount of approved tokens to the SC address

Note that this is vulnerable to front-running, since the user may sign another TX, deploying a contract that doesn't allocate the relayer's reward, and replace their previous TX; The SC address would still be the same because Ethereum only uses the sender address and nonce to determine it. However, it is hard for the user to perform such an attack, because the relayer only funds the deploy wallet with sufficient eth to pay for the original transaction, and therefore the newer transaction can't have a higher fee. An attack where the user would steal the ETH that's sent for deploying the contract is not practical, since that way the user would lose their own tokens that they sent to the SC address derived from nonce 0, which are presumably more valuable than the deploy fee ETH.

Please note that the relayer would only interact with SCs that they consider valid, which means that the hash of the contract bytecode must be in the relayer's whitelist.

Sign up with ETH

If the user prefers to interact with the system using ETH, we can, of course, skip the relayer altogether.

The user may send ETH to a factory contract, which will deploy an Identity contarct and swap the remainder of the sent ETH for a token that the user chooses (e.g. DAI).

The Identity is still needed to allow for the security benefits and the scheduling capabilities.

Transaction routines (and scheduling)

TODO

Two-factor auth

Two (or more) factor authentication can be implemented by requiring N/M signed messages from user addresses (owners) rather than just 1/M.

This will achieve a classic two-factor authentication UX: the user requests to do something on their PC, and they're prompted to sign the same action on their phone.

EDIT: maybe we should consider this out of scope, adds too much complication

Receival (withdraw) address

The Identity contract may allow certain pre-set transactions to happen with only 1/M signatures, but any arbitrary transaction to happen with N/M.

This will allow the user to easily perform most actions, but any critical actions such as moving funds out of the Identity contract will require multiple signatures.

To improve the UX of the withdraw procedure, we can allow the user to set a "withdraw address", and have a pre-set transaction that withdraws to it that only requires 1/M signatures. That way, the user can easily authorize a withdraw to their preset "withdraw address", but changing it would require full two (or more) factor auth.

Ivshti commented 5 years ago

Counterfactual deployment: 100% trustless

A fully trustless counterfactual deployment can be achieved by using a "trick" where we randomly choose the signature values of the deployment transaction, and then calculate the address that it needs to be sent from. This way, we prove to the relayer that we don't have the private key for the transaction (in fact no one has), but it's still a valid transaction that can be sent from a specific address.

In more detail, this works as follows:

  1. An ethereum transaction consists of meaningful fields like gasstart, value, data, etc. and the transaction signature, which consists of r, s and v
  2. To check for a valid signature, ethereum would perform an operation that can be described as ecrecover(txHash, r, s, v) == sender
  3. Rather than calculating a private key, and signing the transaction with it, we flip this process upside down; we calculate r, s, v deterministically from a random seed T, and then compute the result of ecrecover(txHash, r, s, v) to calculate the sender of this transaction
  4. We achieve a signed transaction, for which we're sure we don't know the private key of
  5. The relayer may now safety fund this sender and broadcast the transaction
  6. The sender is different for every T but deterministically calculated, therefore we can deterministically calculate the to-be address of the smart contract too (since that's just keccak256(rlp(sender, nonce))

Proof of "not knowing the private key"

We take a random number T, which is the seed, and we generate an r value by just taking the x coord of G (Gx) and then generate s by doing keccak256(T) mod n v is the recovery byte - we can just use 27

By doing this deterministic operation, we make it extremely difficult to create a private key for which the signature of txHash would output the same r, s values. Managing to do so would essentially mean either breaking keccak256 to calculate the preimage T for which r/s are certain values, or breaking ECDSA to calculate certain txHash/privKey for which r/s are certain values

Factory

Always using a factory is a decent pattern, if it doesn't imply too much extra gas costs.

The reason is that it's easy for the factory to also perform a registry role, if we need it.

This can also make it much easier to integrate ENS.

Ivshti commented 5 years ago

we should consider whether we can use WebCrypto/WebAuthentication for a more secure method of authenticating

resources:

a TLDR is that webcrypto is not that useful cause it doesn't provide a way of protecting/storing private keys, and webauthentication is not ready yet

even if it was, we might need https://github.com/ethereum/EIPs/issues/28#issuecomment-160509569

Ivshti commented 5 years ago

To ensure the smoothest possible signup, we can introduce the concept of "limited"/"quick login" accounts.

Create the Identity contract with one owner (which is a priv key, pwd encrypted and stored in the user's browser), and allow that identity to earn or spend up to X USD worth of crypto.

In the application, we would ask the user whether they want to sign up "with a wallet" or in a quick way, but with limited earning/spending.

Identity contracts will always be deployed with such an in-browser private key as the first owner, and:

Ivshti commented 5 years ago

We also need ot ensure the funds in the Identity contract are never fully withdrawn, otherwise it might get kind of bricked (the relayer wouldn't be able to relay if there's no funds to reward them)

Ivshti commented 5 years ago

Actually, doing N/M multisig where the first key is the in-browser key is dangerous, since the in-browser key can be easily lost

another way to do this in a simple way is just to assume the first key is the "unsafe" one, but still keep the 1/M policy for the other keys; this is very simple to do as well

Ivshti commented 5 years ago

Functions on the multisig:

Each signed message should be executable only once. The way of how this will be achieved may vary - since with scheduled transactions, classic incremental nonce is not the right way.

Ivshti commented 5 years ago

another consideration is whether we should allow relaying actual ethereum signed transactions, rather than wrapping it up in a signed msg; the former would have much better UX with HW wallets, but is problematic in terms of how much is paid for gas

EDIT: relaying actual transactions can't work, cause (1) how much you're paying for the tx can't be signalled safely, (2) we need to do nonce hacks

Ivshti commented 5 years ago

using the "first owner" assumption is not really ergonomic, so we will do a (address => uint8) privilege mapping

the first owner will be set to a full privilege, and when we add a second addr, we will call setPrivilege(newAddr, privLevel.Transaction) and setPrivilege(originalAddr, privLevel.Predefines) together in execute(), therefore demoting the original key to Predefines

Ivshti commented 5 years ago

predefined (and scheduled) channel withdrawal should withdraw to the identity contract; it can be easily combined with another TX to withdraw to the withdrawal address

Ivshti commented 5 years ago

transactions need to have value cause eth might've been sent to the SC before it's deployed

Ivshti commented 5 years ago

No factory

We don't need a factory - there's no point of keeping a registry, so there's no benefit to the factory+registry model originally planned.

The factory would complicate deployment since contracts have their own sender nonce that we have to calculate from. Furthermore, it makes deployment impossible cause we hit the "unpredictable nonce" problem once again.

We wanted the factory to help facilitate deployment with ETH, but that's still pretty easy:

approach 1: using your own ETH wallet, calculate the identity contract address; send ETH to that address and then deploy the identity; then, through the identity, call into uniswap, swapping the ETH to a token (3 transactions)

approach 2: using your own ETH wallet, first call into uniswap to obtain tokens, and then deploy the identity and send the tokens (3 transactions)

approach 3: just do everything with ETH, no relayer

Ivshti commented 5 years ago

this is done, so closing; implemented in extra/Identity.sol