This means that
txHashData now refers to the return statement of encodeTransactionData with the arguments used for execTransaction inserted into that function for the following fields: (to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, nonce), which is the full byte value (in the Bybit hack): 0x1901b3ded2bdbff5db1a87f6d551fa256e9f2bd6517a3bb84f4c2ea863fb3a559622ea68fc75e93bf7286b1257680ca8e2033bfadd8e1cb8a23f3ca18f1031ecf1e0. This is the `EIP-712` pre-image data string that needs to be hashed with `keccak256` to give us the proper message hash to verify signatures against. That step happens in the line
txHash = keccak256(txHashData); [located within the execTransaction function]. The result of that (in the hack transaction) is 0xb3476d061aeb8fc1d605a873c483a2402d88a68a9cdd1a8b47655dd55ba004f8. This is the same value you see in the transaction log when you look up the transaction (ID: 0x46deef0f52e3a983b67abf4714448a41dd7ffd6d32d32da69d62081c68ad7882). However, this is not the hash that the signatures are verified against. We just established this value is derived by correctly taking the transaction data, piping it into
encodeTransactionData (via internal call), obtaining a return statement (EIP-712 pre-image), then hashing that result (keccak256(txHashData)). This is the complete, correct workflow for obtaining a valid EIP-712 hash to verify signatures against. No doubt about it. So, this begs the question...why the heck does the Safe contract take that valid EIP-712 message hash AND PREFIX it with the
EIP-191 ASCII byte string, concatenate that with the EIP-712 message hash, then keccak256 hash the result of THAT to derive the message hash the signatures should be verified against?This COMPLETELY BREAKS the EIP-191 AND provides a viable pathway for ANYONE to break the EIP-712 STANDARD in ALL Safe Contract Versions!