From 996c251462816fb84abff72dfa1d6e3b2aefbdb9 Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:55:34 +0300 Subject: [PATCH 1/2] plugin-c/crypto.cpp: compress a copy of the blake2 state, not the caller's Bytes blake2b_compress updates the state vector h in place; hook_KRYPTO_blake2compress pointed h straight into params->data, mutating the K Bytes object passed to the hook. Bytes terms can be structure-shared on the LLVM backend heap, so the in-place update corrupted unrelated terms sharing the buffer (observed as BLAKE2b IV bytes appearing inside a transaction's calldata term in evm-semantics conformance runs). Copy the 64-byte state to a local buffer and compress that. --- plugin-c/crypto.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin-c/crypto.cpp b/plugin-c/crypto.cpp index 7584c599b..1d6d2133b 100644 --- a/plugin-c/crypto.cpp +++ b/plugin-c/crypto.cpp @@ -192,7 +192,10 @@ struct string *hook_KRYPTO_blake2compress(struct string *params) { } unsigned char *data = (unsigned char *)params->data; uint32_t rounds = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; - uint64_t *h = (uint64_t *)&data[4]; + // blake2b_compress updates h in place; params aliases the caller's Bytes, + // which the hook must not mutate, so compress a copy of the state. + uint64_t h[8]; + memcpy(h, &data[4], sizeof(h)); uint64_t *m = (uint64_t *)&data[68]; uint64_t *t = (uint64_t *)&data[196]; unsigned char f = data[212]; From 2a4956af3b68508dfbc4563f3a6fb62d1073617f Mon Sep 17 00:00:00 2001 From: Andrei <16517508+anvacaru@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:24:43 +0300 Subject: [PATCH 2/2] krypto/src/tests/integration: regression test for blake2f argument aliasing The existing Blake2Compress test only checks the hook's return value, which was correct even while the hook mutated the Bytes it was given. blake2NoAlias passes one Bytes term to Blake2Compress and to the check that follows it, so an in-place update of the argument is observable: it is false before 996c251 and true after. Co-Authored-By: Claude Opus 5 --- krypto/src/tests/integration/conftest.py | 11 +++++++++++ krypto/src/tests/integration/test_hooks.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/krypto/src/tests/integration/conftest.py b/krypto/src/tests/integration/conftest.py index f63010c3f..df0a8801a 100644 --- a/krypto/src/tests/integration/conftest.py +++ b/krypto/src/tests/integration/conftest.py @@ -56,9 +56,20 @@ def definition_dir(krypto_kompile: Callable[..., Path]) -> Path: module TEST imports BOOL + imports BYTES + imports INT + imports K-EQUAL + imports STRING imports KRYPTO syntax Pgm ::= Bool | Bytes | String | G1Point | G2Point configuration $PGM:Pgm + + // Passes the same Bytes term to Blake2Compress and to the check that + // follows it, so that a hook mutating its argument in place is observable. + syntax Bool ::= blake2NoAlias ( Bytes ) [function] + | blake2NoAliasAux ( String , Bytes ) [function] + rule blake2NoAlias(B) => blake2NoAliasAux(Blake2Compress(B), B) + rule blake2NoAliasAux(OUT, B) => lengthString(OUT) ==Int 128 andBool B ==K padRightBytes(b"", 213, 0) endmodule """ return krypto_kompile(definition=definition, main_module='TEST', syntax_module='TEST') diff --git a/krypto/src/tests/integration/test_hooks.py b/krypto/src/tests/integration/test_hooks.py index 8306732a2..6e22282b8 100644 --- a/krypto/src/tests/integration/test_hooks.py +++ b/krypto/src/tests/integration/test_hooks.py @@ -51,6 +51,11 @@ f"Blake2Compress({hex2bytes(213 * '00')})", '"08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b"', ), + ( + 'Blake2CompressNoAliasing', + f"blake2NoAlias({hex2bytes(213 * '00')})", + 'true', + ), ( 'Keccak256raw', 'Keccak256raw(b"foo")',