alloy_zksync/network/unsigned_tx/eip712/
utils.rs

1use k256::sha2::{self, Digest};
2
3// Bytecode length in words must fit in u16.
4const WORD_SIZE: usize = 32;
5const MAX_BYTECODE_LENGTH: usize = WORD_SIZE * u16::MAX as usize;
6
7/// Errors that can occur during bytecode hashing.
8#[derive(Debug, thiserror::Error)]
9pub enum BytecodeHashError {
10    #[error("Bytecode cannot be split into 32-byte words")]
11    BytecodeNotAligned,
12    #[error(
13        "Bytecode length exceeds limit: {num_words} words, the maximum is {MAX_BYTECODE_LENGTH}"
14    )]
15    BytecodeLengthExceedsLimit { num_words: usize },
16    #[error("Bytecode must have odd number of words")]
17    NumberOfWordsMustBeOdd,
18}
19
20/// The 32-byte hash of the bytecode of a zkSync contract is calculated in the following way:
21///
22/// * The first 2 bytes denote the version of bytecode hash format and are currently equal to `[1,0]`.
23/// * The second 2 bytes denote the length of the bytecode in 32-byte words.
24/// * The rest of the 28-byte (i.e. 28 low big-endian bytes) are equal to the last 28 bytes of the sha256 hash of the contract's bytecode.
25///
26/// This function performs validity checks for bytecode:
27/// - The bytecode must be aligned to 32-byte words.
28/// - The bytecode length must not exceed the maximum allowed value.
29/// - The number of words must be odd.
30pub fn hash_bytecode(bytecode: &[u8]) -> Result<[u8; 32], BytecodeHashError> {
31    if bytecode.len() % WORD_SIZE != 0 {
32        return Err(BytecodeHashError::BytecodeNotAligned);
33    }
34
35    let bytecode_length = bytecode.len() / WORD_SIZE;
36    let bytecode_length = u16::try_from(bytecode_length).map_err(|_| {
37        BytecodeHashError::BytecodeLengthExceedsLimit {
38            num_words: bytecode_length,
39        }
40    })?;
41    if bytecode_length % 2 == 0 {
42        return Err(BytecodeHashError::NumberOfWordsMustBeOdd);
43    }
44
45    let bytecode_hash: [u8; 32] = sha2::Sha256::digest(bytecode).into();
46
47    let mut contract_hash: [u8; 32] = [0u8; 32];
48    contract_hash[..2].copy_from_slice(&0x0100_u16.to_be_bytes());
49    contract_hash[2..4].copy_from_slice(&bytecode_length.to_be_bytes());
50    contract_hash[4..].copy_from_slice(&bytecode_hash[4..]);
51
52    Ok(contract_hash)
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use assert_matches::assert_matches;
59
60    #[test]
61    fn bytecode_hash() {
62        // Reference values calculated using zksync codebase.
63        #[rustfmt::skip]
64        let test_vector = [
65            (vec![10u8; 32], hex::decode("01000001e7718454476f04edeb935022ae4f4d90934ab7ce913ff20c8baeb399").unwrap()),
66            (vec![20u8; 96], hex::decode("01000003c743f1d99f4d7dc11f5d9630e32ff5a212c5aaf64c7ac815193463d4").unwrap()),
67        ];
68
69        for (input, expected) in test_vector.iter() {
70            let hash = hash_bytecode(input).unwrap();
71            assert_eq!(&hash[..], &expected[..]);
72        }
73
74        assert_matches!(
75            hash_bytecode(&[]),
76            Err(BytecodeHashError::NumberOfWordsMustBeOdd)
77        );
78        assert_matches!(
79            hash_bytecode(&[1]),
80            Err(BytecodeHashError::BytecodeNotAligned)
81        );
82    }
83}