Optimizing your program
This guide covers the two ergonomic ways to route expensive operations
through ZisK native precompiles: patched crates, which accelerate an
entire dependency with a single Cargo.toml change and no guest code
changes, and zisklib wrappers, which replace individual call sites
with safe Rust functions when no patch exists.
Understanding the approaches
ZisK exposes native operations called precompiles that execute
cryptographic and arithmetic primitives in a single VM step. Patches
and Precompile wrappers (zisklib) are two ergonomic layers on top of them.
| Approach | How it works | Trade-off |
|---|---|---|
| Patches | Swap a crate dependency in Cargo.toml for a ZisK-accelerated fork. Every workspace crate that uses the library is accelerated automatically, with no source changes. | Version pinning: each patch targets a specific upstream version, so upgrading a dependency may need a matching fork. |
| Precompile wrappers | Replace individual call sites with safe Rust functions that dispatch directly to the corresponding ZisK state machine. | Each call site must be updated manually. |
Applying a patch
This guide picks up where the profiling guide left
off and uses the same merkle-tree project. Completing that guide
is a prerequisite as it produces the workspace, the shared
merkle_root logic in common/, and the guest in guest/src/main.rs
that this guide patches and optimizes. If you have not finished
the profiling guide yet, go through it first and then come back
here.
Getting a baseline
Before touching Cargo.toml, capture the unpatched cost so you have
a reference to compare against. Build and run with --profiling summary
to get the full cost breakdown.
cargo-zisk build --release
cargo-zisk run --release -i samples/example-input.bin --profiling summary
merkle-root(1000) => 0xf94e7857a9aa655788bccc391771dbe005b9b9cbd2be8f26c56bd08c6c755da5
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ REPORT SUMMARY ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ STEPS 13,284,475 ║
║ COST 1,760,938,930 ║
║ RAM 0.03 MB / 507.75 MB ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ COST DISTRIBUTION SUMMARY ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ CATEGORY COST % ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Base █████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 293,601,280 16.7% ║
║ Main ████████████████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 903,344,300 51.3% ║
║ Opcodes ███████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 511,698,015 29.1% ║
║ Precompiles ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 895,048 0.1% ║
║ Memory ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 51,400,287 2.9% ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Total 1,760,938,930 100.0% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ COST DISTRIBUTION BY OPCODE ║ ◆ OPS vs FROPS ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ OPCODE COST % ║ OPS + FROPS FROPS % ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ xor ██░░░░░░░░░░░░░░░░░░░░░ 123,323,220 7.0% ║ 126,985,140 3,661,920 2.9% ║
║ or █░░░░░░░░░░░░░░░░░░░░░░ 105,359,820 6.0% ║ 114,561,660 9,201,840 8.0% ║
║ srl_w █░░░░░░░░░░░░░░░░░░░░░░ 102,040,900 5.8% ║ 107,028,730 4,987,830 4.7% ║
║ sll █░░░░░░░░░░░░░░░░░░░░░░ 87,754,220 5.0% ║ 101,518,426 13,764,206 13.6% ║
║ add █░░░░░░░░░░░░░░░░░░░░░░ 49,913,675 2.8% ║ 50,804,000 890,325 1.8% ║
║ and ░░░░░░░░░░░░░░░░░░░░░░░ 35,641,440 2.0% ║ 36,996,600 1,355,160 3.7% ║
║ srl ░░░░░░░░░░░░░░░░░░░░░░░ 2,602,088 0.1% ║ 2,710,473 108,385 4.0% ║
║ signextend_b ░░░░░░░░░░░░░░░░░░░░░░░ 2,545,696 0.1% ║ 2,545,696 0 0.0% ║
║ signextend_w ░░░░░░░░░░░░░░░░░░░░░░░ 2,121,431 0.1% ║ 2,121,431 0 0.0% ║
║ dma_xmemset ░░░░░░░░░░░░░░░░░░░░░░░ 600,400 0.0% ║ ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Total 512,593,063 29.1% ║ 547,184,854 34,591,791 6.3% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ TOP COST FUNCTIONS ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ 0 sha2::sha256::compress256 ████████████████░░░░ 1,418,669,960 80.7% ║
║ 1 common::merkle_root ███████████░░░░░░░░░ 979,073,409 55.7% ║
║ 2 memset ░░░░░░░░░░░░░░░░░░░░ 1,511,012 0.1% ║
║ 3 memcpy ░░░░░░░░░░░░░░░░░░░░ 940,433 0.1% ║
║ 4 std::io::stdio::_print ░░░░░░░░░░░░░░░░░░░░ 230,680 0.0% ║
║ 5 <[u8; 32] as hex::ToHex>::encode_hex::<alloc::string::String> ░░░░░░░░░░░░░░░░░░░░ 214,975 0.0% ║
║ 6 core::fmt::write ░░░░░░░░░░░░░░░░░░░░ 213,944 0.0% ║
║ 7 <std::..::LineWriterShim<…> as std::io::Write>::write_all ░░░░░░░░░░░░░░░░░░░░ 114,043 0.0% ║
║ 8 <std::..::Adapter<…> as core::fmt::Write>::write_str ░░░░░░░░░░░░░░░░░░░░ 112,485 0.0% ║
║ 9 <alloc::string::String as core::fmt::Debug>::fmt ░░░░░░░░░░░░░░░░░░░░ 92,663 0.0% ║
║ 10 <str as core::fmt::Debug>::fmt ░░░░░░░░░░░░░░░░░░░░ 91,975 0.0% ║
║ 11 <hex::BytesToHexChars as core::..::Iterator>::next ░░░░░░░░░░░░░░░░░░░░ 86,313 0.0% ║
║ 12 core::slice::memchr::memrchr ░░░░░░░░░░░░░░░░░░░░ 41,931 0.0% ║
║ 13 <std::io::buffered::bufwriter::BufWriter<…>>::flush_buf ░░░░░░░░░░░░░░░░░░░░ 39,046 0.0% ║
║ 14 sys_write ░░░░░░░░░░░░░░░░░░░░ 37,307 0.0% ║
║ 15 ziskos::io::commit_slice ░░░░░░░░░░░░░░░░░░░░ 31,728 0.0% ║
║ 16 <u64 as core::fmt::Display>::fmt ░░░░░░░░░░░░░░░░░░░░ 29,167 0.0% ║
║ 17 <std::..::Adapter<…> as core::fmt::Write>::write_char ░░░░░░░░░░░░░░░░░░░░ 23,714 0.0% ║
║ 18 <core::fmt::Formatter>::pad_integral ░░░░░░░░░░░░░░░░░░░░ 19,653 0.0% ║
║ 19 <…>::initialize::<…> ░░░░░░░░░░░░░░░░░░░░ 7,830 0.0% ║
║ 20 <usize>::_fmt_inner ░░░░░░░░░░░░░░░░░░░░ 6,974 0.0% ║
║ 21 <…>::reserve::do_reserve_and_handle::<alloc::alloc::Global> ░░░░░░░░░░░░░░░░░░░░ 6,011 0.0% ║
║ 22 <std::..::Once>::call::<<…>::call_once_force<…>::{closure#0}> ░░░░░░░░░░░░░░░░░░░░ 5,448 0.0% ║
║ 23 __rustc::__rust_alloc ░░░░░░░░░░░░░░░░░░░░ 4,104 0.0% ║
║ 24 <alloc::raw_vec::RawVecInner>::finish_grow ░░░░░░░░░░░░░░░░░░░░ 3,819 0.0% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Adding the patch
The guest code stays exactly as it was, no changes needed. Just
open the workspace Cargo.toml and add the patch entry:
[patch.crates-io]
sha2 = { git = "https://github.com/0xPolygonHermez/zisk-patch-hashes", package = "sha2", tag = "patch-sha2-0.10.9-zisk-0.16.0+" }
The tag field encodes both the patched crate version and the ZisK
version it targets (patch-sha2-<crate-version>-zisk-<zisk-version>).
Choose the tag whose crate version matches your sha2 dependency and
whose ZisK version is the closest to the one you are using. The
override applies to the entire workspace, so every crate that depends
on sha2 is accelerated automatically, with no changes to any source
file.
The patch is not part of the example, so add the [patch.crates-io]
entry above yourself even if you cloned the repository.
Rebuilding and measuring
With the patch in place, rebuild and run the same profiling command
as the baseline. Cargo will resolve sha2 to the patched fork
automatically, with no other changes needed. The guest binary changes
because the SHA-256 implementation is different, but the program
logic, inputs, and committed output remain identical:
cargo-zisk build --release
cargo-zisk run --release -i samples/example-input.bin --profiling summary
merkle-root(1000) => "f94e7857a9aa655788bccc391771dbe005b9b9cbd2be8f26c56bd08c6c755da5"
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ REPORT SUMMARY ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ STEPS 610,031 ║
║ COST 389,003,070 ║
║ RAM 0.03 MB / 507.75 MB ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ COST DISTRIBUTION SUMMARY ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ CATEGORY COST % ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Base ███████████████████████████████████████████████████████████░░░░░░░░░░░░░░░░░░░ 293,601,280 75.5% ║
║ Main ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 41,482,108 10.7% ║
║ Opcodes ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 8,922,209 2.3% ║
║ Precompiles █████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 27,048,472 7.0% ║
║ Memory ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 17,949,001 4.6% ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Total 389,003,070 100.0% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ COST DISTRIBUTION BY OPCODE ║ ◆ OPS vs FROPS ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ OPCODE COST % ║ OPS + FROPS FROPS % ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ sha256 ██░░░░░░░░░░░░░░░░░░░░░ 26,153,424 6.7% ║ ║
║ srl ░░░░░░░░░░░░░░░░░░░░░░░ 2,602,088 0.7% ║ 2,710,473 108,385 4.0% ║
║ signextend_w ░░░░░░░░░░░░░░░░░░░░░░░ 2,121,431 0.5% ║ 2,121,431 0 0.0% ║
║ or ░░░░░░░░░░░░░░░░░░░░░░░ 1,860,300 0.5% ║ 2,166,780 306,480 14.1% ║
║ add ░░░░░░░░░░░░░░░░░░░░░░░ 980,950 0.3% ║ 1,120,900 139,950 12.5% ║
║ and ░░░░░░░░░░░░░░░░░░░░░░░ 789,840 0.2% ║ 792,480 2,640 0.3% ║
║ dma_xmemset ░░░░░░░░░░░░░░░░░░░░░░░ 600,400 0.2% ║ ║
║ sll ░░░░░░░░░░░░░░░░░░░░░░░ 298,602 0.1% ║ 2,077,176 1,778,574 85.6% ║
║ dma_memcpy ░░░░░░░░░░░░░░░░░░░░░░░ 294,648 0.1% ║ ║
║ eq ░░░░░░░░░░░░░░░░░░░░░░░ 166,800 0.0% ║ 808,560 641,760 79.4% ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Total 35,970,681 9.2% ║ 39,345,520 3,374,839 8.6% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ TOP COST FUNCTIONS ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ 0 guest::main █████░░░░░░░░░░░░░░░ 95,265,994 24.4% ║
║ 1 common::merkle_root ███░░░░░░░░░░░░░░░░░ 60,918,669 15.6% ║
║ 2 sha2::sha256::compress256 ██░░░░░░░░░░░░░░░░░░ 46,914,220 12.0% ║
║ 3 sha256f_compress_c ██░░░░░░░░░░░░░░░░░░ 46,121,692 11.8% ║
║ 4 <alloc::vec::Vec<…> as alloc::..::SpecFromIterNested<…>>::from_iter ██░░░░░░░░░░░░░░░░░░ 33,868,928 8.7% ║
║ 5 <…>::wrap_mut_2::<(), u64, core::..::{closure#0}>::{closure#0} ██░░░░░░░░░░░░░░░░░░ 33,293,244 8.5% ║
║ 6 memset ░░░░░░░░░░░░░░░░░░░░ 1,511,012 0.4% ║
║ 7 memcpy ░░░░░░░░░░░░░░░░░░░░ 935,688 0.2% ║
║ 8 std::io::stdio::_print ░░░░░░░░░░░░░░░░░░░░ 231,019 0.1% ║
║ 9 core::fmt::write ░░░░░░░░░░░░░░░░░░░░ 214,283 0.1% ║
║ 10 <std::..::LineWriterShim<…> as std::io::Write>::write_all ░░░░░░░░░░░░░░░░░░░░ 114,382 0.0% ║
║ 11 <std::..::Adapter<…> as core::fmt::Write>::write_str ░░░░░░░░░░░░░░░░░░░░ 112,824 0.0% ║
║ 12 <alloc::string::String as core::fmt::Debug>::fmt ░░░░░░░░░░░░░░░░░░░░ 92,663 0.0% ║
║ 13 <str as core::fmt::Debug>::fmt ░░░░░░░░░░░░░░░░░░░░ 91,975 0.0% ║
║ 14 <hex::BytesToHexChars as core::..::Iterator>::next ░░░░░░░░░░░░░░░░░░░░ 86,313 0.0% ║
║ 15 core::slice::memchr::memrchr ░░░░░░░░░░░░░░░░░░░░ 42,287 0.0% ║
║ 16 <std::io::buffered::bufwriter::BufWriter<…>>::flush_buf ░░░░░░░░░░░░░░░░░░░░ 39,046 0.0% ║
║ 17 sys_write ░░░░░░░░░░░░░░░░░░░░ 37,307 0.0% ║
║ 18 <usize as core::fmt::Display>::fmt ░░░░░░░░░░░░░░░░░░░░ 29,167 0.0% ║
║ 19 <std::..::Adapter<…> as core::fmt::Write>::write_char ░░░░░░░░░░░░░░░░░░░░ 23,714 0.0% ║
║ 20 ziskos::io::commit_slice ░░░░░░░░░░░░░░░░░░░░ 19,716 0.0% ║
║ 21 <core::fmt::Formatter>::pad_integral ░░░░░░░░░░░░░░░░░░░░ 19,653 0.0% ║
║ 22 write_output ░░░░░░░░░░░░░░░░░░░░ 19,452 0.0% ║
║ 23 <…>::reserve::do_reserve_and_handle::<alloc::alloc::Global> ░░░░░░░░░░░░░░░░░░░░ 8,004 0.0% ║
║ 24 <…>::initialize::<…> ░░░░░░░░░░░░░░░░░░░░ 7,830 0.0% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Total cost drops with zero changes to guest code.
sha2::sha256::compress256, which previously dominated the top
cost functions, no longer appears in the first place of the ranking.
SHA-256 work has migrated out of Opcodes and into Precompiles
at a fraction of its prior rate, and the xor/or/shift-heavy opcode
pattern thesoftware implementation produced has vanished from the opcode
table.
The cost is now dominated by the Merkle tree structure itself: memory accesses, vector allocation, and the loop, which are the irreducible costs of the algorithm regardless of how SHA-256 is implemented.
Available patches
Tags follow the pattern patch-{X}-zisk-{Y} where X is the patched
crate version and Y is the ZisK version. A + suffix on the ZisK
version (e.g. 0.16.0+) means the patch is forward-compatible from
that release onward. For repos that don't carry tags, pick the
branch that matches your upstream version. The Copy button copies
the latest [patch.crates-io] entry for that crate.
Hash functions
| Crate | Repo | Crate version | Zisk versions | |
|---|---|---|---|---|
sha2 | zisk-patch-hashes | 0.10.9 | 0.11.0–0.15.0, 0.16.0+ | |
sha3 | zisk-patch-hashes | 0.10.8 | 0.11.0–0.15.0, 0.16.0+ | |
tiny-keccak | zisk-patch-tiny-keccak | 2.0.2 | 0.15.0, 0.16.0+ |
Elliptic curves
| Crate | Repo | Crate version | Zisk versions | |
|---|---|---|---|---|
k256 | zisk-patch-elliptic-curves | 0.13.4 | 0.11.0–0.15.0, 0.17.0 | |
p256 | zisk-patch-elliptic-curves | 0.13.4 | 0.11.0–0.15.0, 0.17.0 | |
substrate-bn | zisk-patch-bn | 0.6.0 | 0.11.0–0.15.0, 0.17.0 | |
blst | zisk-patch-blst | 0.3.15 | 0.15.0, 0.17.0 |
Big-integer arithmetic
| Crate | Repo | Crate version | Zisk versions | |
|---|---|---|---|---|
ruint | zisk-patch-ruint | 1.17.0, 1.17.2 | 0.13.1, 0.15.0, 0.17.0 | |
aurora-engine-modexp | zisk-patch-modexp | 1.2.0 | 0.13.0, 0.15.0, 0.16.0+ |
Applying a precompile wrapper
Use zisklib when no patch exists, to avoid version pinning, or for
maximum efficiency. It calls the ZisK precompile directly with no
intermediate crate layer, producing fewer guest instructions than a patch.
Because it is a regular dependency, it must be declared in every crate
that uses it, both common and guest in the example we are working on.
Updating the common crate
To continue with the same merkle-tree add ziskos in the common
crate dependencies.
[package]
name = "merkle-common"
version = "0.1.0"
edition = "2024"
[dependencies]
sha2 = "0.10.9"
hex = "0.4.3"
ziskos = { git = "https://github.com/0xPolygonHermez/zisk.git", tag = "v1.0.0-beta" }
Then update common/src/lib.rs and add a new function that computes merkle_root but calls ziskos::zisklib::sha256:
/// Same as [`merkle_root`] but delegates SHA-256 to the ZisK built-in
/// (`ziskos::zisklib::sha256`), which maps to a highly optimised ROM operation
/// inside the zkVM instead of executing the hash circuit in user space.
pub fn merkle_root_zisklib(mut leaves: Vec<Hash>) -> Hash {
let mut buf = [0u8; 64];
while leaves.len() > 1 {
let next_len = leaves.len().div_ceil(2);
for i in 0..next_len {
let left = leaves[2 * i];
let right = leaves.get(2 * i + 1).copied().unwrap_or(left);
buf[..32].copy_from_slice(&left);
buf[32..].copy_from_slice(&right);
leaves[i] = ziskos::zisklib::sha256(&buf);
}
leaves.truncate(next_len);
}
leaves[0]
}
Updating the guest crate
Create a new guest/src/zisklib.rs so the binaries from the previous
guides keep working. It drops the Sha256 import and replaces the leaf
generation call with ziskos::zisklib::sha256 and the merkle
computation with merkle_common::merkle_root_zisklib. The rest of the
program stays the same:
#![no_main]
ziskos::entrypoint!(main);
use merkle_common::{hex, merkle_root_zisklib, Hash};
fn main() {
// Read the number of leaves from the guest's standard input stream.
let n: u64 = ziskos::io::read::<u64>();
// Build leaves using the ZisK Lib built-in SHA-256 operation.
let leaves: Vec<Hash> = (1..=n).map(|i| ziskos::zisklib::sha256(&i.to_le_bytes())).collect();
// Compute the Merkle root using the built-in SHA-256 variant.
let root = merkle_root_zisklib(leaves);
// Commit the root as a public output so a verifier can inspect it
// without re-executing the program.
ziskos::io::commit_slice(&root);
println!("merkle-root({n}) => 0x{}", hex::encode(root));
}
Register the new binary in guest/Cargo.toml alongside the existing
ones so they all remain buildable:
[[bin]]
name = "zisklib-guest"
path = "src/zisklib.rs"
Rebuilding and measuring
With the wrappers in place, rebuild and run the same profiling command. The committed output is identical to both the baseline and the patched version, only the implementation of SHA-256 changed, not the program's observable behavior:
cargo-zisk build --release --bin zisklib-guest
cargo-zisk run --release --bin zisklib-guest -i samples/example-input.bin --profiling summary
merkle-root(1000) => 0xf94e7857a9aa655788bccc391771dbe005b9b9cbd2be8f26c56bd08c6c755da5
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ REPORT SUMMARY ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ STEPS 510,829 ║
║ COST 374,949,677 ║
║ RAM 0.03 MB / 507.75 MB ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ COST DISTRIBUTION SUMMARY ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ CATEGORY COST % ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Base █████████████████████████████████████████████████████████████░░░░░░░░░░░░░░░░░ 293,601,280 78.3% ║
║ Main ███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 34,736,372 9.3% ║
║ Opcodes ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 8,158,526 2.2% ║
║ Precompiles ██████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 26,653,051 7.1% ║
║ Memory ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 11,800,448 3.1% ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Total 374,949,677 100.0% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ COST DISTRIBUTION BY OPCODE ║ ◆ OPS vs FROPS ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ OPCODE COST % ║ OPS + FROPS FROPS % ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ sha256 ██░░░░░░░░░░░░░░░░░░░░░ 26,153,424 7.0% ║ ║
║ srl ░░░░░░░░░░░░░░░░░░░░░░░ 2,761,300 0.7% ║ 2,976,003 214,703 7.2% ║
║ or ░░░░░░░░░░░░░░░░░░░░░░░ 2,040,300 0.5% ║ 2,526,660 486,360 19.2% ║
║ and ░░░░░░░░░░░░░░░░░░░░░░░ 1,029,660 0.3% ║ 1,032,300 2,640 0.3% ║
║ add ░░░░░░░░░░░░░░░░░░░░░░░ 875,325 0.2% ║ 970,975 95,650 9.9% ║
║ signextend_w ░░░░░░░░░░░░░░░░░░░░░░░ 848,477 0.2% ║ 848,477 0 0.0% ║
║ dma_memcpy ░░░░░░░░░░░░░░░░░░░░░░░ 499,627 0.1% ║ ║
║ sll ░░░░░░░░░░░░░░░░░░░░░░░ 319,166 0.1% ║ 2,183,547 1,864,381 85.4% ║
║ eq ░░░░░░░░░░░░░░░░░░░░░░░ 181,920 0.0% ║ 447,780 265,860 59.4% ║
║ srl_w ░░░░░░░░░░░░░░░░░░░░░░░ 53,053 0.0% ║ 215,498 162,445 75.4% ║
║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ║
║ Total 34,811,577 9.3% ║ 38,484,451 3,672,874 9.5% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ◆ TOP COST FUNCTIONS ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ 0 guest::main ████░░░░░░░░░░░░░░░░ 84,731,804 22.3% ║
║ 1 common::merkle_root ███░░░░░░░░░░░░░░░░░ 50,384,479 13.3% ║
║ 2 ziskos::zisklib::lib::sha256::sha256 ██░░░░░░░░░░░░░░░░░░ 38,455,004 10.1% ║
║ 3 <alloc::vec::Vec<…> as alloc::..::SpecFromIterNested<…>>::from_iter ██░░░░░░░░░░░░░░░░░░ 33,868,928 8.9% ║
║ 4 <…>::wrap_mut_2::<(), u64, core::..::{closure#0}>::{closure#0} ██░░░░░░░░░░░░░░░░░░ 33,293,244 8.8% ║
║ 5 sha2::sha256::compress256 █░░░░░░░░░░░░░░░░░░░ 15,663,000 4.1% ║
║ 6 sha256f_compress_c █░░░░░░░░░░░░░░░░░░░ 15,399,000 4.1% ║
║ 7 memcpy ░░░░░░░░░░░░░░░░░░░░ 1,196,949 0.3% ║
║ 8 memset ░░░░░░░░░░░░░░░░░░░░ 498,000 0.1% ║
║ 9 std::io::stdio::_print ░░░░░░░░░░░░░░░░░░░░ 231,019 0.1% ║
║ 10 core::fmt::write ░░░░░░░░░░░░░░░░░░░░ 214,283 0.1% ║
║ 11 <std::..::LineWriterShim<…> as std::io::Write>::write_all ░░░░░░░░░░░░░░░░░░░░ 114,382 0.0% ║
║ 12 <std::..::Adapter<…> as core::fmt::Write>::write_str ░░░░░░░░░░░░░░░░░░░░ 112,824 0.0% ║
║ 13 <alloc::string::String as core::fmt::Debug>::fmt ░░░░░░░░░░░░░░░░░░░░ 92,663 0.0% ║
║ 14 <str as core::fmt::Debug>::fmt ░░░░░░░░░░░░░░░░░░░░ 91,975 0.0% ║
║ 15 <hex::BytesToHexChars as core::..::Iterator>::next ░░░░░░░░░░░░░░░░░░░░ 86,313 0.0% ║
║ 16 core::slice::memchr::memrchr ░░░░░░░░░░░░░░░░░░░░ 42,287 0.0% ║
║ 17 <std::io::buffered::bufwriter::BufWriter<…>>::flush_buf ░░░░░░░░░░░░░░░░░░░░ 39,046 0.0% ║
║ 18 sys_write ░░░░░░░░░░░░░░░░░░░░ 37,307 0.0% ║
║ 19 <usize as core::fmt::Display>::fmt ░░░░░░░░░░░░░░░░░░░░ 29,167 0.0% ║
║ 20 <std::..::Adapter<…> as core::fmt::Write>::write_char ░░░░░░░░░░░░░░░░░░░░ 23,714 0.0% ║
║ 21 ziskos::io::commit_slice ░░░░░░░░░░░░░░░░░░░░ 19,716 0.0% ║
║ 22 <core::fmt::Formatter>::pad_integral ░░░░░░░░░░░░░░░░░░░░ 19,653 0.0% ║
║ 23 write_output ░░░░░░░░░░░░░░░░░░░░ 19,452 0.0% ║
║ 24 <…>::reserve::do_reserve_and_handle::<alloc::alloc::Global> ░░░░░░░░░░░░░░░░░░░░ 8,004 0.0% ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
The result matches the patched version, and is slightly cheaper on
top of it. sha2 is gone from the dependency tree entirely, so
there is no version to pin and no tag to keep synchronized as ZisK
evolves. Each call also dispatches straight to the precompile
without going through the upstream crate's outer API layer, which
trims a few instructions per invocation, small per call, but
visible in the totals on a tight hashing loop like this one.
Available wrappers
For the full list of zisklib function signatures grouped by curve and
operation, see the zisklib reference.
Summary
Patches and precompile wrappers both route expensive operations through
ZisK native precompiles and produce cost reductions. Patches
require no guest code changes but are version-pinned to a specific
upstream release. Precompile wrappers require updating each call site
but carry no version constraint and stay current as ZisK evolves.
Check the patches table first: if a fork exists, one line in
Cargo.toml is all it takes.
Next steps
With your guest program written and optimized, the next step is to prove it:
- Your first proof: drive the full proving pipeline end to end via CLI or SDK.