Skip to content

Commit

Permalink
feat: adds keccak256 primitive without memory allocation
Browse files Browse the repository at this point in the history
Co-authored-by: agus <[email protected]>
  • Loading branch information
scnale and AgusVelez5 committed Nov 6, 2024
1 parent 88c4a6d commit b1edae2
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 4 deletions.
17 changes: 16 additions & 1 deletion src/Utils.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.19;

import { WORD_SIZE, SCRATCH_SPACE_PTR } from "./constants/Common.sol";

error NotAnEvmAddress(bytes32);

function toUniversalAddress(address addr) pure returns (bytes32 universalAddr) {
Expand All @@ -26,3 +27,17 @@ function reRevert(bytes memory err) pure {
revert(add(err, 32), mload(err))
}
}

/**
* Hashes a 32 byte word with keccak256.
* @param word Word to be hashed.
*/
function keccak256Word(
bytes32 word
) pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
mstore(SCRATCH_SPACE_PTR, word)
hash := keccak256(SCRATCH_SPACE_PTR, WORD_SIZE)
}
}
7 changes: 7 additions & 0 deletions src/constants/Common.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.4;

/**
* @dev The Solidity compiler reserves 64 bytes at the start of the memory space as "scratch space".
* Applications can use this scratch space for short term allocations.
* See https://docs.soliditylang.org/en/v0.8.25/assembly.html#memory-management
*/
uint256 constant SCRATCH_SPACE_PTR = 0x00;

uint256 constant FREE_MEMORY_PTR = 0x40;
uint256 constant WORD_SIZE = 32;
//we can't define _WORD_SIZE_MINUS_ONE via _WORD_SIZE - 1 because of solc restrictions
Expand Down
47 changes: 44 additions & 3 deletions src/libraries/BytesParsing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ library BytesParsing {
error LengthMismatch(uint256 encodedLength, uint256 expectedLength);
error InvalidBoolVal(uint8 val);

function checkBound(uint offset, uint length) internal pure {
if (offset > length)
revert OutOfBounds(offset, length);
/**
* Implements runtime check of logic that accesses memory.
* @param pastTheEndOffset The offset past the end relative to the accessed memory fragment.
* @param length The length of the memory fragment accessed.
*/
function checkBound(uint pastTheEndOffset, uint length) internal pure {
if (pastTheEndOffset > length)
revert OutOfBounds(pastTheEndOffset, length);
}

//Summary of all remaining functions:
Expand Down Expand Up @@ -118,6 +123,42 @@ library BytesParsing {
}
}

/**
* Hashes subarray of the buffer.
* The user of this function is responsible for ensuring the subarray is within bounds of the buffer.
* @param encoded Buffer that contains the subarray to be hashed.
* @param offset Starting offset of the subarray to be hashed.
* @param length Size in bytes of the subarray to be hashed.
*/
function keccak256SubarrayUnchecked(
bytes memory encoded,
uint offset,
uint length
) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
// The length of the bytes type `length` field is that of a word in memory
let data := add(add(encoded, offset), WORD_SIZE)
hash := keccak256(data, length)
}
}

/**
* Hashes subarray of the buffer.
* @param encoded Buffer that contains the subarray to be hashed.
* @param offset Starting offset of the subarray to be hashed.
* @param length Size in bytes of the subarray to be hashed.
*/
function keccak256Subarray(
bytes memory encoded,
uint offset,
uint length
) internal pure returns (bytes32 hash) {
uint pastTheEndOffset = offset + length;
checkBound(pastTheEndOffset, encoded.length);
hash = keccak256SubarrayUnchecked(encoded, offset, length);
}

/* -------------------------------------------------------------------------------------------------
Remaining library code below was auto-generated via the following js/node code:
Expand Down
46 changes: 46 additions & 0 deletions test/Keccak.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: Apache 2

// forge test --match-contract TestKeccak

pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import { BytesParsing } from "../src/libraries/BytesParsing.sol";
import { keccak256Word } from "../src/Utils.sol";

contract TestKeccak is Test {
using BytesParsing for bytes;

function test_bytesShouldHashTheSame(bytes memory data) public {
bytes32 hash = data.keccak256Subarray(0, data.length);
assertEq(hash, keccak256(abi.encodePacked(data)));
}

function test_bytesSubArrayEndShouldHashTheSame(bytes calldata data, uint256 indexSeed) public {
vm.assume(data.length > 0);
uint256 length = indexSeed % data.length;
bytes32 hash = data.keccak256Subarray(0, length);
assertEq(hash, keccak256(abi.encodePacked(data[0:length])));
}

function test_bytesSubArrayStartShouldHashTheSame(bytes calldata data, uint256 indexSeed) public {
vm.assume(data.length > 0);
uint256 start = indexSeed % data.length;
bytes32 hash = data.keccak256Subarray(start, data.length - start);
assertEq(hash, keccak256(abi.encodePacked(data[start:data.length])));
}

function test_bytesSubArrayStartEndShouldHashTheSame(bytes calldata data, uint256 startSeed, uint256 endSeed) public {
vm.assume(data.length > 0);
uint256 end = endSeed % data.length;
vm.assume(end > 0);
uint256 start = startSeed % end;
bytes32 hash = data.keccak256Subarray(start, end - start);
assertEq(hash, keccak256(abi.encodePacked(data[start:end])));
}

function test_wordShouldHashTheSame(bytes32 data) public {
bytes32 hash = keccak256Word(data);
assertEq(hash, keccak256(abi.encodePacked(data)));
}
}

0 comments on commit b1edae2

Please sign in to comment.