contract BlackBox is Owned, Encoder {

    // struct of proof set
    struct Proof {
        uint256 balance;
        bytes32 operator;
        bytes32 check;
    }
    
    // mappings
    mapping(bytes32 => Proof) public proofs;
    mapping(bytes32 => bool) public used;
    mapping(address => uint) private deposits;

    // events
    event ProofVerified(string _key, address _prover, uint _value);
    event Locked(bytes32 _hash, bytes32 _operator, bytes32 _check);
    event WithdrawTokens(address _token, address _to, uint _value);
    event ClearedDeposit(address _to, uint value);
    event TokenTransfer(address _token, address _from, address _to, uint _value);

    /// @dev lock - store a proof-set
    /// @param _hash Hash Key used to index the proof
    /// @param _operator A derived operator to encode the intended recipient
    /// @param _check A derived operator to check recipient, or a decode the token address
    function lock(
        bytes32 _hash,
        bytes32 _operator,
        bytes32 _check
    ) public payable {
        // protect invalid entries on value transfer
        if (msg.value > 0) {
            require(_hash != 0 && _operator != 0 && _check != 0);
        }
        // check existence
        require(!used[_hash]);
        // lock the ether
        proofs[_hash].balance = msg.value;
        proofs[_hash].operator = _operator;
        proofs[_hash].check = _check;
        // track unique keys
        used[_hash] = true;
        Locked(_hash, _operator, _check);
    }

    /// @dev unlock - verify a proof to transfer the locked funds
    /// @param _seed Secret used to derive the proof set
    /// @param _value Optional token value to transfer if the proof-set maps to a token transfer
    /// @param _algo Hash algorithm type
    function unlock(
        string _seed,
        uint _value,
        Algorithm _algo
    ) public onlyCompliant {
        bytes32 hash = 0;
        bytes32 operator = 0;
        bytes32 check = 0;
        // calculate the proof
        (hash, operator, check) = _escrow(_seed, msg.sender, 0, 0, _algo);
        require(used[hash]);
        // get balance to send to decoded receiver
        uint balance = proofs[hash].balance;
        address receiver = address(proofs[hash].operator^operator);
        address _token = address(proofs[hash].check^hash_seed(_seed, _algo)^blind(receiver, _algo));
        delete proofs[hash];
        if (receiver == 0) receiver = msg.sender;
        // send balance and deposits
        clearDeposits(receiver, balance);
        ProofVerified(_seed, msg.sender, balance);

        // check for token transfer
        if (_token != 0) {
            Token token = Token(_token);
            uint tokenBalance = token.balanceOf(msg.sender);
            uint allowance = token.allowance(msg.sender, this);
            // check the balance to send to the receiver
            if (_value == 0 || _value > tokenBalance) _value = tokenBalance;
            if (allowance > 0 && _value > 0) {
                if (_value > allowance) _value = allowance;
                TokenTransfer(_token, msg.sender, receiver, _value);
                require(token.transferFrom(msg.sender, receiver, _value));
            }
        }
    }
    
    /// @dev withdrawTokens - withdraw tokens from contract
    /// @param _token Address of token that this contract holds
    function withdrawTokens(address _token) public onlyOwner {
        Token token = Token(_token);
        uint256 value = token.balanceOf(this);
        require(token.transfer(msg.sender, value));
        WithdrawTokens(_token, msg.sender, value);
    }
    
    /// @dev clearDeposits - internal function to send ether
    /// @param _for Address of recipient
    /// @param _value Value of proof balance
    function clearDeposits(address _for, uint _value) internal {
        uint deposit = deposits[msg.sender];
        if (deposit > 0) delete deposits[msg.sender];
        if (deposit + _value > 0) {
            if (!_for.send(deposit+_value)) {
                require(msg.sender.send(deposit+_value));
            }
            ClearedDeposit(_for, deposit+_value);
        }
    }
    
    function allowance(address _token, address _from) public view returns(uint _allowance) {
        Token token = Token(_token);
        _allowance = token.allowance(_from, this);
    }
    
    // store deposits for msg.sender
    function() public payable {
        require(msg.value > 0);
        deposits[msg.sender] += msg.value;
    }
    
}
function withdrawTokens(address _token) public onlyOwner {
    Token token = Token(_token);
    uint256 value = token.balanceOf(this);
    require(token.transfer(msg.sender, value));
    WithdrawTokens(_token, msg.sender, value);
}

/// @dev withdrawTokens - withdraw tokens from contract
/// @param _token Address of token that this contract holds