Skip to main content

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.

ApproachHow it worksTrade-off
PatchesSwap 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 wrappersReplace 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.

bash
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:

Cargo.toml
[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.

Apply the patch either way

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:

bash
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

CrateRepoCrate versionZisk versions
sha2zisk-patch-hashes0.10.90.11.00.15.0, 0.16.0+
sha3zisk-patch-hashes0.10.80.11.00.15.0, 0.16.0+
tiny-keccakzisk-patch-tiny-keccak2.0.20.15.0, 0.16.0+

Elliptic curves

CrateRepoCrate versionZisk versions
k256zisk-patch-elliptic-curves0.13.40.11.00.15.0, 0.17.0
p256zisk-patch-elliptic-curves0.13.40.11.00.15.0, 0.17.0
substrate-bnzisk-patch-bn0.6.00.11.00.15.0, 0.17.0
blstzisk-patch-blst0.3.150.15.0, 0.17.0

Big-integer arithmetic

CrateRepoCrate versionZisk versions
ruintzisk-patch-ruint1.17.0, 1.17.20.13.1, 0.15.0, 0.17.0
aurora-engine-modexpzisk-patch-modexp1.2.00.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.

common/Cargo.toml
[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:

common/src/lib.rs
/// 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:

guest/src/zisklib.rs
#![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:

guest/Cargo.toml
[[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:

bash
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: