Pārlūkot izejas kodu

script/research/zkvm-metering: tools to generate and verify zk proofs containing single zkvm opcodes for resource usage measurement

oars 8 mēneši atpakaļ
vecāks
revīzija
52461e3fd2
34 mainītis faili ar 975 papildinājumiem un 0 dzēšanām
  1. 64 0
      script/research/zkvm-metering/README.md
  2. 5 0
      script/research/zkvm-metering/generator/.gitignore
  3. 26 0
      script/research/zkvm-metering/generator/Cargo.toml
  4. 48 0
      script/research/zkvm-metering/generator/Makefile
  5. 14 0
      script/research/zkvm-metering/generator/proof/base_add.zk
  6. 14 0
      script/research/zkvm-metering/generator/proof/base_mul.zk
  7. 14 0
      script/research/zkvm-metering/generator/proof/base_sub.zk
  8. 12 0
      script/research/zkvm-metering/generator/proof/bool_check.zk
  9. 15 0
      script/research/zkvm-metering/generator/proof/cond_select.zk
  10. 13 0
      script/research/zkvm-metering/generator/proof/constrain_equal_base.zk
  11. 13 0
      script/research/zkvm-metering/generator/proof/constrain_equal_point.zk
  12. 12 0
      script/research/zkvm-metering/generator/proof/constrain_instance.zk
  13. 12 0
      script/research/zkvm-metering/generator/proof/debug.zk
  14. 15 0
      script/research/zkvm-metering/generator/proof/ec_add.zk
  15. 13 0
      script/research/zkvm-metering/generator/proof/ec_get_x.zk
  16. 13 0
      script/research/zkvm-metering/generator/proof/ec_get_y.zk
  17. 16 0
      script/research/zkvm-metering/generator/proof/ec_mul.zk
  18. 16 0
      script/research/zkvm-metering/generator/proof/ec_mul_base.zk
  19. 16 0
      script/research/zkvm-metering/generator/proof/ec_mul_short.zk
  20. 16 0
      script/research/zkvm-metering/generator/proof/ec_mul_var_base.zk
  21. 13 0
      script/research/zkvm-metering/generator/proof/less_than_loose.zk
  22. 13 0
      script/research/zkvm-metering/generator/proof/less_than_strict.zk
  23. 17 0
      script/research/zkvm-metering/generator/proof/merkle_root.zk
  24. 14 0
      script/research/zkvm-metering/generator/proof/poseidon_hash.zk
  25. 12 0
      script/research/zkvm-metering/generator/proof/range_check.zk
  26. 16 0
      script/research/zkvm-metering/generator/proof/sparse_merkle_root.zk
  27. 12 0
      script/research/zkvm-metering/generator/proof/witness_base.zk
  28. 14 0
      script/research/zkvm-metering/generator/proof/zero_cond.zk
  29. 348 0
      script/research/zkvm-metering/generator/src/main.rs
  30. 5 0
      script/research/zkvm-metering/verifier/.gitignore
  31. 22 0
      script/research/zkvm-metering/verifier/Cargo.toml
  32. 37 0
      script/research/zkvm-metering/verifier/Makefile
  33. 10 0
      script/research/zkvm-metering/verifier/run_heaptrack.sh
  34. 75 0
      script/research/zkvm-metering/verifier/src/main.rs

+ 64 - 0
script/research/zkvm-metering/README.md

@@ -0,0 +1,64 @@
+# ZkVM Metering tool
+- The aim of this tool is to analyze resource usage of ZkVM opcodes
+using [heaptrack](https://github.com/KDE/heaptrack).
+- We have a proof generator and verifier to profile each ZkVM Opcode.
+- The `generator` generates a proof, verifying key and public inputs
+for all the proofs stored in `generator/proof`.
+- Each Zk script file in `generator/proof` contains a single ZkVM
+opcode.
+- The `generator` saves it's outputs to disk for later use by the
+`verifier`.
+- The `verifier` loads the proof, verifying key and public inputs and
+verifies a single proof at a time identified by the opcode name.
+
+#### Steps to profile a ZkVM Opcode
+
+To generate the proofs go to `generator` directory and run these
+commands.
+```
+% make
+% ./generator
+```
+To verify the proof and profile all the opcodes go to `verifier` and
+run these commands. You need to install `heaptrack` before running the
+second command. 
+```
+% make
+% ./run_heaptrack.sh
+```
+`run_heaptrack.sh` will generate `heaptrack` report for all the opcodes
+in `output` directory. If you prover to analyze a single opcode run 
+the following.
+```
+% heaptrack ./verifer [OPCODE_NAME]
+```
+Once the `heaptrack` report is generated you can view it using `heaptrack_gui`. 
+
+#### Analysis Results
+
+| #  | Opcode                | RAM Usage | Verifying Key Size | Proof Size |
+|----|-----------------------|-----------|--------------------|------------|
+| 0  | sparse_merkle_root    | 17.1 MB   | 1.2 MB             | 7.7 kB     |
+| 1  | merkle_root           | 8.1 MB    | 572.2 kB           | 7.7 kB     |
+| 2  | posedion_hash         | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 3  | base_add              | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 4  | base_mul              | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 5  | base_sub              | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 6  | ec_add                | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 7  | ec_mul                | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 8  | ec_mul_base           | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 9  | ec_mul_short          | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 10 | ec_mul_var_base       | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 11 | ec_get_x              | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 12 | ec_get_y              | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 13 | constrain_instance    | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 14 | constrain_equal_base  | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 15 | constrain_equal_point | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 16 | bool_check            | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 17 | cond_select           | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 18 | zero_cond             | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 19 | less_than_strict      | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 20 | less_than_loose       | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 21 | range_check           | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 22 | witness_base          | 2.4 MB    | 145.6 kB           | 7.6 kB     |
+| 23 | debug                 | 2.4 MB    | 145.6 kB           | 7.6 kB     |

+ 5 - 0
script/research/zkvm-metering/generator/.gitignore

@@ -0,0 +1,5 @@
+/target
+Cargo.lock
+rustfmt.toml
+generator
+*.bin

+ 26 - 0
script/research/zkvm-metering/generator/Cargo.toml

@@ -0,0 +1,26 @@
+[package]
+name = "generator"
+version = "0.1.0"
+description = "CLI-utility to generate zk proofs for analyzing zkvm opcodes resource usage"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+repository = "https://codeberg.org/darkrenaissance/darkfi"
+license = "AGPL-3.0-only"
+edition = "2024"
+
+[workspace]
+
+[dependencies]
+darkfi-sdk = {path = "../../../../src/sdk"}
+darkfi = {path = "../../../../", features = ["zk"]}
+halo2_proofs = {version = "0.3.1", features = ["circuit-params"]}
+halo2_gadgets = {version = "0.3.1", features = ["circuit-params"]}
+rand = "0.8.5"
+darkfi-serial = "0.5.0"
+
+[dev-dependencies]
+halo2_proofs = {version = "0.3.1", features = ["dev-graph", "sanity-checks"]}
+halo2_gadgets = "0.3.1"
+
+[patch.crates-io]
+halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v031"}
+halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v031"}

+ 48 - 0
script/research/zkvm-metering/generator/Makefile

@@ -0,0 +1,48 @@
+.POSIX:
+
+# Install prefix
+PREFIX = $(HOME)/.cargo
+
+# Cargo binary
+CARGO = cargo
+
+# Compile target
+RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2)
+# Uncomment when doing musl static builds
+#RUSTFLAGS = -C target-feature=+crt-static -C link-self-contained=yes
+
+# zkas compiler binary
+ZKAS = ../../../../zkas
+
+# zkas circuits
+PROOFS_SRC = $(shell find proof -type f -name '*.zk')
+PROOFS_BIN = $(PROOFS_SRC:=.bin)
+
+SRC = \
+	Cargo.toml \
+	$(shell find src -type f -name '*.rs') \
+
+BIN = $(shell grep '^name = ' Cargo.toml | cut -d' ' -f3 | tr -d '"')
+
+all: $(BIN)
+
+$(PROOFS_BIN): $(ZKAS) $(PROOFS_SRC)
+	$(ZKAS) $(basename $@) -o $@
+
+$(BIN): $(PROOFS_BIN) $(SRC)
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package $@
+	cp -f target/$(RUST_TARGET)/release/$@ $@
+
+fmt:
+	$(CARGO) +nightly fmt --all
+
+clippy:
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clippy --target=$(RUST_TARGET) \
+		--release --all-features --workspace --tests
+
+clean:
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release --package $(BIN)
+	rm -f $(BIN)
+	rm -f proof/*.bin
+
+.PHONY: all fmt clippy clean

+ 14 - 0
script/research/zkvm-metering/generator/proof/base_add.zk

@@ -0,0 +1,14 @@
+k = 11;
+field = "pallas";
+
+constant "BaseAdd" {}
+
+witness "BaseAdd" {
+    Base a,
+    Base b,
+}
+
+circuit "BaseAdd" {
+    sum = base_add(a, b);
+    # constrain_instance(sum)
+}

+ 14 - 0
script/research/zkvm-metering/generator/proof/base_mul.zk

@@ -0,0 +1,14 @@
+k = 11;
+field = "pallas";
+
+constant "BaseMul" {}
+
+witness "BaseMul" {
+    Base a,
+    Base b,
+}
+
+circuit "BaseMul" {
+    product = base_mul(a, b);
+    # constrain_instance(product)
+}

+ 14 - 0
script/research/zkvm-metering/generator/proof/base_sub.zk

@@ -0,0 +1,14 @@
+k = 11;
+field = "pallas";
+
+constant "BaseSub" {}
+
+witness "BaseSub" {
+    Base a,
+    Base b,
+}
+
+circuit "BaseSub" {
+    difference = base_sub(a, b);
+    # constrain_instance(difference)
+}

+ 12 - 0
script/research/zkvm-metering/generator/proof/bool_check.zk

@@ -0,0 +1,12 @@
+k = 11;
+field = "pallas";
+
+constant "BoolCheck" {}
+
+witness "BoolCheck" {
+    Base a,
+}
+
+circuit "BoolCheck" {
+    bool_check(a);
+}

+ 15 - 0
script/research/zkvm-metering/generator/proof/cond_select.zk

@@ -0,0 +1,15 @@
+k = 11;
+field = "pallas";
+
+constant "CondSelect" {}
+
+witness "CondSelect" {
+    Base a,
+    Base b,
+    Base cond,
+}
+
+circuit "CondSelect" {
+    out = cond_select(cond, a, b);
+    #constrain_instance(out);
+}

+ 13 - 0
script/research/zkvm-metering/generator/proof/constrain_equal_base.zk

@@ -0,0 +1,13 @@
+k = 11;
+field = "pallas";
+
+constant "ConstrainEqualBase" {}
+
+witness "ConstrainEqualBase" {
+    Base a,
+    Base b,
+}
+
+circuit "ConstrainEqualBase" {
+    constrain_equal_base(a, b);
+}

+ 13 - 0
script/research/zkvm-metering/generator/proof/constrain_equal_point.zk

@@ -0,0 +1,13 @@
+k = 11;
+field = "pallas";
+
+constant "ConstrainEqualPoint" {}
+
+witness "ConstrainEqualPoint" {
+    EcPoint a,
+    EcPoint b,
+}
+
+circuit "ConstrainEqualPoint" {
+    constrain_equal_point(a, b);
+}

+ 12 - 0
script/research/zkvm-metering/generator/proof/constrain_instance.zk

@@ -0,0 +1,12 @@
+k = 11;
+field = "pallas";
+
+constant "ConstrainInstance" {}
+
+witness "ConstrainInstance" {
+    Base a,
+}
+
+circuit "ConstrainInstance" {
+    constrain_instance(a);
+}

+ 12 - 0
script/research/zkvm-metering/generator/proof/debug.zk

@@ -0,0 +1,12 @@
+k = 11;
+field = "pallas";
+
+constant "Debug" {}
+
+witness "Debug" {
+    Base a,
+}
+
+circuit "Debug" {
+    debug(a);
+}

+ 15 - 0
script/research/zkvm-metering/generator/proof/ec_add.zk

@@ -0,0 +1,15 @@
+k = 11;
+field = "pallas";
+
+constant "EcAdd" {}
+
+witness "EcAdd" {
+    EcPoint a,
+    EcPoint b,
+}
+
+circuit "EcAdd" {
+    sum = ec_add(a, b);
+    #constrain_instance(ec_get_x(sum));
+    #constrain_instance(ec_get_y(sum));
+}

+ 13 - 0
script/research/zkvm-metering/generator/proof/ec_get_x.zk

@@ -0,0 +1,13 @@
+k = 11;
+field = "pallas";
+
+constant "EcGetX" {}
+
+witness "EcGetX" {
+    EcPoint a,
+}
+
+circuit "EcGetX" {
+    a_x = ec_get_x(a);
+    #constrain_instance(a_x);
+}

+ 13 - 0
script/research/zkvm-metering/generator/proof/ec_get_y.zk

@@ -0,0 +1,13 @@
+k = 11;
+field = "pallas";
+
+constant "EcGetY" {}
+
+witness "EcGetY" {
+    EcPoint a,
+}
+
+circuit "EcGetY" {
+    a_y = ec_get_y(a);
+    #constrain_instance(a_y);
+}

+ 16 - 0
script/research/zkvm-metering/generator/proof/ec_mul.zk

@@ -0,0 +1,16 @@
+k = 11;
+field = "pallas";
+
+constant "EcMul" {
+	EcFixedPoint VALUE_COMMIT_RANDOM,
+}
+
+witness "EcMul" {
+    Scalar value_blind,
+}
+
+circuit "EcMul" {
+    vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
+    #constrain_instance(ec_get_x(vcr));
+    #constrain_instance(ec_get_y(vcr));
+}

+ 16 - 0
script/research/zkvm-metering/generator/proof/ec_mul_base.zk

@@ -0,0 +1,16 @@
+k = 11;
+field = "pallas";
+
+constant "EcMulBase" {
+	EcFixedPointBase NULLIFIER_K,
+}
+
+witness "EcMulBase" {
+    Base secret,
+}
+
+circuit "EcMulBase" {
+    public = ec_mul_base(secret, NULLIFIER_K);
+    #constrain_instance(ec_get_x(public));
+    #constrain_instance(ec_get_y(public));
+}

+ 16 - 0
script/research/zkvm-metering/generator/proof/ec_mul_short.zk

@@ -0,0 +1,16 @@
+k = 11;
+field = "pallas";
+
+constant "EcMulShort" {
+	EcFixedPointShort VALUE_COMMIT_VALUE,
+}
+
+witness "EcMulShort" {
+    Base value,
+}
+
+circuit "EcMulShort" {
+    vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
+    #constrain_instance(ec_get_x(vcv));
+    #constrain_instance(ec_get_y(vcv));
+}

+ 16 - 0
script/research/zkvm-metering/generator/proof/ec_mul_var_base.zk

@@ -0,0 +1,16 @@
+k = 11;
+field = "pallas";
+
+constant "EcMulVarBase" {
+}
+
+witness "EcMulVarBase" {
+    Base ephem_secret,
+	EcNiPoint pubkey,
+}
+
+circuit "EcMulVarBase" {
+    ephem_public = ec_mul_var_base(ephem_secret, pubkey);
+    constrain_instance(ec_get_x(ephem_public));
+    constrain_instance(ec_get_y(ephem_public));
+}

+ 13 - 0
script/research/zkvm-metering/generator/proof/less_than_loose.zk

@@ -0,0 +1,13 @@
+k = 11;
+field = "pallas";
+
+constant "LessThanLoose" {}
+
+witness "LessThanLoose" {
+    Base a,
+    Base b,
+}
+
+circuit "LessThanLoose" {
+    less_than_loose(a, b);
+}

+ 13 - 0
script/research/zkvm-metering/generator/proof/less_than_strict.zk

@@ -0,0 +1,13 @@
+k = 11;
+field = "pallas";
+
+constant "LessThanStrict" {}
+
+witness "LessThanStrict" {
+    Base a,
+    Base b,
+}
+
+circuit "LessThanStrict" {
+    less_than_strict(a, b);
+}

+ 17 - 0
script/research/zkvm-metering/generator/proof/merkle_root.zk

@@ -0,0 +1,17 @@
+k = 13;
+field = "pallas";
+
+constant "MerkleRoot" {
+}
+
+witness "MerkleRoot" {
+	Base leaf,
+	Uint32 leaf_pos,
+	MerklePath path,
+}
+
+circuit "MerkleRoot" {
+	root = merkle_root(leaf_pos, path, leaf);
+	# constrain_instance(root)
+}
+

+ 14 - 0
script/research/zkvm-metering/generator/proof/poseidon_hash.zk

@@ -0,0 +1,14 @@
+k = 11;
+field = "pallas";
+
+constant "PoseidonHash" {}
+
+witness "PoseidonHash" {
+    Base a,
+    Base b,
+}
+
+circuit "PoseidonHash" {
+    hash = poseidon_hash(a, b);
+    #constrain_instance(hash);
+}

+ 12 - 0
script/research/zkvm-metering/generator/proof/range_check.zk

@@ -0,0 +1,12 @@
+k = 11;
+field = "pallas";
+
+constant "RangeCheck" {}
+
+witness "RangeCheck" {
+    Base a,
+}
+
+circuit "RangeCheck" {
+    range_check(64, a);
+}

+ 16 - 0
script/research/zkvm-metering/generator/proof/sparse_merkle_root.zk

@@ -0,0 +1,16 @@
+k = 14;
+field = "pallas";
+
+constant "SparseMerkleRoot" {
+}
+
+witness "SparseMerkleRoot" {
+    SparseMerklePath path,
+    Base leaf,
+}
+
+circuit "SparseMerkleRoot" {
+    root = sparse_merkle_root(leaf, path, leaf);
+    # constrain_instance(root)
+}
+

+ 12 - 0
script/research/zkvm-metering/generator/proof/witness_base.zk

@@ -0,0 +1,12 @@
+k = 11;
+field = "pallas";
+
+constant "WitnessBase" {}
+
+witness "WitnessBase" {
+}
+
+circuit "WitnessBase" {
+    two = witness_base(2);
+    #constrain_instance(two);
+}

+ 14 - 0
script/research/zkvm-metering/generator/proof/zero_cond.zk

@@ -0,0 +1,14 @@
+k = 11;
+field = "pallas";
+
+constant "ZeroCond" {}
+
+witness "ZeroCond" {
+    Base a,
+    Base b,
+}
+
+circuit "ZeroCond" {
+    out = zero_cond(a, b);
+    #constrain_instance(out);
+}

+ 348 - 0
script/research/zkvm-metering/generator/src/main.rs

@@ -0,0 +1,348 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{
+    fs::{File, read_dir},
+    io::{Read, Write},
+    path::Path,
+};
+
+use darkfi::{
+    zk::{Proof, ProvingKey, VerifyingKey, Witness, ZkCircuit, empty_witnesses, halo2::Field},
+    zkas::ZkBinary,
+};
+use darkfi_sdk::{
+    crypto::{
+        MerkleNode, MerkleTree,
+        constants::{
+            NullifierK,
+            OrchardFixedBasesFull::ValueCommitR,
+            fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_V_BYTES},
+        },
+        pasta_prelude::{Curve, CurveAffine, CurveExt, Group},
+        smt::{EMPTY_NODES_FP, MemoryStorageFp, PoseidonFp, SmtMemoryFp},
+        util::{fp_mod_fv, poseidon_hash},
+    },
+    pasta::{Ep, Fp, Fq, pallas, pallas::Base},
+};
+use darkfi_serial::serialize;
+use halo2_gadgets::ecc::chip::FixedPoint;
+use halo2_proofs::circuit::Value;
+use rand::rngs::OsRng;
+
+fn main() {
+    let entries = read_dir("proof").unwrap();
+    // Read each compiled zk file bin
+    for entry in entries.flatten() {
+        let path = entry.path();
+        if !(path.is_file() && path.to_str().unwrap().ends_with(".zk.bin")) {
+            continue
+        }
+        let name = path.file_name().unwrap().to_str().unwrap().split(".").next().unwrap();
+        let proof_file = format!("proof/{name}.proof.bin");
+        let vk_file = format!("proof/{name}.vks.bin");
+        let public_inputs_file = format!("proof/{name}.pi.bin");
+
+        // Skip if already generated
+        if Path::new(&proof_file).exists() &&
+            Path::new(&vk_file).exists() &&
+            Path::new(&public_inputs_file).exists()
+        {
+            println!("{name} is already generated");
+            continue;
+        }
+
+        println!("Generating {name} ...");
+
+        // Open zk bin
+        let mut file = File::open(&path).unwrap();
+        let mut buf = vec![];
+        file.read_to_end(&mut buf).unwrap();
+        let zkbin = ZkBinary::decode(&buf).unwrap();
+
+        // Get witnesses and public inputs for that particular zk file
+        let (witnesses, public_inputs) = retrieve_proof_inputs(name);
+
+        // Generate and save Proof
+        let circuit = ZkCircuit::new(witnesses, &zkbin);
+        let proving_key = ProvingKey::build(zkbin.k, &circuit);
+        let proof = Proof::create(&proving_key, &[circuit], &public_inputs, &mut OsRng).unwrap();
+
+        let proof_export = serialize(&proof);
+        let mut f = File::create(&proof_file).unwrap();
+        f.write_all(&proof_export).unwrap();
+
+        // Generate and save Verifying Key
+        let verifier_witnesses = empty_witnesses(&zkbin).unwrap();
+        let circuit = ZkCircuit::new(verifier_witnesses, &zkbin);
+        let verifying_key = VerifyingKey::build(zkbin.k, &circuit);
+
+        let mut vk_export = vec![];
+        verifying_key.write(&mut vk_export).unwrap();
+        let mut f = File::create(&vk_file).unwrap();
+        f.write_all(&vk_export).unwrap();
+
+        // Save Public inputs
+        let public_inputs_export = serialize(&public_inputs);
+        let mut f = File::create(&public_inputs_file).unwrap();
+        f.write_all(&public_inputs_export).unwrap();
+    }
+}
+
+fn retrieve_proof_inputs(name: &str) -> (Vec<Witness>, Vec<Base>) {
+    match name {
+        "sparse_merkle_root" => {
+            let hasher = PoseidonFp::new();
+            let store = MemoryStorageFp::new();
+            let mut smt = SmtMemoryFp::new(store, hasher.clone(), &EMPTY_NODES_FP);
+
+            let leaves =
+                vec![Fp::random(&mut OsRng), Fp::random(&mut OsRng), Fp::random(&mut OsRng)];
+            let leaves: Vec<_> = leaves.into_iter().map(|l| (l, l)).collect();
+            smt.insert_batch(leaves.clone()).unwrap();
+
+            let (pos, leaf) = leaves[2];
+
+            let root = smt.root();
+            let path = smt.prove_membership(&pos);
+
+            let prover_witnesses = vec![
+                Witness::SparseMerklePath(Value::known(path.path)),
+                Witness::Base(Value::known(leaf)),
+            ];
+
+            let public_inputs = vec![root];
+
+            (prover_witnesses, public_inputs)
+        }
+        "merkle_root" => {
+            let mut tree = MerkleTree::new(u32::MAX as usize);
+            let node1 = MerkleNode::from(Fp::random(&mut OsRng));
+            let node2 = MerkleNode::from(Fp::random(&mut OsRng));
+            let node3 = MerkleNode::from(Fp::random(&mut OsRng));
+            tree.append(node1);
+            tree.mark();
+            tree.append(node2);
+            let leaf_pos = tree.mark().unwrap();
+            tree.append(node3);
+
+            let root = tree.root(0).unwrap().inner();
+            let path = tree.witness(leaf_pos, 0).unwrap();
+
+            let prover_witnesses = vec![
+                Witness::Base(Value::known(node2.inner())),
+                Witness::Uint32(Value::known(u64::from(leaf_pos).try_into().unwrap())),
+                Witness::MerklePath(Value::known(path.try_into().unwrap())),
+            ];
+            let public_inputs = vec![root];
+
+            (prover_witnesses, public_inputs)
+        }
+        "base_add" => {
+            let b1 = Fp::from(4u64);
+            let b2 = Fp::from(110u64);
+            let prover_witnesses =
+                vec![Witness::Base(Value::known(b1)), Witness::Base(Value::known(b2))];
+            let public_inputs = vec![b1 + b2];
+
+            (prover_witnesses, public_inputs)
+        }
+        "base_mul" => {
+            let b1 = Fp::from(4u64);
+            let b2 = Fp::from(110u64);
+            let prover_witnesses =
+                vec![Witness::Base(Value::known(b1)), Witness::Base(Value::known(b2))];
+            let public_inputs = vec![b1 * b2];
+
+            (prover_witnesses, public_inputs)
+        }
+        "base_sub" => {
+            let b1 = Fp::from(4u64);
+            let b2 = Fp::from(110u64);
+            let prover_witnesses =
+                vec![Witness::Base(Value::known(b1)), Witness::Base(Value::known(b2))];
+            let public_inputs = vec![b1 - b2];
+
+            (prover_witnesses, public_inputs)
+        }
+        "ec_add" => {
+            let p1 = Ep::random(&mut OsRng);
+            let p2 = Ep::random(&mut OsRng);
+            let sum = (p1 + p2).to_affine();
+            let sum_x = *sum.coordinates().unwrap().x();
+            let sum_y = *sum.coordinates().unwrap().y();
+            let prover_witnesses =
+                vec![Witness::EcPoint(Value::known(p1)), Witness::EcPoint(Value::known(p2))];
+            let public_inputs = vec![sum_x, sum_y];
+
+            (prover_witnesses, public_inputs)
+        }
+        "ec_mul" => {
+            let scalar_blind = Fq::random(&mut OsRng);
+            let vcr = (ValueCommitR.generator() * scalar_blind).to_affine();
+            let vcr_x = *vcr.coordinates().unwrap().x();
+            let vcr_y = *vcr.coordinates().unwrap().y();
+            let prover_witnesses = vec![Witness::Scalar(Value::known(scalar_blind))];
+            let public_inputs = vec![vcr_x, vcr_y];
+
+            (prover_witnesses, public_inputs)
+        }
+        "ec_mul_base" => {
+            let secret_key = Fp::random(&mut OsRng);
+            let pubkey = (NullifierK.generator() * fp_mod_fv(secret_key)).to_affine();
+            let pubkey_x = *pubkey.coordinates().unwrap().x();
+            let pubkey_y = *pubkey.coordinates().unwrap().y();
+            let prover_witnesses = vec![Witness::Base(Value::known(secret_key))];
+            let public_inputs = vec![pubkey_x, pubkey_y];
+
+            (prover_witnesses, public_inputs)
+        }
+        "ec_mul_short" => {
+            // we can't use Fp::random() since it can be more than u64::MAX and we need value to be u64
+            let value = Fp::from(42);
+            let hasher = pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION);
+            let val_commit = hasher(&VALUE_COMMITMENT_V_BYTES);
+
+            let vcv = (val_commit * fp_mod_fv(value)).to_affine();
+            let vcv_x = *vcv.coordinates().unwrap().x();
+            let vcv_y = *vcv.coordinates().unwrap().y();
+            let prover_witnesses = vec![Witness::Base(Value::known(value))];
+            let public_inputs = vec![vcv_x, vcv_y];
+
+            (prover_witnesses, public_inputs)
+        }
+        "ec_mul_var_base" => {
+            let ephem_secret = Fp::random(&mut OsRng);
+            let pubkey = NullifierK.generator() * fp_mod_fv(ephem_secret);
+            let ephem_pub = (pubkey * fp_mod_fv(ephem_secret)).to_affine();
+            let ephem_pub_x = *ephem_pub.coordinates().unwrap().x();
+            let ephem_pub_y = *ephem_pub.coordinates().unwrap().y();
+            let prover_witnesses = vec![
+                Witness::Base(Value::known(ephem_secret)),
+                Witness::EcNiPoint(Value::known(pubkey)),
+            ];
+            let public_inputs = vec![ephem_pub_x, ephem_pub_y];
+
+            (prover_witnesses, public_inputs)
+        }
+        "ec_get_x" => {
+            let p = Ep::random(&mut OsRng);
+            let x = *p.to_affine().coordinates().unwrap().x();
+            let prover_witnesses = vec![Witness::EcPoint(Value::known(p))];
+            let public_inputs = vec![x];
+
+            (prover_witnesses, public_inputs)
+        }
+        "ec_get_y" => {
+            let p = Ep::random(&mut OsRng);
+            let y = *p.to_affine().coordinates().unwrap().y();
+            let prover_witnesses = vec![Witness::EcPoint(Value::known(p))];
+            let public_inputs = vec![y];
+
+            (prover_witnesses, public_inputs)
+        }
+        "poseidon_hash" => {
+            let a = Fp::random(&mut OsRng);
+            let b = Fp::random(&mut OsRng);
+            let hash = poseidon_hash([a, b]);
+            let prover_witnesses =
+                vec![Witness::Base(Value::known(a)), Witness::Base(Value::known(b))];
+            let public_inputs = vec![hash];
+
+            (prover_witnesses, public_inputs)
+        }
+        "constrain_instance" => {
+            let a = Fp::random(&mut OsRng);
+            let prover_witnesses = vec![Witness::Base(Value::known(a))];
+            let public_inputs = vec![a];
+
+            (prover_witnesses, public_inputs)
+        }
+        "witness_base" => (vec![], vec![Fp::from(2)]),
+        "constrain_equal_base" => {
+            let a = Fp::from(23);
+            let prover_witnesses =
+                vec![Witness::Base(Value::known(a)), Witness::Base(Value::known(a))];
+
+            (prover_witnesses, vec![])
+        }
+        "constrain_equal_point" => {
+            let a = Ep::random(&mut OsRng);
+            let prover_witnesses =
+                vec![Witness::EcPoint(Value::known(a)), Witness::EcPoint(Value::known(a))];
+
+            (prover_witnesses, vec![])
+        }
+        "less_than_strict" => {
+            let a = Fp::from(23);
+            let b = Fp::from(42);
+            let prover_witnesses =
+                vec![Witness::Base(Value::known(a)), Witness::Base(Value::known(b))];
+
+            (prover_witnesses, vec![])
+        }
+        "less_than_loose" => {
+            let a = Fp::from(23);
+            let prover_witnesses =
+                vec![Witness::Base(Value::known(a)), Witness::Base(Value::known(a))];
+
+            (prover_witnesses, vec![])
+        }
+        "bool_check" => {
+            let a = Fp::from(1);
+            let prover_witnesses = vec![Witness::Base(Value::known(a))];
+
+            (prover_witnesses, vec![])
+        }
+        "cond_select" => {
+            let a = Fp::from(23);
+            let b = Fp::from(42);
+            let cond = Fp::from(1);
+            let prover_witnesses = vec![
+                Witness::Base(Value::known(a)),
+                Witness::Base(Value::known(b)),
+                Witness::Base(Value::known(cond)),
+            ];
+            let public_inputs = vec![a];
+
+            (prover_witnesses, public_inputs)
+        }
+        "zero_cond" => {
+            let a = Fp::from(0);
+            let b = Fp::from(23);
+            let prover_witnesses =
+                vec![Witness::Base(Value::known(a)), Witness::Base(Value::known(b))];
+            let public_inputs = vec![a];
+
+            (prover_witnesses, public_inputs)
+        }
+        "range_check" => {
+            let a = Fp::from(23);
+            let prover_witnesses = vec![Witness::Base(Value::known(a))];
+
+            (prover_witnesses, vec![])
+        }
+        "debug" => {
+            let a = Fp::from(23);
+            let prover_witnesses = vec![Witness::Base(Value::known(a))];
+
+            (prover_witnesses, vec![])
+        }
+        _ => panic!("unsupported Zk script"),
+    }
+}

+ 5 - 0
script/research/zkvm-metering/verifier/.gitignore

@@ -0,0 +1,5 @@
+/target
+Cargo.lock
+rustfmt.toml
+verifier
+*.zst

+ 22 - 0
script/research/zkvm-metering/verifier/Cargo.toml

@@ -0,0 +1,22 @@
+[package]
+name = "verifier"
+version = "0.1.0"
+description = "CLI-utility to verify zk proofs for analysis"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+repository = "https://codeberg.org/darkrenaissance/darkfi"
+license = "AGPL-3.0-only"
+edition = "2024"
+
+[workspace]
+
+[dependencies]
+darkfi-sdk = {path = "../../../../src/sdk"}
+darkfi = {path = "../../../../", features = ["zk"]}
+darkfi-serial = "0.5.0"
+
+[patch.crates-io]
+halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v031"}
+halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v031"}
+
+[profile.release]
+debug = true

+ 37 - 0
script/research/zkvm-metering/verifier/Makefile

@@ -0,0 +1,37 @@
+.POSIX:
+
+# Install prefix
+PREFIX = $(HOME)/.cargo
+
+# Cargo binary
+CARGO = cargo
+
+# Compile target
+RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2)
+# Uncomment when doing musl static builds
+#RUSTFLAGS = -C target-feature=+crt-static -C link-self-contained=yes
+
+SRC = \
+	Cargo.toml \
+	$(shell find src -type f -name '*.rs') \
+
+BIN = $(shell grep '^name = ' Cargo.toml | cut -d' ' -f3 | tr -d '"')
+
+all: $(BIN)
+
+$(BIN): $(SRC)
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package $@
+	cp -f target/$(RUST_TARGET)/release/$@ $@
+
+fmt:
+	$(CARGO) +nightly fmt --all
+
+clippy:
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clippy --target=$(RUST_TARGET) \
+		--release --all-features --workspace --tests
+
+clean:
+	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release --package $(BIN)
+	rm -f $(BIN)
+
+.PHONY: all fmt clippy clean

+ 10 - 0
script/research/zkvm-metering/verifier/run_heaptrack.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+
+SRC_DIR="../generator/proof"
+
+for file in "$SRC_DIR"/*.zk; do
+    filename=$(basename "$file")
+    name="${filename%.zk}"
+
+    heaptrack --output "output/${name}" ./verifier "$name"
+done

+ 75 - 0
script/research/zkvm-metering/verifier/src/main.rs

@@ -0,0 +1,75 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{
+    env,
+    fs::File,
+    io::{Cursor, Read},
+};
+
+use darkfi::{
+    Result,
+    zk::{Proof, VerifyingKey, ZkCircuit, empty_witnesses},
+    zkas::ZkBinary,
+};
+use darkfi_sdk::pasta::pallas::Base;
+use darkfi_serial::deserialize;
+
+fn main() -> Result<()> {
+    let args: Vec<String> = env::args().collect();
+    if args.len() != 2 {
+        println!("Usage: ./verifier opcode_name");
+        return Ok(())
+    }
+
+    let file_name = &args[1];
+
+    let name = file_name.split(".").next().unwrap();
+    //Load zkbin, proof, verifying key, public inputs from file
+    let mut file = File::open(format!("../generator/proof/{name}.zk.bin"))?;
+    let mut bincode = vec![];
+    file.read_to_end(&mut bincode)?;
+
+    let mut file = File::open(format!("../generator/proof/{name}.proof.bin"))?;
+    let mut proof_bin = vec![];
+    file.read_to_end(&mut proof_bin)?;
+
+    let mut file = File::open(format!("../generator/proof/{name}.vks.bin"))?;
+    let mut vkbin = vec![];
+    file.read_to_end(&mut vkbin)?;
+
+    let mut file = File::open(format!("../generator/proof/{name}.pi.bin"))?;
+    let mut public_inputs_bin = vec![];
+    file.read_to_end(&mut public_inputs_bin)?;
+
+    // Deserialize and Verify
+    let zkbin = ZkBinary::decode(&bincode)?;
+    let verifier_witnesses = empty_witnesses(&zkbin)?;
+
+    // Create the circuit
+    let circuit = ZkCircuit::new(verifier_witnesses, &zkbin);
+
+    let proof: Proof = deserialize(&proof_bin)?;
+    let mut vk_buf = Cursor::new(vkbin);
+    let vk = VerifyingKey::read::<Cursor<Vec<u8>>, ZkCircuit>(&mut vk_buf, circuit)?;
+    //let vk = VerifyingKey::build(zkbin.k, &circuit);
+
+    let public_inputs: Vec<Base> = deserialize(&public_inputs_bin)?;
+
+    Ok(proof.verify(&vk, &public_inputs)?)
+}