Created
December 16, 2024 11:47
-
-
Save darkerego/091894709357390135bae71e676a10d7 to your computer and use it in GitHub Desktop.
Obfuscated Forwarder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.20; | |
contract Forwarder { | |
// admin of the contract | |
address private immutable owner; | |
// Some randomness to ensure that each deployment has unique bytecode | |
bytes32 public immutable entropy = bytes32(keccak256(abi.encode(uint256(uint160(block.timestamp ^ (block.number / block.timestamp)))))); | |
// this key is set at deploy time and never changes | |
bytes32 private immutable staticKey; | |
// this key can be updated | |
bytes32 private dynamicKey; | |
// encrpypted destination address for forwarding | |
bytes32 private encryptedDest; | |
error Unauthorized(); | |
error SetFirst(); | |
error AlreadySet(); | |
error ForwardFailed(bytes returnData); | |
constructor(bytes32 initialDynamicKey){ | |
(staticKey, dynamicKey, owner) = (keccak256(abi.encode(getBytecode(address(this)))), initialDynamicKey, msg.sender); | |
} | |
/* | |
@dev return true if caller is admin | |
*/ | |
function authorizedCaller() internal view returns (bool) { | |
return msg.sender == owner; | |
} | |
/* | |
@dev Revert if caller is not admin | |
*/ | |
modifier auth { | |
if (! authorizedCaller()) { | |
revert Unauthorized(); | |
} | |
_; | |
} | |
/* | |
@dev Set encrypted destination address for forwarding | |
*/ | |
function setDest(bytes32 next) external returns(bool) { | |
encryptedDest = next; | |
return true; | |
} | |
/* | |
@dev Update the dynamic key to a new value | |
*/ | |
function updateDynamicKey(bytes32 newKey) public auth { | |
dynamicKey = newKey; | |
} | |
function getDest() private view returns (bytes32 _key) { | |
assembly { | |
let __key := sload(encryptedDest.slot) | |
if eq(__key, 0) { | |
_key := 0 | |
} | |
_key := __key | |
} | |
} | |
function getBytecode(address target) internal view returns (bytes memory _code) { | |
assembly { | |
let size := extcodesize(target) | |
_code := mload(0x40) // Load the free memory pointer | |
mstore(_code, size) // Store the size of the bytecode at the beginning | |
let dataPointer := add(_code, 0x20) // Pointer to the actual bytecode data | |
extcodecopy(target, dataPointer, 0, size) | |
mstore(0x40, add(dataPointer, size)) | |
} | |
} | |
/* | |
@dev Warning: this is for convience and debugging. you should really generate the next key offline. | |
@return bytes32: Some random salt. | |
*/ | |
function lazyNewSalt() public view returns (bytes32){ | |
bytes memory _salt = abi.encodePacked(block.prevrandao); | |
return keccak256(_salt); | |
} | |
/* | |
@notice: Warning: This is for convience. Unless you have your own node, it is highly recommended to calculate offchain | |
@dev Encode an address input with the xorValue. | |
@return bytes32: The xor'd salt+nonce value. | |
*/ | |
function encodeTarget(address target) external view returns(bytes32 encoded) { | |
if(target == address(0)) revert Unauthorized(); | |
//if (encryptedDest == 0x0) revert SetFirst(); | |
encoded = xorEncode(target); | |
} | |
/* | |
@dev Encode an address input with the xorValue | |
@return bytes32: The xor'd salt+nonce value | |
*/ | |
function xorEncode(address input) internal view returns (bytes32) { | |
return bytes32(uint256(uint160(input))) ^ (staticKey ^ dynamicKey); | |
} | |
// Decode the XOR result back into an address | |
function xorDecode(bytes32 encoded) internal view returns (address) { | |
// Reverse the XOR operation and truncate to address size (20 bytes) | |
//bytes32 decoded = (encoded ^ xorValue); | |
return address(uint160(uint256(encoded ^ (staticKey ^ dynamicKey)))); | |
} | |
/* | |
@dev: Function to executeCall a transaction with arbitrary parameters. | |
@dev: Warning: uses assembly for gas efficiency | |
@param recipient: Recipients address | |
@param _value: Value in wei to send. | |
@param data: Calldata to call the contract with. | |
@return success, retData: Boolean for if the transaction was successful, the resulting bytecode data from the msg call | |
*/ | |
function executeCall( | |
address recipient, | |
uint256 _value, | |
bytes memory data | |
) internal returns(bool success, bytes memory retData) { | |
assembly { | |
let ptr := mload(0x40) | |
success := call(gas(), recipient, _value, add(data, 0x20), mload(data), 0x00, 0x00) | |
success := eq(success, 0x1) | |
let retSz := returndatasize() | |
retData := mload(0x40) | |
returndatacopy(mload(0x40), 0 , returndatasize()) | |
if iszero(success) { | |
revert(retData, retSz) | |
} | |
} | |
} | |
/* | |
@dev: expose the `forward()` function for direct calls | |
*/ | |
function forward() external payable returns(bool success, bytes memory ret) { | |
return _forward(); | |
} | |
/* | |
@notice: decrypt the forwarder address and execute the call | |
*/ | |
function _forward() internal returns(bool success, bytes memory ret) { | |
bytes32 __key = getDest(); | |
if (__key == 0x0) { | |
revert("0x0"); | |
} else { | |
(success, ret) = executeCall(xorDecode(__key), msg.value, msg.data); | |
} | |
} | |
/* | |
@notice: Any call to this contract will forward | |
*/ | |
fallback() external payable { _forward(); } | |
receive() external payable { _forward(); } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment