SignatureBouncer.sol 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. pragma solidity ^0.4.24;
  2. import "../ownership/Ownable.sol";
  3. import "../ownership/rbac/RBAC.sol";
  4. import "../ECRecovery.sol";
  5. /**
  6. * @title SignatureBouncer
  7. * @author PhABC, Shrugs and aflesher
  8. * @dev Bouncer allows users to submit a signature as a permission to do an action.
  9. * @dev If the signature is from one of the authorized bouncer addresses, the signature
  10. * @dev is valid. The owner of the contract adds/removes bouncers.
  11. * @dev Bouncer addresses can be individual servers signing grants or different
  12. * @dev users within a decentralized club that have permission to invite other members.
  13. * @dev
  14. * @dev This technique is useful for whitelists and airdrops; instead of putting all
  15. * @dev valid addresses on-chain, simply sign a grant of the form
  16. * @dev keccak256(abi.encodePacked(`:contractAddress` + `:granteeAddress`)) using a valid bouncer address.
  17. * @dev Then restrict access to your crowdsale/whitelist/airdrop using the
  18. * @dev `onlyValidSignature` modifier (or implement your own using isValidSignature).
  19. * @dev
  20. * @dev In addition to `onlyValidSignature`, `onlyValidSignatureAndMethod` and
  21. * @dev `onlyValidSignatureAndData` can be used to restrict access to only a given method
  22. * @dev or a given method with given parameters respectively.
  23. * @dev
  24. * @dev See the tests Bouncer.test.js for specific usage examples.
  25. * @notice A method that uses the `onlyValidSignatureAndData` modifier must make the _sig
  26. * @notice parameter the "last" parameter. You cannot sign a message that has its own
  27. * @notice signature in it so the last 128 bytes of msg.data (which represents the
  28. * @notice length of the _sig data and the _sig data itself) is ignored when validating.
  29. * @notice Also non fixed sized parameters make constructing the data in the signature
  30. * @notice much more complex. See https://ethereum.stackexchange.com/a/50616 for more details.
  31. */
  32. contract SignatureBouncer is Ownable, RBAC {
  33. using ECRecovery for bytes32;
  34. string public constant ROLE_BOUNCER = "bouncer";
  35. uint constant METHOD_ID_SIZE = 4;
  36. // (signature length size) 32 bytes + (signature size 65 bytes padded) 96 bytes
  37. uint constant SIGNATURE_SIZE = 128;
  38. /**
  39. * @dev requires that a valid signature of a bouncer was provided
  40. */
  41. modifier onlyValidSignature(bytes _sig)
  42. {
  43. require(isValidSignature(msg.sender, _sig));
  44. _;
  45. }
  46. /**
  47. * @dev requires that a valid signature with a specifed method of a bouncer was provided
  48. */
  49. modifier onlyValidSignatureAndMethod(bytes _sig)
  50. {
  51. require(isValidSignatureAndMethod(msg.sender, _sig));
  52. _;
  53. }
  54. /**
  55. * @dev requires that a valid signature with a specifed method and params of a bouncer was provided
  56. */
  57. modifier onlyValidSignatureAndData(bytes _sig)
  58. {
  59. require(isValidSignatureAndData(msg.sender, _sig));
  60. _;
  61. }
  62. /**
  63. * @dev allows the owner to add additional bouncer addresses
  64. */
  65. function addBouncer(address _bouncer)
  66. onlyOwner
  67. public
  68. {
  69. require(_bouncer != address(0));
  70. addRole(_bouncer, ROLE_BOUNCER);
  71. }
  72. /**
  73. * @dev allows the owner to remove bouncer addresses
  74. */
  75. function removeBouncer(address _bouncer)
  76. onlyOwner
  77. public
  78. {
  79. require(_bouncer != address(0));
  80. removeRole(_bouncer, ROLE_BOUNCER);
  81. }
  82. /**
  83. * @dev is the signature of `this + sender` from a bouncer?
  84. * @return bool
  85. */
  86. function isValidSignature(address _address, bytes _sig)
  87. internal
  88. view
  89. returns (bool)
  90. {
  91. return isValidDataHash(
  92. keccak256(abi.encodePacked(address(this), _address)),
  93. _sig
  94. );
  95. }
  96. /**
  97. * @dev is the signature of `this + sender + methodId` from a bouncer?
  98. * @return bool
  99. */
  100. function isValidSignatureAndMethod(address _address, bytes _sig)
  101. internal
  102. view
  103. returns (bool)
  104. {
  105. bytes memory data = new bytes(METHOD_ID_SIZE);
  106. for (uint i = 0; i < data.length; i++) {
  107. data[i] = msg.data[i];
  108. }
  109. return isValidDataHash(
  110. keccak256(abi.encodePacked(address(this), _address, data)),
  111. _sig
  112. );
  113. }
  114. /**
  115. * @dev is the signature of `this + sender + methodId + params(s)` from a bouncer?
  116. * @notice the _sig parameter of the method being validated must be the "last" parameter
  117. * @return bool
  118. */
  119. function isValidSignatureAndData(address _address, bytes _sig)
  120. internal
  121. view
  122. returns (bool)
  123. {
  124. require(msg.data.length > SIGNATURE_SIZE);
  125. bytes memory data = new bytes(msg.data.length - SIGNATURE_SIZE);
  126. for (uint i = 0; i < data.length; i++) {
  127. data[i] = msg.data[i];
  128. }
  129. return isValidDataHash(
  130. keccak256(abi.encodePacked(address(this), _address, data)),
  131. _sig
  132. );
  133. }
  134. /**
  135. * @dev internal function to convert a hash to an eth signed message
  136. * @dev and then recover the signature and check it against the bouncer role
  137. * @return bool
  138. */
  139. function isValidDataHash(bytes32 hash, bytes _sig)
  140. internal
  141. view
  142. returns (bool)
  143. {
  144. address signer = hash
  145. .toEthSignedMessageHash()
  146. .recover(_sig);
  147. return hasRole(signer, ROLE_BOUNCER);
  148. }
  149. }