1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.13;3
4import {IPuzzle} from "./interfaces/IPuzzle.sol";5
6//7//8//    888b    888                        888                            888    888          d8b          8889//    8888b   888                        888                            888    888          Y8P          88810//    88888b  888                        888                            888    888                       88811//    888Y88b 888 888  888 88888b.d88b.  88888b.   .d88b.  888d888      8888888888  .d88b.  888 .d8888b  88888812//    888 Y88b888 888  888 888 "888 "88b 888 "88b d8P  Y8b 888P"        888    888 d8P  Y8b 888 88K      88813//    888  Y88888 888  888 888  888  888 888  888 88888888 888          888    888 88888888 888 "Y8888b. 88814//    888   Y8888 Y88b 888 888  888  888 888 d88P Y8b.     888          888    888 Y8b.     888      X88 Y88b.15//    888    Y888  "Y88888 888  888  888 88888P"   "Y8888  888          888    888  "Y8888  888  88888P'  "Y88816//17//18//       ||====================================================================||19//       ||//$\\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//$\\||20//       ||(100)==================| POINTS RESERVE NOTE |=================(100)||21//       ||\\$//        ~         '------========-------'                 \\$//||22//       ||<< /        /$\              // ____ \\                         \ >>||23//       ||>>|  12    //L\\            // ///..) \\         L38036133B   12 |<<||24//       ||<<|        \\ //           || <||  >\  ||                        |>>||25//       ||>>|         \$/            ||  $$ --/  ||      One Hundred Pts   |<<||26//    ||====================================================================||>||27//    ||//$\\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//$\\||<||28//    ||(100)==================| POINTS RESERVE NOTE |=================(100)||>||29//    ||\\$//        ~         '------========-------'                 \\$//||\||30//    ||<< /        /$\              // ____ \\                         \ >>||)||31//    ||>>|  12    //L\\            // ///..) \\         L38036133B   12 |<<||/||32//    ||<<|        \\ //           || <||  >\  ||                        |>>||=||33//    ||>>|         \$/            ||  $$ --/  ||      One Hundred Pts   |<<||34//    ||<<|      L38036133B        *\\  |\_/  //* series                 |>>||35//    ||>>|  12                     *\\/___\_//*   1989                  |<<||36//    ||<<\      Treasurer    ____/Blast Franklin\_____     Secretary 12 />>||37//    ||//$\                  ~| FEDERATION OF POINTS |~                /$\\||38//    ||(100)==================   ONE HUNDRED POINTS  =================(100)||39//    ||\\$//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\\$//||40//    ||====================================================================||41//42//43// @dev You are <REDACTED>, the infamous digital points thief, renowned for44// dozens of successful heists totaling 513,450,000,000 in stolen points.45//46// You have been hired by a mysterious benefactor to steal from a safe, your usual gig.47//48// Only this safe is not locked with a key, but with a number. 49// You must find a number which defies the laws of mathematics itself.50//51// This number is both a prime and composite number at the same time...52// 53// If you're able to do that, legend has it that the safe contains a treasure worth more than54// all your precious little points combined... a Curta Flag NFT!55contract NumberHeist is IPuzzle {56
57    bytes32 constant VALID_FACTORIALS_HASH = 0x024260b3e934c04b47e60ebbf3a5974c8008d1494ac24bfb1358daf47e752953;58    uint256 constant SOLUTION = 0x8fbcb4375b910093bcf636b6b2f26b26eda2a29ef5a8ee7de44b5743c3bf9a28; // Not so easy...59
60    modifier restoreMemory() {61        _;62        assembly {63            mstore(0x40, 0x80) // NooOoOOoooOooOoooOOOoooOOooOoOoooOOOoooOoOOo my memory...64        }65    }66
67    mapping(address => bool) public solved;68
69    function name() external pure returns (string memory) {70        return "NumberHeist";71    }72
73    function generate(address _seed) external pure returns (uint256 ret) {74        assembly {75            ret := _seed76        }77    }78
79    function verify(uint256 _start, uint256 _solution) external view returns (bool) {80        require(_solution == SOLUTION);81        return solved[address(uint160(_start))];82    }83
84    // The safe is locked with three layers of security against such tampering.85    // However, it's nothing you haven't dealt with before. 86    // Begin the heist!87    function heist(uint256 n, uint256 circleCuttingWitness, uint256 factor1, uint256 factor2) external returns (bool heistSuccess) {88        require(1 < n && n <= 32);89        require(factor1 > 1 && factor2 > 1);90        require(factor1 < n && factor2 < n);91
92        bytes memory magicTome = _initializePuzzle();93
94        // ---- Check Prime By Wilson's ----95        if (!_checkWilsons(n)) {96            return false;97        }98
99        // ---- Check Prime By Circle Cutting ----100        if (!_isPrimeByCircleCutting(magicTome, n, circleCuttingWitness)) {101            return false;102        }103
104        // ---- Check Not Composite By Filson's ----105        if (!_filsonsCompositenessCheck(n)) {106            return false;107        }108
109        // This number has passed 3 layers of prime security checks...110        // But will it defy the laws of mathematics?111        if(n == factor1 * factor2) {112            solved[msg.sender] = true;113            heistSuccess = true;114        }115    }116
117
118    //////////////////////////////////////////////////////////////////////////////////////119    /// WILSON'S120    //////////////////////////////////////////////////////////////////////////////////////121
122    // We will use the famous result from Wilson's Theorem to test for primality:123    //124    //  (n-1)! ≡ -1 (mod n) <=> n is prime125    function _checkWilsons(uint256 n) internal restoreMemory returns (bool) {126        // Caller provides the precomputed factorials... because why not?127        // Largest factorial necessary for wilson's is (n-1)!, therefore we need 0!..31!128
129        // Three entries: [ x! ∀ x <= 12, x! ∀ 12 < x <= 20, x! ∀ 20 < x <= 31 ]130        // First entry factorials fit into 4 byte denominations131        // Second entry factorials fit into 8 byte denominations132        // Third entry factorials fit into 16 byte denominations133        (bool success, bytes memory data) = msg.sender.call("");134        require(success);135
136        // Nothing to see here...137        assembly {138            mstore(0x40, 0x360)139        }140
141        bytes[3] memory precomputedFactorials = abi.decode(data, (bytes[3]));142
143        // hash the precomputedFactorials to ensure they are correct144        require(keccak256(abi.encodePacked(precomputedFactorials[0], precomputedFactorials[1], precomputedFactorials[2])) == VALID_FACTORIALS_HASH, "Not Valid Factorials");145
146        // Declare Pointers147        bytes memory f0 = precomputedFactorials[0];148        bytes memory f1 = precomputedFactorials[1];149        bytes memory f2 = precomputedFactorials[2];150
151        uint256 modResult;152
153        // Verify Wilson's154        assembly {155            let nMinusOneFactorial156
157            if lt(n, 14) {158                let factorialsStart := add(f0, 0x08)159                let factorialSpot := mload(add(factorialsStart, mul(sub(n, 1), 0x08)))160                nMinusOneFactorial := and(factorialSpot, 0xFFFFFFFFFFFFFFFF)161            }162            if and(gt(n, 13), lt(n, 22)) {163                let factorialsStart := add(f1, 0x0c)164                let factorialSpot := mload(add(factorialsStart, mul(sub(n, 14), 0x0c)))165                nMinusOneFactorial := and(factorialSpot, 0xFFFFFFFFFFFFFFFFFFFFFFFF)166            }167            if gt(n, 21) {168                let factorialsStart := add(f2, 0x10)169                let factorialSpot := mload(add(factorialsStart, mul(sub(n, 22), 0x10)))170                nMinusOneFactorial := and(factorialSpot, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)171            }172
173            modResult := addmod(nMinusOneFactorial, 1, n)174        }175
176        return modResult == 0;177    }178
179
180    //////////////////////////////////////////////////////////////////////////////////////181    /// CIRCLE CUTTING182    //////////////////////////////////////////////////////////////////////////////////////183
184    function _initializePuzzle() internal pure returns (bytes memory magicTome) {185        // What could this be?186        assembly {187            magicTome := 0x760188            let tomePtr := add(magicTome, 0x20)189            let tomeSize := 0x100190            mstore(191                tomePtr,192                hex"0000000000000007000000000000000500000000000000150000000000000011"193            )194            mstore(195                add(tomePtr, 0x20),196                hex"0000000000000155000000000000001d00000000000015550000000000000101"197            )198            mstore(199                add(tomePtr, 0x40),200                hex"000000000000104100000000000001dd00000000001555550000000000000131"201            )202            mstore(203                add(tomePtr, 0x60),204                hex"00000000015555550000000000001ddd000000000001c74d0000000000010001"205            )206            mstore(207                add(tomePtr, 0x80),208                hex"000000015555555500000000000010c100000015555555550000000000013131"209            )210            mstore(211                add(tomePtr, 0xA0),212                hex"0000000001C7134D00000000001DDDDD00001555555555550000000000010301"213            )214            mstore(215                add(tomePtr, 0xC0),216                hex"00000100401004010000000001DDDDDD00000010000400010000000001313131"217            )218            mstore(219                add(tomePtr, 0xE0),220                hex"01555555555555550000000000014FC515555555555555550000000000000000"221            )222            mstore(magicTome, tomeSize)223        }224    }225
226    // cyclo-227    // ˈsīklō - Greek228    //      1. circular229    //      2. relating to a circle230    //231    // tomic232    // ˈtɑmɪk - Greek233    //      1. of or relating to cutting, division, sections, etc.234    //235    // Let Φ𝑛 be the nth cyclotomic polynomial, then we propose ∃ 𝑎 s.t.:236    //237    //      𝑝 is prime <=> Φ𝑝−1(𝑎) ≡ 0 (mod 𝑝)238    //239    // Can this be true...?240    function _isPrimeByCircleCutting(bytes memory cyclotomics, uint256 n, uint256 witness) internal returns (bool) {241        if (cyclotomics.length != 0x100) {242            revert("Invalid cyclotomics length");243        }244        // Get the right cyclotomic from the magic tome245        bytes8 cyclotomic = _readCyclotomic(cyclotomics, n-1);246        int256 result = _evaluateCyclotomic(cyclotomic, witness);247
248        return result % int256(n) == 0;249    }250
251    function _readCyclotomic(bytes memory cyclotomics, uint256 n) internal pure returns (bytes8 cyclotomic) {252        assembly {253            let cyclo_size := 0x08254            cyclotomic := and(mload(add(add(cyclotomics, 0x20), mul(sub(n, 1), cyclo_size))), 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000)255        }256    }257
258    function _evaluateCyclotomic(bytes8 cyclotomic, uint256 x) internal returns (int256 res) {259        for (uint256 i = 63; i > 1; i-=2) {260            uint8 cycloByte = uint8(cyclotomic[i / 8]);261
262            uint adjustment = (7 - (i % 8));263
264            uint8 sign = uint8(cycloByte & (1 << (adjustment + 1)));265            uint8 bit = uint8(cycloByte & (1 << adjustment));266
267            if (bit != 0) {268                int256 val = int256(x**((63-i)/2));269                if (sign != 0) {270                    res -= val;271                } else if (sign == 0){272                    res += val;273                } else {274                    revert("Invalid sign");275                }276            }277        }278    }279
280
281    //////////////////////////////////////////////////////////////////////////////////////282    /// FILSON'S283    //////////////////////////////////////////////////////////////////////////////////////284
285    // As a corollary to Wilson's we derive Filson's:286    //287    //  (n-1)! ≡ 0 (mod n) <=> n is composite288    //289    // This interesting result is based on the following observation when n is composite:290    //291    //  ∃ x0, x1 ∈ {1,2,..n-1} s.t. x0 * x1 = a * n for some a ∈ Zn+292    //293    // Proving this is left as an exercise for the reader...294    function _filsonsCompositenessCheck(uint256 n) internal pure returns (bool) {295        return factorial(n-1) % n != 0;296    }297
298    // Use an independent method to compute factorials for Filson's,299    // just in case the pre-computed method is vulnerable ;)300    function factorial(uint n) public pure returns (uint) {301        if (n == 0) {302            return 1;303        }304        uint result = 1;305        for (uint i = 1; i <= n; i++) {306            result *= i;307        }308        return result;309    }310
311}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_MAINNETin.env 
.env
RPC_URL_MAINNET=""- Write solution + run script
 
forge script <PATH_TO_PUZZLE> -f mainnet -vvvThis is still WIP.