contract Encoder {
    enum Algorithm { sha, keccak }

    /// @dev generateProofSet - function for off-chain proof derivation
    /// @param seed Secret used to secure the proof-set
    /// @param caller Address of the caller, or account that verifies the proof-set
    /// @param receiver Address of the encoded recepient
    /// @param tokenAddress Address of the token to transfer from the caller
    /// @param algorithm Hash algorithm to use for generating proof-set
    function generateProofSet(
        string seed,
        address caller,
        address receiver,
        address tokenAddress,
        Algorithm algorithm
    ) pure public returns(bytes32 hash, bytes32 operator, bytes32 check, address check_receiver, address check_token) {
        (hash, operator, check) = _escrow(seed, caller, receiver, tokenAddress, algorithm);
        bytes32 key = hash_seed(seed, algorithm);
        check_receiver = address(hash_data(key, algorithm)^operator);
        if (check_receiver == 0) check_receiver = caller;
        if (tokenAddress != 0) check_token = address(check^key^blind(receiver, algorithm));
    }

    // internal function for generating the proof-set
    function _escrow(
        string seed, 
        address caller,
        address receiver,
        address tokenAddress,
        Algorithm algorithm
    ) pure internal returns(bytes32 index, bytes32 operator, bytes32 check) {
        require(caller != receiver && caller != 0);
        bytes32 x = hash_seed(seed, algorithm);
        if (algorithm == Algorithm.sha) {
            index = sha256(x, caller);
            operator = sha256(x)^bytes32(receiver);
            check = x^sha256(receiver);
        } else {
            index = keccak256(x, caller);
            operator = keccak256(x)^bytes32(receiver);
            check = x^keccak256(receiver);
        }
        if (tokenAddress != 0) {
            check ^= bytes32(tokenAddress);
        }
    }
    
    // internal function for hashing the seed
    function hash_seed(
        string seed, 
        Algorithm algorithm
    ) pure internal returns(bytes32) {
        if (algorithm == Algorithm.sha) return sha256(seed);
        else return keccak256(seed);
    }
    
   // internal function for hashing bytes
    function hash_data(
        bytes32 key, 
        Algorithm algorithm
    ) pure internal returns(bytes32) {
        if (algorithm == Algorithm.sha) return sha256(key);
        else return keccak256(key);
    }
    
    // internal function for hashing an address
    function blind(
        address addr,
        Algorithm algorithm
    ) pure internal returns(bytes32) {
        if (algorithm == Algorithm.sha) return sha256(addr);
        else return keccak256(addr);
    }
    
}
// helper functions for hashing
function blind(
    address addr,
    Algorithm algorithm
) pure internal returns(bytes32) {
    if (algorithm == Algorithm.sha) return sha256(addr);
    else return keccak256(addr);
}

// internal function for hashing an address