/ contracts / account / MultisigRecovery.sol
MultisigRecovery.sol
  1  pragma solidity >=0.5.0 <0.7.0;
  2  
  3  import "../cryptography/MerkleMultiProof.sol";
  4  import "../cryptography/ECDSA.sol";
  5  import "../account/Signer.sol";
  6  import "../ens/ENS.sol";
  7  import "../ens/ResolverInterface.sol";
  8  
  9  /**
 10   * @notice Select privately other accounts that will allow the execution of actions (ERC-2429 compilant)
 11   * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
 12   *         Vitalik Buterin (Ethereum Foundation)
 13   */
 14  contract MultisigRecovery {
 15      //Needed for EIP-1271 check
 16      bytes4 constant internal EIP1271_MAGICVALUE = 0x20c13b0b;
 17      //threshold constant
 18      uint256 public constant THRESHOLD = 100 * 10^18;
 19      //Needed for ENS leafs
 20      ENS public ens;
 21      //flag for used recoveries (user need to define a different publicHash every execute)
 22      mapping(bytes32 => bool) public used;
 23      //just used offchain
 24      mapping(address => uint256) public nonce;
 25      //flag approvals
 26      mapping(bytes32 => mapping(bytes32 => bool)) public approved;
 27      //storage for pending setup
 28      mapping(address => RecoverySet) public pending;
 29      //storage for active recovery
 30      mapping(address => RecoverySet) public active;
 31  
 32      struct RecoverySet {
 33          bytes32 publicHash;
 34          uint256 setupDelay;
 35          uint256 timestamp;
 36      }
 37  
 38      event SetupRequested(address indexed who, uint256 activation);
 39      event Activated(address indexed who);
 40      event Approved(bytes32 indexed approveHash, bytes32 leaf);
 41      event Execution(address indexed who, bool success);
 42  
 43      /**
 44       * @param _ens Address of ENS Registry
 45       **/
 46      constructor(
 47          ENS _ens
 48      )
 49          public
 50      {
 51          ens = _ens;
 52      }
 53  
 54      /**
 55       * @notice Configure recovery parameters of `msg.sender`. `emit Activated(msg.sender)` if there was no previous setup, or `emit SetupRequested(msg.sender, now()+setupDelay)` when reconfiguring.
 56       * @param _publicHash Double hash of executeHash
 57       * @param _setupDelay Delay for changes being active
 58       */
 59      function setup(
 60          bytes32 _publicHash,
 61          uint256 _setupDelay
 62      )
 63          external
 64      {
 65          require(!used[_publicHash], "_publicHash already used");
 66          used[_publicHash] = true;
 67          address who = msg.sender;
 68          RecoverySet memory newSet = RecoverySet(_publicHash, _setupDelay, block.timestamp);
 69          if(active[who].publicHash == bytes32(0)){
 70              active[who] = newSet;
 71              emit Activated(who);
 72          } else {
 73              require(pending[who].timestamp == 0 || pending[who].timestamp + active[who].setupDelay < block.timestamp, "Waiting activation");
 74              pending[who] = newSet;
 75              emit SetupRequested(who, block.timestamp + active[who].setupDelay);
 76          }
 77  
 78      }
 79  
 80      /**
 81       * @notice Activate a pending setup of recovery parameters
 82       * @param _who address whih ready setupDelay.
 83       */
 84      function activate(address _who)
 85          external
 86      {
 87          RecoverySet storage pendingUser = pending[_who];
 88          require(pendingUser.timestamp > 0, "No pending setup");
 89          require(pendingUser.timestamp + active[_who].setupDelay > block.timestamp, "Waiting delay");
 90          active[_who] = pendingUser;
 91          delete pending[_who];
 92          emit Activated(_who);
 93      }
 94  
 95      /**
 96       * @notice Cancels a pending setup to change the recovery parameters
 97       */
 98      function cancelSetup()
 99          external
100      {
101          RecoverySet storage pendingUser = pending[msg.sender];
102          require(pendingUser.timestamp > 0, "No pending setup");
103          require(pendingUser.timestamp + active[msg.sender].setupDelay < block.timestamp, "Waiting activation");
104          delete pending[msg.sender];
105          emit SetupRequested(msg.sender, 0);
106      }
107      
108      /**
109       * @notice Approves a recovery. This method is important for when the address is an contract and dont implements EIP1271.
110       * @param _approveHash Hash of the recovery call
111       * @param _ensNode if present, the _proof is checked against _ensNode.
112       */
113      function approve(
114          bytes32 _approveHash,
115          bytes32 _ensNode
116      )
117          external
118      {
119          approveExecution(msg.sender, _approveHash, _ensNode);
120      }
121  
122      /**
123       * @notice Approve a recovery using an ethereum signed message
124       * @param _signer address of _signature processor. if _signer is a contract, must be ERC1271.
125       * @param _approveHash Hash of the recovery call
126       * @param _ensNode if present, the _proof is checked against _ensName.
127       * @param _signature ERC191 signature
128       */
129      function approvePreSigned(
130          address _signer,
131          bytes32 _approveHash,
132          bytes32 _ensNode,
133          bytes calldata _signature
134      )
135          external
136      {
137          bytes32 signingHash = ECDSA.toERC191SignedMessage(address(this), abi.encodePacked(_getChainID(), _approveHash, _ensNode));
138          require(_signer != address(0), "Invalid signer");
139          require(
140              (
141                  isContract(_signer) && Signer(_signer).isValidSignature(abi.encodePacked(signingHash), _signature) == EIP1271_MAGICVALUE
142              ) || ECDSA.recover(signingHash, _signature) == _signer,
143              "Invalid signature");
144          approveExecution(_signer, _approveHash, _ensNode);
145      }
146  
147      /**
148       * @notice executes an approved transaction revaling publicHash hash, friends addresses and set new recovery parameters
149       * @param _executeHash Seed of `peerHash`
150       * @param _merkleRoot Revealed merkle root
151       * @param _calldest Address will be called
152       * @param _calldata Data to be sent
153       * @param _leafData Pre approved leafhashes and it's weights as siblings ordered by descending weight
154       * @param _proofs parents proofs
155       * @param _proofFlags indexes that select the hashing pairs from calldata `_leafHashes` and `_proofs` and from memory `hashes`
156       */
157      function execute(
158          bytes32 _executeHash,
159          bytes32 _merkleRoot,
160          address _calldest,
161          bytes calldata _calldata,
162          bytes32[] calldata _leafData,
163          bytes32[] calldata _proofs,
164          bool[] calldata _proofFlags
165      )
166          external
167      {
168          bytes32 publicHash = active[_calldest].publicHash;
169          require(publicHash != bytes32(0), "Recovery not set");
170          bytes32 partialReveal = keccak256(abi.encodePacked(_executeHash));
171          require(
172              publicHash == keccak256(
173                  abi.encodePacked(partialReveal, keccak256(abi.encodePacked(_merkleRoot)))
174              ), "merkleRoot or executeHash is not valid"
175          );
176          bytes32 callHash = keccak256(
177              abi.encodePacked(partialReveal, _calldest, _calldata)
178          );
179          uint256 weight = 0;
180          bytes32[] memory leafs = new bytes32[](_proofs.length/2);
181          for(uint256 i = 0; weight < THRESHOLD; i++){
182              bytes32 leafHash = _leafData[i*2];
183              uint256 leafWeight = uint256(_leafData[(i*2)+1]);
184              leafs[i] = keccak256(abi.encodePacked(leafHash,leafWeight));
185              bytes32 approveHash = keccak256(
186                  abi.encodePacked(
187                      leafHash,
188                      callHash
189              ));
190              require(approved[leafHash][approveHash], "Hash not approved");
191              weight += leafWeight;
192              delete approved[leafHash][approveHash];
193          }
194          require(MerkleMultiProof.verifyMultiProof(_merkleRoot, leafs, _proofs, _proofFlags), "Invalid leafHashes");
195          nonce[_calldest]++;
196          delete active[_calldest];
197          delete pending[_calldest];
198          bool success;
199          (success, ) = _calldest.call(_calldata);
200          emit Execution(_calldest, success);
201      }
202  
203      /**
204       * @param _signer address of approval signer
205       * @param _approveHash Hash of the recovery call
206       * @param _ensNode if present, the _proof is checked against _ensNode.
207       */
208      function approveExecution(
209          address _signer,
210          bytes32 _approveHash,
211          bytes32 _ensNode
212      )
213          internal
214      {
215          bool isENS = _ensNode != bytes32(0);
216          require(
217              !isENS || (
218                  _signer == ResolverInterface(ens.resolver(_ensNode)).addr(_ensNode)
219              ),
220              "Invalid ENS entry"
221          );
222          bytes32 leaf = keccak256(isENS ? abi.encodePacked(byte(0x01), _ensNode) : abi.encodePacked(byte(0x00), bytes32(uint256(_signer))));
223          approved[leaf][_approveHash] = true;
224          emit Approved(_approveHash, leaf);
225      }
226  
227      /**
228       * @dev Internal function to determine if an address is a contract
229       * @param _target The address being queried
230       * @return True if `_addr` is a contract
231       */
232      function isContract(address _target) internal view returns(bool result) {
233          assembly {
234              result := gt(extcodesize(_target), 0)
235          }
236      }
237  
238      /**
239       * @notice get network identification where this contract is running
240       */
241      function _getChainID() internal pure returns (uint256) {
242          uint256 id;
243          assembly {
244              //id := chainid()
245          }
246          return id;
247      }
248  }