{"id":16517847,"url":"https://github.com/erhant/zkctf-scalebit","last_synced_at":"2025-10-28T06:32:20.564Z","repository":{"id":222978423,"uuid":"749895395","full_name":"erhant/zkctf-scalebit","owner":"erhant","description":"Circom challenges within zkCTF by Scalebit, solved with Foundry \u0026 Circomkit.","archived":false,"fork":false,"pushed_at":"2024-02-17T11:52:19.000Z","size":275,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-02-18T12:41:38.804Z","etag":null,"topics":["circom","ctf","ethereum","solidity","zk"],"latest_commit_sha":null,"homepage":"https://zkctf.scalebit.xyz/","language":"Circom","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erhant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-01-29T15:59:33.000Z","updated_at":"2024-02-18T03:01:06.000Z","dependencies_parsed_at":"2024-02-17T12:46:12.896Z","dependency_job_id":null,"html_url":"https://github.com/erhant/zkctf-scalebit","commit_stats":null,"previous_names":["erhant/zkctf-scalebit"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhant%2Fzkctf-scalebit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhant%2Fzkctf-scalebit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhant%2Fzkctf-scalebit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhant%2Fzkctf-scalebit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erhant","download_url":"https://codeload.github.com/erhant/zkctf-scalebit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219860053,"owners_count":16556029,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["circom","ctf","ethereum","solidity","zk"],"created_at":"2024-10-11T16:33:59.363Z","updated_at":"2025-10-28T06:32:15.247Z","avatar_url":"https://github.com/erhant.png","language":"Circom","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ZKCTF by Scalebit - Circom Challenges\n\nExplained solutions to the challenges:\n\n- [x] [Checkin](#checkin)\n- [x] [Roundabout](#roundabout)\n- [x] [Familiar Strangers](#familiar-strangers)\n- [ ] Mixer\n\n## Setup\n\nInstall everything here with:\n\n```sh\nyarn install\nforge install\n```\n\nWhen you click on a challenge, you are greeted with:\n\n- an RPC endpoint\n- a faucet link\n- a netcat `nc` command\n\nYou can see further instruction when you enter the netcat command. You will also need a solver account (preferably a throw-away). You will place your solver private key \u0026 RPC urls along with contract addresses under `.env` file, see [example here](./.env.example).\n\n\u003e [!NOTE]\n\u003e\n\u003e When you get the deployer address, you can fund that address at the faucet, along with your throw-away solver address.\n\n## Tests\n\nBoth circuit tests \u0026 contract tests are written:\n\n```sh\nyarn test     # test everything\nyarn test:sol # test contracts\nyarn test:js  # test circuits\n```\n\n## Solutions\n\nBelow are the write-ups.\n\n### Checkin\n\nCompile the circuit:\n\n```sh\nnpx circomkit compile checkin\n```\n\nDownload the given prover key (`zkey`) and rename it as `groth16_pkey.zkey`, place it under the build directory of the circuit. Create an input under `inputs/checkin/default.json` as:\n\n```sh\n{\n  \"a\": 1,\n  \"b\": 1\n}\n```\n\nFinally, prove \u0026 generate calldata:\n\n```sh\nnpx circomkit prove checkin\nnpx circomkit calldata checkin\n```\n\nUse this calldata to verify your on-chain proof, check the script or test to see how thats done. You can submit the solution with:\n\n```sh\nsource .env \u0026\u0026 forge script script/Checkin.s.sol:Solve --rpc-url $CHECKIN_RPC -vvv --broadcast\n```\n\n### Roundabout\n\nIf we read the docs about [MiMC](https://byt3bit.github.io/primesym/mimc/), it has the following scheme:\n\n$$\nE_k(x) = (F_{r-1} \\cdot F_{r-2} \\cdot \\ldots \\cdot F_0)(x) + k\n$$\n\nwhere $x \\in F_{q}$, is the plaintext, $r$ is the number of rounds, $F_i$ is the round function for round $i \\geq 0$, and $k \\in F_q$ is the key. At the bottom of that page, we see the Feistel-MiMC which is what the circuit in this challenge makes use of.\n\nFor Feistel-MiMC Each $F_i$ is defined as:\n\n$$\nF_i(x_i, y_i) = (y_i, x_i + (y_i + k + c_i)^3)\n$$\n\nwhere $c_i$ are round constants, fixed during the instantiation of MiMC. With this in mind, we can check the [circuit itself](https://github.com/erhant/zkctf-scalebit/blob/main/circuits/roundabout.circom#L298), and see two things:\n\n- The circuit recommends 220 rounds (as indicated in a comment there) but we have two rounds, which means the first and last rounds have constant 0:\n  - $c_{first} = c_0 = 0$\n  - $c_{last} = c_1 = 0$\n- The circuit uses the fifth power instead of third, as in $(y_i + k + c_i)^5$\n- $y_0$ is 0 and $x_0$ is $x$ for some input $x$ to the circuit.\n\nSo our two rounds with $k=1$ for an input $a$ would look like the following:\n\n```py\n# inputs\nL_in = a\nR_in = 0\n\n# first round\nxL[0] = (1 + a)^5\nxR[0] = a\n\n# second (and last) round\nxL[1] = (1 + (1 + a)^5)^5\nxR[1] = (1 + a)^5\n\n# outputs\nL_out = xL[0]\nR_out = xR[1]\n```\n\nAs the final output, we only have `R_out` which is just $(1 + a)^5$. The given circuit expects this value to be `3066844496547985532785966973086993824` so we only need to solve the following equation for $a$:\n\n$$\n(1 + a)^5 = 3066844496547985532785966973086993824\n$$\n\nUsing Sage, we can find the answer:\n\n```py\n# bn128 order\np = 21888242871839275222246405745257275088548364400416034343698204186575808495617\nF = GF(p)\n\nc = F(3066844496547985532785966973086993824)\na = c.nth_root(5) - 1\nprint(a)\n```\n\nWe find $a = 19830713$, done!\n\nIn the next part of this challenge, we need to find a suitable $b$ that satisfies an equation. Denote $k = 37622140664026667386099315436167897444086165906536960040182069717656571868$ as given in the problem. Then, we are looking to satisfy:\n\n$$\n9b^2 + k = c^2 \\times b^2\n$$\n\nWe can re-arrange as:\n\n$$\n(c^2 - 9)^{-1} \\times k = b^2\n$$\n\nAgain, we can use Sage:\n\n```py\n# bn128 order\np = 21888242871839275222246405745257275088548364400416034343698204186575808495617\nF = GF(p)\n\nc = F(3066844496547985532785966973086993824)\nk = F(37622140664026667386099315436167897444086165906536960040182069717656571868)\n\nbb = (1 / (c * c - 9)) * k\nb = bb.nth_root(2)\nprint(b)\n```\n\nWe find $b=2$, so we now have all our inputs. To solve the challenge, first compile the circuit:\n\n```sh\nnpx circomkit compile roundabout\n```\n\nDownload the given prover key (`zkey`) and rename it as `groth16_pkey.zkey`, place it under the build directory of the circuit. Create an input under `inputs/roundabout/default.json` as:\n\n```sh\n{\n  \"a\": 19830713,\n  \"b\": 2\n}\n```\n\nFinally, prove \u0026 generate calldata:\n\n```sh\nnpx circomkit prove roundabout\nnpx circomkit calldata roundabout\n```\n\nUse this calldata to verify your on-chain proof, check the script or test to see how thats done. You can submit the solution with:\n\n```sh\nsource .env \u0026\u0026 forge script script/Roundabout.s.sol:Solve --rpc-url $ROUNDABOUT_RPC -vvv --broadcast\n```\n\n### Familiar Strangers\n\nWe have a several inequalities to solve here. This challenge had no contract, but instead a UI was prepared for us to submit the correct values. We describe how to compute them here:\n\n#### Level 1\n\nIn Level 1, we are expected to provide an input `x` such that:\n\n- `x \u003c 6026017665971213533282357846279359759458261226685473132380160` (within 201 bits)\n- `x \u003e -401734511064747568885490523085290650630550748445698208825344` (within 201 bits)\n\nThe `GreaterThan` uses `LessThan` within (see [here](https://circom.erhant.me/comparators/index.html#greaterthan)), which simply switches the places of the input. Also, lets just work with positive numbers as the negative number will be converted to a positive number within the field. The second inequality thus becomes:\n\n- `21888242871839274820511894680509706203057841315125383713147455740877599670273 \u003c x`\n\nIf we look at the number of bits of that huge number, we see that its 254 bits! However, the comparators in Level1 expect 201 bit numbers only. So how do we bypass that? If we look closely, the input to `Num2Bits` is actually `((1 \u003c\u003c n) + in[0]) - in[1]` and the `Num2Bits` is instantiated with `n+1` bits.\n\nTo return 1 from the `LessThan` template we have to make sure the `n`th bit of that operation is 0. We can actually just say `x = in[1] = in[0] + (1 \u003c\u003c n)` which would make the whole thing 0. This gives us `x = 2812141577453232982198433661597034554413855239119887461777408` which is 201 bits! Thankfully, this also satisfies the first inequality, so we can solve Level1 with:\n\n$$\n\\texttt{in} = 2812141577453232982198433661597034554413855239119887461777408\n$$\n\n#### Level 2\n\nIn level 2, we again have two inequalities for an input `x` such that:\n\n- `3533700027045102098369050084895387317199177651876580346993442643999981568 \u003e x` (within 241 bits)\n- `-3618502782768642773547390826438087570129142810943142283802299270005870559232 \u003c x` (within 251 bits)\n\nAgain, lets make all of these use `LessThan` and with no negative numbers.\n\n- `x \u003c 3533700027045102098369050084895387317199177651876580346993442643999981568`\n- `18269740089070632448699014918819187518419221589472892059895904916569937936385 \u003c x`\n\nLooking at the second inequality, we can follow the same approach as before to find by setting `x = in[1] = in[0] + (1 \u003c\u003c n)` which gives us `x = 5897488333439202455083409550285544209858125342430750230241414742016`. Thankfully again, this number is 222 bits and satisfies the first inequality.\n\nThe challenge also required the given number to have more than 70 digits, but this number has 67 digits. If we look closely to this check within the challenge judge code, we see that its just a simple string length check. We can just prepend some zeros to our answer to keep the value same, but make it look like more than 70 digits! With that, our answer is:\n\n$$\n\\texttt{in} = 00005897488333439202455083409550285544209858125342430750230241414742016\n$$\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferhant%2Fzkctf-scalebit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferhant%2Fzkctf-scalebit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferhant%2Fzkctf-scalebit/lists"}