From 052e7cef977b336305c869fccbf24e1794b116ff Mon Sep 17 00:00:00 2001 From: Geoffrey Mureithi Date: Thu, 2 Jan 2025 23:17:06 +0300 Subject: [PATCH] feat: implement kzg data availability hints (#1887) * feat: kzg data availability hints Context: port of the Starknet OS to Rust. We need an implementation of the hints used by the Starknet OS in KZG commitments for data availability. This hint relies on private primitives in cairo-vm and must be implemented in this repository. * add: PR to CHANGELOG * fix: remove unnecessary clones * lint: cargo fmt --- .github/workflows/rust.yml | 2 +- CHANGELOG.md | 2 + Makefile | 9 +- .../cairo-0-kzg-da-hints/reduced_mul.cairo | 29 +++++ vm/Cargo.toml | 1 + .../builtin_hint_processor_definition.rs | 8 ++ .../builtin_hint_processor/kzg_da/mod.rs | 108 ++++++++++++++++++ .../builtin_hint_processor/mod.rs | 4 + vm/src/lib.rs | 1 + vm/src/tests/cairo_run_test.rs | 9 ++ 10 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 cairo_programs/cairo-0-kzg-da-hints/reduced_mul.cairo create mode 100644 vm/src/hint_processor/builtin_hint_processor/kzg_da/mod.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b58b6d84d8..6a76308239 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -323,7 +323,7 @@ jobs: strategy: fail-fast: false matrix: - special_features: ["", "extensive_hints", "mod_builtin", "cairo-0-secp-hints"] + special_features: ["", "extensive_hints", "mod_builtin", "cairo-0-secp-hints", "cairo-0-data-availability-hints"] target: [ test#1, test#2, test#3, test#4, test-no_std#1, test-no_std#2, test-no_std#3, test-no_std#4, test-wasm ] name: Run tests runs-on: ubuntu-22.04 diff --git a/CHANGELOG.md b/CHANGELOG.md index dcc5f7e6ec..fb3f2646d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: implement `kzg` data availability hints [#1887](https://github.com/lambdaclass/cairo-vm/pull/1887) + #### [2.0.0-rc3] - 2024-12-26 * chore: update cairo-lang dependencies to 2.10.0-rc.0 #[1901](https://github.com/lambdaclass/cairo-vm/pull/1901) diff --git a/Makefile b/Makefile index 7468bd0240..9b60105ab3 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,10 @@ SECP_CAIRO0_HINTS_DIR=cairo_programs/cairo-0-secp-hints-feature SECP_CAIRO0_HINTS_FILES:=$(wildcard $(SECP_CAIRO0_HINTS_DIR)/*.cairo) COMPILED_SECP_CAIRO0_HINTS:=$(patsubst $(SECP_CAIRO0_HINTS_DIR)/%.cairo, $(SECP_CAIRO0_HINTS_DIR)/%.json, $(SECP_CAIRO0_HINTS_FILES)) +KZG_DA_CAIRO0_HINTS_DIR=cairo_programs/cairo-0-kzg-da-hints +KZG_DA_CAIRO0_HINTS_FILES:=$(wildcard $(KZG_DA_CAIRO0_HINTS_DIR)/*.cairo) +COMPILED_KZG_DA_CAIRO0_HINTS:=$(patsubst $(KZG_DA_CAIRO0_HINTS_DIR)/%.cairo, $(KZG_DA_CAIRO0_HINTS_DIR)/%.json, $(KZG_DA_CAIRO0_HINTS_FILES)) + PRINT_TEST_DIR=cairo_programs/print_feature PRINT_TEST_FILES:=$(wildcard $(PRINT_TEST_DIR)/*.cairo) COMPILED_PRINT_TESTS:=$(patsubst $(PRINT_TEST_DIR)/%.cairo, $(PRINT_TEST_DIR)/%.json, $(PRINT_TEST_FILES)) @@ -243,7 +247,7 @@ run: check: cargo check -cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) $(COMPILED_PRINT_TESTS) $(COMPILED_MOD_BUILTIN_TESTS) $(COMPILED_SECP_CAIRO0_HINTS) +cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) $(COMPILED_PRINT_TESTS) $(COMPILED_MOD_BUILTIN_TESTS) $(COMPILED_SECP_CAIRO0_HINTS) $(COMPILED_KZG_DA_CAIRO0_HINTS) cairo_proof_programs: $(COMPILED_PROOF_TESTS) $(COMPILED_MOD_BUILTIN_PROOF_TESTS) cairo_bench_programs: $(COMPILED_BENCHES) cairo_1_test_contracts: $(CAIRO_1_COMPILED_CASM_CONTRACTS) @@ -268,7 +272,7 @@ test-wasm: cairo_proof_programs cairo_test_programs # NOTE: release mode is needed to avoid "too many locals" error wasm-pack test --release --node vm --no-default-features test-extensive_hints: cairo_proof_programs cairo_test_programs - $(TEST_COMMAND) --workspace --features "test_utils, cairo-1-hints, cairo-0-secp-hints, extensive_hints" + $(TEST_COMMAND) --workspace --features "test_utils, cairo-1-hints, cairo-0-secp-hints, cairo-0-data-availability-hints, extensive_hints" check-fmt: cargo fmt --all -- --check @@ -349,6 +353,7 @@ clean: rm -f $(BENCH_DIR)/*.json rm -f $(BAD_TEST_DIR)/*.json rm -f $(SECP_CAIRO0_HINTS_DIR)/*.json + rm -f $(KZG_DA_CAIRO0_HINTS_DIR)/*.json rm -f $(PRINT_TEST_DIR)/*.json rm -f $(CAIRO_1_CONTRACTS_TEST_DIR)/*.sierra rm -f $(CAIRO_1_CONTRACTS_TEST_DIR)/*.casm diff --git a/cairo_programs/cairo-0-kzg-da-hints/reduced_mul.cairo b/cairo_programs/cairo-0-kzg-da-hints/reduced_mul.cairo new file mode 100644 index 0000000000..8f54d7d0cb --- /dev/null +++ b/cairo_programs/cairo-0-kzg-da-hints/reduced_mul.cairo @@ -0,0 +1,29 @@ +%builtins range_check + +from starkware.starknet.core.os.data_availability.bls_field import reduced_mul, BigInt3 + +func main{range_check_ptr: felt}() { + let x = BigInt3(0, 0, 0); + let y = BigInt3(1, 1, 1); + + let res = reduced_mul(x, y); + + assert res = BigInt3(0, 0, 0); + + let x = BigInt3(100, 99, 98); + let y = BigInt3(10, 9, 8); + + let res = reduced_mul(x, y); + + assert res = BigInt3( + 49091481911800146991175221, 43711329369885800715738617, 405132241597509509195407 + ); + + let x = BigInt3(47503316700827173496989353, 17218105161352860131668522, 527908748911931938599018); + let y = BigInt3(50964737623371959432443726, 60451660835701602854498663, 5043009036652075489876599); + + let res = reduced_mul(x, y); + assert res = BigInt3(43476011663489831917914902, 15057238271740518603165849, 1923992965848504555868221); + + return (); +} diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 3437a30034..a01a0ce2c0 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -30,6 +30,7 @@ cairo-1-hints = [ tracer = [] mod_builtin = [] cairo-0-secp-hints = [] +cairo-0-data-availability-hints = [] # Note that these features are not retro-compatible with the cairo Python VM. test_utils = ["std", "dep:arbitrary", "starknet-types-core/arbitrary", "starknet-types-core/std"] # This feature will reference every test-oriented feature diff --git a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index 15c8318b15..f5ae7a96c4 100644 --- a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -966,6 +966,14 @@ impl HintProcessorLogic for BuiltinHintProcessor { &hint_data.ap_tracking, constants, ), + #[cfg(feature = "cairo-0-data-availability-hints")] + super::kzg_da::WRITE_DIVMOD_SEGMENT => super::kzg_da::write_div_mod_segment( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + constants, + ), code => Err(HintError::UnknownHint(code.to_string().into_boxed_str())), } diff --git a/vm/src/hint_processor/builtin_hint_processor/kzg_da/mod.rs b/vm/src/hint_processor/builtin_hint_processor/kzg_da/mod.rs new file mode 100644 index 0000000000..8da7274056 --- /dev/null +++ b/vm/src/hint_processor/builtin_hint_processor/kzg_da/mod.rs @@ -0,0 +1,108 @@ +use core::str::FromStr; + +use super::{ + hint_utils::get_relocatable_from_var_name, + secp::{bigint_utils::BigInt3, secp_utils::SECP_P}, +}; +use crate::{ + hint_processor::hint_processor_definition::HintReference, + serde::deserialize_program::ApTracking, + types::relocatable::MaybeRelocatable, + vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, + Felt252, +}; +use crate::{ + stdlib::{collections::HashMap, ops::Deref, prelude::*}, + types::exec_scope::ExecutionScopes, +}; +use lazy_static::lazy_static; +use num_bigint::BigInt; +use num_integer::Integer; +use num_traits::FromPrimitive; +use num_traits::Zero; + +lazy_static! { + static ref BLS_BASE: BigInt = BigInt::from_u64(2).unwrap().pow(86); + static ref BLS_PRIME: BigInt = BigInt::from_str( + "52435875175126190479447740508185965837690552500527637822603658699938581184513" + ) + .unwrap(); +} +pub const WRITE_DIVMOD_SEGMENT: &str = r#"from starkware.starknet.core.os.data_availability.bls_utils import BLS_PRIME, pack, split + +a = pack(ids.a, PRIME) +b = pack(ids.b, PRIME) + +q, r = divmod(a * b, BLS_PRIME) + +# By the assumption: |a|, |b| < 2**104 * ((2**86) ** 2 + 2**86 + 1) < 2**276.001. +# Therefore |q| <= |ab| / BLS_PRIME < 2**299. +# Hence the absolute value of the high limb of split(q) < 2**127. +segments.write_arg(ids.q.address_, split(q)) +segments.write_arg(ids.res.address_, split(r))"#; + +pub fn write_div_mod_segment( + vm: &mut VirtualMachine, + _exec_scopes: &mut ExecutionScopes, + ids_data: &HashMap, + ap_tracking: &ApTracking, + _constants: &HashMap, +) -> Result<(), HintError> { + let a = bls_pack( + &BigInt3::from_var_name("a", vm, ids_data, ap_tracking)?, + &SECP_P, + ); + let b = bls_pack( + &BigInt3::from_var_name("b", vm, ids_data, ap_tracking)?, + &SECP_P, + ); + let (q, r) = (a * b).div_mod_floor(&BLS_PRIME); + let q_reloc = get_relocatable_from_var_name("q", vm, ids_data, ap_tracking)?; + let res_reloc = get_relocatable_from_var_name("res", vm, ids_data, ap_tracking)?; + + let q_arg: Vec = bls_split(q) + .into_iter() + .map(|ref n| Felt252::from(n).into()) + .collect::>(); + let res_arg: Vec = bls_split(r) + .into_iter() + .map(|ref n| Felt252::from(n).into()) + .collect::>(); + vm.write_arg(q_reloc, &q_arg).map_err(HintError::Memory)?; + vm.write_arg(res_reloc, &res_arg) + .map_err(HintError::Memory)?; + Ok(()) +} + +fn bls_split(mut num: BigInt) -> Vec { + use num_traits::Signed; + let mut a = Vec::new(); + for _ in 0..2 { + let residue = &num % BLS_BASE.deref(); + num /= BLS_BASE.deref(); + a.push(residue); + } + assert!(num.abs() < BigInt::from_u128(1 << 127).unwrap()); + a.push(num); + a +} + +fn as_int(value: BigInt, prime: &BigInt) -> BigInt { + let half_prime = prime / 2u32; + if value > half_prime { + value - prime + } else { + value + } +} + +fn bls_pack(z: &BigInt3, prime: &BigInt) -> BigInt { + let limbs = &z.limbs; + limbs + .iter() + .enumerate() + .fold(BigInt::zero(), |acc, (i, limb)| { + let limb_as_int = as_int(limb.to_bigint(), prime); + acc + limb_as_int * &BLS_BASE.pow(i as u32) + }) +} diff --git a/vm/src/hint_processor/builtin_hint_processor/mod.rs b/vm/src/hint_processor/builtin_hint_processor/mod.rs index dd647ca944..497c556913 100644 --- a/vm/src/hint_processor/builtin_hint_processor/mod.rs +++ b/vm/src/hint_processor/builtin_hint_processor/mod.rs @@ -38,3 +38,7 @@ pub mod uint384_extension; pub mod uint_utils; pub mod usort; pub mod vrf; + +#[cfg(feature = "cairo-0-data-availability-hints")] +#[cfg_attr(docsrs, doc(cfg(feature = "cairo-0-data-availability-hints")))] +pub mod kzg_da; diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 90545d0f47..22798e3f97 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -9,6 +9,7 @@ //! - implementations of [`arbitrary::Arbitrary`](https://docs.rs/arbitrary/latest/arbitrary/) for some structs. //! - `cairo-1-hints`: Enable hints that were introduced in Cairo 1. Not enabled by default. //! - `cairo-0-secp-hints`: Enable secp hints that were introduced in Cairo 0. Not enabled by default. +//! - `cairo-0-data-availability-hints`: Enable data availability hints that were introduced in Cairo 0. Not enabled by default. #![cfg_attr(docsrs, feature(doc_cfg))] #![deny(warnings)] diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index f33ed945de..d847852929 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1308,3 +1308,12 @@ fn cairo_run_secp_cairo0_ec_mul_by_uint256() { ); run_program_simple(program_data.as_slice()); } + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +#[cfg(feature = "cairo-0-data-availability-hints")] +fn cairo_run_data_availability_reduced_mul() { + let program_data = + include_bytes!("../../../cairo_programs/cairo-0-kzg-da-hints/reduced_mul.json"); + run_program_simple(program_data.as_slice()); +}