Ether Vaults

I still see people occasionally asking what smart contracts are good for. All the attention goes to really ambitious projects, but people underestimate the usefulness of a page of code.

For example...here's an article by a Bitcoin researcher, talking about an anti-theft scheme he and his colleagues invented. He says it would be "easy" to implement; all it requires is a hard fork of the blockchain, adding a new opcode.

I guess that's easy for some definition of easy. On Ethereum I implemented the same thing in a smart contract.

The basic idea is to have a hot wallet account, plus a contract which holds the bulk of your ether, and stores a vault key and recovery key. Most of the money is vaulted and inaccessible. To unvault some of it, use the vault key. After a delay the vault key can send the money to your hotwallet.

The recovery key can reverse the vault key, putting the ether back in long-term storage. In my version, the recovery key can also change the hotwallet, since the vault key is useless to an attacker who doesn't also have the hotwallet.

If both vault key and recovery key are compromised, the owner and attacker can keep reversing each other, indefinitely. But there's another twist: the recovery key can also lock the ether forever. So if all three of your keys are compromised (hot wallet, vault, recovery), you still lose your ether, but you can at least prevent the attacker from getting it. The idea is that this reduces the incentive to attack in the first place.

Here's the code. (Don't put a large amount of ether in this just yet.)

contract Vault {
    address public hotwallet;
    address public vaultkey;
    address public recoverykey;
    uint public unvaultedAmount;
    uint public redeemtime;
    bool public destroyed;

    modifier only_vaultkey() { 
        if (msg.sender != vaultkey) throw;
        _
    }

    modifier only_recoverykey() { 
        if (msg.sender != recoverykey) throw;
        _
    }

    modifier not_destroyed() {
        if (destroyed) throw;
        _
    }

    event Unvault(uint _amount);
    event Redeem();
    event Recover(address _newwallet);
    event Destroy();

    function Vault(address _hotwallet, address _vaultkey, address _recoverykey) {
        hotwallet = _hotwallet;
        vaultkey = _vaultkey;
        recoverykey = _recoverykey;
    }

    function () not_destroyed {}

    //Vault key can unvault funds, 
    //and (after delay) send unvaulted funds to hotwallet

    function unvault(uint _amount) only_vaultkey not_destroyed {
        if (_amount > this.balance) return;
        unvaultedAmount = _amount;
        redeemtime = block.timestamp + 4 weeks;
        Unvault(_amount);
    }

    function redeem() only_vaultkey not_destroyed {
        if (block.timestamp < redeemtime) return;
        uint sendamount = unvaultedAmount;
        unvaultedAmount = 0;
        Redeem();
        if (!hotwallet.call.value(sendamount)()) throw;
    }

    //Recovery key can put funds back in vault, 
    //or lock up the funds forever

    function recover(address _newHotwallet) only_recoverykey not_destroyed {
        unvaultedAmount = 0;
        hotwallet = _newHotwallet;
        Recover(_newHotwallet);
    }

    function destroy() only_recoverykey not_destroyed {
        destroyed = true;
        Destroy();
    }
}

For anyone unfamiliar with Solidity code:

  • When you put a modifier on a function, the function body pastes into the _ of the modifier. So, for example, only_vaultkey adds the line "if (msg.sender != vaultkey) throw;" to the beginning of the function.

  • Events are cheap ways to record information on the blockchain. They're stored in a way that's not accessible to contracts, but can be easily queried by client code. For example, "event Redeem();" declares the Redeem event, then just calling Redeem() actually writes the event to the chain.

It's not a perfect security solution. An attacker with the recovery key could hold your money hostage, saying he'll destroy your ether unless you send him half. At least that requires him to contact you instead of just silently burglarizing your funds. If you generate your recovery key offline and keep it in a safe deposit box, the situation isn't likely to come up.

You might want to make further changes. With the recovery key in a safe deposit box, maybe you'd rather not have the destroy function at all. The point is, it's easy to build whatever solution works for you.

The whole thing took me 20 minutes to write, and a little more to make minor changes in response to reddit comments. One reason it's so much easier than on Bitcoin is that Ethereum scripts are stateful and use account balances instead of Bitcoin's UTXOs. Vitalik wrote an nice article about that, and I was stoked to see he linked my reddit post of this contract as an example (ctrl-f "20").

The reddit version is older; here's I've updated it to follow the rules, changed the UI slightly, and added the "not_destroyed" modifier so people can't send ether to a destroyed vault.