SignatureBouncer.sol 4.9 KB

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