|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +pragma solidity ^0.8.0; |
| 4 | + |
| 5 | +import {Setup} from "./Setup.sol"; |
| 6 | +/** |
| 7 | + * @title Locker |
| 8 | + * @author BrokenAppendix |
| 9 | + */ |
| 10 | + |
| 11 | +struct signature { |
| 12 | + uint8 v; |
| 13 | + bytes32 r; |
| 14 | + bytes32 s; |
| 15 | +} |
| 16 | + |
| 17 | +event LockerDeployed( |
| 18 | + address lockerAddress, uint256 lockId, uint8[] v, bytes32[] r, bytes32[] s, address[] controllers, uint256 threshold |
| 19 | +); |
| 20 | + |
| 21 | +// SlockDotIt ECLocker factory |
| 22 | +contract Locker { |
| 23 | + uint256 public immutable lockId; |
| 24 | + bytes32 public immutable msgHash; |
| 25 | + address[] public controllers; |
| 26 | + uint256 public immutable threshold; |
| 27 | + uint256 public tokens; |
| 28 | + |
| 29 | + mapping(bytes32 => bool) public usedSignatures; |
| 30 | + |
| 31 | + constructor(uint256 _lockId, signature[] memory signatures, address[] memory _controllers, uint256 _threshold) { |
| 32 | + require(_controllers.length >= _threshold && _threshold > 0, "Invalid config"); |
| 33 | + |
| 34 | + lockId = _lockId; |
| 35 | + threshold = _threshold; |
| 36 | + controllers = _controllers; |
| 37 | + tokens = 1; |
| 38 | + |
| 39 | + // Compute the expected hash |
| 40 | + bytes32 _msgHash; |
| 41 | + assembly { |
| 42 | + mstore(0x00, "\x19Ethereum Signed Message:\n32") // 28 bytes |
| 43 | + mstore(0x1C, _lockId) |
| 44 | + _msgHash := keccak256(0x00, 0x3c) |
| 45 | + } |
| 46 | + msgHash = _msgHash; |
| 47 | + |
| 48 | + validateMultiSig(signatures); |
| 49 | + |
| 50 | + // Flatten signature arrays |
| 51 | + uint8[] memory vArr = new uint8[](signatures.length); |
| 52 | + bytes32[] memory rArr = new bytes32[](signatures.length); |
| 53 | + bytes32[] memory sArr = new bytes32[](signatures.length); |
| 54 | + |
| 55 | + for (uint256 i = 0; i < signatures.length; i++) { |
| 56 | + vArr[i] = signatures[i].v; |
| 57 | + rArr[i] = signatures[i].r; |
| 58 | + sArr[i] = signatures[i].s; |
| 59 | + } |
| 60 | + |
| 61 | + emit LockerDeployed(address(this), lockId, vArr, rArr, sArr, controllers, threshold); |
| 62 | + } |
| 63 | + |
| 64 | + function distribute(signature[] memory signatures) external { |
| 65 | + validateMultiSig(signatures); |
| 66 | + tokens -= 1; |
| 67 | + } |
| 68 | + |
| 69 | + function isSolved() external view returns (bool) { |
| 70 | + return tokens == 0; |
| 71 | + } |
| 72 | + |
| 73 | + function validateMultiSig(signature[] memory signatures) public { |
| 74 | + address[] memory seen = new address[](controllers.length); |
| 75 | + uint256 validCount = 0; |
| 76 | + for (uint256 i = 0; i < signatures.length; i++) { |
| 77 | + address recovered = _isValidSignature(signatures[i]); |
| 78 | + require(!_isInArray(recovered, seen), "Same signer cannot sign multiple times"); |
| 79 | + |
| 80 | + // Ensure no duplicate |
| 81 | + for (uint256 j = 0; j < validCount; j++) { |
| 82 | + require(seen[j] != recovered, "Duplicate signer"); |
| 83 | + } |
| 84 | + |
| 85 | + /// seenの上書きはできるっちゃできる |
| 86 | + /// が、それができるならそもそもdistributeもできる |
| 87 | + seen[validCount] = recovered; |
| 88 | + validCount++; |
| 89 | + } |
| 90 | + require(validCount == threshold, "Not enough valid signers"); |
| 91 | + } |
| 92 | + |
| 93 | + function _isValidSignature(signature memory sig) internal returns (address) { |
| 94 | + uint8 v = sig.v; |
| 95 | + bytes32 r = sig.r; |
| 96 | + bytes32 s = sig.s; |
| 97 | + address _address = ecrecover(msgHash, v, r, s); |
| 98 | + require(_isInArray(_address, controllers), "Signer is not a controller"); |
| 99 | + |
| 100 | + bytes32 signatureHash = keccak256(abi.encode([uint256(r), uint256(s), uint256(v)])); |
| 101 | + require(!usedSignatures[signatureHash], "Signature has already been used"); |
| 102 | + usedSignatures[signatureHash] = true; |
| 103 | + return _address; |
| 104 | + } |
| 105 | + |
| 106 | + function _isInArray(address addr, address[] memory arr) internal pure returns (bool) { |
| 107 | + for (uint256 i = 0; i < arr.length; i++) { |
| 108 | + if (arr[i] == addr) return true; |
| 109 | + } |
| 110 | + return false; |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +/** |
| 115 | + * @dev This is the Setup Contract which checks if the challenge is solved or not |
| 116 | + * (not a part of the challenge) |
| 117 | + */ |
| 118 | + |
| 119 | +// Private Keys randomly generated online |
| 120 | +// Signatures generated in signature_generator.js |
| 121 | +// Signatures retrieved by player by reading events in read_signatures.js |
| 122 | + |
| 123 | +contract SetupLocker is Setup { |
| 124 | + constructor(address player_address) payable Setup(player_address) {} |
| 125 | + |
| 126 | + signature[] signatures; |
| 127 | + address[] controllers; |
| 128 | + |
| 129 | + function deploy() public override returns (address) { |
| 130 | + uint256 lockId = 0; |
| 131 | + signatures.push( |
| 132 | + signature({ |
| 133 | + v: 27, |
| 134 | + r: 0x36ade3c84a9768d762f611fbba09f0f678c55cd73a734b330a9602b7426b18d9, |
| 135 | + s: 0x6f326347e65ae8b25830beee7f3a4374f535a8f6eedb5221efba0f17eceea9a9 |
| 136 | + }) |
| 137 | + ); |
| 138 | + signatures.push( |
| 139 | + signature({ |
| 140 | + v: 28, |
| 141 | + r: 0x57f4f9e4f2ef7280c23b31c0360384113bc7aa130073c43bb8ff83d4804bd2a7, |
| 142 | + s: 0x694430205a6b625cc8506e945208ad32bec94583bf4ec116598708f3b65e4910 |
| 143 | + }) |
| 144 | + ); |
| 145 | + signatures.push( |
| 146 | + signature({ |
| 147 | + v: 27, |
| 148 | + r: 0xe2e9d4367932529bf0c5c814942d2ff9ae3b5270a240be64b89f839cd4c78d5d, |
| 149 | + s: 0x6c0c845b7a88f5a2396d7f75b536ad577bbdb27ea8c03769a958b2a9d67117d2 |
| 150 | + }) |
| 151 | + ); |
| 152 | + controllers.push(0x9dF23180748A2E168a24F5BBAB2a50eE38A7d309); |
| 153 | + controllers.push(0x8Ab87699287fe024A8b4d53385AC848930b19FfF); |
| 154 | + controllers.push(0x10Bab59adbDd06E90996361181b7d2129A5Eeb5A); |
| 155 | + uint256 threshold = 3; |
| 156 | + |
| 157 | + Locker _instance = new Locker(lockId, signatures, controllers, threshold); |
| 158 | + |
| 159 | + /// 作問ミスを修正 |
| 160 | + challenge = address(_instance); |
| 161 | + |
| 162 | + return address(_instance); |
| 163 | + } |
| 164 | + |
| 165 | + function isSolved() external view override returns (bool) { |
| 166 | + return Locker(challenge).isSolved(); |
| 167 | + } |
| 168 | +} |
0 commit comments