Skip to content
This repository has been archived by the owner on Mar 25, 2024. It is now read-only.

Commit

Permalink
feat: Remove padding from keccak256 precompile contract
Browse files Browse the repository at this point in the history
  • Loading branch information
vladbochok committed Oct 4, 2023
1 parent ef5e5f7 commit 6bfc135
Showing 1 changed file with 51 additions and 74 deletions.
125 changes: 51 additions & 74 deletions contracts/precompiles/Keccak256.yul
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice The contract used to emulate EVM's keccak256 opcode.
* @dev It accepts the data to be hashed, pad it by the specification
* and uses `precompileCall` to call the zkEVM built-in precompiles.
* @dev Thus keccak256 precompile circuit operates over padded data to perform efficient sponge round computation.
* @dev It accepts the data to be hashed in the calldata, propagate it to the zkEVM built-in circuit precompile via `precompileCall` and burn .
*/
object "Keccak256" {
code {
return(0, 0)
}
code { }
object "Keccak256_deployed" {
code {
////////////////////////////////////////////////////////////////
Expand All @@ -26,24 +21,38 @@ object "Keccak256" {
ret := 40
}

/// @dev Returns a 32-bit mask value
function UINT32_BIT_MASK() -> ret {
ret := 0xffffffff
}

////////////////////////////////////////////////////////////////
// HELPER FUNCTIONS
////////////////////////////////////////////////////////////////

/// @dev Load raw calldata fat pointer
function getCalldataPtr() -> calldataPtr {
calldataPtr := verbatim_0i_1o("get_global::ptr_calldata")
}

// @dev Packs precompile parameters into one word.
// Note: functions expect to work with 32/64 bits unsigned integers.
// Caller should ensure the type matching before!
/// @dev Packs precompile parameters into one word.
/// Note: functions expect to work with 32/64 bits unsigned integers.
/// Caller should ensure the type matching before!
function unsafePackPrecompileParams(
uint32_inputOffsetInWords,
uint32_inputLengthInWords,
uint32_inputOffsetInBytes,
uint32_inputLengthInBytes,
uint32_outputOffsetInWords,
uint32_outputLengthInWords,
uint32_memoryPageToRead,
uint32_memoryPageToWrite,
uint64_perPrecompileInterpreted
) -> rawParams {
rawParams := uint32_inputOffsetInWords
rawParams := or(rawParams, shl(32, uint32_inputLengthInWords))
rawParams := uint32_inputOffsetInBytes
rawParams := or(rawParams, shl(32, uint32_inputLengthInBytes))
rawParams := or(rawParams, shl(64, uint32_outputOffsetInWords))
rawParams := or(rawParams, shl(96, uint32_outputLengthInWords))
rawParams := or(rawParams, shl(128, uint32_memoryPageToRead))
rawParams := or(rawParams, shl(160, uint32_memoryPageToWrite))
rawParams := or(rawParams, shl(192, uint64_perPrecompileInterpreted))
}

Expand All @@ -56,73 +65,41 @@ object "Keccak256" {
////////////////////////////////////////////////////////////////
// FALLBACK
////////////////////////////////////////////////////////////////

// 1. Load raw calldata fat pointer
let calldataFatPtr := getCalldataPtr()

// Copy calldata to memory for pad it
let bytesSize := calldatasize()
calldatacopy(0, 0, bytesSize)

let precompileParams
let gasToPay

// Most often keccak256 is called with "short" input, so optimize it as a special case.
// NOTE: we consider the special case for sizes less than `BLOCK_SIZE() - 1`, so
// there is only one round and it is and padding can be done branchless
switch lt(bytesSize, sub(BLOCK_SIZE(), 1))
case true {
// Write the 0x01 after the payload bytes and 0x80 at last byte of padded bytes
mstore(bytesSize, 0x0100000000000000000000000000000000000000000000000000000000000000)
mstore(
sub(BLOCK_SIZE(), 1),
0x8000000000000000000000000000000000000000000000000000000000000000
)

precompileParams := unsafePackPrecompileParams(
0, // input offset in words
5, // input length in words (Math.ceil(136/32) = 5)
0, // output offset in words
1, // output length in words
1 // number of rounds
)
gasToPay := KECCAK_ROUND_GAS_COST()
// 2. Parse calldata fat pointer
let ptrOffset := and(calldataFatPtr, UINT32_BIT_MASK())
// TODO: Remove this check before merging.
// Assert that calldata ptr offset if zero.
if ptrOffset {
revert(0, 0)
}
default {
let padLen := sub(BLOCK_SIZE(), mod(bytesSize, BLOCK_SIZE()))
let paddedByteSize := add(bytesSize, padLen)
let ptrMemoryPage := and(shr(32, calldataFatPtr), UINT32_BIT_MASK())
let ptrStart := and(shr(64, calldataFatPtr), UINT32_BIT_MASK())
let ptrLength := and(shr(96, calldataFatPtr), UINT32_BIT_MASK())

switch eq(padLen, 1)
case true {
// Write 0x81 after the payload bytes
mstore(bytesSize, 0x8100000000000000000000000000000000000000000000000000000000000000)
}
default {
// Write the 0x01 after the payload bytes and 0x80 at last byte of padded bytes
mstore(bytesSize, 0x0100000000000000000000000000000000000000000000000000000000000000)
mstore(
sub(paddedByteSize, 1),
0x8000000000000000000000000000000000000000000000000000000000000000
)
}

let numRounds := div(paddedByteSize, BLOCK_SIZE())
precompileParams := unsafePackPrecompileParams(
0, // input offset in words
div(add(paddedByteSize, 31), 32), // input length in words (safe to pass, never exceed `type(uint32).max`)
0, // output offset in words
1, // output length in words
numRounds // number of rounds (safe to pass, never exceed `type(uint64).max`)
)
gasToPay := mul(KECCAK_ROUND_GAS_COST(), numRounds)
}
// 3. Pack precompile parameters
let precompileParams := unsafePackPrecompileParams(
ptrStart, // input offset in bytes
ptrLength, // input length in bytes (safe to pass, never exceed `type(uint32).max`)
0, // output offset in words
1, // output length in words (NOTE: VM doesn't check this value for now, but this could change in future)
ptrMemoryPage, // memory page to read from
0, // memory page to write to (0 means write to heap)
0 // per precompile interpreted value (0 since circuit doesn't react on this value anyway)
)
// 4. Calculate number of required hash rounds per calldata
let numRounds := div(add(ptrLength, sub(BLOCK_SIZE(), 1)), BLOCK_SIZE())
let gasToPay := mul(KECCAK_ROUND_GAS_COST(), numRounds)

// 5. Call precompile
let success := precompileCall(precompileParams, gasToPay)

switch success
case 0 {
if iszero(success) {
revert(0, 0)
}
default {
return(0, 32)
}
return(0, 32)
}
}
}

0 comments on commit 6bfc135

Please sign in to comment.