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

feat(vm 1.4.1): Remove padding from keccak256 precompile contract #36

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 [email protected]
* @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)
}
}
}