SignatureBouncer.sol 4.7 KB

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