Closed Ivshti closed 5 years ago
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:
gasstart
, value
, data
, etc. and the transaction signature, which consists of r
, s
and v
ecrecover(txHash, r, s, v) == sender
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 transactionT
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))
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
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.
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
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:
isLimited = owners == 1
)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)
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
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.
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
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
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
transactions need to have value cause eth might've been sent to the SC before it's deployed
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
this is done, so closing; implemented in extra/Identity.sol
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:
constructor(address[] owners)
execute(Transaction tx, bytes signature)
: as long astx, txFeeToken, txFeeAmount
is signed, it will execute the transaction and sendtx.feeAmount
oftx.feeToken
tomsg.sender
(to redeem their gas spend)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 takingtx.feeTokenAmount
Each
tx
should have atx.nonce
counted by the contract, to eliminate the possibility of double-spends.Since the
msg.sender
of any transaction done throughexecute
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:
Getting paid in tokens
Each signed message that represents a transaction also contains
feeTokenAddr
andfeeTokenAmount
, that will be paid out to themsg.sender
Counterfactual deployment
The process of deploying the contract, withour requiring the user to hold any ETH, is as follows:
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.