From ebe7d5d6c461096107ffe0d9693e99484acd82f6 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 14:29:14 +0200 Subject: [PATCH 01/11] Add Skyscraper EVM implementation --- .gitmodules | 3 + skyscraper-evm/.github/workflows/test.yml | 40 ++++++++ skyscraper-evm/.gitignore | 14 +++ skyscraper-evm/README.md | 66 ++++++++++++ skyscraper-evm/foundry.lock | 8 ++ skyscraper-evm/foundry.toml | 6 ++ skyscraper-evm/lib/forge-std | 1 + skyscraper-evm/src/Skyscraper.sol | 120 ++++++++++++++++++++++ skyscraper-evm/test/Skyscraper.sol | 82 +++++++++++++++ 9 files changed, 340 insertions(+) create mode 100644 .gitmodules create mode 100644 skyscraper-evm/.github/workflows/test.yml create mode 100644 skyscraper-evm/.gitignore create mode 100644 skyscraper-evm/README.md create mode 100644 skyscraper-evm/foundry.lock create mode 100644 skyscraper-evm/foundry.toml create mode 160000 skyscraper-evm/lib/forge-std create mode 100644 skyscraper-evm/src/Skyscraper.sol create mode 100644 skyscraper-evm/test/Skyscraper.sol diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..c195df1d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "skyscraper-evm/lib/forge-std"] + path = skyscraper-evm/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/skyscraper-evm/.github/workflows/test.yml b/skyscraper-evm/.github/workflows/test.yml new file mode 100644 index 00000000..4481ec6a --- /dev/null +++ b/skyscraper-evm/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/skyscraper-evm/.gitignore b/skyscraper-evm/.gitignore new file mode 100644 index 00000000..85198aaa --- /dev/null +++ b/skyscraper-evm/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/skyscraper-evm/README.md b/skyscraper-evm/README.md new file mode 100644 index 00000000..8817d6ab --- /dev/null +++ b/skyscraper-evm/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/skyscraper-evm/foundry.lock b/skyscraper-evm/foundry.lock new file mode 100644 index 00000000..56436427 --- /dev/null +++ b/skyscraper-evm/foundry.lock @@ -0,0 +1,8 @@ +{ + "lib/forge-std": { + "tag": { + "name": "v1.10.0", + "rev": "8bbcf6e3f8f62f419e5429a0bd89331c85c37824" + } + } +} \ No newline at end of file diff --git a/skyscraper-evm/foundry.toml b/skyscraper-evm/foundry.toml new file mode 100644 index 00000000..25b918f9 --- /dev/null +++ b/skyscraper-evm/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/skyscraper-evm/lib/forge-std b/skyscraper-evm/lib/forge-std new file mode 160000 index 00000000..8bbcf6e3 --- /dev/null +++ b/skyscraper-evm/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 8bbcf6e3f8f62f419e5429a0bd89331c85c37824 diff --git a/skyscraper-evm/src/Skyscraper.sol b/skyscraper-evm/src/Skyscraper.sol new file mode 100644 index 00000000..b671197a --- /dev/null +++ b/skyscraper-evm/src/Skyscraper.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {console} from "forge-std/console.sol"; + +contract Skyscraper { + // BN254 field modulus + uint256 internal constant P = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + uint256 internal constant SIGMA_INV = + 9915499612839321149637521777990102151350674507940716049588462388200839649614; + + // Non-zero round constants + uint256 internal constant RC_1 = + 17829420340877239108687448009732280677191990375576158938221412342251481978692; + uint256 internal constant RC_2 = + 5852100059362614845584985098022261541909346143980691326489891671321030921585; + uint256 internal constant RC_3 = + 17048088173265532689680903955395019356591870902241717143279822196003888806966; + uint256 internal constant RC_4 = + 71577923540621522166602308362662170286605786204339342029375621502658138039; + uint256 internal constant RC_5 = + 1630526119629192105940988602003704216811347521589219909349181656165466494167; + uint256 internal constant RC_6 = + 7807402158218786806372091124904574238561123446618083586948014838053032654983; + uint256 internal constant RC_7 = + 13329560971460034925899588938593812685746818331549554971040309989641523590611; + uint256 internal constant RC_8 = + 16971509144034029782226530622087626979814683266929655790026304723118124142299; + uint256 internal constant RC_9 = + 8608910393531852188108777530736778805001620473682472554749734455948859886057; + uint256 internal constant RC_10 = + 10789906636021659141392066577070901692352605261812599600575143961478236801530; + uint256 internal constant RC_11 = + 18708129585851494907644197977764586873688181219062643217509404046560774277231; + uint256 internal constant RC_12 = + 8383317008589863184762767400375936634388677459538766150640361406080412989586; + uint256 internal constant RC_13 = + 10555553646766747611187318546907885054893417621612381305146047194084618122734; + uint256 internal constant RC_14 = + 18278062107303135832359716534360847832111250949377506216079581779892498540823; + uint256 internal constant RC_15 = + 9307964587880364850754205696017897664821998926660334400055925260019288889718; + uint256 internal constant RC_16 = + 13066217995902074168664295654459329310074418852039335279433003242098078040116; + + uint256 internal constant BYTES_MASK_LOW = + 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f; + uint256 internal constant BYTES_MASK_HIGH = + 0x8080808080808080808080808080808080808080808080808080808080808080; + + // SkyscraperV2 over Bn254 scalar field with no Montgomery factor. + // Requires l and r to be in the range [0, P-1]. + function permute( + uint256 l, + uint256 r + ) public pure returns (uint256, uint256) { + (l, r) = ss(l, r, 0, RC_1); + (l, r) = ss(l, r, RC_2, RC_3); + (l, r) = ss(l, r, RC_4, RC_5); + (l, r) = bb(l, r, RC_6, RC_7); + (l, r) = ss(l, r, RC_8, RC_9); + (l, r) = bb(l, r, RC_10, RC_11); + (l, r) = ss(l, r, RC_12, RC_13); + (l, r) = ss(l, r, RC_14, RC_15); + (l, r) = ss(l, r, RC_16, 0); + return (l, r); + } + + function ss( + uint256 l, + uint256 r, + uint256 rc_a, + uint256 rc_b + ) public pure returns (uint256, uint256) { + r = addmod( + rc_a, + addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P), + P + ); + l = addmod( + rc_b, + addmod(mulmod(mulmod(r, r, P), SIGMA_INV, P), l, P), + P + ); + return (l, r); + } + + function bb( + uint256 l, + uint256 r, + uint256 rc_a, + uint256 rc_b + ) public pure returns (uint256, uint256) { + r = addmod(rc_a, addmod(bar(l), r, P), P); + l = addmod(rc_b, addmod(bar(r), l, P), P); + return (l, r); + } + + function bar(uint256 x) public pure returns (uint256) { + return sbox((x << 128) | (x >> 128)) % P; + } + + // SWAR 32-byte parallel SBOX. + function sbox(uint256 x) public pure returns (uint256) { + uint256 x1 = rot1(x); + uint256 x2 = rot1(x1); + uint256 x3 = rot1(x2); + uint256 x4 = rot1(x3); + return x1 ^ ((~x2) & x3 & x4); + } + + // Bitwise rotate a byte left one place, rotates 32 bytes in parallel using SWAR. + function rot1(uint256 x) public pure returns (uint256) { + uint256 left = (x & BYTES_MASK_LOW) << 1; + uint256 right = (x & BYTES_MASK_HIGH) >> 7; + return left | right; + } +} diff --git a/skyscraper-evm/test/Skyscraper.sol b/skyscraper-evm/test/Skyscraper.sol new file mode 100644 index 00000000..c6367ef4 --- /dev/null +++ b/skyscraper-evm/test/Skyscraper.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {Skyscraper} from "../src/Skyscraper.sol"; +import {console} from "forge-std/console.sol"; + +contract SkyscraperTest is Test, Skyscraper { + function test_rot_1() public { + uint256 result = rot1(0x010203); + console.logBytes32(bytes32(result)); + assertEq(result, 0x020406); + } + + function test_sbox() public { + uint256 result = sbox(0xcd1783142b1e); + console.logBytes32(bytes32(result)); + assertEq(result, 0xd30e172846bc); + } + + function test_bar() public { + uint256 result = bar( + 13251711941470795978907268022756015766767985221093713388330058285942871890923 + ); + console.logBytes32(bytes32(result)); + assertEq( + result, + 8538086118276539577536391439548092640553835458646834916786764569256164366265 + ); + } + + function test_ss_2() public { + uint256 l = 11818428481613126259506041491792444971306025298632020312923851211664140080269; + uint256 r = 16089984100220651117533376273482359701319211672522891227502963383930673183481; + (uint256 l_out, uint256 r_out) = ss(l, r, RC_2, RC_3); + assertEq( + l_out, + 2897520731550929941842826131888578795995028656093850302425034320680216166225 + ); + assertEq( + r_out, + 10274752619072178425540318899508997829349102488123199431506343228471746115261 + ); + } + + function test_bb_6() public { + uint256 l = 13251711941470795978907268022756015766767985221093713388330058285942871890923; + uint256 r = 1017722258958995329580328739423576514309327442471989504101393158056883989572; + (uint256 l_out, uint256 r_out) = bb(l, r, RC_6, RC_7); + assertEq( + l_out, + 3193610555912363022088172260048956988022957239290210718020144819371540058981 + ); + assertEq( + r_out, + 17363210535454321713488811303876243393424286347736908007836172565366081010820 + ); + } + + function test_zero() public { + (uint256 l, uint256 r) = permute(0, 0); + assertEq( + l, + 5793276905781313965269111743763131906666794041798623267477617572701829069290 + ); + assertEq( + r, + 12296274483727574983376829575121280934973829438414198530604912453551798647077 + ); + } + + function test_bench_permute() public { + uint256 startGas = gasleft(); + uint256 l = 0; + uint256 r = 0; + for (uint256 i = 0; i < 1000; i++) { + (l, r) = permute(l, r); + } + uint256 gasUsed = startGas - gasleft(); + emit log_named_uint("gas per call", gasUsed / 1000); + } +} From f5c8a1b208c4093cc36451121578d47e8c21779d Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 14:37:45 +0200 Subject: [PATCH 02/11] Optimize and bench: 2588 gas for compress --- skyscraper-evm/foundry.toml | 8 ++++++++ skyscraper-evm/src/Skyscraper.sol | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/skyscraper-evm/foundry.toml b/skyscraper-evm/foundry.toml index 25b918f9..75b2c61b 100644 --- a/skyscraper-evm/foundry.toml +++ b/skyscraper-evm/foundry.toml @@ -4,3 +4,11 @@ out = "out" libs = ["lib"] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +# Enable optimizer +optimizer = true + +# How aggressively to optimize (higher = more runtime-opt, bigger bytecode) +optimizer_runs = 10000000 + +via_ir = true diff --git a/skyscraper-evm/src/Skyscraper.sol b/skyscraper-evm/src/Skyscraper.sol index b671197a..47539bf0 100644 --- a/skyscraper-evm/src/Skyscraper.sol +++ b/skyscraper-evm/src/Skyscraper.sol @@ -50,6 +50,12 @@ contract Skyscraper { uint256 internal constant BYTES_MASK_HIGH = 0x8080808080808080808080808080808080808080808080808080808080808080; + function compress(uint256 l, uint256 r) public pure returns (uint256) { + uint256 t = l; + (l, r) = permute(l, r); + return addmod(t, l, P); + } + // SkyscraperV2 over Bn254 scalar field with no Montgomery factor. // Requires l and r to be in the range [0, P-1]. function permute( From 373315a929bea15dffd6a340c9fb2827fa0b65c1 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 15:34:11 +0200 Subject: [PATCH 03/11] Optimize: 2184 gas for compress --- skyscraper-evm/src/Skyscraper.sol | 73 ++++++++++++++++++++++++------ skyscraper-evm/test/Skyscraper.sol | 48 ++++++++++++++++---- 2 files changed, 98 insertions(+), 23 deletions(-) diff --git a/skyscraper-evm/src/Skyscraper.sol b/skyscraper-evm/src/Skyscraper.sol index 47539bf0..313a57dc 100644 --- a/skyscraper-evm/src/Skyscraper.sol +++ b/skyscraper-evm/src/Skyscraper.sol @@ -45,9 +45,9 @@ contract Skyscraper { uint256 internal constant RC_16 = 13066217995902074168664295654459329310074418852039335279433003242098078040116; - uint256 internal constant BYTES_MASK_LOW = + uint256 internal constant MASK_LOW = 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f; - uint256 internal constant BYTES_MASK_HIGH = + uint256 internal constant MASK_HIGH = 0x8080808080808080808080808080808080808080808080808080808080808080; function compress(uint256 l, uint256 r) public pure returns (uint256) { @@ -64,13 +64,13 @@ contract Skyscraper { ) public pure returns (uint256, uint256) { (l, r) = ss(l, r, 0, RC_1); (l, r) = ss(l, r, RC_2, RC_3); - (l, r) = ss(l, r, RC_4, RC_5); + (l, r) = ss_reduce_l(l, r, RC_4, RC_5); (l, r) = bb(l, r, RC_6, RC_7); - (l, r) = ss(l, r, RC_8, RC_9); + (l, r) = ss_reduce_l(l, r, RC_8, RC_9); (l, r) = bb(l, r, RC_10, RC_11); (l, r) = ss(l, r, RC_12, RC_13); (l, r) = ss(l, r, RC_14, RC_15); - (l, r) = ss(l, r, RC_16, 0); + (l, r) = ss_reduce_lr(l, r, RC_16, 0); return (l, r); } @@ -79,6 +79,32 @@ contract Skyscraper { uint256 r, uint256 rc_a, uint256 rc_b + ) public pure returns (uint256, uint256) { + r = rc_a + addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P); + l = rc_b + addmod(mulmod(mulmod(r, r, P), SIGMA_INV, P), l, P); + return (l, r); + } + + function ss_reduce_l( + uint256 l, + uint256 r, + uint256 rc_a, + uint256 rc_b + ) public pure returns (uint256, uint256) { + r = rc_a + addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P); + l = addmod( + rc_b, + addmod(mulmod(mulmod(r, r, P), SIGMA_INV, P), l, P), + P + ); + return (l, r); + } + + function ss_reduce_lr( + uint256 l, + uint256 r, + uint256 rc_a, + uint256 rc_b ) public pure returns (uint256, uint256) { r = addmod( rc_a, @@ -93,34 +119,53 @@ contract Skyscraper { return (l, r); } + // Requires l to be reduced. function bb( uint256 l, uint256 r, uint256 rc_a, uint256 rc_b ) public pure returns (uint256, uint256) { - r = addmod(rc_a, addmod(bar(l), r, P), P); - l = addmod(rc_b, addmod(bar(r), l, P), P); + uint256 x = (l << 128) | (l >> 128); // Rotate left by 128 bits + uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); // Bytewise rotate left 1 + uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); + uint256 x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); + uint256 x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); + x = x1 ^ ((~x2) & x3 & x4); + r = addmod(rc_a, addmod(x, r, P), P); + + x = (r << 128) | (r >> 128); // Rotate left by 128 bits + x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); // Bytewise rotate left 1 + x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); + x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); + x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); + x = x1 ^ ((~x2) & x3 & x4); + l = rc_b + addmod(x, l, P); return (l, r); } function bar(uint256 x) public pure returns (uint256) { - return sbox((x << 128) | (x >> 128)) % P; + x = (x << 128) | (x >> 128); // Rotate left by 128 bits + uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); // Bytewise rotate left 1 + uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); + uint256 x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); + uint256 x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); + return x1 ^ ((~x2) & x3 & x4); } // SWAR 32-byte parallel SBOX. function sbox(uint256 x) public pure returns (uint256) { - uint256 x1 = rot1(x); - uint256 x2 = rot1(x1); - uint256 x3 = rot1(x2); - uint256 x4 = rot1(x3); + uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); + uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); + uint256 x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); + uint256 x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); return x1 ^ ((~x2) & x3 & x4); } // Bitwise rotate a byte left one place, rotates 32 bytes in parallel using SWAR. function rot1(uint256 x) public pure returns (uint256) { - uint256 left = (x & BYTES_MASK_LOW) << 1; - uint256 right = (x & BYTES_MASK_HIGH) >> 7; + uint256 left = (x & MASK_LOW) << 1; + uint256 right = (x & MASK_HIGH) >> 7; return left | right; } } diff --git a/skyscraper-evm/test/Skyscraper.sol b/skyscraper-evm/test/Skyscraper.sol index c6367ef4..0f110969 100644 --- a/skyscraper-evm/test/Skyscraper.sol +++ b/skyscraper-evm/test/Skyscraper.sol @@ -8,13 +8,11 @@ import {console} from "forge-std/console.sol"; contract SkyscraperTest is Test, Skyscraper { function test_rot_1() public { uint256 result = rot1(0x010203); - console.logBytes32(bytes32(result)); assertEq(result, 0x020406); } function test_sbox() public { uint256 result = sbox(0xcd1783142b1e); - console.logBytes32(bytes32(result)); assertEq(result, 0xd30e172846bc); } @@ -22,9 +20,8 @@ contract SkyscraperTest is Test, Skyscraper { uint256 result = bar( 13251711941470795978907268022756015766767985221093713388330058285942871890923 ); - console.logBytes32(bytes32(result)); assertEq( - result, + result % P, 8538086118276539577536391439548092640553835458646834916786764569256164366265 ); } @@ -34,11 +31,11 @@ contract SkyscraperTest is Test, Skyscraper { uint256 r = 16089984100220651117533376273482359701319211672522891227502963383930673183481; (uint256 l_out, uint256 r_out) = ss(l, r, RC_2, RC_3); assertEq( - l_out, + l_out % P, 2897520731550929941842826131888578795995028656093850302425034320680216166225 ); assertEq( - r_out, + r_out % P, 10274752619072178425540318899508997829349102488123199431506343228471746115261 ); } @@ -48,11 +45,11 @@ contract SkyscraperTest is Test, Skyscraper { uint256 r = 1017722258958995329580328739423576514309327442471989504101393158056883989572; (uint256 l_out, uint256 r_out) = bb(l, r, RC_6, RC_7); assertEq( - l_out, + l_out % P, 3193610555912363022088172260048956988022957239290210718020144819371540058981 ); assertEq( - r_out, + r_out % P, 17363210535454321713488811303876243393424286347736908007836172565366081010820 ); } @@ -69,14 +66,47 @@ contract SkyscraperTest is Test, Skyscraper { ); } - function test_bench_permute() public { + function test_bench_ss() public { uint256 startGas = gasleft(); uint256 l = 0; uint256 r = 0; + for (uint256 i = 0; i < 1000; i++) { + (l, r) = ss(l, r, RC_2, RC_3); + } + uint256 gasUsed = startGas - gasleft(); + emit log_named_uint("gas per call", gasUsed / 1000); + } + + function test_bench_bb() public { + uint256 startGas = gasleft(); + uint256 l = 0; + uint256 r = 0; + for (uint256 i = 0; i < 1000; i++) { + (l, r) = bb(l, r, RC_6, RC_7); + } + uint256 gasUsed = startGas - gasleft(); + emit log_named_uint("gas per call", gasUsed / 1000); + } + + function test_bench_permute() public { + uint256 startGas = gasleft(); + uint256 l = RC_9; + uint256 r = RC_12; for (uint256 i = 0; i < 1000; i++) { (l, r) = permute(l, r); } uint256 gasUsed = startGas - gasleft(); emit log_named_uint("gas per call", gasUsed / 1000); } + + function test_bench_compress() public { + uint256 startGas = gasleft(); + uint256 l = RC_5; + uint256 r = RC_8; + for (uint256 i = 0; i < 1000; i++) { + l = compress(l, r); + } + uint256 gasUsed = startGas - gasleft(); + emit log_named_uint("gas per call", gasUsed / 1000); + } } From 2364c246b37b43fe949c497564568f0352b8573b Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 15:42:21 +0200 Subject: [PATCH 04/11] Add SHA3 comparisson --- skyscraper-evm/test/Skyscraper.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/skyscraper-evm/test/Skyscraper.sol b/skyscraper-evm/test/Skyscraper.sol index 0f110969..b0e2a3a1 100644 --- a/skyscraper-evm/test/Skyscraper.sol +++ b/skyscraper-evm/test/Skyscraper.sol @@ -109,4 +109,15 @@ contract SkyscraperTest is Test, Skyscraper { uint256 gasUsed = startGas - gasleft(); emit log_named_uint("gas per call", gasUsed / 1000); } + + function test_bench_sha3() public { + uint256 startGas = gasleft(); + uint256 l = RC_5; + uint256 r = RC_8; + for (uint256 i = 0; i < 1000; i++) { + l = uint256(keccak256(abi.encodePacked(l, r))); + } + uint256 gasUsed = startGas - gasleft(); + emit log_named_uint("gas per call", gasUsed / 1000); + } } From 8dcb20bbede4d743851707e9d4030535f0a0ed33 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 16:05:11 +0200 Subject: [PATCH 05/11] Optimize: 1906 gas for compress --- skyscraper-evm/src/Skyscraper.sol | 49 +++++++++++------------------- skyscraper-evm/test/Skyscraper.sol | 4 +-- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/skyscraper-evm/src/Skyscraper.sol b/skyscraper-evm/src/Skyscraper.sol index 313a57dc..81954a77 100644 --- a/skyscraper-evm/src/Skyscraper.sol +++ b/skyscraper-evm/src/Skyscraper.sol @@ -61,7 +61,7 @@ contract Skyscraper { function permute( uint256 l, uint256 r - ) public pure returns (uint256, uint256) { + ) internal pure returns (uint256, uint256) { (l, r) = ss(l, r, 0, RC_1); (l, r) = ss(l, r, RC_2, RC_3); (l, r) = ss_reduce_l(l, r, RC_4, RC_5); @@ -70,7 +70,7 @@ contract Skyscraper { (l, r) = bb(l, r, RC_10, RC_11); (l, r) = ss(l, r, RC_12, RC_13); (l, r) = ss(l, r, RC_14, RC_15); - (l, r) = ss_reduce_lr(l, r, RC_16, 0); + (l, r) = ss(l, r, RC_16, 0); return (l, r); } @@ -79,9 +79,11 @@ contract Skyscraper { uint256 r, uint256 rc_a, uint256 rc_b - ) public pure returns (uint256, uint256) { - r = rc_a + addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P); - l = rc_b + addmod(mulmod(mulmod(r, r, P), SIGMA_INV, P), l, P); + ) internal pure returns (uint256, uint256) { + unchecked { + r = rc_a + addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P); + l = rc_b + addmod(mulmod(mulmod(r, r, P), SIGMA_INV, P), l, P); + } return (l, r); } @@ -90,27 +92,10 @@ contract Skyscraper { uint256 r, uint256 rc_a, uint256 rc_b - ) public pure returns (uint256, uint256) { - r = rc_a + addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P); - l = addmod( - rc_b, - addmod(mulmod(mulmod(r, r, P), SIGMA_INV, P), l, P), - P - ); - return (l, r); - } - - function ss_reduce_lr( - uint256 l, - uint256 r, - uint256 rc_a, - uint256 rc_b - ) public pure returns (uint256, uint256) { - r = addmod( - rc_a, - addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P), - P - ); + ) internal pure returns (uint256, uint256) { + unchecked { + r = rc_a + addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P); + } l = addmod( rc_b, addmod(mulmod(mulmod(r, r, P), SIGMA_INV, P), l, P), @@ -125,7 +110,7 @@ contract Skyscraper { uint256 r, uint256 rc_a, uint256 rc_b - ) public pure returns (uint256, uint256) { + ) internal pure returns (uint256, uint256) { uint256 x = (l << 128) | (l >> 128); // Rotate left by 128 bits uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); // Bytewise rotate left 1 uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); @@ -140,11 +125,13 @@ contract Skyscraper { x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); x = x1 ^ ((~x2) & x3 & x4); - l = rc_b + addmod(x, l, P); + unchecked { + l = rc_b + addmod(x, l, P); + } return (l, r); } - function bar(uint256 x) public pure returns (uint256) { + function bar(uint256 x) internal pure returns (uint256) { x = (x << 128) | (x >> 128); // Rotate left by 128 bits uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); // Bytewise rotate left 1 uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); @@ -154,7 +141,7 @@ contract Skyscraper { } // SWAR 32-byte parallel SBOX. - function sbox(uint256 x) public pure returns (uint256) { + function sbox(uint256 x) internal pure returns (uint256) { uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); uint256 x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); @@ -163,7 +150,7 @@ contract Skyscraper { } // Bitwise rotate a byte left one place, rotates 32 bytes in parallel using SWAR. - function rot1(uint256 x) public pure returns (uint256) { + function rot1(uint256 x) internal pure returns (uint256) { uint256 left = (x & MASK_LOW) << 1; uint256 right = (x & MASK_HIGH) >> 7; return left | right; diff --git a/skyscraper-evm/test/Skyscraper.sol b/skyscraper-evm/test/Skyscraper.sol index b0e2a3a1..e02670c8 100644 --- a/skyscraper-evm/test/Skyscraper.sol +++ b/skyscraper-evm/test/Skyscraper.sol @@ -57,11 +57,11 @@ contract SkyscraperTest is Test, Skyscraper { function test_zero() public { (uint256 l, uint256 r) = permute(0, 0); assertEq( - l, + l % P, 5793276905781313965269111743763131906666794041798623267477617572701829069290 ); assertEq( - r, + r % P, 12296274483727574983376829575121280934973829438414198530604912453551798647077 ); } From 7a12f00c297f2e2b6eb16bf83ea824ac594da4e8 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 16:11:13 +0200 Subject: [PATCH 06/11] Without sigma: 1665 gas for compress --- skyscraper-evm/src/Skyscraper.sol | 55 +++++++++++++++++++++++++++++- skyscraper-evm/test/Skyscraper.sol | 27 ++++++++++----- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/skyscraper-evm/src/Skyscraper.sol b/skyscraper-evm/src/Skyscraper.sol index 81954a77..8cbc45c1 100644 --- a/skyscraper-evm/src/Skyscraper.sol +++ b/skyscraper-evm/src/Skyscraper.sol @@ -56,6 +56,15 @@ contract Skyscraper { return addmod(t, l, P); } + function compress_sigma( + uint256 l, + uint256 r + ) public pure returns (uint256) { + uint256 t = l; + (l, r) = permute_sigma(l, r); + return addmod(t, l, P); + } + // SkyscraperV2 over Bn254 scalar field with no Montgomery factor. // Requires l and r to be in the range [0, P-1]. function permute( @@ -74,11 +83,55 @@ contract Skyscraper { return (l, r); } + // SkyscraperV2 over Bn254 scalar field with Montgomery factor. + // Requires l and r to be in the range [0, P-1]. + function permute_sigma( + uint256 l, + uint256 r + ) internal pure returns (uint256, uint256) { + (l, r) = sss(l, r, 0, RC_1); + (l, r) = sss(l, r, RC_2, RC_3); + (l, r) = sss_reduce_l(l, r, RC_4, RC_5); + (l, r) = bb(l, r, RC_6, RC_7); + (l, r) = sss_reduce_l(l, r, RC_8, RC_9); + (l, r) = bb(l, r, RC_10, RC_11); + (l, r) = sss(l, r, RC_12, RC_13); + (l, r) = sss(l, r, RC_14, RC_15); + (l, r) = sss(l, r, RC_16, 0); + return (l, r); + } + function ss( uint256 l, uint256 r, uint256 rc_a, uint256 rc_b + ) internal pure returns (uint256, uint256) { + unchecked { + r = rc_a + addmod(mulmod(l, l, P), r, P); + l = rc_b + addmod(mulmod(r, r, P), l, P); + } + return (l, r); + } + + function ss_reduce_l( + uint256 l, + uint256 r, + uint256 rc_a, + uint256 rc_b + ) internal pure returns (uint256, uint256) { + unchecked { + r = rc_a + addmod(mulmod(l, l, P), r, P); + } + l = addmod(rc_b, addmod(mulmod(r, r, P), l, P), P); + return (l, r); + } + + function sss( + uint256 l, + uint256 r, + uint256 rc_a, + uint256 rc_b ) internal pure returns (uint256, uint256) { unchecked { r = rc_a + addmod(mulmod(mulmod(l, l, P), SIGMA_INV, P), r, P); @@ -87,7 +140,7 @@ contract Skyscraper { return (l, r); } - function ss_reduce_l( + function sss_reduce_l( uint256 l, uint256 r, uint256 rc_a, diff --git a/skyscraper-evm/test/Skyscraper.sol b/skyscraper-evm/test/Skyscraper.sol index e02670c8..21042bdd 100644 --- a/skyscraper-evm/test/Skyscraper.sol +++ b/skyscraper-evm/test/Skyscraper.sol @@ -29,7 +29,7 @@ contract SkyscraperTest is Test, Skyscraper { function test_ss_2() public { uint256 l = 11818428481613126259506041491792444971306025298632020312923851211664140080269; uint256 r = 16089984100220651117533376273482359701319211672522891227502963383930673183481; - (uint256 l_out, uint256 r_out) = ss(l, r, RC_2, RC_3); + (uint256 l_out, uint256 r_out) = sss(l, r, RC_2, RC_3); assertEq( l_out % P, 2897520731550929941842826131888578795995028656093850302425034320680216166225 @@ -55,7 +55,7 @@ contract SkyscraperTest is Test, Skyscraper { } function test_zero() public { - (uint256 l, uint256 r) = permute(0, 0); + (uint256 l, uint256 r) = permute_sigma(0, 0); assertEq( l % P, 5793276905781313965269111743763131906666794041798623267477617572701829069290 @@ -77,23 +77,23 @@ contract SkyscraperTest is Test, Skyscraper { emit log_named_uint("gas per call", gasUsed / 1000); } - function test_bench_bb() public { + function test_bench_ss_sigma() public { uint256 startGas = gasleft(); uint256 l = 0; uint256 r = 0; for (uint256 i = 0; i < 1000; i++) { - (l, r) = bb(l, r, RC_6, RC_7); + (l, r) = sss(l, r, RC_2, RC_3); } uint256 gasUsed = startGas - gasleft(); emit log_named_uint("gas per call", gasUsed / 1000); } - function test_bench_permute() public { + function test_bench_bb() public { uint256 startGas = gasleft(); - uint256 l = RC_9; - uint256 r = RC_12; + uint256 l = 0; + uint256 r = 0; for (uint256 i = 0; i < 1000; i++) { - (l, r) = permute(l, r); + (l, r) = bb(l, r, RC_6, RC_7); } uint256 gasUsed = startGas - gasleft(); emit log_named_uint("gas per call", gasUsed / 1000); @@ -110,6 +110,17 @@ contract SkyscraperTest is Test, Skyscraper { emit log_named_uint("gas per call", gasUsed / 1000); } + function test_bench_compress_sigma() public { + uint256 startGas = gasleft(); + uint256 l = RC_5; + uint256 r = RC_8; + for (uint256 i = 0; i < 1000; i++) { + l = compress_sigma(l, r); + } + uint256 gasUsed = startGas - gasleft(); + emit log_named_uint("gas per call", gasUsed / 1000); + } + function test_bench_sha3() public { uint256 startGas = gasleft(); uint256 l = RC_5; From b31318a0b3f6173d50f5dafe977cb880877aa367 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 16:40:56 +0200 Subject: [PATCH 07/11] Add Readme --- skyscraper-evm/.github/workflows/test.yml | 40 ------------- skyscraper-evm/README.md | 71 ++++------------------- 2 files changed, 11 insertions(+), 100 deletions(-) delete mode 100644 skyscraper-evm/.github/workflows/test.yml diff --git a/skyscraper-evm/.github/workflows/test.yml b/skyscraper-evm/.github/workflows/test.yml deleted file mode 100644 index 4481ec6a..00000000 --- a/skyscraper-evm/.github/workflows/test.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: CI - -on: - push: - pull_request: - workflow_dispatch: - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Show Forge version - run: | - forge --version - - - name: Run Forge fmt - run: | - forge fmt --check - id: fmt - - - name: Run Forge build - run: | - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test diff --git a/skyscraper-evm/README.md b/skyscraper-evm/README.md index 8817d6ab..37aad853 100644 --- a/skyscraper-evm/README.md +++ b/skyscraper-evm/README.md @@ -1,66 +1,17 @@ -## Foundry +## Skyscraper in EVM -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +This is an optimized EVM implementation of SkyscraperV2 [0]. -Foundry consists of: +It comes in two flavors: `compress` and `compress_sigma`. The former has $σ = 1$ and the latter sets $σ$ to the value typical for Montgomery multiplication (which gives better native performance). -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +The gas costs are approximately 1665 and 1906 gas respectively. -## Documentation +* Skyscraper: 1665 gas. +* PosseidonV2: 14,934 gas [1] +* Posseidon: 13,488 gas [2] -https://book.getfoundry.sh/ +Analysis of the EVM assembly code shows that there is at most around 200 gas that can be optimized away with manual stack management. Despite not using inline assembly and manual inlining, the current implementation is already very close to the theoretical minimum gas cost when compiled with optimizations. -## Usage - -### Build - -```shell -$ forge build -``` - -### Test - -```shell -$ forge test -``` - -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +[0]: https://eprint.iacr.org/2025/058 +[1]: https://github.com/zemse/poseidon2-evm +[2]: https://github.com/chancehudson/poseidon-solidity From b90dabeedeac0c232c6f51438afdadea39a7e1ca Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 16:52:29 +0200 Subject: [PATCH 08/11] Add comparison hashes --- skyscraper-evm/README.md | 8 ++++++-- skyscraper-evm/test/Skyscraper.sol | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/skyscraper-evm/README.md b/skyscraper-evm/README.md index 37aad853..6ef524a5 100644 --- a/skyscraper-evm/README.md +++ b/skyscraper-evm/README.md @@ -4,11 +4,15 @@ This is an optimized EVM implementation of SkyscraperV2 [0]. It comes in two flavors: `compress` and `compress_sigma`. The former has $σ = 1$ and the latter sets $σ$ to the value typical for Montgomery multiplication (which gives better native performance). -The gas costs are approximately 1665 and 1906 gas respectively. +The gas costs are approximately 1665 and 1906 gas respectively. Compare with other 64 byte to 32 byte hash functions: -* Skyscraper: 1665 gas. +* SkyscraperV2: 1665 gas. +* SkyscraperV2 native friendly: 1906 gas. * PosseidonV2: 14,934 gas [1] * Posseidon: 13,488 gas [2] +* Keccak256: 266 gas +* Sha256: 495 gas +* Ripemd160: 1263 gas Analysis of the EVM assembly code shows that there is at most around 200 gas that can be optimized away with manual stack management. Despite not using inline assembly and manual inlining, the current implementation is already very close to the theoretical minimum gas cost when compiled with optimizations. diff --git a/skyscraper-evm/test/Skyscraper.sol b/skyscraper-evm/test/Skyscraper.sol index 21042bdd..5e1c1161 100644 --- a/skyscraper-evm/test/Skyscraper.sol +++ b/skyscraper-evm/test/Skyscraper.sol @@ -131,4 +131,26 @@ contract SkyscraperTest is Test, Skyscraper { uint256 gasUsed = startGas - gasleft(); emit log_named_uint("gas per call", gasUsed / 1000); } + + function test_bench_sha256() public { + uint256 startGas = gasleft(); + uint256 l = RC_5; + uint256 r = RC_8; + for (uint256 i = 0; i < 1000; i++) { + l = uint256(sha256(abi.encodePacked(l, r))); + } + uint256 gasUsed = startGas - gasleft(); + emit log_named_uint("gas per call", gasUsed / 1000); + } + + function test_bench_ripemd160() public { + uint256 startGas = gasleft(); + uint256 l = RC_5; + uint256 r = RC_8; + for (uint256 i = 0; i < 1000; i++) { + l = uint256(bytes32(ripemd160(abi.encodePacked(l, r)))); + } + uint256 gasUsed = startGas - gasleft(); + emit log_named_uint("gas per call", gasUsed / 1000); + } } From 247f735644bc0fd568759399a611514ead2c4884 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 17:06:45 +0200 Subject: [PATCH 09/11] Fix Posseidon number --- skyscraper-evm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skyscraper-evm/README.md b/skyscraper-evm/README.md index 6ef524a5..b0ab21ec 100644 --- a/skyscraper-evm/README.md +++ b/skyscraper-evm/README.md @@ -9,7 +9,7 @@ The gas costs are approximately 1665 and 1906 gas respectively. Compare with oth * SkyscraperV2: 1665 gas. * SkyscraperV2 native friendly: 1906 gas. * PosseidonV2: 14,934 gas [1] -* Posseidon: 13,488 gas [2] +* Posseidon: 21,124 gas [2] * Keccak256: 266 gas * Sha256: 495 gas * Ripemd160: 1263 gas From 0db138446dec19ec0fbf7468397e6eab51a0e27b Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 17:29:59 +0200 Subject: [PATCH 10/11] Optimize: 1557 gas for compress --- skyscraper-evm/README.md | 4 +-- skyscraper-evm/src/Skyscraper.sol | 58 ++++++++++++++++++------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/skyscraper-evm/README.md b/skyscraper-evm/README.md index b0ab21ec..ddefdcca 100644 --- a/skyscraper-evm/README.md +++ b/skyscraper-evm/README.md @@ -6,8 +6,8 @@ It comes in two flavors: `compress` and `compress_sigma`. The former has $σ = 1 The gas costs are approximately 1665 and 1906 gas respectively. Compare with other 64 byte to 32 byte hash functions: -* SkyscraperV2: 1665 gas. -* SkyscraperV2 native friendly: 1906 gas. +* SkyscraperV2: 1557 gas. +* SkyscraperV2 native friendly: 1798 gas. * PosseidonV2: 14,934 gas [1] * Posseidon: 21,124 gas [2] * Keccak256: 266 gas diff --git a/skyscraper-evm/src/Skyscraper.sol b/skyscraper-evm/src/Skyscraper.sol index 8cbc45c1..103da7e2 100644 --- a/skyscraper-evm/src/Skyscraper.sol +++ b/skyscraper-evm/src/Skyscraper.sol @@ -45,10 +45,18 @@ contract Skyscraper { uint256 internal constant RC_16 = 13066217995902074168664295654459329310074418852039335279433003242098078040116; - uint256 internal constant MASK_LOW = + uint256 internal constant MASK_L1 = 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f; - uint256 internal constant MASK_HIGH = + uint256 internal constant MASK_H1 = 0x8080808080808080808080808080808080808080808080808080808080808080; + uint256 internal constant MASK_L2 = + 0x3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F; + uint256 internal constant MASK_H2 = + 0xC0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0; + uint256 internal constant MASK_L3 = + 0x1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F; + uint256 internal constant MASK_H3 = + 0xE0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0; function compress(uint256 l, uint256 r) public pure returns (uint256) { uint256 t = l; @@ -165,19 +173,19 @@ contract Skyscraper { uint256 rc_b ) internal pure returns (uint256, uint256) { uint256 x = (l << 128) | (l >> 128); // Rotate left by 128 bits - uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); // Bytewise rotate left 1 - uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); - uint256 x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); - uint256 x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); - x = x1 ^ ((~x2) & x3 & x4); + uint256 x1 = ((x & MASK_L1) << 1) | ((x & MASK_H1) >> 7); // Bytewise rotate left 1 + uint256 x2 = ((x1 & MASK_L1) << 1) | ((x1 & MASK_H1) >> 7); + uint256 x3 = x1 & x2; + uint256 x4 = ((x3 & MASK_L2) << 2) | ((x3 & MASK_H2) >> 6); + x = x1 ^ ((~x2) & x4); r = addmod(rc_a, addmod(x, r, P), P); x = (r << 128) | (r >> 128); // Rotate left by 128 bits - x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); // Bytewise rotate left 1 - x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); - x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); - x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); - x = x1 ^ ((~x2) & x3 & x4); + x1 = ((x & MASK_L1) << 1) | ((x & MASK_H1) >> 7); // Bytewise rotate left 1 + x2 = ((x1 & MASK_L1) << 1) | ((x1 & MASK_H1) >> 7); + x3 = x1 & x2; + x4 = ((x3 & MASK_L2) << 2) | ((x3 & MASK_H2) >> 6); + x = x1 ^ ((~x2) & x4); unchecked { l = rc_b + addmod(x, l, P); } @@ -186,26 +194,28 @@ contract Skyscraper { function bar(uint256 x) internal pure returns (uint256) { x = (x << 128) | (x >> 128); // Rotate left by 128 bits - uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); // Bytewise rotate left 1 - uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); - uint256 x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); - uint256 x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); - return x1 ^ ((~x2) & x3 & x4); + uint256 x1 = ((x & MASK_L1) << 1) | ((x & MASK_H1) >> 7); // Bytewise rotate left 1 + uint256 x2 = ((x1 & MASK_L1) << 1) | ((x1 & MASK_H1) >> 7); + uint256 x3 = x1 & x2; + uint256 x4 = ((x3 & MASK_L2) << 2) | ((x3 & MASK_H2) >> 6); + return x1 ^ ((~x2) & x4); } // SWAR 32-byte parallel SBOX. function sbox(uint256 x) internal pure returns (uint256) { - uint256 x1 = ((x & MASK_LOW) << 1) | ((x & MASK_HIGH) >> 7); - uint256 x2 = ((x1 & MASK_LOW) << 1) | ((x1 & MASK_HIGH) >> 7); - uint256 x3 = ((x2 & MASK_LOW) << 1) | ((x2 & MASK_HIGH) >> 7); - uint256 x4 = ((x3 & MASK_LOW) << 1) | ((x3 & MASK_HIGH) >> 7); - return x1 ^ ((~x2) & x3 & x4); + uint256 x1 = ((x & MASK_L1) << 1) | ((x & MASK_H1) >> 7); + uint256 x2 = ((x1 & MASK_L1) << 1) | ((x1 & MASK_H1) >> 7); + + uint256 t = x & x1; + t = ((t & MASK_L3) << 3) | ((t & MASK_H3) >> 5); + + return x1 ^ ((~x2) & t); } // Bitwise rotate a byte left one place, rotates 32 bytes in parallel using SWAR. function rot1(uint256 x) internal pure returns (uint256) { - uint256 left = (x & MASK_LOW) << 1; - uint256 right = (x & MASK_HIGH) >> 7; + uint256 left = (x & MASK_L1) << 1; + uint256 right = (x & MASK_H1) >> 7; return left | right; } } From 96916d92d8beddd6508b94ec37f0b2c9bb4efe6b Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Aug 2025 20:22:08 +0200 Subject: [PATCH 11/11] Cleanup --- skyscraper-evm/src/Skyscraper.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/skyscraper-evm/src/Skyscraper.sol b/skyscraper-evm/src/Skyscraper.sol index 103da7e2..a21b7eb7 100644 --- a/skyscraper-evm/src/Skyscraper.sol +++ b/skyscraper-evm/src/Skyscraper.sol @@ -197,7 +197,7 @@ contract Skyscraper { uint256 x1 = ((x & MASK_L1) << 1) | ((x & MASK_H1) >> 7); // Bytewise rotate left 1 uint256 x2 = ((x1 & MASK_L1) << 1) | ((x1 & MASK_H1) >> 7); uint256 x3 = x1 & x2; - uint256 x4 = ((x3 & MASK_L2) << 2) | ((x3 & MASK_H2) >> 6); + uint256 x4 = ((x3 & MASK_L2) << 2) | ((x3 & MASK_H2) >> 6); // Bytewise rotate left 2 return x1 ^ ((~x2) & x4); } @@ -205,10 +205,8 @@ contract Skyscraper { function sbox(uint256 x) internal pure returns (uint256) { uint256 x1 = ((x & MASK_L1) << 1) | ((x & MASK_H1) >> 7); uint256 x2 = ((x1 & MASK_L1) << 1) | ((x1 & MASK_H1) >> 7); - uint256 t = x & x1; t = ((t & MASK_L3) << 3) | ((t & MASK_H3) >> 5); - return x1 ^ ((~x2) & t); }