contract RedemptionCertificate is Claimable {
    using ECRecovery for bytes32;

    /// @dev A set of addresses that are approved to sign on behalf of this contract
    mapping(address => bool) public signers;

    /// @dev The nonce associated with each hash(accountId).  In this case, the account is an external
    /// concept that does not correspond to an Ethereum address.  Therefore, the hash of the accountId
    /// is used
    mapping(bytes32 => uint256) public nonces;

    address public token;
    address public tokenHolder;

    event TokenHolderChanged(address oldTokenHolder, address newTokenHolder);
    event CertificateRedeemed(string accountId, uint256 amount, address recipient);
    event SignerAdded(address signer);
    event SignerRemoved(address signer);

    constructor(address _token, address _tokenHolder)
    public
    {
        token = _token;
        tokenHolder = _tokenHolder;
    }


    /**
     * @dev ensures that the hash was signed by a valid signer.  Also increments the associated
     * account nonce to ensure that the same hash/signature cannot be used again
     */
    modifier onlyValidSignatureOnce(string accountId, bytes32 hash, bytes signature) {
        address signedBy = hash.recover(signature);
        require(signers[signedBy]);
        _;
        nonces[hashAccountId(accountId)]++;
    }


    /**
     * @dev Attempts to withdraw tokens from this contract, using the signature as proof
     * that the caller is entitled to the specified amount.
     */
    function withdraw(string accountId, uint256 amount, address recipient, bytes signature)
    onlyValidSignatureOnce(
        accountId,
        generateWithdrawalHash(accountId, amount, recipient),
        signature)
    public
    returns (bool)
    {
        require(ERC20(token).transferFrom(tokenHolder, recipient, amount));
        emit CertificateRedeemed(accountId, amount, recipient);
        return true;
    }




    /// Helper Methods

    /**
     * @dev Generates the hash of the message that needs to be signed by an approved signer.
     * The nonce is read directly from the contract's state.
     */
    function generateWithdrawalHash(string accountId, uint256 amount, address recipient)
     view
     public
    returns (bytes32)
    {
        bytes32 accountHash = hashAccountId(accountId);
        bytes memory message = abi.encodePacked(address(this), recipient, amount, nonces[accountHash]);
        bytes32 messageHash = keccak256(message);
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
    }

    /**
     * @dev converts and accoutId to a bytes32
     */
    function hashAccountId(string accountId)
    pure
    internal
    returns (bytes32)
    {
        return keccak256(abi.encodePacked(accountId));
    }






    /// Admin Methods

    function updateTokenHolder(address newTokenHolder)
     onlyOwner
      external
    {
        address oldTokenHolder = tokenHolder;
        tokenHolder = newTokenHolder;
        emit TokenHolderChanged(oldTokenHolder, newTokenHolder);
    }

    function addSigner(address signer)
     onlyOwner
     external
    {
        signers[signer] = true;
        emit SignerAdded(signer);
    }

    function removeSigner(address signer)
     onlyOwner
     external
    {
        signers[signer] = false;
        emit SignerRemoved(signer);
    }
}
function generateWithdrawalHash(string accountId, uint256 amount, address recipient)
 view
 public
returns (bytes32)
{
    bytes32 accountHash = hashAccountId(accountId);
    bytes memory message = abi.encodePacked(address(this), recipient, amount, nonces[accountHash]);
    bytes32 messageHash = keccak256(message);
    return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
}

/**
 * @dev Generates the hash of the message that needs to be signed by an approved signer.
 * The nonce is read directly from the contract's state.
 */