Skip to content

Commit 6c23e65

Browse files
committed
feat: Adds Modexp function for modular exponentiation support in the library
1 parent b625437 commit 6c23e65

File tree

8 files changed

+115
-2
lines changed

8 files changed

+115
-2
lines changed

foundry.toml

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
src = 'src'
33
out = 'artifacts'
44
libs = ["node_modules", "lib"]
5+
evm_version = "shanghai"
56
ffi=true
67
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

hufftest.sh

100644100755
File mode changed.

src/Math.huff

+42-1
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,45 @@
4242
complete:
4343
}
4444

45-
45+
#define macro MODEXP() = takes (3) returns (1) {
46+
dup1
47+
iszero MODULUS_ZERO jumpi // Compare it to zero
48+
MODULUS_NOT_ZERO jump
49+
50+
MODULUS_NOT_ZERO:
51+
// Setting up correct memory layout
52+
0x20 0x00 mstore // Store length of base (32 bytes) at memory 0x00
53+
0x20 0x20 mstore // Store length of exponent (32 bytes) at memory 0x40
54+
0x20 0x40 mstore // Store length of modulus (32 bytes) at memory 0x80
55+
56+
swap2 // Bring 'base' to top
57+
0x60 mstore // Store 'base' at memory 0x20
58+
swap1 // Bring 'exponent' to top
59+
0x80 mstore // Store 'exponent' at memory 0x60
60+
0xa0 mstore // Store 'modulus' at memory 0xa0
61+
62+
// Prepare staticcall to precompiled contract
63+
gas // Provide all available gas for the call
64+
0x05 // Address of the modexp precompiled contract (0x05)
65+
0x00 0xc0 // Start of input data in memory (0x00) and input data size (192 bytes)
66+
0x00 0x20 // Start of output data in memory (0x00) and output data size (32 bytes)
67+
staticcall
68+
69+
// Verify the call success
70+
iszero STATIC_CALL_FAILED jump // Check if the staticcall was successful
71+
CALL_SUCCESSFUL jump // If staticcall returned 0 (failure), revert
72+
73+
MODULUS_ZERO:
74+
0x00 0x00 revert // Revert if modulus is zero
75+
76+
STATIC_CALL_FAILED:
77+
0x00 0x00 revert // Revert if staticcall failed
78+
79+
// Label for call success
80+
CALL_SUCCESSFUL:
81+
0x00 mload // Load the result from memory location 0x00
82+
END jump // Jump to the end of the macro
83+
84+
END:
85+
// The result of base^exponent % modulus is now on top of the stack
86+
}

src/interfaces/IMath.sol

+2
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ interface IMath {
1212
function divideNumbers(uint256, uint256) external view returns (uint256);
1313

1414
function abs(uint256, uint256) external view returns (uint256);
15+
16+
function modExp(uint256, uint256, uint256) external view returns (uint256);
1517
}

src/wrappers/MathWrapper.huff

+13-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define function multiplyNumbers(uint256,uint256) nonpayable returns (uint256)
77
#define function divideNumbers(uint256,uint256) nonpayable returns (uint256)
88
#define function abs(uint256,uint256) nonpayable returns (uint256)
9+
#define function modExp(uint256,uint256,uint256) nonpayable returns (uint256)
910

1011
#define macro ADD_WRAPPER() = takes (2) returns (1) {
1112
0x04 calldataload // [num1]
@@ -47,7 +48,14 @@
4748
0x20 0x00 return // []
4849
}
4950

50-
51+
#define macro MODEXP_WRAPPER() = takes (3) returns (1) {
52+
0x04 calldataload // [base]
53+
0x24 calldataload // [exponent, base]
54+
0x44 calldataload // [modulus, exponent, base]
55+
MODEXP() // [base^exponent mod modulus]
56+
0x00 mstore // []
57+
0x20 0x00 return // []
58+
}
5159

5260

5361
#define macro MAIN() = takes (0) returns (0) {
@@ -63,6 +71,7 @@
6371
dup1 0xd3f3cd7b eq multiplyNumbers jumpi
6472
dup1 0x8fce12ed eq divideNumbers jumpi
6573
dup1 0xe093a157 eq abs jumpi
74+
dup1 0x3148f14f eq modExp jumpi
6675

6776

6877
addNumbers:
@@ -76,6 +85,9 @@
7685

7786
divideNumbers:
7887
DIVIDE_WRAPPER()
88+
89+
modExp:
90+
MODEXP_WRAPPER()
7991

8092
abs:
8193
ABS_WRAPPER()

test/foundry/Math.t.sol

+25
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,29 @@ contract MathTest is Test {
9393
uint256 _result = a > b ? a - b : b - a;
9494
require(math.abs(a, b) == _result);
9595
}
96+
97+
function testModExp() public {
98+
// Example test: 2^3 % 5 should equal 3
99+
uint256 base = 10;
100+
uint256 exponent = 3;
101+
uint256 modulus = 13;
102+
uint256 expected = 12;
103+
104+
uint256 result = math.modExp(base, exponent, modulus);
105+
assertEq(result, expected, "modExp did not return the expected value");
106+
}
107+
108+
function testModExp_fuzz(uint256 b, uint256 e, uint256 m) public {
109+
// To avoid testing with modulus zero, which would revert
110+
vm.assume(m > 1);
111+
// To avoid gas issues, cap the exponent
112+
uint256 exponent = e % 256;
113+
114+
// The actual modExp calculation can be complicated to emulate in Solidity due to gas constraints,
115+
// so here we just test that the function does not revert and returns a value
116+
// less than the modulus.
117+
uint256 result = math.modExp(b, exponent, m);
118+
119+
assertLt(result, m, "modExp result should be less than the modulus");
120+
}
96121
}

test/foundry/MathForkTest.t.sol

+11
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,15 @@ contract MathForkTest is Test {
3636
function testAbs() public view {
3737
require(math.abs(1, 10) == 9);
3838
}
39+
40+
function testModExp() public {
41+
// Example test: 2^3 % 5 should equal 3
42+
uint256 base = 2;
43+
uint256 exponent = 3;
44+
uint256 modulus = 5;
45+
uint256 expected = 3;
46+
47+
uint256 result = math.modExp(base, exponent, modulus);
48+
assertEq(result, expected, "modExp did not return the expected value");
49+
}
3950
}

test/huff/Math.t.huff

+21
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,24 @@
7575
ASSERT_EQ() // [4e18==result]
7676
}
7777

78+
#define test TEST_MODEXP() = {
79+
// Test case 1: Simple modular exponentiation
80+
// Using small numbers for easy manual verification: 2^3 % 5 = 3
81+
0x05 // [modulus = 5]
82+
0x03 // [exponent = 3, modulus]
83+
0x02 // [base = 2, exponent, modulus]
84+
MODEXP() // [result]
85+
0x03 // [expected = 3, result]
86+
ASSERT_EQ() // [3 == result]
87+
88+
// Test case 2: Larger numbers
89+
// We need to choose numbers such that we can calculate the expected result manually or with a tool
90+
// For example: (0x04)^2 % 0x05 = 0x01
91+
0x05 // [modulus = 5]
92+
0x02 // [exponent = 2, modulus]
93+
0x04 // [base = 4, exponent, modulus]
94+
MODEXP() // [result]
95+
0x01 // [expected = 4, result]
96+
ASSERT_EQ() // [4 == result]
97+
}
98+

0 commit comments

Comments
 (0)