Repository: nspcc-dev/neo-go
Pull request #3425 (2024-04-27 15:20:56)

native: add test for custom Koblitz witness verification script
Every single thing is already implemented in the protocol for Koblitz verification scripts. An alternative to https://github.com/neo-project/neo/pull/3205.

### A note for reviewers

This PR contains a large number of file changes, but two most important files are:
* Native CryptoLib's `verifyWithECDsa` extension (`./pkg/core/native/crypto.go` file);
* Custom witness scripts builder (`./pkg/core/native/native_test/cryptolib_verificacation_test.go` file);

### Single signature verification

| What user signs| Invocation script length  |  Verification script length | Witness verification cost*  | 
|---|---|---|---|
| **`keccak256([4-bytes-network-magic-LE, txHash-bytes-BE])`**\** | 66 bytes  |  110 bytes | 2154270 GAS  | 
| `keccak256([var-bytes-network-magic, txHash-bytes-BE])`  | 66 bytes  |  98 bytes | 2092530 GAS  | 
| `keccak256([var-bytes-network-magic, txHash-bytes-BE])` (static magic)  | 66 bytes  |  95 bytes | 2092320 GAS  | 
| `keccak256(sha256([var-bytes-network-magic, txHash-bytes-BE]))`  |  66 bytes |  136 bytes |  4120620 GAS | 
| `keccak256(sha256([4-bytes-network-magic-LE, txHash-bytes-BE]))`  | 66 bytes  |  186 bytes | 5116020 GAS  | 

 \* Verification cost is based on the default execution fee value.
 \** The proposed verification method. Other methods are less preferable and are added for comparison.

### Multisignature verification

| What user signs| Multisig M out of N | Invocation script length  |  Verification script length | Witness verification cost*  | 
|---|---|---|---|---|
| **`keccak256([4-bytes-network-magic-LE, txHash-bytes-BE])`**\** | 3 out of 4 |198 bytes (= 66*3 bytes)  |  264 bytes | 8390070 GAS  |

 \* Verification cost is based on the default execution fee value.
 \** The proposed verification method. Other methods are less preferable and are added for comparison.

@roman-khimov, let's review&improve&merge, and after that I may create a comment in the core repo (or even an issue).

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/nspcc-dev/neo-go/pull/3425?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 86.10%. Comparing base [(`198af7f`)](https://app.codecov.io/gh/nspcc-dev/neo-go/commit/198af7f3aef039995b8f8cacefefb3a807b41ff3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev) to head [(`3b003ed`)](https://app.codecov.io/gh/nspcc-dev/neo-go/pull/3425?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev).

> :exclamation: Current head 3b003ed differs from pull request most recent head 27f824d. Consider uploading reports for the commit 27f824d to get more accurate results


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3425      +/-   ##
==========================================
- Coverage   86.13%   86.10%   -0.03%     
==========================================
  Files         331      331              
  Lines       38114    38114              
==========================================
- Hits        32828    32817      -11     
- Misses       3773     3781       +8     
- Partials     1513     1516       +3     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/nspcc-dev/neo-go/pull/3425?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nspcc-dev).

Jim8y: @AnnaShaleva I have no preference to solutions, yours here is much more elegent, i can close mine when you are done. But please make it:

1. support Keccak256 hasher. 2. support multi-sig.

Erik has asked to make it scalable, but i think currently we can only focus on dealing k1.
AnnaShaleva: Let it be here, CryptoLib extension is there, and I'm still working on the multisignature verification script, will update soon.
AnnaShaleva: Multisignature version is ready, let's review and improve it (a couple of TODOs are there, we need to decide about them). But overall the algorithm works as expected and is quite clear.

Meanwhile I'll add results for other multisig combinations and port CryptoLib's change to the C# repo.
roman-khimov: Multisignature is interesting wrt `calculatenetworkfee`, btw. I'm sure our implementation can handle simple signature case, but multisig cost currently depends on correct/incorrect flow (and POPITEM from https://github.com/nspcc-dev/neo-go/pull/3425#discussion_r1587983599 can make it worse), so we can have a trouble estimating verification cost for it.
roman-khimov: @AnnaShaleva, take a look at https://github.com/nspcc-dev/neo-go/tree/koblitz-static, I've not updated costs, but they're about the same. This variation is likely to be easier to adopt.
roman-khimov: Conflicts.

Patch:
diff --git a/examples/engine/go.mod b/examples/engine/go.mod
index b8c1f57b6..a3e1af0dc 100644
--- a/examples/engine/go.mod
+++ b/examples/engine/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/engine
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/engine/go.sum b/examples/engine/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/engine/go.sum
+++ b/examples/engine/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/events/go.mod b/examples/events/go.mod
index b0bda4b37..8c9910e5a 100644
--- a/examples/events/go.mod
+++ b/examples/events/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/events
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/events/go.sum b/examples/events/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/events/go.sum
+++ b/examples/events/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/iterator/go.mod b/examples/iterator/go.mod
index 2b3d13d3e..57de4a87e 100644
--- a/examples/iterator/go.mod
+++ b/examples/iterator/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/iterator
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/iterator/go.sum b/examples/iterator/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/iterator/go.sum
+++ b/examples/iterator/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/nft-d/go.mod b/examples/nft-d/go.mod
index 42df68efc..87060a972 100644
--- a/examples/nft-d/go.mod
+++ b/examples/nft-d/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/nft
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/nft-d/go.sum b/examples/nft-d/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/nft-d/go.sum
+++ b/examples/nft-d/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/nft-nd-nns/go.mod b/examples/nft-nd-nns/go.mod
index 78522923e..249993a39 100644
--- a/examples/nft-nd-nns/go.mod
+++ b/examples/nft-nd-nns/go.mod
@@ -4,7 +4,7 @@ go 1.20
 
 require (
 	github.com/nspcc-dev/neo-go v0.102.1-0.20231020181554-d89c8801d689
-	github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+	github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
 	github.com/stretchr/testify v1.8.4
 )
 
diff --git a/examples/nft-nd-nns/go.sum b/examples/nft-nd-nns/go.sum
index bb122c9db..2e6044811 100644
--- a/examples/nft-nd-nns/go.sum
+++ b/examples/nft-nd-nns/go.sum
@@ -200,8 +200,8 @@ github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22/go.mod h
 github.com/nspcc-dev/hrw v1.0.9 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y=
 github.com/nspcc-dev/neo-go v0.102.1-0.20231020181554-d89c8801d689 h1:WnEdGAQwaW0C8wnNnQZ+rM/JfFKZDSTOqwm8cS0TOdk=
 github.com/nspcc-dev/neo-go v0.102.1-0.20231020181554-d89c8801d689/go.mod h1:x+wmcYqpZYJwLp1l/pHZrqNp3RSWlkMymWGDij3/OPo=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
 github.com/nspcc-dev/neofs-api-go/v2 v2.14.0 h1:jhuN8Ldqz7WApvUJRFY0bjRXE1R3iCkboMX5QVZhHVk=
 github.com/nspcc-dev/neofs-crypto v0.4.0 h1:5LlrUAM5O0k1+sH/sktBtrgfWtq1pgpDs09fZo+KYi4=
 github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11 h1:QOc8ZRN5DXlAeRPh5QG9u8rMLgoeRNiZF5/vL7QupWg=
diff --git a/examples/nft-nd/go.mod b/examples/nft-nd/go.mod
index e9f47c373..b84c32fb7 100644
--- a/examples/nft-nd/go.mod
+++ b/examples/nft-nd/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/nft-nd
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/nft-nd/go.sum b/examples/nft-nd/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/nft-nd/go.sum
+++ b/examples/nft-nd/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/oracle/go.mod b/examples/oracle/go.mod
index cf5e26961..031ffbf68 100644
--- a/examples/oracle/go.mod
+++ b/examples/oracle/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/oracle
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/oracle/go.sum b/examples/oracle/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/oracle/go.sum
+++ b/examples/oracle/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/runtime/go.mod b/examples/runtime/go.mod
index d49e669e2..a243086af 100644
--- a/examples/runtime/go.mod
+++ b/examples/runtime/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/runtime
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/runtime/go.sum b/examples/runtime/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/runtime/go.sum
+++ b/examples/runtime/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/storage/go.mod b/examples/storage/go.mod
index 786d4d324..f4eca85b6 100644
--- a/examples/storage/go.mod
+++ b/examples/storage/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/storage
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/storage/go.sum b/examples/storage/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/storage/go.sum
+++ b/examples/storage/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/timer/go.mod b/examples/timer/go.mod
index e00b24175..95043975b 100644
--- a/examples/timer/go.mod
+++ b/examples/timer/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/timer
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/timer/go.sum b/examples/timer/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/timer/go.sum
+++ b/examples/timer/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/token/go.mod b/examples/token/go.mod
index 64494aad8..077dd8891 100644
--- a/examples/token/go.mod
+++ b/examples/token/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/token
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/token/go.sum b/examples/token/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/token/go.sum
+++ b/examples/token/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/examples/zkp/cubic_circuit/go.mod b/examples/zkp/cubic_circuit/go.mod
index 54702a26a..bab4104c3 100644
--- a/examples/zkp/cubic_circuit/go.mod
+++ b/examples/zkp/cubic_circuit/go.mod
@@ -33,7 +33,7 @@ require (
 	github.com/mmcloughlin/addchain v0.4.0 // indirect
 	github.com/mr-tron/base58 v1.2.0 // indirect
 	github.com/nspcc-dev/go-ordered-json v0.0.0-20231123160306-3374ff1e7a3c // indirect
-	github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 // indirect
+	github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb // indirect
 	github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_golang v1.13.0 // indirect
diff --git a/examples/zkp/cubic_circuit/go.sum b/examples/zkp/cubic_circuit/go.sum
index 59d49427b..809bda2cb 100644
--- a/examples/zkp/cubic_circuit/go.sum
+++ b/examples/zkp/cubic_circuit/go.sum
@@ -216,8 +216,8 @@ github.com/nspcc-dev/go-ordered-json v0.0.0-20231123160306-3374ff1e7a3c/go.mod h
 github.com/nspcc-dev/hrw v1.0.9 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y=
 github.com/nspcc-dev/neo-go v0.103.1 h1:BfRBceHUu8jSc1KQy7CzmQ/pa+xzAmgcyteGf0/IGgM=
 github.com/nspcc-dev/neo-go v0.103.1/go.mod h1:MD7MPiyshUwrE5n1/LzxeandbItaa/iLW/bJb6gNs/U=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
 github.com/nspcc-dev/neofs-api-go/v2 v2.14.0 h1:jhuN8Ldqz7WApvUJRFY0bjRXE1R3iCkboMX5QVZhHVk=
 github.com/nspcc-dev/neofs-crypto v0.4.0 h1:5LlrUAM5O0k1+sH/sktBtrgfWtq1pgpDs09fZo+KYi4=
 github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11 h1:QOc8ZRN5DXlAeRPh5QG9u8rMLgoeRNiZF5/vL7QupWg=
diff --git a/examples/zkp/xor_compat/go.mod b/examples/zkp/xor_compat/go.mod
index a605846d1..8882b7ca2 100644
--- a/examples/zkp/xor_compat/go.mod
+++ b/examples/zkp/xor_compat/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/zkp/xor
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/examples/zkp/xor_compat/go.sum b/examples/zkp/xor_compat/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/examples/zkp/xor_compat/go.sum
+++ b/examples/zkp/xor_compat/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/go.mod b/go.mod
index 75cee142c..0b7590384 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
 	github.com/mr-tron/base58 v1.2.0
 	github.com/nspcc-dev/dbft v0.2.0
 	github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2
-	github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+	github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
 	github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11
 	github.com/nspcc-dev/rfc6979 v0.2.1
 	github.com/pierrec/lz4 v2.6.1+incompatible
diff --git a/go.sum b/go.sum
index b0e22e3b6..0b6fe0e15 100644
--- a/go.sum
+++ b/go.sum
@@ -94,8 +94,8 @@ github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 h1:mD9hU
 github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2/go.mod h1:U5VfmPNM88P4RORFb6KSUVBdJBDhlqggJZYGXGPxOcc=
 github.com/nspcc-dev/hrw v1.0.9 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y=
 github.com/nspcc-dev/hrw v1.0.9/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkPG06MU=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
 github.com/nspcc-dev/neofs-api-go/v2 v2.14.0 h1:jhuN8Ldqz7WApvUJRFY0bjRXE1R3iCkboMX5QVZhHVk=
 github.com/nspcc-dev/neofs-api-go/v2 v2.14.0/go.mod h1:DRIr0Ic1s+6QgdqmNFNLIqMqd7lNMJfYwkczlm1hDtM=
 github.com/nspcc-dev/neofs-crypto v0.4.0 h1:5LlrUAM5O0k1+sH/sktBtrgfWtq1pgpDs09fZo+KYi4=
diff --git a/internal/contracts/oracle_contract/go.mod b/internal/contracts/oracle_contract/go.mod
index 2fe0af1fd..f6959e04b 100644
--- a/internal/contracts/oracle_contract/go.mod
+++ b/internal/contracts/oracle_contract/go.mod
@@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/internal/examples/oracle
 
 go 1.20
 
-require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7
+require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb
diff --git a/internal/contracts/oracle_contract/go.sum b/internal/contracts/oracle_contract/go.sum
index 84d8058e4..0d9f2bcc7 100644
--- a/internal/contracts/oracle_contract/go.sum
+++ b/internal/contracts/oracle_contract/go.sum
@@ -1,2 +1,2 @@
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7 h1:wULMfaNToxlinz9cYWyZA4kAy6CTUBtJ1yFHjCSgs10=
-github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240322141543-1840c057bdd7/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb h1:yU1nu3dtytjkHWHNQBdSKnHs6jU9wtNcXn3Hn46EfNY=
+github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240513071056-6e0926e59ffb/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
diff --git a/pkg/core/native/crypto.go b/pkg/core/native/crypto.go
index 81979bfbd..d63a20c70 100644
--- a/pkg/core/native/crypto.go
+++ b/pkg/core/native/crypto.go
@@ -18,6 +18,7 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
+	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
 	"github.com/twmb/murmur3"
 	"golang.org/x/crypto/sha3"
@@ -28,13 +29,18 @@ type Crypto struct {
 	interop.ContractMD
 }
 
-// NamedCurve identifies named elliptic curves.
-type NamedCurve byte
+// HashFunc is a delegate representing a hasher function with 256 bytes output length.
+type HashFunc func([]byte) util.Uint256
 
-// Various named elliptic curves.
+// NamedCurveHash identifies a pair of named elliptic curve and hash function.
+type NamedCurveHash byte
+
+// Various pairs of named elliptic curves and hash functions.
 const (
-	Secp256k1 NamedCurve = 22
-	Secp256r1 NamedCurve = 23
+	Secp256k1Sha256    NamedCurveHash = 22
+	Secp256r1Sha256    NamedCurveHash = 23
+	Secp256k1Keccak256 NamedCurveHash = 122
+	Secp256r1Keccak256 NamedCurveHash = 123
 )
 
 const cryptoContractID = -3
@@ -63,7 +69,7 @@ func newCrypto() *Crypto {
 		manifest.NewParameter("message", smartcontract.ByteArrayType),
 		manifest.NewParameter("pubkey", smartcontract.ByteArrayType),
 		manifest.NewParameter("signature", smartcontract.ByteArrayType),
-		manifest.NewParameter("curve", smartcontract.IntegerType))
+		manifest.NewParameter("curveHash", smartcontract.IntegerType))
 	md = newMethodAndPrice(c.verifyWithECDsa, 1<<15, callflag.NoneFlag)
 	c.AddMethod(md, desc)
 
@@ -142,7 +148,6 @@ func (c *Crypto) verifyWithECDsa(_ *interop.Context, args []stackitem.Item) stac
 	if err != nil {
 		panic(fmt.Errorf("invalid message stackitem: %w", err))
 	}
-	hashToCheck := hash.Sha256(msg)
 	pubkey, err := args[1].TryBytes()
 	if err != nil {
 		panic(fmt.Errorf("invalid pubkey stackitem: %w", err))
@@ -151,10 +156,11 @@ func (c *Crypto) verifyWithECDsa(_ *interop.Context, args []stackitem.Item) stac
 	if err != nil {
 		panic(fmt.Errorf("invalid signature stackitem: %w", err))
 	}
-	curve, err := curveFromStackitem(args[3])
+	curve, hasher, err := curveHasherFromStackitem(args[3])
 	if err != nil {
-		panic(fmt.Errorf("invalid curve stackitem: %w", err))
+		panic(fmt.Errorf("invalid curveHash stackitem: %w", err))
 	}
+	hashToCheck := hasher(msg)
 	pkey, err := keys.NewPublicKeyFromBytes(pubkey, curve)
 	if err != nil {
 		panic(fmt.Errorf("failed to decode pubkey: %w", err))
@@ -163,22 +169,26 @@ func (c *Crypto) verifyWithECDsa(_ *interop.Context, args []stackitem.Item) stac
 	return stackitem.NewBool(res)
 }
 
-func curveFromStackitem(si stackitem.Item) (elliptic.Curve, error) {
+func curveHasherFromStackitem(si stackitem.Item) (elliptic.Curve, HashFunc, error) {
 	curve, err := si.TryInteger()
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	if !curve.IsInt64() {
-		return nil, errors.New("not an int64")
+		return nil, nil, errors.New("not an int64")
 	}
 	c := curve.Int64()
 	switch c {
-	case int64(Secp256k1):
-		return secp256k1.S256(), nil
-	case int64(Secp256r1):
-		return elliptic.P256(), nil
+	case int64(Secp256k1Sha256):
+		return secp256k1.S256(), hash.Sha256, nil
+	case int64(Secp256r1Sha256):
+		return elliptic.P256(), hash.Sha256, nil
+	case int64(Secp256k1Keccak256):
+		return secp256k1.S256(), Keccak256, nil
+	case int64(Secp256r1Keccak256):
+		return elliptic.P256(), Keccak256, nil
 	default:
-		return nil, errors.New("unsupported curve type")
+		return nil, nil, errors.New("unsupported curve/hash type")
 	}
 }
 
@@ -295,13 +305,7 @@ func (c *Crypto) keccak256(_ *interop.Context, args []stackitem.Item) stackitem.
 	if err != nil {
 		panic(err)
 	}
-
-	digest := sha3.NewLegacyKeccak256()
-	_, err = digest.Write(bs)
-	if err != nil {
-		panic(err)
-	}
-	return stackitem.NewByteArray(digest.Sum(nil))
+	return stackitem.NewByteArray(Keccak256(bs).BytesBE())
 }
 
 // Metadata implements the Contract interface.
@@ -333,3 +337,14 @@ func (c *Crypto) PostPersist(ic *interop.Context) error {
 func (c *Crypto) ActiveIn() *config.Hardfork {
 	return nil
 }
+
+// Keccak256 hashes the incoming byte slice using the
+// keccak256 algorithm.
+func Keccak256(data []byte) util.Uint256 {
+	var hash util.Uint256
+	hasher := sha3.NewLegacyKeccak256()
+	_, _ = hasher.Write(data)
+
+	hasher.Sum(hash[:0])
+	return hash
+}
diff --git a/pkg/crypto/hash/hash.go b/pkg/crypto/hash/hash.go
index a997a3514..999b291da 100644
--- a/pkg/crypto/hash/hash.go
+++ b/pkg/crypto/hash/hash.go
@@ -16,7 +16,10 @@ type Hashable interface {
 	Hash() util.Uint256
 }
 
-func getSignedData(net uint32, hh Hashable) []byte {
+// GetSignedData returns the concatenated byte slice containing of the network
+// magic in constant-length 4-bytes LE representation and hashable item hash in BE
+// representation.
+func GetSignedData(net uint32, hh Hashable) []byte {
 	var b = make([]byte, 4+util.Uint256Size)
 	binary.LittleEndian.PutUint32(b, net)
 	h := hh.Hash()
@@ -27,7 +30,7 @@ func getSignedData(net uint32, hh Hashable) []byte {
 // NetSha256 calculates a network-specific hash of the Hashable item that can then
 // be signed/verified.
 func NetSha256(net uint32, hh Hashable) util.Uint256 {
-	return Sha256(getSignedData(net, hh))
+	return Sha256(GetSignedData(net, hh))
 }
 
 // Sha256 hashes the incoming byte slice
diff --git a/pkg/interop/native/crypto/crypto.go b/pkg/interop/native/crypto/crypto.go
index db0c3d750..b3574fcde 100644
--- a/pkg/interop/native/crypto/crypto.go
+++ b/pkg/interop/native/crypto/crypto.go
@@ -13,13 +13,15 @@ import (
 // Hash represents CryptoLib contract hash.
 const Hash = "\x1b\xf5\x75\xab\x11\x89\x68\x84\x13\x61\x0a\x35\xa1\x28\x86\xcd\xe0\xb6\x6c\x72"
 
-// NamedCurve represents a named elliptic curve.
-type NamedCurve byte
+// NamedCurveHash represents a pair of named elliptic curve and hash function.
+type NamedCurveHash byte
 
-// Various named elliptic curves.
+// Various pairs of named elliptic curves and hash functions.
 const (
-	Secp256k1 NamedCurve = 22
-	Secp256r1 NamedCurve = 23
+	Secp256k1Sha256    NamedCurveHash = 22
+	Secp256r1Sha256    NamedCurveHash = 23
+	Secp256k1Keccak256 NamedCurveHash = 122
+	Secp256r1Keccak256 NamedCurveHash = 123
 )
 
 // Sha256 calls `sha256` method of native CryptoLib contract and computes SHA256 hash of b.
@@ -40,8 +42,8 @@ func Murmur32(b []byte, seed int) []byte {
 
 // VerifyWithECDsa calls `verifyWithECDsa` method of native CryptoLib contract and checks that sig is
 // a correct msg's signature for the given pub (serialized public key on the given curve).
-func VerifyWithECDsa(msg []byte, pub interop.PublicKey, sig interop.Signature, curve NamedCurve) bool {
-	return neogointernal.CallWithToken(Hash, "verifyWithECDsa", int(contract.NoneFlag), msg, pub, sig, curve).(bool)
+func VerifyWithECDsa(msg []byte, pub interop.PublicKey, sig interop.Signature, curveHash NamedCurveHash) bool {
+	return neogointernal.CallWithToken(Hash, "verifyWithECDsa", int(contract.NoneFlag), msg, pub, sig, curveHash).(bool)
 }
 
 // Bls12381Point represents BLS12-381 curve point (G1 or G2 in the Affine or
diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go
index a51e3f781..7a2b1033c 100644
--- a/pkg/vm/emit/emit.go
+++ b/pkg/vm/emit/emit.go
@@ -29,6 +29,11 @@ func Opcodes(w *io.BinWriter, ops ...opcode.Opcode) {
 	}
 }
 
+// InitSlot emits INITSLOT instruction with the specified size of locals/args slots.
+func InitSlot(w *io.BinWriter, locals, args uint8) {
+	Instruction(w, opcode.INITSLOT, []byte{locals, args})
+}
+
 // Bool emits a bool type to the given buffer.
 func Bool(w *io.BinWriter, ok bool) {
 	var opVal = opcode.PUSHT


Test patch:
diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go
index 47a2a7433..249e6717e 100644
--- a/pkg/compiler/native_test.go
+++ b/pkg/compiler/native_test.go
@@ -74,8 +74,10 @@ func TestRoleManagementRole(t *testing.T) {
 }
 
 func TestCryptoLibNamedCurve(t *testing.T) {
-	require.EqualValues(t, native.Secp256k1, crypto.Secp256k1)
-	require.EqualValues(t, native.Secp256r1, crypto.Secp256r1)
+	require.EqualValues(t, native.Secp256k1Sha256, crypto.Secp256k1Sha256)
+	require.EqualValues(t, native.Secp256r1Sha256, crypto.Secp256r1Sha256)
+	require.EqualValues(t, native.Secp256k1Keccak256, crypto.Secp256k1Keccak256)
+	require.EqualValues(t, native.Secp256r1Keccak256, crypto.Secp256r1Keccak256)
 }
 
 func TestOracleContractValues(t *testing.T) {
@@ -233,7 +235,7 @@ func TestNativeHelpersCompile(t *testing.T) {
 		{"sha256", []string{"[]byte{1, 2, 3}"}},
 		{"ripemd160", []string{"[]byte{1, 2, 3}"}},
 		{"murmur32", []string{"[]byte{1, 2, 3}", "123"}},
-		{"verifyWithECDsa", []string{"[]byte{1, 2, 3}", pub, sig, "crypto.Secp256k1"}},
+		{"verifyWithECDsa", []string{"[]byte{1, 2, 3}", pub, sig, "crypto.Secp256k1Sha256"}},
 		{"bls12381Serialize", []string{"crypto.Bls12381Point{}"}},
 		{"bls12381Deserialize", []string{"[]byte{1, 2, 3}"}},
 		{"bls12381Equal", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}},
diff --git a/pkg/core/native/crypto_test.go b/pkg/core/native/crypto_test.go
index d5d79355e..0227a9da8 100644
--- a/pkg/core/native/crypto_test.go
+++ b/pkg/core/native/crypto_test.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
 	"github.com/nspcc-dev/neo-go/pkg/core/interop"
+	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
 	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
 	"github.com/nspcc-dev/neo-go/pkg/vm"
 	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@@ -118,29 +119,44 @@ func TestMurmur32(t *testing.T) {
 }
 
 func TestCryptoLibVerifyWithECDsa(t *testing.T) {
-	t.Run("R1", func(t *testing.T) {
-		testECDSAVerify(t, Secp256r1)
+	t.Run("R1 sha256", func(t *testing.T) {
+		testECDSAVerify(t, Secp256r1Sha256)
 	})
-	t.Run("K1", func(t *testing.T) {
-		testECDSAVerify(t, Secp256k1)
+	t.Run("K1 sha256", func(t *testing.T) {
+		testECDSAVerify(t, Secp256k1Sha256)
+	})
+	t.Run("R1 keccak256", func(t *testing.T) {
+		testECDSAVerify(t, Secp256r1Keccak256)
+	})
+	t.Run("K1 keccak256", func(t *testing.T) {
+		testECDSAVerify(t, Secp256k1Keccak256)
 	})
 }
 
-func testECDSAVerify(t *testing.T, curve NamedCurve) {
+func testECDSAVerify(t *testing.T, curve NamedCurveHash) {
 	var (
 		priv   *keys.PrivateKey
 		err    error
 		c      = newCrypto()
 		ic     = &interop.Context{VM: vm.New()}
 		actual stackitem.Item
+		hasher HashFunc
 	)
 	switch curve {
-	case Secp256k1:
+	case Secp256k1Sha256:
+		priv, err = keys.NewSecp256k1PrivateKey()
+		hasher = hash.Sha256
+	case Secp256r1Sha256:
+		priv, err = keys.NewPrivateKey()
+		hasher = hash.Sha256
+	case Secp256k1Keccak256:
 		priv, err = keys.NewSecp256k1PrivateKey()
-	case Secp256r1:
+		hasher = Keccak256
+	case Secp256r1Keccak256:
 		priv, err = keys.NewPrivateKey()
+		hasher = Keccak256
 	default:
-		t.Fatal("unknown curve")
+		t.Fatal("unknown curve/hash")
 	}
 	require.NoError(t, err)
 
@@ -162,7 +178,7 @@ func testECDSAVerify(t *testing.T, curve NamedCurve) {
 	}
 
 	msg := []byte("test message")
-	sign := priv.Sign(msg)
+	sign := priv.SignHash(hasher(msg))
 
 	t.Run("bad message item", func(t *testing.T) {
 		runCase(t, true, false, stackitem.NewInterop("cheburek"), priv.PublicKey().Bytes(), sign, int64(curve))
@@ -183,7 +199,7 @@ func testECDSAVerify(t *testing.T, curve NamedCurve) {
 		runCase(t, true, false, msg, priv.PublicKey().Bytes(), sign, new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(1)))
 	})
 	t.Run("unknown curve", func(t *testing.T) {
-		runCase(t, true, false, msg, priv.PublicKey().Bytes(), sign, int64(123))
+		runCase(t, true, false, msg, priv.PublicKey().Bytes(), sign, int64(124))
 	})
 	t.Run("invalid signature", func(t *testing.T) {
 		s := priv.Sign(msg)
@@ -254,3 +270,13 @@ func TestCryptolib_ScalarFromBytes_Compat(t *testing.T) {
 		})
 	}
 }
+
+func TestKeccak256(t *testing.T) {
+	input := []byte("hello")
+	data := Keccak256(input)
+
+	expected := "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"
+	actual := hex.EncodeToString(data.BytesBE())
+
+	require.Equal(t, expected, actual)
+}
diff --git a/pkg/core/native/native_test/cryptolib_verification_test.go b/pkg/core/native/native_test/cryptolib_verification_test.go
new file mode 100644
index 000000000..2143a1217
--- /dev/null
+++ b/pkg/core/native/native_test/cryptolib_verification_test.go
@@ -0,0 +1,893 @@
+package native_test
+
+import (
+	"math/big"
+	"sort"
+	"testing"
+
+	"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
+	"github.com/nspcc-dev/neo-go/pkg/core/native"
+	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
+	"github.com/nspcc-dev/neo-go/pkg/core/state"
+	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
+	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
+	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
+	"github.com/nspcc-dev/neo-go/pkg/io"
+	"github.com/nspcc-dev/neo-go/pkg/neotest"
+	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
+	"github.com/nspcc-dev/neo-go/pkg/util"
+	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
+	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
+	"github.com/stretchr/testify/require"
+)
+
+// TestCryptoLib_KoblitzVerificationScript builds transaction with custom witness that contains
+// the Koblitz tx signature bytes and Koblitz signature verification script.
+// This test ensures that transaction signed by Koblitz key passes verification and can
+// be successfully accepted to the chain.
+func TestCryptoLib_KoblitzVerificationScript(t *testing.T) {
+	check := func(
+		t *testing.T,
+		buildVerificationScript func(t *testing.T, pub *keys.PublicKey) []byte,
+		constructMsg func(t *testing.T, magic uint32, tx hash.Hashable) []byte,
+	) {
+		c := newGasClient(t)
+		gasInvoker := c.WithSigners(c.Committee)
+		e := c.Executor
+
+		// Consider the user that is able to sign txs only with Secp256k1 private key.
+		// Let this user build, sign and push a GAS transfer transaction from its account
+		// to some other account.
+		pk, err := keys.NewSecp256k1PrivateKey()
+		require.NoError(t, err)
+
+		// Firstly, we need to build the N3 user's account address based on the user's public key.
+		// The address itself is Hash160 from the verification script corresponding to the user's public key.
+		// Since user's private key belongs to Koblitz curve, we can't use System.Crypto.CheckSig interop
+		// in the verification script. Likely, we have a 'verifyWithECDsa' method in native CriptoLib contract
+		// that is able to check Koblitz signature. So let's build custom verification script based on this call.
+		// The script should call 'verifyWithECDsa' method of native CriptoLib contract with Koblitz curve identifier
+		// and check the provided message signature against the user's Koblitz public key.
+		vrfBytes := buildVerificationScript(t, pk.PublicKey())
+
+		// Construct the user's account script hash. It's effectively a verification script hash.
+		from := hash.Hash160(vrfBytes)
+
+		// Supply this account with some initial balance so that the user is able to pay for his transactions.
+		gasInvoker.Invoke(t, true, "transfer", c.Committee.ScriptHash(), from, 10000_0000_0000, nil)
+
+		// Construct transaction that transfers 5 GAS from the user's account to some other account.
+		to := util.Uint160{1, 2, 3}
+		amount := 5
+		tx := gasInvoker.PrepareInvokeNoSign(t, "transfer", from, to, amount, nil)
+		tx.Signers = []transaction.Signer{
+			{
+				Account: from,
+				Scopes:  transaction.CalledByEntry,
+			},
+		}
+		neotest.AddNetworkFee(t, e.Chain, tx)
+		neotest.AddSystemFee(e.Chain, tx, -1)
+
+		// Add some more network fee to pay for the witness verification. This value may be calculated precisely,
+		// but let's keep some inaccurate value for the test.
+		tx.NetworkFee += 540_0000
+
+		// This transaction (along with the network magic) should be signed by the user's Koblitz private key.
+		msg := constructMsg(t, uint32(e.Chain.GetConfig().Magic), tx)
+
+		// The user has to sign the hash of the message by his Koblitz key.
+		// Please, note that this Keccak256 hash may easily be replaced by sha256 hash if needed.
+		signature := pk.SignHash(native.Keccak256(msg))
+
+		// Ensure that signature verification passes. This line here is just for testing purposes,
+		// it won't be present in the real code.
+		require.True(t, pk.PublicKey().Verify(signature, native.Keccak256(msg).BytesBE()))
+
+		// Build invocation witness script for the user's account.
+		invBytes := buildKoblitzInvocationScript(t, [][]byte{signature})
+
+		// Construct witness for signer #0 (the user itself).
+		tx.Scripts = []transaction.Witness{
+			{
+				InvocationScript:   invBytes,
+				VerificationScript: vrfBytes,
+			},
+		}
+
+		// Add transaction to the chain. No error is expected on new block addition. Note, that this line performs
+		// all those checks that are executed during transaction acceptance in the real network.
+		e.AddNewBlock(t, tx)
+
+		// Double-check: ensure funds have been transferred.
+		e.CheckGASBalance(t, to, big.NewInt(int64(amount)))
+	}
+
+	// The proposed preferable witness verification script
+	// (110 bytes, 2154270 GAS including Invocation script execution).
+	// The user has to sign the keccak256([4-bytes-network-magic-LE, txHash-bytes-BE]).
+	check(t, buildKoblitzVerificationScript, constructMessage)
+
+	// Below presented some variations of verification scripts that were also considered, but
+	// they are not as good as the first one.
+
+	// The simplest witness verification script with low length and low execution cost
+	// (98 bytes, 2092530 GAS including Invocation script execution).
+	// The user has to sign the keccak256([var-bytes-network-magic, txHash-bytes-BE]).
+	check(t, buildKoblitzVerificationScriptSimpleSingleHash, constructMessageNoHash)
+
+	// Even more simple witness verification script with low length and low execution cost
+	// (95 bytes, 2092320 GAS including Invocation script execution).
+	// The user has to sign the keccak256([var-bytes-network-magic, txHash-bytes-BE]).
+	// The difference is that network magic is a static value, thus, both verification script and
+	// user address are network-specific.
+	check(t, buildKoblitzVerificationScriptSimpleSingleHashStaticMagic, constructMessageNoHash)
+
+	// More complicated verification script with higher length and higher execution cost
+	// (136 bytes, 4120620 GAS including Invocation script execution).
+	// The user has to sign the keccak256(sha256([var-bytes-network-magic, txHash-bytes-BE])).
+	check(t, buildKoblitzVerificationScriptSimple, constructMessageSimple)
+
+	// Witness verification script that follows the existing standard CheckSig account generation rules
+	// and has larger length and higher execution cost.
+	// (186 bytes, 5116020 GAS including Invocation script execution).
+	// The user has to sign the keccak256(sha256([4-bytes-network-magic-LE, txHash-bytes-BE]))
+	check(t, buildKoblitzVerificationScriptCompat, constructMessageCompat)
+}
+
+// buildKoblitzVerificationScript builds witness verification script for Koblitz public key.
+// This method checks
+//
+//	keccak256([4-bytes-network-magic-LE, txHash-bytes-BE])
+//
+// instead of (comparing with N3)
+//
+//	sha256([4-bytes-network-magic-LE, txHash-bytes-BE]).
+func buildKoblitzVerificationScript(t *testing.T, pub *keys.PublicKey) []byte {
+	criptoLibH := state.CreateNativeContractHash(nativenames.CryptoLib)
+
+	// vrf is witness verification script corresponding to the pub.
+	vrf := io.NewBufBinWriter()
+	emit.Int(vrf.BinWriter, int64(native.Secp256k1Keccak256)) // push Koblitz curve identifier.
+	emit.Opcodes(vrf.BinWriter, opcode.SWAP)                  // swap curve identifier with the signature.
+	emit.Bytes(vrf.BinWriter, pub.Bytes())                    // emit the caller's public key.
+	// Construct and push the signed message. The signed message is effectively the network-dependent transaction hash,
+	// i.e. msg = [4-network-magic-bytes-LE, tx-hash-BE]
+	// Firstly, retrieve network magic (it's uint32 wrapped into BigInteger and represented as Integer stackitem on stack).
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetNetwork) // push network magic (Integer stackitem), can have 0-5 bytes length serialized.
+	// Convert network magic to 4-bytes-length LE byte array representation.
+	emit.Int(vrf.BinWriter, 0x100000000)
+	emit.Opcodes(vrf.BinWriter, opcode.ADD, // some new number that is 5 bytes at least when serialized, but first 4 bytes are intact network value (LE).
+		opcode.PUSH4, opcode.LEFT) // cut the first 4 bytes out of a number that is at least 5 bytes long, the result is 4-bytes-length LE network representation.
+	// Retrieve executing transaction hash.
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetScriptContainer) // push the script container (executing transaction, actually).
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH0, opcode.PICKITEM)                // pick 0-th transaction item (the transaction hash).
+	// Concatenate network magic and transaction hash.
+	emit.Opcodes(vrf.BinWriter, opcode.CAT) // this instruction will convert network magic to bytes using BigInteger rules of conversion.
+	// Continue construction of 'verifyWithECDsa' call.
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH4, opcode.PACK)                              // pack arguments for 'verifyWithECDsa' call.
+	emit.AppCallNoArgs(vrf.BinWriter, criptoLibH, "verifyWithECDsa", callflag.NoneFlag) // emit the call to 'verifyWithECDsa' itself.
+	require.NoError(t, vrf.Err)
+
+	return vrf.Bytes()
+	// Here's an example of the resulting witness verification script (110 bytes length, always constant length, with constant length of signed data):
+	// NEO-GO-VM > loadbase64 ABhQDCECoIi/qx5LS+3n1GJFcoYbQByyDDsU6QaHvYhiJypOYWZBxfug4AMAAAAAAQAAAJ4UjUEtUQgwEM6LFMAfDA92ZXJpZnlXaXRoRUNEc2EMFBv1dasRiWiEE2EKNaEohs3gtmxyQWJ9W1I=
+	// READY: loaded 110 instructions
+	// NEO-GO-VM 0 > ops
+	// INDEX    OPCODE       PARAMETER
+	// 0        PUSHINT8     122 (7a)    <<
+	// 2        SWAP
+	// 3        PUSHDATA1    02a088bfab1e4b4bede7d4624572861b401cb20c3b14e90687bd8862272a4e6166
+	// 38       SYSCALL      System.Runtime.GetNetwork (c5fba0e0)
+	// 43       PUSHINT64    4294967296 (0000000001000000)
+	// 52       ADD
+	// 53       PUSH4
+	// 54       LEFT
+	// 55       SYSCALL      System.Runtime.GetScriptContainer (2d510830)
+	// 60       PUSH0
+	// 61       PICKITEM
+	// 62       CAT
+	// 63       PUSH4
+	// 64       PACK
+	// 65       PUSH0
+	// 66       PUSHDATA1    766572696679576974684543447361 ("verifyWithECDsa")
+	// 83       PUSHDATA1    1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+	// 105      SYSCALL      System.Contract.Call (627d5b52)
+}
+
+// buildKoblitzVerificationScriptSimpleSingleHash builds witness verification script for Koblitz public key.
+// This method differs from buildKoblitzVerificationScriptCompat in that it checks
+//
+//	keccak256([var-bytes-network-magic, txHash-bytes-BE])
+//
+// instead of (comparing with N3)
+//
+//	sha256([4-bytes-network-magic-LE, txHash-bytes-BE]).
+func buildKoblitzVerificationScriptSimpleSingleHash(t *testing.T, pub *keys.PublicKey) []byte {
+	criptoLibH := state.CreateNativeContractHash(nativenames.CryptoLib)
+
+	// vrf is witness verification script corresponding to the pub.
+	// vrf is witness verification script corresponding to the pk.
+	vrf := io.NewBufBinWriter()
+	emit.Int(vrf.BinWriter, int64(native.Secp256k1Keccak256)) // push Koblitz curve identifier.
+	emit.Opcodes(vrf.BinWriter, opcode.SWAP)                  // swap curve identifier with the signature.
+	emit.Bytes(vrf.BinWriter, pub.Bytes())                    // emit the caller's public key.
+	// Construct and push the signed message. The signed message is effectively the network-dependent transaction hash,
+	// i.e. msg = [network-magic-bytes, tx.Hash()]
+	// Firstly, retrieve network magic (it's uint32 wrapped into BigInteger and represented as Integer stackitem on stack).
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetNetwork) // push network magic.
+	// Retrieve executing transaction hash.
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetScriptContainer) // push the script container (executing transaction, actually).
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH0, opcode.PICKITEM)                // pick 0-th transaction item (the transaction hash).
+	// Concatenate network magic and transaction hash.
+	emit.Opcodes(vrf.BinWriter, opcode.CAT) // this instruction will convert network magic to bytes using BigInteger rules of conversion.
+	// Continue construction of 'verifyWithECDsa' call.
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH4, opcode.PACK)                              // pack arguments for 'verifyWithECDsa' call.
+	emit.AppCallNoArgs(vrf.BinWriter, criptoLibH, "verifyWithECDsa", callflag.NoneFlag) // emit the call to 'verifyWithECDsa' itself.
+	require.NoError(t, vrf.Err)
+
+	return vrf.Bytes()
+	// Here's an example of the resulting witness verification script (98 bytes length, always constant length, with variable length of signed data):
+	// NEO-GO-VM > loadbase64 ABZQDCEDY9ekgSWnbN6m4JjJ8SjoKSDtQo5ftMrx1/gcFsrQwgVBxfug4EEtUQgwEM6LFMAfDA92ZXJpZnlXaXRoRUNEc2EMFBv1dasRiWiEE2EKNaEohs3gtmxyQWJ9W1I=
+	// READY: loaded 98 instructions
+	// NEO-GO-VM 0 > ops
+	// INDEX    OPCODE       PARAMETER
+	// 0        PUSHINT8     122 (7a)    <<
+	// 2        SWAP
+	// 3        PUSHDATA1    0363d7a48125a76cdea6e098c9f128e82920ed428e5fb4caf1d7f81c16cad0c205
+	// 38       SYSCALL      System.Runtime.GetNetwork (c5fba0e0)
+	// 43       SYSCALL      System.Runtime.GetScriptContainer (2d510830)
+	// 48       PUSH0
+	// 49       PICKITEM
+	// 50       CAT
+	// 51       PUSH4
+	// 52       PACK
+	// 53       PUSH0
+	// 54       PUSHDATA1    766572696679576974684543447361 ("verifyWithECDsa")
+	// 71       PUSHDATA1    1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+	// 93       SYSCALL      System.Contract.Call (627d5b52)
+}
+
+// buildKoblitzVerificationScriptSimpleSingleHashStaticMagic builds witness verification script for Koblitz public key.
+// This method differs from buildKoblitzVerificationScriptCompat in that it checks
+//
+//	keccak256([var-bytes-network-magic, txHash-bytes-BE])
+//
+// instead of (comparing with N3)
+//
+//	sha256([4-bytes-network-magic-LE, txHash-bytes-BE]).
+//
+// and it uses static magic value (simple PUSHINT* + magic, or PUSHDATA1 + magicBytes is also possible)
+// which results in network-specific verification script and, consequently, network-specific user address.
+func buildKoblitzVerificationScriptSimpleSingleHashStaticMagic(t *testing.T, pub *keys.PublicKey) []byte {
+	criptoLibH := state.CreateNativeContractHash(nativenames.CryptoLib)
+
+	// vrf is witness verification script corresponding to the pub.
+	// vrf is witness verification script corresponding to the pk.
+	vrf := io.NewBufBinWriter()
+	emit.Int(vrf.BinWriter, int64(native.Secp256k1Keccak256)) // push Koblitz curve identifier.
+	emit.Opcodes(vrf.BinWriter, opcode.SWAP)                  // swap curve identifier with the signature.
+	emit.Bytes(vrf.BinWriter, pub.Bytes())                    // emit the caller's public key.
+	// Construct and push the signed message. The signed message is effectively the network-dependent transaction hash,
+	// i.e. msg = [network-magic-bytes, tx.Hash()]
+	// Firstly, push static network magic (it's 42 for unit test chain).
+	emit.Int(vrf.BinWriter, 42)
+	// Retrieve executing transaction hash.
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetScriptContainer) // push the script container (executing transaction, actually).
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH0, opcode.PICKITEM)                // pick 0-th transaction item (the transaction hash).
+	// Concatenate network magic and transaction hash.
+	emit.Opcodes(vrf.BinWriter, opcode.CAT) // this instruction will convert network magic to bytes using BigInteger rules of conversion.
+	// Continue construction of 'verifyWithECDsa' call.
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH4, opcode.PACK)                              // pack arguments for 'verifyWithECDsa' call.
+	emit.AppCallNoArgs(vrf.BinWriter, criptoLibH, "verifyWithECDsa", callflag.NoneFlag) // emit the call to 'verifyWithECDsa' itself.
+	require.NoError(t, vrf.Err)
+
+	return vrf.Bytes()
+	// Here's an example of the resulting witness verification script (95 bytes length, always constant length, with variable length of signed data):
+	// NEO-GO-VM > loadbase64 ABZQDCECluEwgK3pKiq3IjOMKiSe6Ng6FPZJxoMhZkFl8GvREL0AKkEtUQgwEM6LFMAfDA92ZXJpZnlXaXRoRUNEc2EMFBv1dasRiWiEE2EKNaEohs3gtmxyQWJ9W1I=
+	// READY: loaded 95 instructions
+	// NEO-GO-VM 0 > ops
+	// INDEX    OPCODE       PARAMETER
+	// 0        PUSHINT8     122 (7a)    <<
+	// 2        SWAP
+	// 3        PUSHDATA1    0296e13080ade92a2ab722338c2a249ee8d83a14f649c68321664165f06bd110bd
+	// 38       PUSHINT8     42 (2a)
+	// 40       SYSCALL      System.Runtime.GetScriptContainer (2d510830)
+	// 45       PUSH0
+	// 46       PICKITEM
+	// 47       CAT
+	// 48       PUSH4
+	// 49       PACK
+	// 50       PUSH0
+	// 51       PUSHDATA1    766572696679576974684543447361 ("verifyWithECDsa")
+	// 68       PUSHDATA1    1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+	// 90       SYSCALL      System.Contract.Call (627d5b52)
+}
+
+// buildKoblitzVerificationScriptSimple builds witness verification script for Koblitz public key.
+// This method differs from buildKoblitzVerificationScriptCompat in that it checks
+//
+//	keccak256(sha256([var-bytes-network-magic, txHash-bytes-BE]))
+//
+// instead of (comparing with N3)
+//
+//	sha256([4-bytes-network-magic-LE, txHash-bytes-BE]).
+//
+// It produces constant-length verification script (136 bytes) independently of the network parameters.
+// However, the length of signed message is variable and depends on the network magic (since network
+// magic Integer stackitem being converted to Buffer has the resulting byte slice length that depends on
+// the magic).
+func buildKoblitzVerificationScriptSimple(t *testing.T, pub *keys.PublicKey) []byte {
+	criptoLibH := state.CreateNativeContractHash(nativenames.CryptoLib)
+
+	// vrf is witness verification script corresponding to the pub.
+	// vrf is witness verification script corresponding to the pk.
+	vrf := io.NewBufBinWriter()
+	emit.Int(vrf.BinWriter, int64(native.Secp256k1Keccak256)) // push Koblitz curve identifier.
+	emit.Opcodes(vrf.BinWriter, opcode.SWAP)                  // swap curve identifier with the signature.
+	emit.Bytes(vrf.BinWriter, pub.Bytes())                    // emit the caller's public key.
+	// Construct and push the signed message. The signed message is effectively the network-dependent transaction hash,
+	// i.e. msg = Sha256([network-magic-bytes, tx.Hash()])
+	// Firstly, retrieve network magic (it's uint32 wrapped into BigInteger and represented as Integer stackitem on stack).
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetNetwork) // push network magic.
+	// Retrieve executing transaction hash.
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetScriptContainer) // push the script container (executing transaction, actually).
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH0, opcode.PICKITEM,                // pick 0-th transaction item (the transaction hash).
+		opcode.CAT,   // concatenate network magic and transaction hash; this instruction will convert network magic to bytes using BigInteger rules of conversion.
+		opcode.PUSH1, // push 1 (the number of arguments of 'sha256' method of native CryptoLib).
+		opcode.PACK)  // pack arguments for 'sha256' call.
+	emit.AppCallNoArgs(vrf.BinWriter, criptoLibH, "sha256", callflag.NoneFlag) // emit the call to 'sha256' itself.
+	// Continue construction of 'verifyWithECDsa' call.
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH4, opcode.PACK)                              // pack arguments for 'verifyWithECDsa' call.
+	emit.AppCallNoArgs(vrf.BinWriter, criptoLibH, "verifyWithECDsa", callflag.NoneFlag) // emit the call to 'verifyWithECDsa' itself.
+	require.NoError(t, vrf.Err)
+
+	return vrf.Bytes()
+	// Here's an example of the resulting witness verification script (136 bytes length, always constant length, with variable length of signed data):
+	// NEO-GO-VM 0 > loadbase64 ABZQDCEDp38Tevu0to16RQqloo/jNfgExYmoCElLS2JuuYcH831Bxfug4EEtUQgwEM6LEcAfDAZzaGEyNTYMFBv1dasRiWiEE2EKNaEohs3gtmxyQWJ9W1IUwB8MD3ZlcmlmeVdpdGhFQ0RzYQwUG/V1qxGJaIQTYQo1oSiGzeC2bHJBYn1bUg==
+	// READY: loaded 136 instructions
+	// NEO-GO-VM 0 > ops
+	// INDEX    OPCODE       PARAMETER
+	// 0        PUSHINT8     122 (7a)    <<
+	// 2        SWAP
+	// 3        PUSHDATA1    03a77f137afbb4b68d7a450aa5a28fe335f804c589a808494b4b626eb98707f37d
+	// 38       SYSCALL      System.Runtime.GetNetwork (c5fba0e0)
+	// 43       SYSCALL      System.Runtime.GetScriptContainer (2d510830)
+	// 48       PUSH0
+	// 49       PICKITEM
+	// 50       CAT
+	// 51       PUSH1
+	// 52       PACK
+	// 53       PUSH0
+	// 54       PUSHDATA1    736861323536 ("sha256")
+	// 62       PUSHDATA1    1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+	// 84       SYSCALL      System.Contract.Call (627d5b52)
+	// 89       PUSH4
+	// 90       PACK
+	// 91       PUSH0
+	// 92       PUSHDATA1    766572696679576974684543447361 ("verifyWithECDsa")
+	// 109      PUSHDATA1    1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+	// 131      SYSCALL      System.Contract.Call (627d5b52)
+}
+
+// buildKoblitzVerificationScript builds custom verification script for the provided Koblitz public key.
+// It checks that the following message is signed by the provided public key:
+//
+//	keccak256(sha256([4-bytes-network-magic-LE, txHash-bytes-BE]))
+//
+// It produces constant-length verification script (186 bytes) independently of the network parameters.
+func buildKoblitzVerificationScriptCompat(t *testing.T, pub *keys.PublicKey) []byte {
+	criptoLibH := state.CreateNativeContractHash(nativenames.CryptoLib)
+
+	// vrf is witness verification script corresponding to the pub.
+	vrf := io.NewBufBinWriter()
+	emit.Int(vrf.BinWriter, int64(native.Secp256k1Keccak256)) // push Koblitz curve identifier.
+	emit.Opcodes(vrf.BinWriter, opcode.SWAP)                  // swap curve identifier with the signature.
+	emit.Bytes(vrf.BinWriter, pub.Bytes())                    // emit the caller's public key.
+	// Construct and push the signed message. The signed message is effectively the network-dependent transaction hash,
+	// i.e. msg = Sha256([4-bytes-network-magic-LE, tx.Hash()])
+	// Firstly, convert network magic (uint32) to LE buffer.
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetNetwork) // push network magic.
+	// First byte: n & 0xFF
+	emit.Opcodes(vrf.BinWriter, opcode.DUP)
+	emit.Int(vrf.BinWriter, 0xFF) // TODO: this can be optimize in order not to allocate 0xFF every time, but need to compare execution price.
+	emit.Opcodes(vrf.BinWriter, opcode.AND,
+		opcode.SWAP, // Swap with the original network n.
+		opcode.PUSH8,
+		opcode.SHR)
+	// Second byte: n >> 8 & 0xFF
+	emit.Opcodes(vrf.BinWriter, opcode.DUP)
+	emit.Int(vrf.BinWriter, 0xFF)
+	emit.Opcodes(vrf.BinWriter, opcode.AND,
+		opcode.SWAP, // Swap with the n >> 8.
+		opcode.PUSH8,
+		opcode.SHR)
+	// Third byte: n >> 16 & 0xFF
+	emit.Opcodes(vrf.BinWriter, opcode.DUP)
+	emit.Int(vrf.BinWriter, 0xFF)
+	emit.Opcodes(vrf.BinWriter, opcode.AND,
+		opcode.SWAP, // Swap with the n >> 16.
+		opcode.PUSH8,
+		opcode.SHR)
+	// Fourth byte: n >> 24 & 0xFF
+	emit.Int(vrf.BinWriter, 0xFF) // no DUP is needed since it's the last shift.
+	emit.Opcodes(vrf.BinWriter, opcode.AND)
+	// Put these 4 bytes into buffer.
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH4, opcode.NEWBUFFER) // allocate new 4-bytes-length buffer.
+	emit.Opcodes(vrf.BinWriter,
+		// Set fourth byte.
+		opcode.DUP, opcode.PUSH3,
+		opcode.PUSH3, opcode.ROLL,
+		opcode.SETITEM,
+		// Set third byte.
+		opcode.DUP, opcode.PUSH2,
+		opcode.PUSH3, opcode.ROLL,
+		opcode.SETITEM,
+		// Set second byte.
+		opcode.DUP, opcode.PUSH1,
+		opcode.PUSH3, opcode.ROLL,
+		opcode.SETITEM,
+		// Set first byte.
+		opcode.DUP, opcode.PUSH0,
+		opcode.PUSH3, opcode.ROLL,
+		opcode.SETITEM)
+	// Retrieve executing transaction hash.
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetScriptContainer) // push the script container (executing transaction, actually).
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH0, opcode.PICKITEM,                // pick 0-th transaction item (the transaction hash).
+		opcode.CAT,   // concatenate network magic and transaction hash.
+		opcode.PUSH1, // push 1 (the number of arguments of 'sha256' method of native CryptoLib).
+		opcode.PACK)  // pack arguments for 'sha256' call.
+	emit.AppCallNoArgs(vrf.BinWriter, criptoLibH, "sha256", callflag.NoneFlag) // emit the call to 'sha256' itself.
+	// Continue construction of 'verifyWithECDsa' call.
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH4, opcode.PACK)                              // pack arguments for 'verifyWithECDsa' call.
+	emit.AppCallNoArgs(vrf.BinWriter, criptoLibH, "verifyWithECDsa", callflag.NoneFlag) // emit the call to 'verifyWithECDsa' itself.
+	require.NoError(t, vrf.Err)
+
+	return vrf.Bytes()
+	// Here's an example of the resulting witness verification script (186 bytes length, always constant length, the length of signed data is also always constant):
+	// NEO-GO-VM 0 > loadbase64 ABZQDCECYn75w2MePMuPvExbbEnjjM7eWnmvseGwcI+7lYp4AtdBxfug4EoB/wCRUBipSgH/AJFQGKlKAf8AkVAYqQH/AJEUiEoTE1LQShITUtBKERNS0EoQE1LQQS1RCDAQzosRwB8MBnNoYTI1NgwUG/V1qxGJaIQTYQo1oSiGzeC2bHJBYn1bUhTAHwwPdmVyaWZ5V2l0aEVDRHNhDBQb9XWrEYlohBNhCjWhKIbN4LZsckFifVtS
+	// READY: loaded 186 instructions
+	// NEO-GO-VM 0 > ops
+	// INDEX    OPCODE       PARAMETER
+	// 0        PUSHINT8     122 (7a)    <<
+	// 2        SWAP
+	// 3        PUSHDATA1    02627ef9c3631e3ccb8fbc4c5b6c49e38ccede5a79afb1e1b0708fbb958a7802d7
+	// 38       SYSCALL      System.Runtime.GetNetwork (c5fba0e0)
+	// 43       DUP
+	// 44       PUSHINT16    255 (ff00)
+	// 47       AND
+	// 48       SWAP
+	// 49       PUSH8
+	// 50       SHR
+	// 51       DUP
+	// 52       PUSHINT16    255 (ff00)
+	// 55       AND
+	// 56       SWAP
+	// 57       PUSH8
+	// 58       SHR
+	// 59       DUP
+	// 60       PUSHINT16    255 (ff00)
+	// 63       AND
+	// 64       SWAP
+	// 65       PUSH8
+	// 66       SHR
+	// 67       PUSHINT16    255 (ff00)
+	// 70       AND
+	// 71       PUSH4
+	// 72       NEWBUFFER
+	// 73       DUP
+	// 74       PUSH3
+	// 75       PUSH3
+	// 76       ROLL
+	// 77       SETITEM
+	// 78       DUP
+	// 79       PUSH2
+	// 80       PUSH3
+	// 81       ROLL
+	// 82       SETITEM
+	// 83       DUP
+	// 84       PUSH1
+	// 85       PUSH3
+	// 86       ROLL
+	// 87       SETITEM
+	// 88       DUP
+	// 89       PUSH0
+	// 90       PUSH3
+	// 91       ROLL
+	// 92       SETITEM
+	// 93       SYSCALL      System.Runtime.GetScriptContainer (2d510830)
+	// 98       PUSH0
+	// 99       PICKITEM
+	// 100      CAT
+	// 101      PUSH1
+	// 102      PACK
+	// 103      PUSH0
+	// 104      PUSHDATA1    736861323536 ("sha256")
+	// 112      PUSHDATA1    1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+	// 134      SYSCALL      System.Contract.Call (627d5b52)
+	// 139      PUSH4
+	// 140      PACK
+	// 141      PUSH0
+	// 142      PUSHDATA1    766572696679576974684543447361 ("verifyWithECDsa")
+	// 159      PUSHDATA1    1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+	// 181      SYSCALL      System.Contract.Call (627d5b52)
+}
+
+// buildKoblitzInvocationScript builds witness invocation script for the transaction signatures. The signature
+// itself may be produced by public key over any curve (not required Koblitz, the algorithm is the same).
+// The signatures expected to be sorted by public key (if multiple signatures are provided).
+func buildKoblitzInvocationScript(t *testing.T, signatures [][]byte) []byte {
+	//Exactly like during standard
+	// signature verification, the resulting script pushes Koblitz signature bytes onto stack.
+	inv := io.NewBufBinWriter()
+	for _, sig := range signatures {
+		emit.Bytes(inv.BinWriter, sig) // message signature bytes.
+	}
+	require.NoError(t, inv.Err)
+
+	return inv.Bytes()
+	// Here's an example of the resulting single witness invocation script (66 bytes length, always constant length):
+	// NEO-GO-VM > loadbase64 DEBMGKU/MdSizlzaVNDUUbd1zMZQJ43eTaZ4vBCpmkJ/wVh1TYrAWEbFyHhkqq+aYxPCUS43NKJdJTXavcjB8sTP
+	// READY: loaded 66 instructions
+	// NEO-GO-VM 0 > ops
+	// INDEX    OPCODE       PARAMETER
+	// 0        PUSHDATA1    4c18a53f31d4a2ce5cda54d0d451b775ccc650278dde4da678bc10a99a427fc158754d8ac05846c5c87864aaaf9a6313c2512e3734a25d2535dabdc8c1f2c4cf    <<
+	//
+	// Here's an example of the 3 out of 4 multisignature invocation script (66 * m bytes length, always constant length):
+	// NEO-GO-VM > loadbase64 DEBsPMY3+7sWyZf0gCVcqPzwZ79p+KpeylgtbYIrXp4Tdi6E/8q3DIrEgK7DdVe3YdbfE+VPrpwym/ufBb8MRTB6DED5B9OZDGWdJApRfuy9LeUTa2mLsXP7mBRa181g0Jo7beylWzVgDqHHF2PilECMcLmRbFRknmQm4KgiGkDE+O6ZDEAYt61O2dMfasJHiQD95M5b4mR6NBnDsMTo2e59H3y4YguroVLiUxnQSc4qu9LWvEIKr4/ytjCCuANXOkJmSw8C
+	// READY: loaded 198 instructions
+	// NEO-GO-VM 0 > ops
+	// INDEX    OPCODE       PARAMETER
+	// 0        PUSHDATA1    6c3cc637fbbb16c997f480255ca8fcf067bf69f8aa5eca582d6d822b5e9e13762e84ffcab70c8ac480aec37557b761d6df13e54fae9c329bfb9f05bf0c45307a    <<
+	// 66       PUSHDATA1    f907d3990c659d240a517eecbd2de5136b698bb173fb98145ad7cd60d09a3b6deca55b35600ea1c71763e294408c70b9916c54649e6426e0a8221a40c4f8ee99
+	// 132      PUSHDATA1    18b7ad4ed9d31f6ac2478900fde4ce5be2647a3419c3b0c4e8d9ee7d1f7cb8620baba152e25319d049ce2abbd2d6bc420aaf8ff2b63082b803573a42664b0f02
+}
+
+// constructMessage constructs message for signing that consists of the
+// unhashed constant 4-bytes length LE magic and transaction hash bytes:
+//
+//	[4-bytes-network-magic-LE, txHash-bytes-BE]
+func constructMessage(t *testing.T, magic uint32, tx hash.Hashable) []byte {
+	return hash.GetSignedData(magic, tx)
+}
+
+// constructMessageNoHash constructs message for signing that consists of the
+// unhashed magic and transaction hash bytes:
+//
+//	[var-bytes-network-magic, txHash-bytes-BE]
+func constructMessageNoHash(t *testing.T, magic uint32, tx hash.Hashable) []byte {
+	m := big.NewInt(int64(magic))
+	return append(m.Bytes(), tx.Hash().BytesBE()...)
+}
+
+// constructMessageCompat constructs message for signing that does not follow N3 rules,
+// but entails smaller verification script size and smaller verification price:
+//
+//	sha256([var-bytes-network-magic, txHash-bytes-BE])
+func constructMessageSimple(t *testing.T, magic uint32, tx hash.Hashable) []byte {
+	m := big.NewInt(int64(magic))
+	return hash.Sha256(append(m.Bytes(), tx.Hash().BytesBE()...)).BytesBE()
+}
+
+// constructMessageCompat constructs message for signing following the N3 rules:
+//
+//	sha256([4-bytes-network-magic-LE, txHash-bytes-BE])
+func constructMessageCompat(t *testing.T, magic uint32, tx hash.Hashable) []byte {
+	return hash.NetSha256(magic, tx).BytesBE()
+}
+
+// TestCryptoLib_KoblitzMultisigVerificationScript builds transaction with custom witness that contains
+// the Koblitz tx multisignature bytes and Koblitz multisignature verification script.
+// This test ensures that transaction signed by m out of n Koblitz keys passes verification and can
+// be successfully accepted to the chain.
+func TestCryptoLib_KoblitzMultisigVerificationScript(t *testing.T) {
+	check := func(
+		t *testing.T,
+		buildVerificationScript func(t *testing.T, m int, pub keys.PublicKeys) []byte,
+		constructMsg func(t *testing.T, magic uint32, tx hash.Hashable) []byte,
+	) {
+		c := newGasClient(t)
+		gasInvoker := c.WithSigners(c.Committee)
+		e := c.Executor
+
+		// Consider 4 users willing to sign 3/4 multisignature transaction Secp256k1 private keys.
+		const (
+			n = 4
+			m = 3
+		)
+		pks := make([]*keys.PrivateKey, n)
+		for i := range pks {
+			var err error
+			pks[i], err = keys.NewSecp256k1PrivateKey()
+			require.NoError(t, err)
+		}
+		// Sort private keys by their public keys.
+		sort.Slice(pks, func(i, j int) bool {
+			return pks[i].PublicKey().Cmp(pks[j].PublicKey()) < 0
+		})
+
+		// Firstly, we need to build the N3 multisig account address based on the users' public keys.
+		// Pubs must be sorted, exactly like for the standard CheckMultisig.
+		pubs := make(keys.PublicKeys, n)
+		for i := range pks {
+			pubs[i] = pks[i].PublicKey()
+		}
+		vrfBytes := buildVerificationScript(t, m, pubs)
+
+		// Construct the user's account script hash. It's effectively a verification script hash.
+		from := hash.Hash160(vrfBytes)
+
+		// Supply this account with some initial balance so that the user is able to pay for his transactions.
+		gasInvoker.Invoke(t, true, "transfer", c.Committee.ScriptHash(), from, 10000_0000_0000, nil)
+
+		// Construct transaction that transfers 5 GAS from the user's account to some other account.
+		to := util.Uint160{1, 2, 3}
+		amount := 5
+		tx := gasInvoker.PrepareInvokeNoSign(t, "transfer", from, to, amount, nil)
+		tx.Signers = []transaction.Signer{
+			{
+				Account: from,
+				Scopes:  transaction.CalledByEntry,
+			},
+		}
+		neotest.AddNetworkFee(t, e.Chain, tx)
+		neotest.AddSystemFee(e.Chain, tx, -1)
+
+		// Add some more network fee to pay for the witness verification. This value may be calculated precisely,
+		// but let's keep some inaccurate value for the test.
+		tx.NetworkFee = 8995470
+
+		// This transaction (along with the network magic) should be signed by the user's Koblitz private key.
+		msg := constructMsg(t, uint32(e.Chain.GetConfig().Magic), tx)
+
+		// The users have to sign the hash of the message by their Koblitz key. Collect m signatures from first m keys.
+		// Signatures must be sorted by public key.
+		sigs := make([][]byte, m)
+		for i := range sigs {
+			j := i
+			if i > 0 {
+				j++ // Add some shift to ensure that verification script works correctly.
+			}
+			if i > 3 {
+				j++ // Add more shift for large number of public keys for the same purpose.
+			}
+			sigs[i] = pks[j].SignHash(native.Keccak256(msg))
+		}
+
+		// Build invocation witness script for the signatures.
+		invBytes := buildKoblitzInvocationScript(t, sigs)
+
+		// Construct witness for signer #0 (the multisig account itself).
+		tx.Scripts = []transaction.Witness{
+			{
+				InvocationScript:   invBytes,
+				VerificationScript: vrfBytes,
+			},
+		}
+
+		// Add transaction to the chain. No error is expected on new block addition. Note, that this line performs
+		// all those checks that are executed during transaction acceptance in the real network.
+		e.AddNewBlock(t, tx)
+
+		// Double-check: ensure funds have been transferred.
+		e.CheckGASBalance(t, to, big.NewInt(int64(amount)))
+	}
+
+	// The proposed multisig verification script.
+	// (264 bytes, 8390070 GAS including Invocation script execution for 3/4 multisig).
+	// The user has to sign the keccak256([4-bytes-network-magic-LE, txHash-bytes-BE]).
+	check(t, buildKoblitzMultisigVerificationScript, constructMessage)
+}
+
+// buildKoblitzMultisigVerificationScript builds witness verification script for m signatures out of n Koblitz public keys.
+// Public keys must be sorted. Signatures (pushed by witness Invocation script) must be sorted by public keys.
+// It checks m out of n multisignature of the following message:
+//
+//	keccak256([4-bytes-network-magic-LE, txHash-bytes-BE])
+func buildKoblitzMultisigVerificationScript(t *testing.T, m int, pubs keys.PublicKeys) []byte {
+	if len(pubs) == 0 {
+		t.Fatalf("empty pubs list")
+	}
+	if m > len(pubs) {
+		t.Fatalf("m must be not greater than the number of public keys")
+	}
+
+	n := len(pubs) // public keys must be sorted.
+	cryptoLibH := state.CreateNativeContractHash(nativenames.CryptoLib)
+
+	// In fact, the following algorithm is implemented via NeoVM instructions:
+	//
+	// func Check(sigs []interop.Signature) bool {
+	// 	if m != len(sigs) {
+	// 		return false
+	// 	}
+	// 	var pubs []interop.PublicKey = []interop.PublicKey{...}
+	// 	msg := append(convert.ToBytes(runtime.GetNetwork()), runtime.GetScriptContainer().Hash...)
+	// 	var sigCnt = 0
+	// 	var pubCnt = 0
+	// 	for ; sigCnt < m && pubCnt < n; { // sigs must be sorted by pub
+	// 		sigCnt += crypto.VerifyWithECDsa(msg, pubs[pubCnt], sigs[sigCnt], crypto.Secp256k1Keccak256)
+	// 		pubCnt++
+	// 	}
+	// 	return sigCnt == m
+	// }
+	vrf := io.NewBufBinWriter()
+
+	// Start the same way as regular multisig script.
+	emit.Int(vrf.BinWriter, int64(m)) // push m.
+	for _, pub := range pubs {
+		emit.Bytes(vrf.BinWriter, pub.Bytes()) // push public keys in compressed form.
+	}
+	emit.Int(vrf.BinWriter, int64(n)) // push n.
+
+	// Initialize slots for local variables. Locals slot scheme:
+	// LOC0 -> sigs
+	// LOC1 -> pubs
+	// LOC2 -> msg (ByteString)
+	// LOC3 -> sigCnt (Integer)
+	// LOC4 -> pubCnt (Integer)
+	// LOC5 -> n
+	// LOC6 -> m
+	emit.InitSlot(vrf.BinWriter, 7, 0)
+
+	// Store n.
+	emit.Opcodes(vrf.BinWriter, opcode.STLOC5)
+
+	// Pack public keys and store at LOC1.
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC5)
+	emit.Opcodes(vrf.BinWriter, opcode.PACK, opcode.STLOC1)
+
+	// Store m.
+	emit.Opcodes(vrf.BinWriter, opcode.STLOC6)
+
+	// Check the number of signatures is m. Abort the execution if not.
+	emit.Opcodes(vrf.BinWriter, opcode.DEPTH)                // push the number of signatures onto stack.
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC6)               // load m.
+	emit.Instruction(vrf.BinWriter, opcode.JMPEQ, []byte{0}) // here and below short jumps are sufficient.
+	sigsLenCheckEndOffset := vrf.Len()                       // offset of the signatures count check.
+	emit.Opcodes(vrf.BinWriter, opcode.ABORT)                // abort execution if length of the signatures not equal to m.
+
+	// Start the check.
+	checkStartOffset := vrf.Len()
+
+	// Pack signatures and store at LOC0.
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC6) // load m.
+	emit.Opcodes(vrf.BinWriter, opcode.PACK, opcode.STLOC0)
+
+	// Get message and store it at LOC2.
+	// msg = [4-network-magic-bytes-LE, tx-hash-BE]
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetNetwork) // push network magic (Integer stackitem), can have 0-5 bytes length serialized.
+	// Convert network magic to 4-bytes-length LE byte array representation.
+	emit.Int(vrf.BinWriter, 0x100000000)
+	emit.Opcodes(vrf.BinWriter, opcode.ADD, // some new number that is 5 bytes at least when serialized, but first 4 bytes are intact network value (LE).
+		opcode.PUSH4, opcode.LEFT) // cut the first 4 bytes out of a number that is at least 5 bytes long, the result is 4-bytes-length LE network representation.
+	// Retrieve executing transaction hash.
+	emit.Syscall(vrf.BinWriter, interopnames.SystemRuntimeGetScriptContainer) // push the script container (executing transaction, actually).
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH0, opcode.PICKITEM)                // pick 0-th transaction item (the transaction hash).
+	// Concatenate network magic and transaction hash.
+	emit.Opcodes(vrf.BinWriter, opcode.CAT)    // this instruction will convert network magic to bytes using BigInteger rules of conversion.
+	emit.Opcodes(vrf.BinWriter, opcode.STLOC2) // store msg as a local variable #2.
+
+	// Initialize local variables: sigCnt, pubCnt.
+	emit.Opcodes(vrf.BinWriter, opcode.PUSH0, opcode.STLOC3, // initialize sigCnt.
+		opcode.PUSH0, opcode.STLOC4) // initialize pubCnt.
+
+	// Loop condition check.
+	loopStartOffset := vrf.Len()
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC3) // load sigCnt.
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC6) // push m.
+	emit.Opcodes(vrf.BinWriter, opcode.GE,     // sigCnt >= m
+		opcode.LDLOC4) // load pubCnt
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC5) // push n.
+	emit.Opcodes(vrf.BinWriter, opcode.GE,     // pubCnt >= n
+		opcode.OR) // sigCnt >= m || pubCnt >= n
+	emit.Instruction(vrf.BinWriter, opcode.JMPIF, []byte{0}) // jump to the end of the script if (sigCnt >= m || pubCnt >= n).
+	loopConditionOffset := vrf.Len()
+
+	// Loop start. Prepare arguments and call CryptoLib's verifyWithECDsa.
+	emit.Int(vrf.BinWriter, int64(native.Secp256k1Keccak256)) // push Koblitz curve identifier.
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC0,                // load signatures.
+		opcode.LDLOC3,             // load sigCnt.
+		opcode.PICKITEM,           // pick signature at index sigCnt.
+		opcode.LDLOC1,             // load pubs.
+		opcode.LDLOC4,             // load pubCnt.
+		opcode.PICKITEM,           // pick pub at index pubCnt.
+		opcode.LDLOC2,             // load msg.
+		opcode.PUSH4, opcode.PACK) // pack 4 arguments for 'verifyWithECDsa' call.
+	emit.AppCallNoArgs(vrf.BinWriter, cryptoLibH, "verifyWithECDsa", callflag.NoneFlag) // emit the call to 'verifyWithECDsa' itself.
+
+	// Update loop variables.
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC3, opcode.ADD, opcode.STLOC3, // increment sigCnt if signature is valid.
+		opcode.LDLOC4, opcode.INC, opcode.STLOC4) // increment pubCnt.
+
+	// End of the loop.
+	emit.Instruction(vrf.BinWriter, opcode.JMP, []byte{0}) // jump to the start of cycle.
+	loopEndOffset := vrf.Len()
+
+	// Return condition: the number of valid signatures should be equal to m.
+	progRetOffset := vrf.Len()
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC3)   // load sigCnt.
+	emit.Opcodes(vrf.BinWriter, opcode.LDLOC6)   // push m.
+	emit.Opcodes(vrf.BinWriter, opcode.NUMEQUAL) // push m == sigCnt.
+
+	require.NoError(t, vrf.Err)
+	script := vrf.Bytes()
+
+	// Set JMP* instructions offsets. "-1" is for short JMP parameter offset. JMP parameters
+	// are relative offsets.
+	script[sigsLenCheckEndOffset-1] = byte(checkStartOffset - sigsLenCheckEndOffset + 2)
+	script[loopEndOffset-1] = byte(loopStartOffset - loopEndOffset + 2)
+	script[loopConditionOffset-1] = byte(progRetOffset - loopConditionOffset + 2)
+
+	return script
+	// Here's an example of the resulting single witness invocation script (264 bytes length, the length may vary depending on m/n):
+	// NEO-GO-VM > loadbase64 EwwhAg1khs9yqTuG8R7dEj8/GhCqKwkL+6shSOczeaHENFo8DCECibz2wVNY1zRkRCbn+Qr87lQFjStnrQrwv1CSoea/91sMIQPiiV+wNGl5g5SVULR+BM/G2n6WO0WrGIsq+GBRqQHYwAwhAuwZz40NwnerrmSusUUgNqsZiv0WFj3KQE1BYd7lU7mDFFcHAHVtwHF2Q24oAzhuwHBBxfug4AMAAAAAAQAAAJ4UjUEtUQgwEM6LchBzEHRrbrhsbbiSJEIAGGhrzmlszmoUwB8MD3ZlcmlmeVdpdGhFQ0RzYQwUG/V1qxGJaIQTYQo1oSiGzeC2bHJBYn1bUmuec2ycdCK5a26z
+	// READY: loaded 264 instructions
+	// NEO-GO-VM 0 > ops
+	// INDEX    OPCODE       PARAMETER
+	// 0        PUSH3            <<
+	// 1        PUSHDATA1    020d6486cf72a93b86f11edd123f3f1a10aa2b090bfbab2148e73379a1c4345a3c
+	// 36       PUSHDATA1    0289bcf6c15358d734644426e7f90afcee54058d2b67ad0af0bf5092a1e6bff75b
+	// 71       PUSHDATA1    03e2895fb034697983949550b47e04cfc6da7e963b45ab188b2af86051a901d8c0
+	// 106      PUSHDATA1    02ec19cf8d0dc277abae64aeb1452036ab198afd16163dca404d4161dee553b983
+	// 141      PUSH4
+	// 142      INITSLOT     7 local, 0 arg
+	// 145      STLOC5
+	// 146      LDLOC5
+	// 147      PACK
+	// 148      STLOC1
+	// 149      STLOC6
+	// 150      DEPTH
+	// 151      LDLOC6
+	// 152      JMPEQ        155 (3/03)
+	// 154      ABORT
+	// 155      LDLOC6
+	// 156      PACK
+	// 157      STLOC0
+	// 158      SYSCALL      System.Runtime.GetNetwork (c5fba0e0)
+	// 163      PUSHINT64    4294967296 (0000000001000000)
+	// 172      ADD
+	// 173      PUSH4
+	// 174      LEFT
+	// 175      SYSCALL      System.Runtime.GetScriptContainer (2d510830)
+	// 180      PUSH0
+	// 181      PICKITEM
+	// 182      CAT
+	// 183      STLOC2
+	// 184      PUSH0
+	// 185      STLOC3
+	// 186      PUSH0
+	// 187      STLOC4
+	// 188      LDLOC3
+	// 189      LDLOC6
+	// 190      GE
+	// 191      LDLOC4
+	// 192      LDLOC5
+	// 193      GE
+	// 194      OR
+	// 195      JMPIF        261 (66/42)
+	// 197      PUSHINT8     122 (7a)
+	// 199      LDLOC0
+	// 200      LDLOC3
+	// 201      PICKITEM
+	// 202      LDLOC1
+	// 203      LDLOC4
+	// 204      PICKITEM
+	// 205      LDLOC2
+	// 206      PUSH4
+	// 207      PACK
+	// 208      PUSH0
+	// 209      PUSHDATA1    766572696679576974684543447361 ("verifyWithECDsa")
+	// 226      PUSHDATA1    1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b")
+	// 248      SYSCALL      System.Contract.Call (627d5b52)
+	// 253      LDLOC3
+	// 254      ADD
+	// 255      STLOC3
+	// 256      LDLOC4
+	// 257      INC
+	// 258      STLOC4
+	// 259      JMP          188 (-71/b9)
+	// 261      LDLOC3
+	// 262      LDLOC6
+	// 263      NUMEQUAL
+}
diff --git a/pkg/core/native/native_test/management_test.go b/pkg/core/native/native_test/management_test.go
index 6bba730fb..1818076f1 100644
--- a/pkg/core/native/native_test/management_test.go
+++ b/pkg/core/native/native_test/management_test.go
@@ -40,7 +40,7 @@ var (
 	defaultCSS = map[string]string{
 		nativenames.Management:  `{"id":-1,"hash":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"ContractManagement","abi":{"methods":[{"name":"deploy","offset":0,"parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Array","safe":false},{"name":"deploy","offset":7,"parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Array","safe":false},{"name":"destroy","offset":14,"parameters":[],"returntype":"Void","safe":false},{"name":"getContract","offset":21,"parameters":[{"name":"hash","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getContractById","offset":28,"parameters":[{"name":"id","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getContractHashes","offset":35,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"getMinimumDeploymentFee","offset":42,"parameters":[],"returntype":"Integer","safe":true},{"name":"hasMethod","offset":49,"parameters":[{"name":"hash","type":"Hash160"},{"name":"method","type":"String"},{"name":"pcount","type":"Integer"}],"returntype":"Boolean","safe":true},{"name":"setMinimumDeploymentFee","offset":56,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","safe":false},{"name":"update","offset":63,"parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":70,"parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false}],"events":[{"name":"Deploy","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Update","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Destroy","parameters":[{"name":"Hash","type":"Hash160"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
 		nativenames.StdLib:      `{"id":-2,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"manifest":{"name":"StdLib","abi":{"methods":[{"name":"atoi","offset":0,"parameters":[{"name":"value","type":"String"}],"returntype":"Integer","safe":true},{"name":"atoi","offset":7,"parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"base58CheckDecode","offset":14,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base58CheckEncode","offset":21,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base58Decode","offset":28,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base58Encode","offset":35,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"base64Decode","offset":42,"parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","safe":true},{"name":"base64Encode","offset":49,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","safe":true},{"name":"deserialize","offset":56,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"itoa","offset":63,"parameters":[{"name":"value","type":"Integer"}],"returntype":"String","safe":true},{"name":"itoa","offset":70,"parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","safe":true},{"name":"jsonDeserialize","offset":77,"parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","safe":true},{"name":"jsonSerialize","offset":84,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"memoryCompare","offset":91,"parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":98,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":105,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"memorySearch","offset":112,"parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","safe":true},{"name":"serialize","offset":119,"parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","safe":true},{"name":"strLen","offset":126,"parameters":[{"name":"str","type":"String"}],"returntype":"Integer","safe":true},{"name":"stringSplit","offset":133,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","safe":true},{"name":"stringSplit","offset":140,"parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
-		nativenames.CryptoLib:   `{"id":-3,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":2135988409},"manifest":{"name":"CryptoLib","abi":{"methods":[{"name":"bls12381Add","offset":0,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Deserialize","offset":7,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Equal","offset":14,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","safe":true},{"name":"bls12381Mul","offset":21,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Pairing","offset":28,"parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Serialize","offset":35,"parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","safe":true},{"name":"murmur32","offset":42,"parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","safe":true},{"name":"ripemd160","offset":49,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"sha256","offset":56,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"verifyWithECDsa","offset":63,"parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curve","type":"Integer"}],"returntype":"Boolean","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
+		nativenames.CryptoLib:   `{"id":-3,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":2135988409},"manifest":{"name":"CryptoLib","abi":{"methods":[{"name":"bls12381Add","offset":0,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Deserialize","offset":7,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Equal","offset":14,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","safe":true},{"name":"bls12381Mul","offset":21,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Pairing","offset":28,"parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Serialize","offset":35,"parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","safe":true},{"name":"murmur32","offset":42,"parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","safe":true},{"name":"ripemd160","offset":49,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"sha256","offset":56,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"verifyWithECDsa","offset":63,"parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
 		nativenames.Ledger:      `{"id":-4,"hash":"0xda65b600f7124ce6c79950c1772a36403104f2be","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"LedgerContract","abi":{"methods":[{"name":"currentHash","offset":0,"parameters":[],"returntype":"Hash256","safe":true},{"name":"currentIndex","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"getBlock","offset":14,"parameters":[{"name":"indexOrHash","type":"ByteArray"}],"returntype":"Array","safe":true},{"name":"getTransaction","offset":21,"parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","safe":true},{"name":"getTransactionFromBlock","offset":28,"parameters":[{"name":"blockIndexOrHash","type":"ByteArray"},{"name":"txIndex","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getTransactionHeight","offset":35,"parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","safe":true},{"name":"getTransactionSigners","offset":42,"parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","safe":true},{"name":"getTransactionVMState","offset":49,"parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
 		nativenames.Neo:         `{"id":-5,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":65467259},"manifest":{"name":"NeoToken","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"getAccountState","offset":14,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getAllCandidates","offset":21,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"getCandidateVote","offset":28,"parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","safe":true},{"name":"getCandidates","offset":35,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommittee","offset":42,"parameters":[],"returntype":"Array","safe":true},{"name":"getGasPerBlock","offset":49,"parameters":[],"returntype":"Integer","safe":true},{"name":"getNextBlockValidators","offset":56,"parameters":[],"returntype":"Array","safe":true},{"name":"getRegisterPrice","offset":63,"parameters":[],"returntype":"Integer","safe":true},{"name":"registerCandidate","offset":70,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"setGasPerBlock","offset":77,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRegisterPrice","offset":84,"parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","safe":false},{"name":"symbol","offset":91,"parameters":[],"returntype":"String","safe":true},{"name":"totalSupply","offset":98,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":105,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"unclaimedGas","offset":112,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"unregisterCandidate","offset":119,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"vote","offset":126,"parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-17"],"trusts":[],"extra":null},"updatecounter":0}`,
 		nativenames.Gas:         `{"id":-6,"hash":"0xd2a4cff31913016155e38e474a2c06d08be276cf","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2663858513},"manifest":{"name":"GasToken","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"symbol","offset":14,"parameters":[],"returntype":"String","safe":true},{"name":"totalSupply","offset":21,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":28,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-17"],"trusts":[],"extra":null},"updatecounter":0}`,
@@ -52,7 +52,7 @@ var (
 	// cockatriceCSS holds serialized native contract states built for genesis block (with UpdateCounter 0)
 	// under assumption that hardforks from Aspidochelone to Cockatrice (included) are enabled.
 	cockatriceCSS = map[string]string{
-		nativenames.CryptoLib: `{"id":-3,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"CryptoLib","abi":{"methods":[{"name":"bls12381Add","offset":0,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Deserialize","offset":7,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Equal","offset":14,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","safe":true},{"name":"bls12381Mul","offset":21,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Pairing","offset":28,"parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Serialize","offset":35,"parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","safe":true},{"name":"keccak256","offset":42,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"murmur32","offset":49,"parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","safe":true},{"name":"ripemd160","offset":56,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"sha256","offset":63,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"verifyWithECDsa","offset":70,"parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curve","type":"Integer"}],"returntype":"Boolean","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
+		nativenames.CryptoLib: `{"id":-3,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"CryptoLib","abi":{"methods":[{"name":"bls12381Add","offset":0,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Deserialize","offset":7,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Equal","offset":14,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","safe":true},{"name":"bls12381Mul","offset":21,"parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Pairing","offset":28,"parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","safe":true},{"name":"bls12381Serialize","offset":35,"parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","safe":true},{"name":"keccak256","offset":42,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"murmur32","offset":49,"parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","safe":true},{"name":"ripemd160","offset":56,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"sha256","offset":63,"parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","safe":true},{"name":"verifyWithECDsa","offset":70,"parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","safe":true}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`,
 		nativenames.Neo:       `{"id":-5,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"NeoToken","abi":{"methods":[{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":7,"parameters":[],"returntype":"Integer","safe":true},{"name":"getAccountState","offset":14,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getAllCandidates","offset":21,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"getCandidateVote","offset":28,"parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","safe":true},{"name":"getCandidates","offset":35,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommittee","offset":42,"parameters":[],"returntype":"Array","safe":true},{"name":"getCommitteeAddress","offset":49,"parameters":[],"returntype":"Hash160","safe":true},{"name":"getGasPerBlock","offset":56,"parameters":[],"returntype":"Integer","safe":true},{"name":"getNextBlockValidators","offset":63,"parameters":[],"returntype":"Array","safe":true},{"name":"getRegisterPrice","offset":70,"parameters":[],"returntype":"Integer","safe":true},{"name":"registerCandidate","offset":77,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"setGasPerBlock","offset":84,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRegisterPrice","offset":91,"parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","safe":false},{"name":"symbol","offset":98,"parameters":[],"returntype":"String","safe":true},{"name":"totalSupply","offset":105,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":112,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"unclaimedGas","offset":119,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","safe":true},{"name":"unregisterCandidate","offset":126,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","safe":false},{"name":"vote","offset":133,"parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-17"],"trusts":[],"extra":null},"updatecounter":0}`,
 	}
 )
diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go
index 57e5ed4ef..f6c89d7fd 100644
--- a/pkg/services/rpcsrv/server_test.go
+++ b/pkg/services/rpcsrv/server_test.go
@@ -89,7 +89,7 @@ const (
 	faultedTxHashLE                   = "82279bfe9bada282ca0f8cb8e0bb124b921af36f00c69a518320322c6f4fef60"
 	faultedTxBlock             uint32 = 23
 	invokescriptContractAVM           = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
-	block20StateRootLE                = "397c69adbc0201d59623fa913bfff4a2da25c792c484d1d278c061709f2c21cf"
+	block20StateRootLE                = "2d95b1149230d40c3043a84d42249b7b344f8755ea9fd0b2d95c5d011af85fc7"
 )
 
 var (
@@ -3152,10 +3152,13 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
 			body := doRPCCall(`{"jsonrpc": "2.0", "id": 1, "method": "calculatenetworkfee", "params": ["bm90IGEgdHJhbnNhY3Rpb24K"]}"`, httpSrv.URL, t)
 			_ = checkErrGetResult(t, body, true, neorpc.InvalidParamsCode, "Invalid params")
 		})
-		calcReq := func(t *testing.T, tx *transaction.Transaction) []byte {
-			rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "calculatenetworkfee", "params": ["%s"]}"`, base64.StdEncoding.EncodeToString(tx.Bytes()))
+		calcReqExactly := func(t *testing.T, tx string) []byte {
+			rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "calculatenetworkfee", "params": ["%s"]}"`, tx)
 			return doRPCCall(rpc, httpSrv.URL, t)
 		}
+		calcReq := func(t *testing.T, tx *transaction.Transaction) []byte {
+			return calcReqExactly(t, base64.StdEncoding.EncodeToString(tx.Bytes()))
+		}
 		t.Run("non-contract with zero verification", func(t *testing.T) {
 			tx := &transaction.Transaction{
 				Script:  []byte{byte(opcode.RET)},
@@ -3235,6 +3238,13 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
 			}
 			checkCalc(t, tx, 2315100) // Perfectly matches FeeIsMultiSigContract() C# test.
 		})
+		t.Run("Koblitz custom multisignature witness", func(t *testing.T) {
+			tx := "AAIAAACWP5gAAAAAAAAAAAAAAAAAAgAAAAEGyZgQyJQyWjzvqUZochi8rGE9RQEAVgsVDBQBAgMAAAAAAAAAAAAAAAAAAAAAAAwUBsmYEMiUMlo876lGaHIYvKxhPUUUwB8MCHRyYW5zZmVyDBTPduKL0AYsSkeO41VhARMZ88+k0kFifVtSAcYMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9CAETDCECbSVpJ0BN2xiveIGNU0LzEz4V7FUAp1NV8s7YI4kq9eQMIQOaHY3bh+3OmXuU9t72Pj62loLM7gZDgXJwnBV2zO4u1wwhAsvP18ohoYHcHBPt4wwPqAOstOhazEegr4klYmDlWpzeDCED9EsK7L0qFMP1QpBBfKMMVXPLa894ONINLRtjtBLBM6oUVwcAdW3AcXZDbigDOG7AcEHF+6DgAwAAAAABAAAAnhSNQS1RCDAQzotyEHMQdGtuuGxtuJIkQgB6aGvOaWzOahTAEAwPdmVyaWZ5V2l0aEVDRHNhDBQb9XWrEYlohBNhCjWhKIbN4LZsckFifVtSa55zbJx0IrlrbrM="
+			resp := checkErrGetResult(t, calcReqExactly(t, tx), false, 0)
+			res := new(result.NetworkFee)
+			require.NoError(t, json.Unmarshal(resp, res))
+			require.Equal(t, int64(8992070), res.Value)
+		})
 		checkContract := func(t *testing.T, verAcc util.Uint160, invoc []byte, fee int64) {
 			txScript, err := smartcontract.CreateCallWithAssertScript(chain.UtilityTokenHash(), "transfer",
 				verAcc, verAcc, 1, nil)