SignatureBouncer.sol 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. pragma solidity ^0.5.0;
  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 _signaature 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. /**
  44. * @dev requires that a valid signature of a signer was provided
  45. */
  46. modifier onlyValidSignature(bytes memory signature) {
  47. require(_isValidSignature(msg.sender, signature));
  48. _;
  49. }
  50. /**
  51. * @dev requires that a valid signature with a specifed method of a signer was provided
  52. */
  53. modifier onlyValidSignatureAndMethod(bytes memory signature) {
  54. require(_isValidSignatureAndMethod(msg.sender, signature));
  55. _;
  56. }
  57. /**
  58. * @dev requires that a valid signature with a specifed method and params of a signer was provided
  59. */
  60. modifier onlyValidSignatureAndData(bytes memory signature) {
  61. require(_isValidSignatureAndData(msg.sender, signature));
  62. _;
  63. }
  64. /**
  65. * @dev is the signature of `this + sender` from a signer?
  66. * @return bool
  67. */
  68. function _isValidSignature(address account, bytes memory signature) internal view returns (bool) {
  69. return _isValidDataHash(keccak256(abi.encodePacked(address(this), account)), signature);
  70. }
  71. /**
  72. * @dev is the signature of `this + sender + methodId` from a signer?
  73. * @return bool
  74. */
  75. function _isValidSignatureAndMethod(address account, bytes memory signature) internal view returns (bool) {
  76. bytes memory data = new bytes(_METHOD_ID_SIZE);
  77. for (uint i = 0; i < data.length; i++) {
  78. data[i] = msg.data[i];
  79. }
  80. return _isValidDataHash(keccak256(abi.encodePacked(address(this), account, data)), signature);
  81. }
  82. /**
  83. * @dev is the signature of `this + sender + methodId + params(s)` from a signer?
  84. * @notice the signature parameter of the method being validated must be the "last" parameter
  85. * @return bool
  86. */
  87. function _isValidSignatureAndData(address account, bytes memory signature) internal view returns (bool) {
  88. require(msg.data.length > _SIGNATURE_SIZE);
  89. bytes memory data = new bytes(msg.data.length - _SIGNATURE_SIZE);
  90. for (uint i = 0; i < data.length; i++) {
  91. data[i] = msg.data[i];
  92. }
  93. return _isValidDataHash(keccak256(abi.encodePacked(address(this), account, data)), signature);
  94. }
  95. /**
  96. * @dev internal function to convert a hash to an eth signed message
  97. * and then recover the signature and check it against the signer role
  98. * @return bool
  99. */
  100. function _isValidDataHash(bytes32 hash, bytes memory signature) internal view returns (bool) {
  101. address signer = hash.toEthSignedMessageHash().recover(signature);
  102. return signer != address(0) && isSigner(signer);
  103. }
  104. }