For blockchain governance bodies and multi-sig committees, verifying transaction data is not optional but absolutely vital. When approving protocol upgrades or moving large sums, trusting only the front-end interface can be dangerously misleading.
As seen in the Bybit exploit, even a reputable wallet’s UI can be compromised to present false information. Without independent verification, signers risk authorizing transactions that differ significantly from what they believe they’re approving. Transaction verification acts as a safeguard: it ensures that the raw transaction details (addresses, calldata, amounts, proposal IDs) are exactly as expected.
Other recent exploits (e.g., the Radiant Capital $50M hack) underscore that failing to double-check the data behind a transaction can be catastrophic. In governance scenarios – where proposals can upgrade contracts or transfer treasury funds – the stakes are especially high. This is why robust verification tools and practices are crucial for protecting protocols and user assets.
To help address these risks, Cyfrin has introduced the ZKsync Upgrade Verification Tool, a command-line utility purpose-built for secure governance transactions. This tool is essential for participants in the ZKsync governance ecosystem, particularly security council members and guardians. It simplifies the process of verifying and understanding governance proposals and upgrades by delivering three key functionalities:
get-zk-id)
get-upgrades
)get-eth-id
).This tool empowers users to verify proposal details against what’s displayed in governance platforms like Tally (used by ZKsync), understand the exact impact a proposal will have on the protocol, and confirm Ethereum proposal IDs before approval or signing. Whether you're ensuring a proposal aligns with its description or validating its integrity across ZKsync and Ethereum, this tool streamlines the process.
In the ZKsync governance process, verifying proposals and upgrades is critical to maintaining the protocol’s security and integrity. This tool addresses several needs:
get-zk-id
subcommand extracts the proposal ID from a ZKsync transaction hash, displaying it in both hexadecimal and decimal formats. This allows users to confirm that the ID matches what’s shown on governance platforms, ensuring transparency and accuracy pre-vote.get-upgrades
, users can decode a proposal’s input data to see the full list of targets, values, and calldata. If the proposal includes calls to the ETH bridge (e.g., sendToL1
), it further decodes the Ethereum transactions being proposed, providing a clear picture of the proposal’s impact—crucial for assessing its validity and intent.get-eth-id
computes the Keccak256 hash of Ethereum upgrade proposals embedded within a ZKsync proposal. This hash serves as the Ethereum-side proposal ID, enabling users to verify that it matches expectations post-vote, reducing the risk of approving unintended actions.These features make the tool indispensable for anyone involved in ZKsync governance, ensuring proposals are thoroughly vetted before execution.
The ZKsync upgrade verification tool is a command-line application that operates with subcommands, a transaction hash, and an RPC URL to connect to the ZKsync network. It is straightforward to use and requires only a few inputs. The governor address is optional and defaults to a predefined value if omitted. Below are examples of how to use each subcommand:
zkgov-check get-zk-id <tx-hash> --rpc-url $ZKSYNC_RPC_URL
Output: The proposal ID in hex and decimal, e.g., 0xe06945bf...
and 101504078....
zkgov-check get-upgrades <tx-hash> --rpc-url $ZKSYNC_RPC_URL
Output: A detailed list of targets, values, and calldata, plus any Ethereum transactions if sendToL1
is called.
zkgov-check get-upgrades <tx-hash> --rpc-url $ZKSYNC_RPC_URL --decode
The --decode
option allows us to interpret the calldata intended for execution on Ethereum. This makes it possible to view the function being called and the values of its arguments in a human-readable format.
zkgov-check get-eth-id <tx-hash> --rpc-url $ZKSYNC_RPC_URL
To generate the Ethereum-side proposal ID based on JSON file containing the executor, salt, and the set of calls:
zkgov-check get-eth-id --from-file <file-path>
Output: The Keccak256 hash for each Ethereum transaction in the proposal.
The ZKsync upgrade verification tool is available in two implementations: one built with Bash and the other with Rust. You can choose whichever stack you prefer to work with:
• Bash implementation
• Rust implementation
In this article, we’ll be working with the Rust version.
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/cyfrin/zksync-upgrade-verification-rs/releases/latest/download/zkgov-check-installer.sh | sh
git clone https://github.com/Cyfrin/zksync-upgrade-verification
cd zksync-upgrade-verification
cargo build --release
sudo mv target/release/zkgov-check /usr/local/bin/
zkgov-check --help
ZkSync Governance Tool
Usage: zkgov-check [OPTIONS] <COMMAND>
Commands:
get-zk-id
get-upgrades
get-eth-id
help Print this message or the help of the given subcommand(s)
Options:
--rpc-url <RPC_URL>
--governor <GOVERNOR> [default: 0x76705327e682F2d96943280D99464Ab61219e34f]
--decode
-h, --help Print help
-V, --version Print version
Done!
This tool’s simplicity and robustness make it a powerful companion for navigating ZKsync’s decentralized governance process with confidence.
Using the ZKsync Upgrade Verification Tool can dramatically improve the security of governance operations. The following is a step-by-step guide on how a security council or guardian member might use it to verify a proposal, taking ZIP-7 as an example:
1. Get the hash of the transaction containing the proposal: When a new protocol upgrade is proposed, you’ll need to get the hash of the transaction where the proposal was created. You can get this from Tally or a block explorer.
2. Run the tool to get the proposal ID: Use the get_zk_id
command on the transaction hash. For instance:
zkgov-check get-zk-id 0x94d49c27617ea2dfd78bb3316e6849bdb1a1dd80ddd22151ecb6c644d3fd86f6 --rpc-url $ZKSYNC_RPC_URL
The tool will output the proposal ID in both hex and decimal formats:
Proposal ID
Hex: 0x7551655cb1bd662c2090a1227ea4eef89a4fdefc83bf33b06ca5b41b53fcadd4
Decimal: 53064417471903525695516096129021600825622830249245179379231067906906888383956
This proposal ID should match the one shown in the proposal in Tally.
3. Fetch and cross-verify the Ethereum-side ID: For added security, retrieve the proposal ID as recorded on Ethereum by running the get-eth-id
command. Here are two ways to do it:
zkgov-check get-eth-id 0x94d49c27617ea2dfd78bb3316e6849bdb1a1dd80ddd22151ecb6c644d3fd86f6 --rpc-url $ZKSYNC_RPC_URL
Ethereum proposal ID #1: 0x5ebd899d036aae29b12babe196b11380d8304e98ac86390ac18a56ff51ada9bd
zkgov-check get-eth-id --from-file test-data/zip5.json
Ethereum Proposal ID
Proposal ID: 0xa34bdc028de549c0fbd0374e64eb5977e78f62331f6a55f4f2211348c4902d13
Encoded Proposal: 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000ece8e30bfc92c2a8e11e6cb2e17b70868572e3f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000320000000000000000000000000303a465b659cbb0ab36ee643ea362c509eeb521300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba509700000000000000000000000000000000000000000000000000000000000000000000000000000000c2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba509700000000000000000000000000000000000000000000000000000000000000000000000000000000d7f9f54194c633f36ccd5f3da84ad4a1c38cb2cb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba5097000000000000000000000000000000000000000000000000000000000000000000000000000000005d8ba173dc6c3c90c8f7c04c9288bef5fdbad06e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba509700000000000000000000000000000000000000000000000000000000000000000000000000000000f553e6d903aa43420ed7e3bc2313be9286a8f98700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba509700000000000000000000000000000000000000000000000000000000
4. Verify the calldata of ZKsync and Ethereum transactions: A complicated task made easier by the get-upgrades
command:
zkgov-check get-upgrades 0x94d49c27617ea2dfd78bb3316e6849bdb1a1dd80ddd22151ecb6c644d3fd86f6 --decode --rpc-url $ZKSYNC_RPC_URL
ZKsync Transactions
ZKsync Transaction #1:
Target Address: 0x0000000000000000000000000000000000008008
Value: 0
Calldata: 0x62f84b24000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000005c00000000000000000000000009da9f5dad070649811d77c40ccdcab479ce3fa0700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba509700000000000000000000000000000000000000000000000000000000000000000000000000000000590e6587b37dc4152b6b036ff88a835bd2ab892400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba5097000000000000000000000000000000000000000000000000000000000000000000000000000000005c03468829a26981c410a7930bd4853622f0b2e500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba50970000000000000000000000000000000000000000000000000000000000000000000000000000000034899f8b01cf52160c88ddb9e29ec3c26901916500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000479ba509700000000000000000000000000000000000000000000000000000000000000000000000000000000c2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000440d14edf700000000000000000000000000000000000000000000000000000000000000e8000000000000000000000000c29d04a93f893700015138e3e334eb828dac3cef00000000000000000000000000000000000000000000000000000000000000000000000000000000303a465b659cbb0ab36ee643ea362c509eeb52130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e43f58f5b500000000000000000000000000000000000000000000000000000000000000e8000000000000000000000000c2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c0000000000000000000000001ff1dc3cb9eedbc6eb2d99c03b30a05ca625fb5a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000590e6587b37dc4152b6b036ff88a835bd2ab8924000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000164e34a329a00000000000000000000000000000000000000000000000000000000000000e8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000004d89b79a893ac95eb46e96e452ad21f71144c9180000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000640528f3f700000000000000000000000006aa7a7b07108f7c5539645e32dd5c21cbf9eb66000000000000000000000000c2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c0000000000000000000000005d8ba173dc6c3c90c8f7c04c9288bef5fdbad06e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
(ETH transaction)
Ethereum Transaction
Call:
Target: 0x9da9f5dad070649811d77c40ccdcab479ce3fa07
Value: 0
Calldata: 0x79ba5097
Function: acceptOwnership()()
Call:
Target: 0x590e6587b37dc4152b6b036ff88a835bd2ab8924
Value: 0
Calldata: 0x79ba5097
Function: acceptOwnership()()
Call:
Target: 0x5c03468829a26981c410a7930bd4853622f0b2e5
Value: 0
Calldata: 0x79ba5097
Function: acceptOwnership()()
Call:
Target: 0x34899f8b01cf52160c88ddb9e29ec3c269019165
Value: 0
Calldata: 0x79ba5097
Function: acceptOwnership()()
Call:
Target: 0xc2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c
Value: 0
Calldata: 0x0d14edf700000000000000000000000000000000000000000000000000000000000000e8000000000000000000000000c29d04a93f893700015138e3e334eb828dac3cef
Function: registerAlreadyDeployedHyperchain(uint256,address)(232, 0xc29d04a93f893700015138e3e334eb828dac3cef)
Call:
Target: 0x303a465b659cbb0ab36ee643ea362c509eeb5213
Value: 0
Calldata: 0x3f58f5b500000000000000000000000000000000000000000000000000000000000000e8000000000000000000000000c2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c0000000000000000000000001ff1dc3cb9eedbc6eb2d99c03b30a05ca625fb5a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000
Function: createNewChain(uint256,address,address,uint256,address,bytes)(232, 0xc2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c, 0x1ff1dc3cb9eedbc6eb2d99c03b30a05ca625fb5a, 0, 0x0000000000000000000000000000000000000000, 0x)
Call:
Target: 0x590e6587b37dc4152b6b036ff88a835bd2ab8924
Value: 0
Calldata: 0xe34a329a00000000000000000000000000000000000000000000000000000000000000e8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000004d89b79a893ac95eb46e96e452ad21f71144c9180000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000640528f3f700000000000000000000000006aa7a7b07108f7c5539645e32dd5c21cbf9eb66000000000000000000000000c2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c0000000000000000000000005d8ba173dc6c3c90c8f7c04c9288bef5fdbad06e00000000000000000000000000000000000000000000000000000000
Function: executeUpgrade(uint256,((address,uint8,bool,bytes4[])[],address,bytes))(232, ([], 0x4d89b79a893ac95eb46e96e452ad21f71144c918, 0x0528f3f700000000000000000000000006aa7a7b07108f7c5539645e32dd5c21cbf9eb66000000000000000000000000c2ee6b6af7d616f6e27ce7f4a451aedc2b0f5f5c0000000000000000000000005d8ba173dc6c3c90c8f7c04c9288bef5fdbad06e))
Executor: 0x0000000000000000000000000000000000000000
Salt: 0x0000000000000000000000000000000000000000000000000000000000000000
ZKsync Transactions
should be identical to what you can see in Tally.Ethereum Transaction
section is basically a decode of the sendToL1
calls, which helps us know what exactly is going on inside the calls sent to Ethereum. These calls should then be verified to make sense compared to the motivation behind the proposal.With increasingly more sophisticated attacks on blockchain governance, the adage “Don’t trust, verify” has never been more relevant. The ZKsync Upgrade Verification Tool exemplifies the kind of rigorous verification process that should become standard practice for governance committees and multi-sig operators.
By verifying transaction IDs, cross-checking proposal data, and simulating outcomes, this tool ensures that the approved actions match the original intent—nothing more, nothing less. This reduces the risk that busy signers will approve unintended transactions generated by malicious proposals or deceptive front-end spoofing.
The lessons from the Bybit hack and similar exploits are clear: no matter how secure a contract or multi-sig is, the weakest link is often the human who approves a transaction based on unverified information. By incorporating verification scripts and independent checks, we add a robust layer of security.
Governance participants are strongly encouraged to adopt such tools and practices – whether it’s ZKsync’s CLI for protocol upgrades or other safe-transaction verifiers for multi-sigs – to protect the integrity of their decisions.
In summary, secure blockchain governance hinges on diligence and the right tools. Verifying each transaction’s details can mean the difference between a routine upgrade and a devastating exploit. Solutions like the ZKsync Upgrade Verification Tool empower the community to conduct thorough due diligence on every proposal, promoting greater trust in the system.
As decentralized organizations and protocols manage ever larger sums and critical infrastructure, building a culture of “verify before you sign” is the key to resilience. This is how we can confidently step into the future of on-chain governance – secure, transparent, and disaster-proof.