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

Integration of optimized keccak precompile #41

Open
wants to merge 46 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6bfc135
feat: Remove padding from keccak256 precompile contract
vladbochok Oct 4, 2023
58eceb4
Merge branch 'vb-remove-padding-from-keccak256-precompile-contract' i…
StanislavBreadless Oct 11, 2023
3c7f059
migration for keccak
StanislavBreadless Oct 12, 2023
2c85388
remove todo
StanislavBreadless Oct 13, 2023
1af9f21
use staticcall where appropriate
StanislavBreadless Oct 13, 2023
cbb1de8
upd comments
StanislavBreadless Oct 13, 2023
74116ed
remove unused var
StanislavBreadless Oct 16, 2023
e416085
upd scripts
StanislavBreadless Oct 16, 2023
2664215
fix staticall params
StanislavBreadless Oct 18, 2023
d89a918
Zero ptr keccak test (#44)
AntonD3 Oct 20, 2023
db2c0d8
add unit test for upgrade keccak
StanislavBreadless Oct 20, 2023
9bec033
remove redundant test contract
StanislavBreadless Oct 20, 2023
13664fc
use latest vm version
StanislavBreadless Oct 23, 2023
217e115
readme for testing
StanislavBreadless Oct 23, 2023
bd52d32
add constructor code
StanislavBreadless Nov 2, 2023
cc1d9c6
better tests for keccak precompile
StanislavBreadless Nov 2, 2023
40d31d5
Fix N-01
vladbochok Nov 3, 2023
a90e2c0
Add missing comments
vladbochok Nov 3, 2023
d6c93ed
Reorder getter functions in the Bootloader
vladbochok Nov 3, 2023
4f1b6c6
Fix the formula
vladbochok Nov 3, 2023
7afd89d
better random tests
StanislavBreadless Nov 3, 2023
e84ea6e
added test to validate keccak output
koloz193 Nov 15, 2023
62d0ee7
added test to make sure upgradeIfNexessary worked as intended
koloz193 Nov 15, 2023
5234858
mined a few blocks to trigger keccak upgrade
koloz193 Nov 15, 2023
d695779
added in additional check to make sure the mock keccak was in use
koloz193 Nov 15, 2023
a4dc0d9
remove unneeded tests + make compilation work out of the box
StanislavBreadless Nov 16, 2023
54c4a77
fix build scripts
StanislavBreadless Nov 21, 2023
bc495d0
Merge pull request #63 from matter-labs/vb-fix-oz-keccak256-audit-n-04
StanislavBreadless Nov 21, 2023
7b26445
Merge pull request #62 from matter-labs/vb-fix-oz-keccak256-audit-n-03
StanislavBreadless Nov 21, 2023
123bb68
Merge pull request #58 from matter-labs/sb-enhance-keccak-tests
StanislavBreadless Nov 21, 2023
d2001f6
Merge pull request #61 from matter-labs/vb-fix-oz-keccak256-audit-n-02
StanislavBreadless Nov 21, 2023
6903d17
Merge pull request #60 from matter-labs/vb-fix-oz-keccak256-audit-n-01
StanislavBreadless Nov 21, 2023
b560437
Merge pull request #57 from matter-labs/sb-add-constructor-code
StanislavBreadless Nov 21, 2023
4c5a6cd
Update README.md
StanislavBreadless Nov 21, 2023
9703ff5
Update contracts/precompiles/Keccak256.yul
StanislavBreadless Nov 21, 2023
66cfc62
resolve nits
StanislavBreadless Nov 21, 2023
cb89135
Merge remote-tracking branch 'origin/v1-4-1-integration' into v1-4-1-…
StanislavBreadless Nov 21, 2023
1a674e1
sync with latest dev
StanislavBreadless Nov 21, 2023
8d0915d
fmt
StanislavBreadless Nov 26, 2023
2e036be
remove outdated comment
StanislavBreadless Nov 28, 2023
b1728f3
recalculate hashes
StanislavBreadless Nov 28, 2023
fdaf998
fix lint
StanislavBreadless Nov 28, 2023
820cc14
upd hashes
StanislavBreadless Nov 28, 2023
ef0eb0c
Merge pull request #78 from matter-labs/sb-remove-outdated-comment
StanislavBreadless Nov 28, 2023
8979a4f
Fix typographical errors (#91)
StanislavBreadless Jan 9, 2024
51f5f0a
Fix misleading comment (#92)
StanislavBreadless Jan 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Expand Up @@ -62,6 +62,16 @@ Once the audit for the release branch is complete and all the fixes from the aud

Since scripts, READMEs, etc., are code that is not subject to audits, these are to be merged directly into the `main` branch. The rest of the release branches as well as the `dev` branch should merge `main` to synchronize with these changes.

## Testing

The tests of the system contracts utilize the zkSync test node. In order to run the tests, execute the following command in the root of hte repository:
StanislavBreadless marked this conversation as resolved.
Show resolved Hide resolved

```
bash ./scripts/quick-setup.sh
```

The `quick-setup.sh` script will proceeed to install all the necessary dependencies and will run the tests.

## License

The zkSync Era system-contracts are distributed under the terms of the MIT license.
Expand Down
89 changes: 69 additions & 20 deletions bootloader/bootloader.yul
Expand Up @@ -488,6 +488,10 @@ object "Bootloader" {
ret := 0x000000000000000000000000000000000000800e
}

function KECCAK256_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008010
}

function L1_MESSENGER_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008008
}
Expand Down Expand Up @@ -634,6 +638,68 @@ object "Bootloader" {
}
}

/// @dev Checks whether the code hash of the Keccak256 precompile contract is correct and updates it if needed.
/// @dev When we upgrade to the new version of the Keccak256 precompile contract, the keccak precompile will not work correctly
/// and so the upgrade it should be done before any `keccak` calls.
function upgradeKeccakIfNeeded() {
let expectedCodeHash := {{KECCAK256_EXPECTED_CODE_HASH}}

let actualCodeHash := getRawCodeHash(KECCAK256_ADDR(), true)
if iszero(eq(expectedCodeHash, actualCodeHash)) {
// The `mimicCallOnlyResult` requires that the first word of the data
// contains its length. Here is 36 bytes, i.e. 4 byte selector + 32 byte hash.
mstore(0, 36)
mstore(32, {{PADDED_FORCE_DEPLOY_KECCAK256_SELECTOR}})
mstore(36, expectedCodeHash)

// We'll use a mimicCall to simulate the correct sender.
let success := mimicCallOnlyResult(
CONTRACT_DEPLOYER_ADDR(),
FORCE_DEPLOYER(),
0,
0,
0,
0,
0,
0
)

if iszero(success) {
assertionError("keccak256 upgrade fail")
}
}
}

function getRawCodeHash(addr, assertSuccess) -> ret {
mstore(0, {{RIGHT_PADDED_GET_RAW_CODE_HASH_SELECTOR}})
mstore(4, addr)
let success := staticcall(
gas(),
ACCOUNT_CODE_STORAGE_ADDR(),
0,
36,
0,
32
)

// In case the call to the account code storage fails,
// it most likely means that the caller did not provide enough gas for
// the call.
// In case the caller is certain that the amount of gas provided is enough, i.e.
// (`assertSuccess` = true), then we should panic.
if iszero(success) {
if assertSuccess {
// The call must've succeeded, but it didn't. So we revert the bootloader.
assertionError("getRawCodeHash failed")
}

// Most likely not enough gas provided, revert the current frame.
nearCallPanic()
}

ret := mload(0)
}

/// @dev Calculates the canonical hash of the L1->L2 transaction that will be
/// sent to L1 as a message to the L1 contract that a certain operation has been processed.
function getCanonicalL1TxHash(txDataOffset) -> ret {
Expand Down Expand Up @@ -1956,26 +2022,7 @@ object "Bootloader" {
/// @dev Checks whether an address is an EOA (i.e. has not code deployed on it)
/// @param addr The address to check
function isEOA(addr) -> ret {
mstore(0, {{RIGHT_PADDED_GET_RAW_CODE_HASH_SELECTOR}})
mstore(4, addr)
let success := call(
gas(),
ACCOUNT_CODE_STORAGE_ADDR(),
0,
0,
36,
0,
32
)

if iszero(success) {
// The call to the account code storage should always succeed
nearCallPanic()
}

let rawCodeHash := mload(0)

ret := iszero(rawCodeHash)
ret := iszero(getRawCodeHash(addr, false))
StanislavBreadless marked this conversation as resolved.
Show resolved Hide resolved
}

/// @dev Calls the `payForTransaction` method of an account
Expand Down Expand Up @@ -3657,6 +3704,8 @@ object "Bootloader" {
/// the operator still provides it to make sure that its data is in sync.
let EXPECTED_BASE_FEE := mload(192)

upgradeKeccakIfNeeded()

validateOperatorProvidedPrices(L1_GAS_PRICE, FAIR_L2_GAS_PRICE)

let baseFee := 0
Expand Down
23 changes: 20 additions & 3 deletions contracts/ContractDeployer.sol
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.0;

import {ImmutableData} from "./interfaces/IImmutableSimulator.sol";
import {IContractDeployer} from "./interfaces/IContractDeployer.sol";
import {CREATE2_PREFIX, CREATE_PREFIX, NONCE_HOLDER_SYSTEM_CONTRACT, ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT, FORCE_DEPLOYER, MAX_SYSTEM_CONTRACT_ADDRESS, KNOWN_CODE_STORAGE_CONTRACT, ETH_TOKEN_SYSTEM_CONTRACT, IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT, COMPLEX_UPGRADER_CONTRACT} from "./Constants.sol";
import {CREATE2_PREFIX, CREATE_PREFIX, NONCE_HOLDER_SYSTEM_CONTRACT, ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT, FORCE_DEPLOYER, MAX_SYSTEM_CONTRACT_ADDRESS, KNOWN_CODE_STORAGE_CONTRACT, ETH_TOKEN_SYSTEM_CONTRACT, IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT, COMPLEX_UPGRADER_CONTRACT, KECCAK256_SYSTEM_CONTRACT} from "./Constants.sol";

import {Utils} from "./libraries/Utils.sol";
import {EfficientCall} from "./libraries/EfficientCall.sol";
Expand Down Expand Up @@ -228,8 +228,24 @@ contract ContractDeployer is IContractDeployer, ISystemContract {
false,
_deployment.callConstructor
);
}

/// @notice The method that is temporarily needed to upgrade the Keccak256 precompile. It is to be removed in the
/// future. Unlike a normal forced deployment, it does not update account information as it requires updating a
/// mapping, and so requires Keccak256 precompile to work already.
/// @dev This method expects the sender (FORCE_DEPLOYER) to provide the correct bytecode hash for the Keccak256
/// precompile.
function forceDeployKeccak256(bytes32 _keccak256BytecodeHash) external payable onlyCallFrom(FORCE_DEPLOYER) {
_ensureBytecodeIsKnown(_keccak256BytecodeHash);

emit ContractDeployed(_sender, _deployment.bytecodeHash, _deployment.newAddress);
_constructContract(
msg.sender,
address(KECCAK256_SYSTEM_CONTRACT),
_keccak256BytecodeHash,
msg.data[0:0],
false,
false
);
}

/// @notice This method is to be used only during an upgrade to set bytecodes on specific addresses.
Expand Down Expand Up @@ -295,7 +311,6 @@ contract ContractDeployer is IContractDeployer, ISystemContract {
_storeAccountInfo(_newAddress, newAccountInfo);

_constructContract(msg.sender, _newAddress, _bytecodeHash, _input, false, true);
emit ContractDeployed(msg.sender, _bytecodeHash, _newAddress);
}

/// @notice Check that bytecode hash is marked as known on the `KnownCodeStorage` system contracts
Expand Down Expand Up @@ -352,5 +367,7 @@ contract ContractDeployer is IContractDeployer, ISystemContract {
// If we do not call the constructor, we need to set the constructed code hash.
ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.storeAccountConstructedCodeHash(_newAddress, _bytecodeHash);
}

emit ContractDeployed(_sender, _bytecodeHash, _newAddress);
}
}
121 changes: 46 additions & 75 deletions contracts/precompiles/Keccak256.yul
@@ -1,15 +1,10 @@
/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
StanislavBreadless marked this conversation as resolved.
Show resolved Hide resolved
* @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 .
StanislavBreadless marked this conversation as resolved.
Show resolved Hide resolved
*/
object "Keccak256" {
code {
return(0, 0)
}
code { }
StanislavBreadless marked this conversation as resolved.
Show resolved Hide resolved
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,35 @@ 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()
}
default {
let padLen := sub(BLOCK_SIZE(), mod(bytesSize, BLOCK_SIZE()))
let paddedByteSize := add(bytesSize, padLen)
// 2. Parse calldata fat pointer
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)
}
}
}
9 changes: 9 additions & 0 deletions contracts/test-contracts/AlwaysRevert.sol
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract AlwaysRevert {
fallback() external {
revert("");
}
}