In a state channel network, multiple channels need to be funded on chain — be they user-to-user or user-to-hub connections. There are a few approaches that we know of:
- Escrowing assets in a singleton contract for multiple channels
- Escrowing assets in an ethereum account per channel.
a. Where the account is a contract
b. Where the account is not a contract
From a gas point of view, the main pros and cons are as follows:
- No contract creation costs are necessary per channel — the single contract need be deployed only once (per chain).
- Escrow of ETH and tokens is relatively expensive and cumbersome. Each deposit must update some storage in the escrow contract in order to declare which channel is being deposited into: in other words, a “layer 2 accounting system” needs to be implemented in the escrow contract.
- This further implies that deposits must be made via a contract call to the escrow contract. If the asset is an ERC20 token (far and away the most popular standard), this necessitates i) an
approvecall to the token itself followed by ii) the
depositcall to the escrow contract (which internally calls
transferFromon the token, and atomically updates the layer 2 accounting system). This flow causes excess reads/writes to storage in the token contract (e.g. the
- Depending on how the layer 2 accounting system works, the costs can be partially mitigated by clearing storage slots when the channel is liquidated, but only so long as the relevant gas refunds remain a feature (see EIP 3529).
- Yet a further nuisance is the need to perform i) and ii) above in two separate transactions, implying poor UX due to longer waits and higher gas overheads due to the base transaction fee.
- Contract creation and code deposit are gas expensive. This can be partially mitigated by having the contract be a very lightweight proxy such as an EIP-1167 Proxy. The proxy points to a singleton “Master Adjudicator” which need be deployed only once (per chain). The costs can also be partially mitigated by
SELFDESTRUCTgas refunds, so long as they remain a feature of mainnet (not for much longer, see EIP 3529 scheduled for London).
- Contract addresses deployed using
CREATEhave difficult-to-predict addresses. This is solved by having the escrow contract deployed with
CREATE2. There are benefits to having predictable escrow contract addresses: deployment can be delayed until the funds need to be liquidated, adding some extra privacy and security.
- Escrow of ETH and tokens can be as cheap and easy as is possible: just send the assets to the escrow contract for the appropriate channel.
- This architecture would be made possible by EIP 3074. See this comment.
- Escrow of ETH and tokens can be as cheap and easy as is possible, just send the assets to the escrow account (called a synthetic EOA) for the appropriate channel
- A singleton Invoker contract controls all of the synthetic EOAs. There is a unique synthetic EOA address for each channel
- No layer 2 accounting system is necessary
- No contract creation or code deposits are needed
- It’s the best of both worlds, having our cake and eating it too!
- There are some modest additional gas expenditures for
AUTH_CALLwhen liquidating the channel.
Adjudicatorcontract is deployed ahead of time by the state channel protocol team, and plays the role of an EIP-3074
Then, state channel participants generate a 32 bytes
channelIdin the established way: they hash the combination of their state channel wallet addresses with the
The participants then derive a
channelId. They do this by interpreting the
channelIdas an EIP-3074 signature, i.e. a ECDSA signature on the digest:
digest = keccak256(MAGIC || padTo32Bytes(Adjudicator) || padTo32Bytes(0)) channelAddress = ecrecover(digest, syntheticSignature(channelId))
Note that we set the EIP-3074
commit to 0.
Participants then send funds directly to
channelAddress, for example via
approvetransaction is necessary.
When it comes to liquidating the channel, any participant holding a “support proof” (state channel jargon for the requisite off-chain state and signatures to close a channel) may submit that proof to the Adjudicator. It will
- performs all of the usual checks according to the state channel protocol;
auth(commit = 0, yParity = 0, r = channelId, s = channelId), which sets msg.sender to channelAddress;
authcallto interact with token contracts and move tokens from channelAddress to the beneficiaries of the channel.
channelAddress is a “synthetic EOA”, meaning an EOA for which the private key is not known or used. In that sense it is similar to a contract; but a crucial difference is that no code or storage needs to (or even can) be deployed “under” the channelAddress.
This is because the “synthetic signature”, in this case the
channelId, confers the
Adjudicator the power to control precisely one Ethereum account: namely, the account with the
channelAddress address. I’m glossing over some details around how to construct a synthetic signature from the
channelId – we can discuss that over at Reliable and safe generation of synthetic signatures.
The term “synthetic EOA” is somewhat oxymoronic: the account is not externally owned by any single party, or even by a set of parties. It is controlled by a single contract only.
The collision resistance of the signature scheme makes it infeasible that any other contract can control the synthetic EOA.
Now it is important for funds in a state channel escrow to be liquidated only when strict conditions (set by the state channel protocol) are met. It would be possible in principle to make use of the
commit field in the
AUTH message format, to commit to the relevant cryptographic proof for liquidating the channel. Alternatively, the
commit field is set to zero, which gives the maximum level of control to the Adjudicator.
Adjudicator must be trusted (in the sense of having its behaviour verified or audited), there is no real change in the trust model by making it an EIP-3074
Invoker and handing it complete control over a set of synthetic EOAs.