SignatureBouncer.sol 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. pragma solidity ^0.5.0;
  2. import "../GSN/Context.sol";
  3. import "../access/roles/SignerRole.sol";
  4. import "../cryptography/ECDSA.sol";
  5. /**
  6. * @title SignatureBouncer
  7. * @author PhABC, Shrugs and aflesher
  8. * @dev SignatureBouncer allows users to submit a signature as a permission to
  9. * do an action.
  10. * If the signature is from one of the authorized signer addresses, the
  11. * signature is valid.
  12. * Note that SignatureBouncer offers no protection against replay attacks, users
  13. * must add this themselves!
  14. *
  15. * Signer addresses can be individual servers signing grants or different
  16. * users within a decentralized club that have permission to invite other
  17. * members. This technique is useful for whitelists and airdrops; instead of
  18. * putting all valid addresses on-chain, simply sign a grant of the form
  19. * keccak256(abi.encodePacked(`:contractAddress` + `:granteeAddress`)) using a
  20. * valid signer address.
  21. * Then restrict access to your crowdsale/whitelist/airdrop using the
  22. * `onlyValidSignature` modifier (or implement your own using _isValidSignature).
  23. * In addition to `onlyValidSignature`, `onlyValidSignatureAndMethod` and
  24. * `onlyValidSignatureAndData` can be used to restrict access to only a given
  25. * method or a given method with given parameters respectively.
  26. * See the tests in SignatureBouncer.test.js for specific usage examples.
  27. *
  28. * @notice A method that uses the `onlyValidSignatureAndData` modifier must make
  29. * the _signature parameter the "last" parameter. You cannot sign a message that
  30. * has its own signature in it so the last 128 bytes of msg.data (which
  31. * represents the length of the _signature data and the _signature data itself)
  32. * is ignored when validating. Also non fixed sized parameters make constructing
  33. * the data in the signature much more complex.
  34. * See https://ethereum.stackexchange.com/a/50616 for more details.
  35. */
  36. contract SignatureBouncer is Context, SignerRole {
  37. using ECDSA for bytes32;
  38. // Function selectors are 4 bytes long, as documented in
  39. // https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector
  40. uint256 private constant _METHOD_ID_SIZE = 4;
  41. // Signature size is 65 bytes (tightly packed v + r + s), but gets padded to 96 bytes
  42. uint256 private constant _SIGNATURE_SIZE = 96;
  43. constructor () internal {
  44. // solhint-disable-previous-line no-empty-blocks
  45. }
  46. /**
  47. * @dev Requires that a valid signature of a signer was provided.
  48. */
  49. modifier onlyValidSignature(bytes memory signature) {
  50. require(_isValidSignature(_msgSender(), signature), "SignatureBouncer: invalid signature for caller");
  51. _;
  52. }
  53. /**
  54. * @dev Requires that a valid signature with a specified method of a signer was provided.
  55. */
  56. modifier onlyValidSignatureAndMethod(bytes memory signature) {
  57. // solhint-disable-next-line max-line-length
  58. require(_isValidSignatureAndMethod(_msgSender(), signature), "SignatureBouncer: invalid signature for caller and method");
  59. _;
  60. }
  61. /**
  62. * @dev Requires that a valid signature with a specified method and params of a signer was provided.
  63. */
  64. modifier onlyValidSignatureAndData(bytes memory signature) {
  65. // solhint-disable-next-line max-line-length
  66. require(_isValidSignatureAndData(_msgSender(), signature), "SignatureBouncer: invalid signature for caller and data");
  67. _;
  68. }
  69. /**
  70. * @dev is the signature of `this + account` from a signer?
  71. * @return bool
  72. */
  73. function _isValidSignature(address account, bytes memory signature) internal view returns (bool) {
  74. return _isValidDataHash(keccak256(abi.encodePacked(address(this), account)), signature);
  75. }
  76. /**
  77. * @dev is the signature of `this + account + methodId` from a signer?
  78. * @return bool
  79. */
  80. function _isValidSignatureAndMethod(address account, bytes memory signature) internal view returns (bool) {
  81. bytes memory msgData = _msgData();
  82. bytes memory data = new bytes(_METHOD_ID_SIZE);
  83. for (uint256 i = 0; i < data.length; i++) {
  84. data[i] = msgData[i];
  85. }
  86. return _isValidDataHash(keccak256(abi.encodePacked(address(this), account, data)), signature);
  87. }
  88. /**
  89. * @dev is the signature of `this + account + methodId + params(s)` from a signer?
  90. * @notice the signature parameter of the method being validated must be the "last" parameter
  91. * @return bool
  92. */
  93. function _isValidSignatureAndData(address account, bytes memory signature) internal view returns (bool) {
  94. bytes memory msgData = _msgData();
  95. require(msgData.length > _SIGNATURE_SIZE, "SignatureBouncer: data is too short");
  96. bytes memory data = new bytes(msgData.length - _SIGNATURE_SIZE);
  97. for (uint256 i = 0; i < data.length; i++) {
  98. data[i] = msgData[i];
  99. }
  100. return _isValidDataHash(keccak256(abi.encodePacked(address(this), account, data)), signature);
  101. }
  102. /**
  103. * @dev Internal function to convert a hash to an eth signed message
  104. * and then recover the signature and check it against the signer role.
  105. * @return bool
  106. */
  107. function _isValidDataHash(bytes32 hash, bytes memory signature) internal view returns (bool) {
  108. address signer = hash.toEthSignedMessageHash().recover(signature);
  109. return signer != address(0) && isSigner(signer);
  110. }
  111. }