1// SPDX-License-Identifier: MIT2
3pragma solidity ^0.8.13;4
5import {IPuzzle} from "./IPuzzle.sol";6import {TxHashSimulator} from "./TxHashSimulator.sol";7
8contract Submerged is IPuzzle {9 TxHashSimulator public simulator = new TxHashSimulator();10
11 mapping (bytes32 => bool) public submergedTxs;12 mapping (bytes32 => bool) public submergedSeeds;13
14 function name() external pure returns (string memory) {15 return "Submerged";16 }17
18 function generate(address seed) external pure returns (uint256) {19 return uint256(keccak256(abi.encode(seed)));20 }21
22 function verify(uint256 seed, uint256 solution) external view returns (bool) {23 return submergedSeeds[keccak256(abi.encode(seed, solution))];24 }25
26 function proveSubmergedTx() external {27 bytes32 txHash = simulator.TXHASH();28 require(txHash != bytes32(0), "must use TxHashSimulator");29 require(address(tx.origin).balance == 0, "not fully submerged");30
31 submergedTxs[txHash] = true;32 }33
34 function proveSubmergedSeed(bytes calldata rawTx) external {35 require(submergedTxs[keccak256(rawTx)], "tx not submerged");36
37 bytes calldata data = rawTx;38 uint256 off;39 (, off) = RLP.parseList(data);40 data = data[off:];41
42 data = RLP.skip(data); // nonce43 data = RLP.skip(data); // gasPrice44 data = RLP.skip(data); // gasLimit45 data = RLP.skip(data); // to46 data = RLP.skip(data); // value47
48 (data, ) = RLP.splitBytes(data); // extra the tx calldata49 bytes32 seed = bytes32(data[:32]);50
51 submergedSeeds[seed] = true;52 }53}54
55contract TxHashSimulator {56 // wen TSTORE57 bytes32 public TXHASH;58
59 function getCurrentTxHash() internal view returns (bytes32 txHash) {60 require(tx.origin == msg.sender, "can only verify txHash if sender is origin");61
62 // collect parameters63 // note: gasLimit could be derived from entry gas and calldata,64 // but it's cheaper to require it in calldata65 uint256 gasLimit;66 bytes32 seed;67 assembly {68 seed := calldataload(0)69 gasLimit := calldataload(32)70 }71
72 uint8 v = 27;73 bytes32 r;74 bytes32 s;75 assembly {76 mstore(0, seed)77 r := keccak256(0, 32)78 mstore(0, r)79 s := keccak256(0, 32)80 }81
82 bytes[] memory txList = new bytes[](9);83 txList[0] = RLP.encodeUint(0); // nonce84 txList[1] = RLP.encodeUint(tx.gasprice); // gas price85 txList[2] = RLP.encodeUint(gasLimit); // claimed gasLimit86 txList[3] = RLP.encodeUint(uint256(uint160(address(this)))); // to address87 txList[4] = RLP.encodeUint(msg.value); // tx value88 txList[5] = RLP.encodeBytes(msg.data); // tx data89 txList[6] = RLP.encodeUint(uint256(v)); // v90 txList[7] = RLP.encodeUint(uint256(r)); // r91 txList[8] = RLP.encodeUint(uint256(s)); // s92
93 txHash = keccak256(RLP.encodeList(txList));94
95 // truncate the tx fields to exclude the signature data96 assembly {97 mstore(txList, 6)98 }99
100 bytes32 signingHash = keccak256(RLP.encodeList(txList));101 require(102 ecrecover(signingHash, v, r, s) == msg.sender,103 "could not verify tx hash"104 );105 }106
107 fallback() external {108 TXHASH = getCurrentTxHash();109
110 assembly {111 let target := calldataload(64)112 let len := sub(calldatasize(), 96)113 calldatacopy(0, 96, len)114 let res := call(115 gas(),116 target,117 callvalue(),118 0,119 len,120 0,121 0122 )123 sstore(TXHASH.slot, 0)124 if res {125 returndatacopy(0, 0, returndatasize())126 return(0, returndatasize())127 }128 returndatacopy(0, 0, returndatasize())129 revert(0, returndatasize())130 }131 }132}133
134
135library RLP {136 function parseUint(bytes calldata buf) internal pure returns (uint256 result, uint256 size) {137 assembly {138 // check that we have at least one byte of input139 if iszero(buf.length) {140 revert(0, 0)141 }142 let first32 := calldataload(buf.offset)143 let kind := shr(248, first32)144
145 // ensure it's a not a long string or list (> 0xB7)146 // also ensure it's not a short string longer than 32 bytes (> 0xA0)147 if gt(kind, 0xA0) {148 revert(0, 0)149 }150
151 switch lt(kind, 0x80)152 case true {153 // small single byte154 result := kind155 size := 1156 }157 case false {158 // short string159 size := sub(kind, 0x80)160
161 // ensure it's not reading out of bounds162 if lt(buf.length, size) {163 revert(0, 0)164 }165
166 switch eq(size, 32)167 case true {168 // if it's exactly 32 bytes, read it from calldata169 result := calldataload(add(buf.offset, 1))170 }171 case false {172 // if it's < 32 bytes, we've already read it from calldata173 result := shr(shl(3, sub(32, size)), shl(8, first32))174 }175 size := add(size, 1)176 }177 }178 }179
180 function nextSize(bytes calldata buf) internal pure returns (uint256 size) {181 assembly {182 if iszero(buf.length) {183 revert(0, 0)184 }185 let first32 := calldataload(buf.offset)186 let kind := shr(248, first32)187
188 switch lt(kind, 0x80)189 case true {190 // small single byte191 size := 1192 }193 case false {194 switch lt(kind, 0xB8)195 case true {196 // short string197 size := add(1, sub(kind, 0x80))198 }199 case false {200 switch lt(kind, 0xC0)201 case true {202 // long string203 let lengthSize := sub(kind, 0xB7)204
205 // ensure that we don't overflow206 if gt(lengthSize, 31) {207 revert(0, 0)208 }209
210 // ensure that we don't read out of bounds211 if lt(buf.length, lengthSize) {212 revert(0, 0)213 }214 size := shr(mul(8, sub(32, lengthSize)), shl(8, first32))215 size := add(size, add(1, lengthSize))216 }217 case false {218 switch lt(kind, 0xF8)219 case true {220 // short list221 size := add(1, sub(kind, 0xC0))222 }223 case false {224 let lengthSize := sub(kind, 0xF7)225
226 // ensure that we don't overflow227 if gt(lengthSize, 31) {228 revert(0, 0)229 }230 // ensure that we don't read out of bounds231 if lt(buf.length, lengthSize) {232 revert(0, 0)233 }234 size := shr(mul(8, sub(32, lengthSize)), shl(8, first32))235 size := add(size, add(1, lengthSize))236 }237 }238 }239 }240 }241 }242
243 function skip(bytes calldata buf) internal pure returns (bytes calldata) {244 uint256 size = RLP.nextSize(buf);245 assembly {246 buf.offset := add(buf.offset, size)247 buf.length := sub(buf.length, size)248 }249 return buf;250 }251
252 function parseList(bytes calldata buf)253 internal254 pure255 returns (uint256 listSize, uint256 offset)256 {257 assembly {258 // check that we have at least one byte of input259 if iszero(buf.length) {260 revert(0, 0)261 }262 let first32 := calldataload(buf.offset)263 let kind := shr(248, first32)264
265 // ensure it's a list266 if lt(kind, 0xC0) {267 revert(0, 0)268 }269
270 switch lt(kind, 0xF8)271 case true {272 // short list273 listSize := sub(kind, 0xC0)274 offset := 1275 }276 case false {277 // long list278 let lengthSize := sub(kind, 0xF7)279
280 // ensure that we don't overflow281 if gt(lengthSize, 31) {282 revert(0, 0)283 }284 // ensure that we don't read out of bounds285 if lt(buf.length, lengthSize) {286 revert(0, 0)287 }288 listSize := shr(mul(8, sub(32, lengthSize)), shl(8, first32))289 offset := add(lengthSize, 1)290 }291 }292 }293
294 function splitBytes(bytes calldata buf)295 internal296 pure297 returns (bytes calldata result, bytes calldata rest)298 {299 uint256 offset;300 uint256 size;301 assembly {302 // check that we have at least one byte of input303 if iszero(buf.length) {304 revert(0, 0)305 }306 let first32 := calldataload(buf.offset)307 let kind := shr(248, first32)308
309 // ensure it's a not list310 if gt(kind, 0xBF) {311 revert(0, 0)312 }313
314 switch lt(kind, 0x80)315 case true {316 // small single byte317 offset := 0318 size := 1319 }320 case false {321 switch lt(kind, 0xB8)322 case true {323 // short string324 offset := 1325 size := sub(kind, 0x80)326 }327 case false {328 // long string329 let lengthSize := sub(kind, 0xB7)330
331 // ensure that we don't overflow332 if gt(lengthSize, 31) {333 revert(0, 0)334 }335 // ensure we don't read out of bounds336 if lt(buf.length, lengthSize) {337 revert(0, 0)338 }339 size := shr(mul(8, sub(32, lengthSize)), shl(8, first32))340 offset := add(lengthSize, 1)341 }342 }343
344 result.offset := add(buf.offset, offset)345 result.length := size346
347 let end := add(offset, size)348 rest.offset := add(buf.offset, end)349 rest.length := sub(buf.length, end)350 }351 }352
353 function encodeLength(uint256 len, uint8 offset) internal pure returns (bytes memory result) {354 if (len < 56) {355 result = new bytes(1);356 assembly {357 mstore(358 add(result, 32),359 shl(248, add(offset, len))360 )361 }362 } else {363 require(len < 2**32, "lengths exceeding UINT32_MAX are unsupported");364 if (len > 2**24) {365 result = new bytes(5);366 assembly {367 mstore(368 add(result, 32),369 or(370 shl(248, add(offset, 59)),371 shl(216, len)372 )373 )374 }375 } else if (len > 2**16) {376 result = new bytes(4);377 assembly {378 mstore(379 add(result, 32),380 or(381 shl(248, add(offset, 58)),382 shl(224, len)383 )384 )385 }386 } else if (len > 2**8) {387 result = new bytes(3);388 assembly {389 mstore(390 add(result, 32),391 or(392 shl(248, add(offset, 57)),393 shl(232, len)394 )395 )396 }397 } else {398 result = new bytes(2);399 assembly {400 mstore(401 add(result, 32),402 or(403 shl(248, add(offset, 56)),404 shl(240, len)405 )406 )407 }408 }409 }410 }411
412 function encodeList(bytes[] memory encodedElems) internal view returns (bytes memory result) {413 uint256 totalLength;414 assembly {415 let elemsStart := add(encodedElems, 0x20)416 let elemsEnd := add(elemsStart, mul(0x20, mload(encodedElems)))417 for {let off := elemsStart} lt(off, elemsEnd) {off := add(off, 0x20)} {418 let elem := mload(off)419 totalLength := add(totalLength, mload(elem))420 }421 }422 bytes memory encodedLength = encodeLength(totalLength, 0xc0);423 unchecked { totalLength += encodedLength.length; }424 result = new bytes(totalLength);425 assembly {426 let ptr := add(result, 0x20)427 mstore(ptr, mload(add(encodedLength, 0x20)))428 ptr := add(ptr, mload(encodedLength))429
430 let elemsStart := add(encodedElems, 0x20)431 let elemsEnd := add(elemsStart, mul(0x20, mload(encodedElems)))432 for {let off := elemsStart} lt(off, elemsEnd) {off := add(off, 0x20)} {433 let elem := mload(off)434 let len := mload(elem)435 if iszero(staticcall(436 gas(),437 4,438 add(elem, 0x20),439 len,440 ptr,441 len442 )) {443 revert(0, 0)444 }445 ptr := add(ptr, len)446 }447 }448 }449
450
451 function encodeBytes(bytes memory elem) internal pure returns (bytes memory) {452 return abi.encodePacked(453 encodeLength(elem.length, 0x80),454 elem455 );456 }457
458 function encodeUint(uint256 value) internal pure returns (bytes memory) {459 // allocate our result bytes460 bytes memory result = new bytes(33);461 462 if (value == 0) {463 // store length = 1, value = 0x80464 assembly {465 mstore(add(result, 1), 0x180)466 }467 return result;468 }469 470 if (value < 128) {471 // store length = 1, value = value472 assembly {473 mstore(add(result, 1), or(0x100, value))474 }475 return result;476 }477 478 if (value > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) {479 // length 33, prefix 0xa0 followed by value480 assembly { 481 mstore(add(result, 1), 0x21a0)482 mstore(add(result, 33), value)483 }484 return result;485 }486 487 if (value > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) {488 // length 32, prefix 0x9f followed by value489 assembly {490 mstore(add(result, 1), 0x209f)491 mstore(add(result, 33), shl(8, value))492 }493 return result;494 }495 496 assembly {497 let length := 1498 for {499 let min := 0x100500 } lt(sub(min, 1), value) {501 min := shl(8, min)502 } {503 length := add(length, 1)504 }505 506 let bytesLength := add(length, 1)507 508 // bytes length field509 let hi := shl(mul(bytesLength, 8), bytesLength)510 511 // rlp encoding of value512 let lo := or(shl(mul(length, 8), add(length, 0x80)), value)513 514 mstore(add(result, bytesLength), or(hi, lo))515 } 516 return result; 517 }518}
Time Left
Solve locally (WIP)
- Clone GitHub repo + install deps
git clone https://github.com/waterfall-mkt/curta-puzzles.git && cd curta-puzzles && forge install
- Set
RPC_URL_MAINNET
in.env
.env
RPC_URL_MAINNET=""
- Write solution + run script
forge script <PATH_TO_PUZZLE> -f mainnet -vvv
This is still WIP.