Skip to content

Commit

Permalink
feat: Adds Modexp function for modular exponentiation support in the …
Browse files Browse the repository at this point in the history
…library
  • Loading branch information
mw2000 committed Apr 22, 2024
1 parent b625437 commit 6c23e65
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 2 deletions.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
src = 'src'
out = 'artifacts'
libs = ["node_modules", "lib"]
evm_version = "shanghai"
ffi=true
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
Empty file modified hufftest.sh
100644 → 100755
Empty file.
43 changes: 42 additions & 1 deletion src/Math.huff
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,45 @@
complete:
}


#define macro MODEXP() = takes (3) returns (1) {
dup1
iszero MODULUS_ZERO jumpi // Compare it to zero
MODULUS_NOT_ZERO jump

MODULUS_NOT_ZERO:
// Setting up correct memory layout
0x20 0x00 mstore // Store length of base (32 bytes) at memory 0x00
0x20 0x20 mstore // Store length of exponent (32 bytes) at memory 0x40
0x20 0x40 mstore // Store length of modulus (32 bytes) at memory 0x80

swap2 // Bring 'base' to top
0x60 mstore // Store 'base' at memory 0x20
swap1 // Bring 'exponent' to top
0x80 mstore // Store 'exponent' at memory 0x60
0xa0 mstore // Store 'modulus' at memory 0xa0

// Prepare staticcall to precompiled contract
gas // Provide all available gas for the call
0x05 // Address of the modexp precompiled contract (0x05)
0x00 0xc0 // Start of input data in memory (0x00) and input data size (192 bytes)
0x00 0x20 // Start of output data in memory (0x00) and output data size (32 bytes)
staticcall

// Verify the call success
iszero STATIC_CALL_FAILED jump // Check if the staticcall was successful
CALL_SUCCESSFUL jump // If staticcall returned 0 (failure), revert

MODULUS_ZERO:
0x00 0x00 revert // Revert if modulus is zero

STATIC_CALL_FAILED:
0x00 0x00 revert // Revert if staticcall failed

// Label for call success
CALL_SUCCESSFUL:
0x00 mload // Load the result from memory location 0x00
END jump // Jump to the end of the macro

END:
// The result of base^exponent % modulus is now on top of the stack
}
2 changes: 2 additions & 0 deletions src/interfaces/IMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ interface IMath {
function divideNumbers(uint256, uint256) external view returns (uint256);

function abs(uint256, uint256) external view returns (uint256);

function modExp(uint256, uint256, uint256) external view returns (uint256);
}
14 changes: 13 additions & 1 deletion src/wrappers/MathWrapper.huff
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define function multiplyNumbers(uint256,uint256) nonpayable returns (uint256)
#define function divideNumbers(uint256,uint256) nonpayable returns (uint256)
#define function abs(uint256,uint256) nonpayable returns (uint256)
#define function modExp(uint256,uint256,uint256) nonpayable returns (uint256)

#define macro ADD_WRAPPER() = takes (2) returns (1) {
0x04 calldataload // [num1]
Expand Down Expand Up @@ -47,7 +48,14 @@
0x20 0x00 return // []
}


#define macro MODEXP_WRAPPER() = takes (3) returns (1) {
0x04 calldataload // [base]
0x24 calldataload // [exponent, base]
0x44 calldataload // [modulus, exponent, base]
MODEXP() // [base^exponent mod modulus]
0x00 mstore // []
0x20 0x00 return // []
}


#define macro MAIN() = takes (0) returns (0) {
Expand All @@ -63,6 +71,7 @@
dup1 0xd3f3cd7b eq multiplyNumbers jumpi
dup1 0x8fce12ed eq divideNumbers jumpi
dup1 0xe093a157 eq abs jumpi
dup1 0x3148f14f eq modExp jumpi


addNumbers:
Expand All @@ -76,6 +85,9 @@

divideNumbers:
DIVIDE_WRAPPER()

modExp:
MODEXP_WRAPPER()

abs:
ABS_WRAPPER()
Expand Down
25 changes: 25 additions & 0 deletions test/foundry/Math.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,29 @@ contract MathTest is Test {
uint256 _result = a > b ? a - b : b - a;
require(math.abs(a, b) == _result);
}

function testModExp() public {
// Example test: 2^3 % 5 should equal 3
uint256 base = 10;
uint256 exponent = 3;
uint256 modulus = 13;
uint256 expected = 12;

uint256 result = math.modExp(base, exponent, modulus);
assertEq(result, expected, "modExp did not return the expected value");
}

function testModExp_fuzz(uint256 b, uint256 e, uint256 m) public {
// To avoid testing with modulus zero, which would revert
vm.assume(m > 1);
// To avoid gas issues, cap the exponent
uint256 exponent = e % 256;

// The actual modExp calculation can be complicated to emulate in Solidity due to gas constraints,
// so here we just test that the function does not revert and returns a value
// less than the modulus.
uint256 result = math.modExp(b, exponent, m);

assertLt(result, m, "modExp result should be less than the modulus");
}
}
11 changes: 11 additions & 0 deletions test/foundry/MathForkTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,15 @@ contract MathForkTest is Test {
function testAbs() public view {
require(math.abs(1, 10) == 9);
}

function testModExp() public {
// Example test: 2^3 % 5 should equal 3
uint256 base = 2;
uint256 exponent = 3;
uint256 modulus = 5;
uint256 expected = 3;

uint256 result = math.modExp(base, exponent, modulus);
assertEq(result, expected, "modExp did not return the expected value");
}
}
21 changes: 21 additions & 0 deletions test/huff/Math.t.huff
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,24 @@
ASSERT_EQ() // [4e18==result]
}

#define test TEST_MODEXP() = {
// Test case 1: Simple modular exponentiation
// Using small numbers for easy manual verification: 2^3 % 5 = 3
0x05 // [modulus = 5]
0x03 // [exponent = 3, modulus]
0x02 // [base = 2, exponent, modulus]
MODEXP() // [result]
0x03 // [expected = 3, result]
ASSERT_EQ() // [3 == result]

// Test case 2: Larger numbers
// We need to choose numbers such that we can calculate the expected result manually or with a tool
// For example: (0x04)^2 % 0x05 = 0x01
0x05 // [modulus = 5]
0x02 // [exponent = 2, modulus]
0x04 // [base = 4, exponent, modulus]
MODEXP() // [result]
0x01 // [expected = 4, result]
ASSERT_EQ() // [4 == result]
}

0 comments on commit 6c23e65

Please sign in to comment.