From 9b851faf4ae223eea688e471db5eaa2150e30b0f Mon Sep 17 00:00:00 2001 From: Joey Kraut <108701651+joeykraut@users.noreply.github.com> Date: Sat, 25 Jan 2025 11:24:51 -0800 Subject: [PATCH] crypto: poseidon2: Define huff structure and implement simple sbox (#3) * forge install: foundry-huff * crypto: poseidon2: roundUtils: Define huff structure and add sbox --- .github/workflows/test.yml | 4 ++-- .gitignore | 3 +++ .gitmodules | 3 +++ foundry.toml | 5 ++-- lib/foundry-huff | 1 + remappings.txt | 5 ++++ script/Counter.s.sol | 19 --------------- src/Counter.sol | 14 ----------- src/crypto/poseidon2/main.huff | 9 +++++++ src/crypto/poseidon2/roundUtils.huff | 30 +++++++++++++++++++++++ test/Counter.t.sol | 24 ------------------- test/Poseidon.t.sol | 35 +++++++++++++++++++++++++++ test/huff/testPoseidonUtils.huff | 36 ++++++++++++++++++++++++++++ 13 files changed, 127 insertions(+), 61 deletions(-) create mode 160000 lib/foundry-huff create mode 100644 remappings.txt delete mode 100644 script/Counter.s.sol delete mode 100644 src/Counter.sol create mode 100644 src/crypto/poseidon2/main.huff create mode 100644 src/crypto/poseidon2/roundUtils.huff delete mode 100644 test/Counter.t.sol create mode 100644 test/Poseidon.t.sol create mode 100644 test/huff/testPoseidonUtils.huff diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 762a296..7426123 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: true - name: Foundry project + name: Foundry checks runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -41,5 +41,5 @@ jobs: - name: Run Forge tests run: | - forge test -vvv + forge test --ffi -vvv id: test diff --git a/.gitignore b/.gitignore index 85198aa..478b9a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Workspace +.vscode + # Compiler files cache/ out/ diff --git a/.gitmodules b/.gitmodules index 888d42d..ec71528 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/foundry-huff"] + path = lib/foundry-huff + url = https://github.com/huff-language/foundry-huff diff --git a/foundry.toml b/foundry.toml index 25b918f..803b416 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,7 @@ [profile.default] +solc_version = "0.8.20" +evm_version = "cancun" src = "src" out = "out" libs = ["lib"] - -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +remappings = ["forge-std/=lib/forge-std/src/"] diff --git a/lib/foundry-huff b/lib/foundry-huff new file mode 160000 index 0000000..7d1ce15 --- /dev/null +++ b/lib/foundry-huff @@ -0,0 +1 @@ +Subproject commit 7d1ce15ccf92bd68458c7e28e0ae847b64b4fc74 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..50b57e3 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,5 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +foundry-huff/=lib/foundry-huff/src/ +solidity-stringutils/=lib/foundry-huff/lib/solidity-stringutils/ +stringutils/=lib/foundry-huff/lib/solidity-stringutils/ diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/crypto/poseidon2/main.huff b/src/crypto/poseidon2/main.huff new file mode 100644 index 0000000..3a491f6 --- /dev/null +++ b/src/crypto/poseidon2/main.huff @@ -0,0 +1,9 @@ +/// @title Poseidon2 +/// @author @joeykraut +/// @notice A Poseidon2 implementation in Huff. See https://eprint.iacr.org/2023/323 +/// for more details. Inspired by https://github.com/zemse/poseidon2-evm/tree/main + +#define macro MAIN() = takes(0) returns(0) { +} + + diff --git a/src/crypto/poseidon2/roundUtils.huff b/src/crypto/poseidon2/roundUtils.huff new file mode 100644 index 0000000..9387c9c --- /dev/null +++ b/src/crypto/poseidon2/roundUtils.huff @@ -0,0 +1,30 @@ +/// Helpers for Poseidon2 round functions + +/// @dev The scalar field modulus of BN254 +#define constant PRIME = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 + +/// @dev Push the prime onto the stack +#define macro PUSH_PRIME() = { + [PRIME] +} + +/// @dev Apply the sbox function to the element on top of the stack, +/// which is replaced by its fifth power mod p +/// I.e. transforms the stack [x, ...] -> [x^5 mod p, ...] +#define macro SBOX() = takes(2) returns(1) { + // Takes [x, PRIME] + // Prelude: setup primes on the stack in the order they'll be used + dup2 dup1 // [PRIME, PRIME, x, PRIME] + dup3 // [x, PRIME, PRIME, x, PRIME] + dup1 // [x, x, PRIME, PRIME, x, PRIME] + + // Compute x^2 (mod p) + mulmod // [x^2 mod p, PRIME, x, PRIME] + + // Compute x^4 (mod p) + dup1 // [x^2 mod p, x^2 mod p, PRIME, x, PRIME] + mulmod // [x^4 mod p, x, PRIME] + + // Compute x^5 (mod p) + mulmod // [x^5 mod p] +} \ No newline at end of file diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/Poseidon.t.sol b/test/Poseidon.t.sol new file mode 100644 index 0000000..1e1a90e --- /dev/null +++ b/test/Poseidon.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; +import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; + +contract PoseidonTest is Test { + /// @dev The Poseidon main contract + PoseidonSuite public poseidonSuite; + + /// @dev The BN254 field modulus from roundUtils.huff + uint256 PRIME = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + /// @dev Deploy the PoseidonSuite contract + function setUp() public { + poseidonSuite = PoseidonSuite(HuffDeployer.deploy("../test/huff/testPoseidonUtils")); + } + + /// @dev Test the sbox function applied to a single input + function testSboxSingle() public { + uint256 testValue = vm.randomUint(); + uint256 result = poseidonSuite.testSboxSingle(testValue); + + // Calculate expected x^5 mod p + uint256 x2 = mulmod(testValue, testValue, PRIME); + uint256 x4 = mulmod(x2, x2, PRIME); + uint256 expected = mulmod(testValue, x4, PRIME); + assertEq(result, expected, "Expected result to match x^5 mod p"); + } +} + +interface PoseidonSuite { + function testSboxSingle(uint256) external returns (uint256); +} diff --git a/test/huff/testPoseidonUtils.huff b/test/huff/testPoseidonUtils.huff new file mode 100644 index 0000000..e8f85bb --- /dev/null +++ b/test/huff/testPoseidonUtils.huff @@ -0,0 +1,36 @@ +/// @title Test Poseidon Utils +/// @author @joeykraut +/// @notice Test the utils for the Poseidon2 permutation + +#include "../../src/crypto/poseidon2/roundUtils.huff" + +/// @dev Test the sbox function applied to a single input +#define function testSboxSingle(uint256) nonpayable returns(uint256) + +/// @dev Entrypoint to the poseidon test suite +#define macro MAIN() = takes(0) returns(0) { + // Get the function selector + 0x0 calldataload 0xe0 shr // [SELECTOR] + __FUNC_SIG(testSboxSingle) eq testSboxSingle jumpi + + // Revert if the function selector is not valid + 0x1 0x0 mstore + 0x1 0x0 revert + + testSboxSingle: + TEST_SBOX_SINGLE() +} + +/// @notice Test the sbox function applied to a single input +#define macro TEST_SBOX_SINGLE() = takes(0) returns(0) { + // Get the input from calldata + PUSH_PRIME() + 0x04 calldataload + + // Call the sbox function + SBOX() + + // Return the result + 0x00 mstore + 0x20 0x00 return +}