From 9407adb6f41d48a9a683ab2a6da270e12fb52aff Mon Sep 17 00:00:00 2001 From: Rej Ect <99460023+rejected-l@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:29:59 +0200 Subject: [PATCH 01/32] ci: bump actions/checkout to v5 (#338) --- .github/workflows/CI.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6b5a3440..a7a692c4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,7 +15,7 @@ jobs: timeout-minutes: 60 runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run rustfmt run: cargo fmt --all -- --check @@ -24,7 +24,7 @@ jobs: timeout-minutes: 60 runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run clippy (no guests) run: cargo clippy --workspace --exclude header-chain-circuit --exclude final-spv-circuit @@ -34,7 +34,7 @@ jobs: timeout-minutes: 60 runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build guests run: | REPR_GUEST_BUILD=1 BITCOIN_NETWORK=mainnet cargo build -p header-chain-circuit --release @@ -46,7 +46,7 @@ jobs: timeout-minutes: 60 runs-on: self-hosted steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Cache Build Artifacts uses: actions/cache@v4 @@ -79,7 +79,7 @@ jobs: hash::blake3::tests::test_blake3_randominputs hash::blake3::tests::test_blake3_randominputs_multipleof64bytes steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Cache Build Artifacts uses: actions/cache@v4 @@ -110,7 +110,7 @@ jobs: # needs: build # runs-on: self-hosted # steps: -# - uses: actions/checkout@v4 +# - uses: actions/checkout@v5 # # - name: Cache Build Artifacts # uses: actions/cache@v4 From 94d596d01b1c74fa4755cbd5f8185fcf5413ab04 Mon Sep 17 00:00:00 2001 From: lynndell2010 <30288722+lynndell2010@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:54:21 +0800 Subject: [PATCH 02/32] fix #348 (#349) fix #348 The bigint multiplication can be optimized Co-authored-by: lynndell | bitlayer --- bitvm/src/bigint/mul.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/bitvm/src/bigint/mul.rs b/bitvm/src/bigint/mul.rs index 6a7a3d54..51f2cde0 100644 --- a/bitvm/src/bigint/mul.rs +++ b/bitvm/src/bigint/mul.rs @@ -7,18 +7,15 @@ impl BigIntImpl { script! { { Self::convert_to_be_bits_toaltstack() } - { push_to_stack(0,Self::N_LIMBS as usize) } - - OP_FROMALTSTACK OP_IF - { Self::copy(1) } - { Self::add(1, 0) } + { Self::copy(0) } + OP_ELSE + { push_to_stack(0, Self::N_LIMBS as usize)} OP_ENDIF for _ in 1..N_BITS - 1 { - { Self::roll(1) } - { Self::double(0) } + { Self::double(1) } { Self::roll(1) } OP_FROMALTSTACK OP_IF @@ -27,8 +24,7 @@ impl BigIntImpl { OP_ENDIF } - { Self::roll(1) } - { Self::double(0) } + { Self::double(1) } OP_FROMALTSTACK OP_IF { Self::add(1, 0) } From c3b0e6604c535b5572857678f9d4dd3a9c33a3ac Mon Sep 17 00:00:00 2001 From: lynndell2010 <30288722+lynndell2010@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:54:39 +0800 Subject: [PATCH 03/32] fix #350 (#351) fix #350 MSM chunk-count formula incorrect Co-authored-by: lynndell | bitlayer --- bitvm/src/chunk/g16_runner_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitvm/src/chunk/g16_runner_utils.rs b/bitvm/src/chunk/g16_runner_utils.rs index 0a08263f..0cb88408 100644 --- a/bitvm/src/chunk/g16_runner_utils.rs +++ b/bitvm/src/chunk/g16_runner_utils.rs @@ -293,7 +293,7 @@ pub(crate) fn wrap_hint_msm( pub_vky: Vec, ) -> Vec { let num_chunks_per_scalar = - (Fr::N_BITS + WINDOW_G1_MSM - 1) / (WINDOW_G1_MSM * BATCH_SIZE_PER_CHUNK); + (Fr::N_BITS + WINDOW_G1_MSM * BATCH_SIZE_PER_CHUNK - 1) / (WINDOW_G1_MSM * BATCH_SIZE_PER_CHUNK); let hint_scalars: Vec> = scalars .iter() From cc571225d194832a6e9ea032754b3fe175a415b8 Mon Sep 17 00:00:00 2001 From: lynndell2010 <30288722+lynndell2010@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:54:50 +0800 Subject: [PATCH 04/32] fix #363 Insufficient validation of generated hints (#364) fix #363 Insufficient validation of generated hints Co-authored-by: lynndell | bitlayer --- bitvm/src/chunk/api_runtime_utils.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bitvm/src/chunk/api_runtime_utils.rs b/bitvm/src/chunk/api_runtime_utils.rs index c5c4f7d6..dadc3188 100644 --- a/bitvm/src/chunk/api_runtime_utils.rs +++ b/bitvm/src/chunk/api_runtime_utils.rs @@ -418,6 +418,10 @@ fn utils_execute_chunked_g16( println!("final {:?}", segments[i].scr_type); panic!(); } + if exec_result.remaining_script != "OP_PUSHNUM_1" && exec_result.remaining_script != "" { + println!("Script terminated early {:?} {:?}", exec_result.remaining_script, segments[i].scr_type); + panic!(); + } } else { println!( "disprove script {}: tapindex {}, {:?}", From d7f53c28c983cf7fcee9041e774309770df4fad9 Mon Sep 17 00:00:00 2001 From: AndreW0ng <58464497+wz14@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:59:46 +0800 Subject: [PATCH 05/32] add validation of num of mocked segments (#374) --- bitvm/src/chunk/api.rs | 4 ++-- bitvm/src/chunk/api_compiletime_utils.rs | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/bitvm/src/chunk/api.rs b/bitvm/src/chunk/api.rs index dcb9b72c..bafbb8a2 100644 --- a/bitvm/src/chunk/api.rs +++ b/bitvm/src/chunk/api.rs @@ -21,8 +21,8 @@ use super::wrap_hasher::BLAKE3_HASH_LENGTH; pub const NUM_PUBS: usize = 1; pub const NUM_U256: usize = 14; pub const NUM_HASH: usize = 363; -const VALIDATING_TAPS: usize = 1; -const HASHING_TAPS: usize = NUM_HASH; +pub const VALIDATING_TAPS: usize = 1; +pub const HASHING_TAPS: usize = NUM_HASH; pub const NUM_TAPS: usize = HASHING_TAPS + VALIDATING_TAPS; pub type PublicInputs = [ark_bn254::Fr; NUM_PUBS]; diff --git a/bitvm/src/chunk/api_compiletime_utils.rs b/bitvm/src/chunk/api_compiletime_utils.rs index 4d59d1c4..b32e4a68 100644 --- a/bitvm/src/chunk/api_compiletime_utils.rs +++ b/bitvm/src/chunk/api_compiletime_utils.rs @@ -5,7 +5,7 @@ use crate::bn254::ell_coeffs::AffinePairing; use crate::bn254::ell_coeffs::BnAffinePairing; use crate::bn254::fp254impl::Fp254Impl; use crate::bn254::fq::Fq; -use crate::chunk::api::{NUM_PUBS, NUM_TAPS}; +use crate::chunk::api::{NUM_HASH, NUM_PUBS, NUM_TAPS, NUM_U256, VALIDATING_TAPS}; use crate::chunk::elements::ElementType; use crate::treepp; use ark_bn254::Bn254; @@ -175,6 +175,10 @@ fn generate_segments_using_mock_proof(vk: Vkey, skip_evaluation: bool) -> Vec Date: Thu, 9 Oct 2025 15:59:53 +0800 Subject: [PATCH 06/32] [Zellic Audit] Hash Bug (Mark 3 09.07) (#316) * add verify_bigint_on_stack for bigint * change name and remove unreachable script --- bitvm/src/bigint/std.rs | 113 ++++++++++++++++++++++++++++++++++++++- bitvm/src/hash/blake3.rs | 25 +++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) diff --git a/bitvm/src/bigint/std.rs b/bitvm/src/bigint/std.rs index 106fe205..0935a31f 100644 --- a/bitvm/src/bigint/std.rs +++ b/bitvm/src/bigint/std.rs @@ -488,6 +488,81 @@ impl BigIntImpl { ) } } + + /// Validate that the BigInt on stack has valid limb values + /// + /// This function checks that each limb in the BigInt representation + /// does not exceed the maximum value allowed for the given LIMB_SIZE + /// and ensures no limb is negative. + /// + /// ## Stack Effects: + /// - Input: BigInt limbs (MSB first, LSB on top) + /// - Output: Same BigInt limbs + validation result (1 if valid, 0 if invalid) + /// + /// ## Validation Rules: + /// - Each limb must be >= 0 (no negative values) + /// - Each limb must be < (1 << LIMB_SIZE) + /// - The head limb must be < (1 << HEAD_OFFSET) where HEAD_OFFSET is the remaining bits + /// + /// ## Note: + /// This function is expensive in terms of script size and should be used carefully + pub fn is_valid_bigint_with_limb_size(limb_size: u32) -> Script { + let n_limbs = N_BITS.div_ceil(limb_size); + let head = N_BITS - (n_limbs - 1) * limb_size; + script! { + // Start with validation result = 1 (valid) + 1 + + // Validate each regular limb (except the head) + for i in 0..n_limbs - 1 { + // Pick the limb from stack (limbs are ordered MSB first, LSB on top) + { i + 1 } OP_PICK + + // Check if limb >= 0 (not negative) + OP_DUP + 0 OP_GREATERTHANOREQUAL + + // Check if limb < (1 << LIMB_SIZE) + OP_SWAP + { 1 << limb_size } + OP_LESSTHAN + + // AND the boolean results + OP_BOOLAND OP_BOOLAND + } + + // Validate the head limb (MSB) separately as it may have fewer bits + { n_limbs } OP_PICK + + // Check if head limb >= 0 (not negative) + OP_DUP + 0 OP_GREATERTHANOREQUAL + + // Check if head limb < (1 << HEAD) + OP_SWAP + { 1 << head } + OP_LESSTHAN + + // AND the boolean results + OP_BOOLAND OP_BOOLAND + } + } + + /// Validate BigInt and fail script if invalid + pub fn verify_bigint_on_stack_with_limb_size(limb_size: u32) -> Script { + script! { + { Self::is_valid_bigint_with_limb_size(limb_size) } + OP_VERIFY + } + } + + pub fn verify_bigint_on_stack() -> Script { + Self::verify_bigint_on_stack_with_limb_size(Self::LIMB_SIZE) + } + + pub fn is_valid_bigint() -> Script { + Self::is_valid_bigint_with_limb_size(Self::LIMB_SIZE) + } } /// Extracts a window of bits from a u32 limb on top of stack @@ -538,13 +613,49 @@ pub fn extract_digits(start_index: u32, window: u32) -> Script { #[cfg(test)] mod test { use crate::bigint::std::extract_digits; + use crate::bigint::U256; use crate::bigint::{BigIntImpl, U254}; - use crate::run; + use crate::{execute_script, run}; use bitcoin_script::script; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; + #[test] + fn test_valid_bigint() { + let invalid_bigint = ( + script! { + {0} {0} {0} {0} {0} {0} {0} {0} {-1} + }, + false, + ); + + let invalid_bigint2 = ( + script! { + {1 << U256::HEAD} {0} {0} {0} {0} {0} {0} {0} {0} + }, + false, + ); + + let valid_bigint = ( + script! { + {0x1234} {0x1234} {0x1234} {0x1234} {0x1234} {0x1234} {0x1234} {0x1234} {0x1234} + }, + true, + ); + + for (bigint, expected) in [invalid_bigint, invalid_bigint2, valid_bigint] { + let res = execute_script(script! { + {bigint.clone()} + { U256::is_valid_bigint() } + OP_TOALTSTACK + for _ in 0..9 { OP_DROP } + OP_FROMALTSTACK + }); + assert_eq!(res.success, expected); + } + } + #[test] fn test_zip() { const N_BITS: u32 = 1450; diff --git a/bitvm/src/hash/blake3.rs b/bitvm/src/hash/blake3.rs index 8b703276..36ed346c 100644 --- a/bitvm/src/hash/blake3.rs +++ b/bitvm/src/hash/blake3.rs @@ -93,6 +93,7 @@ fn blake3( // unpack the compact form of message stack.custom( script!( + {U256::verify_bigint_on_stack_with_limb_size(limb_len as u32)} {U256::transform_limbsize(limb_len as u32, 4)} for _ in 0..64{ OP_TOALTSTACK @@ -106,6 +107,7 @@ fn blake3( stack.custom( script!( + {U256::verify_bigint_on_stack_with_limb_size(limb_len as u32)} {U256::transform_limbsize(limb_len as u32, 4)} for _ in 0..64{ OP_FROMALTSTACK @@ -374,6 +376,8 @@ pub fn blake3_verify_output_script(expected_output: [u8; 32]) -> Script { #[cfg(test)] mod tests { use super::*; + use crate::bn254::fp254impl::Fp254Impl; + use crate::bn254::fq::Fq; use crate::{execute_script, execute_script_buf_without_stack_limit}; use bitcoin::ScriptBuf; use bitcoin_script_stack::optimizer; @@ -607,4 +611,25 @@ mod tests { fn test_maximum_alstack_element_calculation_with_all_limbs() { test_maximum_alstack_element_calculation_with_limbs(&ALL_POSSIBLE_LIMB_LENGTHS); } + + #[test] + fn test_failure_on_invalid_input() { + let zero = script! { + {0} {0} {0} {0} {0} {0} {0} {0} {0} + }; + let fake_zero = script! { + {0} {0} {0} {0} {0} {0} {0} {0} {-1} + }; + let res = execute_script(script! { + // fake_zero has same hash as zero + {zero.clone()} {zero.clone()} {blake3_compute_script(64)} + for _ in 0..64 { + OP_TOALTSTACK + } + {zero.clone()} {fake_zero.clone()} {blake3_compute_script(64)} + }); + println! {"{:?} {:?} {:?} {:?}", res.success, res.final_stack, res.stats, res.last_opcode}; + assert_eq!(res.success, false); + assert_eq!(res.last_opcode, Some(bitcoin::opcodes::all::OP_VERIFY)); + } } From a2195dc4f162c976ed1cc7d1c9bc119505d32bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eevket=20Onur=20YILMAZ?= Date: Thu, 9 Oct 2025 11:04:08 +0300 Subject: [PATCH 07/32] Zellic Audit G1 fixes (#334) * zellic g1 fixes * test for read from stack * deleted hinted_x_from_eval_point and hinted_y_from_eval_point functions * fmt * used fq::is_zero instead of comparing bytes to zero * changed & to && --- bitvm/src/bn254/g1.rs | 153 +++++++++-------------------------------- bitvm/src/bn254/g2.rs | 21 +++--- bitvm/src/bn254/msm.rs | 2 +- 3 files changed, 46 insertions(+), 130 deletions(-) diff --git a/bitvm/src/bn254/g1.rs b/bitvm/src/bn254/g1.rs index 7b8b3769..27d94474 100644 --- a/bitvm/src/bn254/g1.rs +++ b/bitvm/src/bn254/g1.rs @@ -1,3 +1,4 @@ +use super::fq2::Fq2; use crate::bn254::fp254impl::Fp254Impl; use crate::bn254::fq::Fq; use crate::bn254::utils::Hint; @@ -5,8 +6,7 @@ use crate::treepp::{script, Script}; use ark_ec::AffineRepr; use ark_ff::{AdditiveGroup, Field}; use num_bigint::BigUint; - -use super::fq2::Fq2; +use num_traits::Zero; pub struct G1Affine; @@ -132,13 +132,6 @@ impl G1Affine { (script, hints) } - pub fn push_zero() -> Script { - script! { - { Fq::push_zero() } - { Fq::push_zero() } - } - } - pub fn push(element: ark_bn254::G1Affine) -> Script { script! { { Fq::push_u32_le(&BigUint::from(element.x).to_u32_digits()) } @@ -148,12 +141,17 @@ impl G1Affine { pub fn read_from_stack(witness: Vec>) -> ark_bn254::G1Affine { assert_eq!(witness.len() as u32, Fq::N_LIMBS * 2); - let x = Fq::read_u32_le(witness[0..Fq::N_LIMBS as usize].to_vec()); - let y = Fq::read_u32_le(witness[Fq::N_LIMBS as usize..2 * Fq::N_LIMBS as usize].to_vec()); + let x: ark_bn254::Fq = + BigUint::from_slice(&Fq::read_u32_le(witness[0..Fq::N_LIMBS as usize].to_vec())).into(); + let y: ark_bn254::Fq = BigUint::from_slice(&Fq::read_u32_le( + witness[Fq::N_LIMBS as usize..2 * Fq::N_LIMBS as usize].to_vec(), + )) + .into(); + let is_inf = x.is_zero() && y.is_zero(); ark_bn254::G1Affine { - x: BigUint::from_slice(&x).into(), - y: BigUint::from_slice(&y).into(), - infinity: false, + x, + y, + infinity: is_inf, } } @@ -346,18 +344,17 @@ impl G1Affine { let (y_sq, y_sq_hint) = Fq::hinted_square(y); let mut hints = Vec::new(); + hints.extend(y_sq_hint); hints.extend(x_sq_hint); hints.extend(x_cu_hint); - hints.extend(y_sq_hint); let scr = script! { + { y_sq } { Fq::copy(1) } { x_sq } { Fq::roll(2) } { x_cu } { Fq::push_hex("3") } { Fq::add(1, 0) } - { Fq::roll(1) } - { y_sq } { Fq::equal(1, 0) } }; (scr, hints) @@ -408,53 +405,6 @@ impl G1Affine { } } -/// input of func (params): -/// p.x, p.y -/// Input Hints On Stack -/// tmul hints, p.y_inverse -/// output on stack: -/// x' = -p.x / p.y -pub fn hinted_x_from_eval_point( - p: ark_bn254::G1Affine, - py_inv: ark_bn254::Fq, -) -> (Script, Vec) { - let mut hints = Vec::new(); - - let (hinted_script1, hint1) = Fq::hinted_mul(1, p.y, 0, py_inv); - let (hinted_script2, hint2) = Fq::hinted_mul(1, py_inv, 0, -p.x); - let script = script! { // Stack: [hints, pyd, px, py] - {Fq::copy(2)} // Stack: [hints, pyd, px, py, pyd] - {hinted_script1} - {Fq::push_one()} - {Fq::equalverify(1, 0)} // Stack: [hints, pyd, px] - {Fq::neg(0)} // Stack: [hints, pyd, -px] - {hinted_script2} - }; - hints.extend(hint1); - hints.extend(hint2); - (script, hints) -} - -/// input of func (params): -/// p.y -/// Input Hints On Stack -/// tmul hints, p.y_inverse -/// output on stack: -/// [] -pub fn hinted_y_from_eval_point(py: ark_bn254::Fq, py_inv: ark_bn254::Fq) -> (Script, Vec) { - let mut hints = Vec::new(); - - let (hinted_script1, hint1) = Fq::hinted_mul(1, py_inv, 0, py); - let script = script! {// [hints,..., pyd_calc, py] - {hinted_script1} - {Fq::push_one()} - {Fq::equalverify(1,0)} - }; - hints.extend(hint1); - - (script, hints) -} - /// input of func (params): /// p.x, p.y /// Input Hints On Stack @@ -466,20 +416,21 @@ pub fn hinted_from_eval_point(p: ark_bn254::G1Affine) -> (Script, Vec) { let mut hints = Vec::new(); let py_inv = p.y().unwrap().inverse().unwrap(); - - let (hinted_script1, hint1) = hinted_y_from_eval_point(p.y, py_inv); - let (hinted_script2, hint2) = hinted_x_from_eval_point(p, py_inv); + let (hinted_script1, hint1) = Fq::hinted_mul(1, p.y, 0, py_inv); + let (hinted_script2, hint2) = Fq::hinted_mul(1, py_inv, 0, -p.x); let script = script! { // [hints, yinv, x, y] {Fq::copy(2)} - {Fq::copy(1)} {hinted_script1} + {Fq::push_one()} + {Fq::equalverify(1,0)} - // [hints, yinv, x, y] - {Fq::copy(2)} + // [hints, yinv, x] + {Fq::copy(1)} {Fq::toaltstack()} + {Fq::neg(0)} {hinted_script2} {Fq::fromaltstack()} }; @@ -494,8 +445,8 @@ pub fn hinted_from_eval_points(p: ark_bn254::G1Affine) -> (Script, Vec) { let py_inv = p.y().unwrap().inverse().unwrap(); - let (hinted_script1, hint1) = hinted_y_from_eval_point(p.y, py_inv); - let (hinted_script2, hint2) = hinted_x_from_eval_point(p, py_inv); + let (hinted_script1, hint1) = Fq::hinted_mul(1, p.y, 0, py_inv); + let (hinted_script2, hint2) = Fq::hinted_mul(1, py_inv, 0, -p.x); let script = script! { // [yinv, hints,.., x, y] @@ -506,11 +457,15 @@ pub fn hinted_from_eval_points(p: ark_bn254::G1Affine) -> (Script, Vec) { {Fq2::fromaltstack()} // [hints, yinv, x, y] {Fq::copy(2)} - {Fq::copy(1)} + {hinted_script1} - // [hints, yinv, x, y] - {Fq::copy(2)} + {Fq::push_one()} + {Fq::equalverify(1,0)} + + // [hints, yinv, x] + {Fq::copy(1)} {Fq::toaltstack()} + {Fq::neg(0)} {hinted_script2} {Fq::fromaltstack()} }; @@ -528,7 +483,6 @@ mod test { use crate::bn254::fq::Fq; use crate::bn254::fq2::Fq2; use crate::bn254::g1::G1Affine; - use crate::bn254::g2::G2Affine; use super::*; use crate::{treepp::*, ExecuteInfo}; @@ -561,14 +515,14 @@ mod test { assert_eq!(a, recovered_a); - let b = ark_bn254::G2Affine::rand(&mut prng); + let b = ark_bn254::G1Affine::identity(); let script = script! { - {G2Affine::push(b)} + {G1Affine::push(b)} }; let res = execute_script(script); let witness = extract_witness_from_stack(res); - let recovered_b = G2Affine::read_from_stack(witness); + let recovered_b = G1Affine::read_from_stack(witness); assert_eq!(b, recovered_b); } @@ -865,45 +819,4 @@ mod test { let exec_result = execute_script(script); assert!(exec_result.success); } - - #[test] - fn test_hintedx_from_eval_point() { - let mut prng = ChaCha20Rng::seed_from_u64(0); - let p = ark_bn254::G1Affine::rand(&mut prng); - let (ell_by_constant_affine_script, hints) = - hinted_x_from_eval_point(p, p.y.inverse().unwrap()); - let script = script! { - for tmp in hints { - { tmp.push() } - } - { Fq::push_u32_le(&BigUint::from(p.y.inverse().unwrap()).to_u32_digits()) } - { Fq::push_u32_le(&BigUint::from(p.x).to_u32_digits()) } - { Fq::push_u32_le(&BigUint::from(p.y).to_u32_digits()) } - { ell_by_constant_affine_script.clone() } - { Fq::push_u32_le(&BigUint::from(-p.x / p.y).to_u32_digits()) } - {Fq::equalverify(1,0)} - OP_TRUE - }; - let exec_result = execute_script(script); - assert!(exec_result.success); - } - - #[test] - fn test_hintedy_from_eval_point() { - let mut prng = ChaCha20Rng::seed_from_u64(0); - let p = ark_bn254::G1Affine::rand(&mut prng); - let (ell_by_constant_affine_script, hints) = - hinted_y_from_eval_point(p.y, p.y.inverse().unwrap()); - let script = script! { - for tmp in hints { - { tmp.push() } - } - { Fq::push_u32_le(&BigUint::from(p.y.inverse().unwrap()).to_u32_digits()) } - { Fq::push_u32_le(&BigUint::from(p.y).to_u32_digits()) } - { ell_by_constant_affine_script.clone() } - OP_TRUE - }; - let exec_result = execute_script(script); - assert!(exec_result.success); - } } diff --git a/bitvm/src/bn254/g2.rs b/bitvm/src/bn254/g2.rs index 151f89a6..67079f3c 100644 --- a/bitvm/src/bn254/g2.rs +++ b/bitvm/src/bn254/g2.rs @@ -9,6 +9,7 @@ use crate::treepp::{script, Script}; use ark_ec::bn::BnConfig; use ark_ff::{AdditiveGroup, Field}; use num_bigint::BigUint; +use num_traits::Zero; use std::str::FromStr; pub struct G2Affine; @@ -90,11 +91,12 @@ impl G2Affine { let (y_sq, y_sq_hint) = Fq2::hinted_square(y); let mut hints = Vec::new(); + hints.extend(y_sq_hint); hints.extend(x_sq_hint); hints.extend(x_cu_hint); - hints.extend(y_sq_hint); let scr = script! { + { y_sq } { Fq2::copy(2) } { x_sq } { Fq2::roll(4) } @@ -102,8 +104,6 @@ impl G2Affine { { Fq::push_dec("19485874751759354771024239261021720505790618469301721065564631296452457478373") } { Fq::push_dec("266929791119991161246907387137283842545076965332900288569378510910307636690") } { Fq2::add(2, 0) } - { Fq2::roll(2) } - { y_sq } { Fq2::equal() } }; (scr, hints) @@ -122,10 +122,13 @@ impl G2Affine { let y = Fq2::read_from_stack( witness[2 * Fq::N_LIMBS as usize..4 * Fq::N_LIMBS as usize].to_vec(), ); + + let is_inf = x.is_zero() && y.is_zero(); + ark_bn254::G2Affine { x, y, - infinity: false, + infinity: is_inf, } } } @@ -770,7 +773,7 @@ mod test { use super::*; use crate::bn254::fq::Fq; use crate::bn254::fq2::Fq2; - use crate::bn254::g1::{hinted_from_eval_point, G1Affine}; + use crate::bn254::g1::hinted_from_eval_point; use crate::bn254::g2::G2Affine; use crate::{treepp::*, ExecuteInfo}; use ark_ff::AdditiveGroup; @@ -789,18 +792,18 @@ mod test { #[test] fn test_read_from_stack() { let mut prng = ChaCha20Rng::seed_from_u64(0); - let a = ark_bn254::G1Affine::rand(&mut prng); + let a = ark_bn254::G2Affine::rand(&mut prng); let script = script! { - {G1Affine::push(a)} + {G2Affine::push(a)} }; let res = execute_script(script); let witness = extract_witness_from_stack(res); - let recovered_a = G1Affine::read_from_stack(witness); + let recovered_a = G2Affine::read_from_stack(witness); assert_eq!(a, recovered_a); - let b = ark_bn254::G2Affine::rand(&mut prng); + let b = ark_bn254::G2Affine::identity(); let script = script! { {G2Affine::push(b)} }; diff --git a/bitvm/src/bn254/msm.rs b/bitvm/src/bn254/msm.rs index 3483e165..d4e3d00b 100644 --- a/bitvm/src/bn254/msm.rs +++ b/bitvm/src/bn254/msm.rs @@ -87,7 +87,7 @@ pub(crate) fn dfs_with_constant_mul( { G1Affine::push(p_mul[(mask + (1 << index)) as usize]) } OP_ELSE if mask == 0 { - { G1Affine::push_zero() } + { G1Affine::identity() } } else { { G1Affine::push(p_mul[mask as usize]) } } From 0b398a45b983c870bf93a3015faed7ab90e8a264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eevket=20Onur=20YILMAZ?= Date: Thu, 9 Oct 2025 11:04:56 +0300 Subject: [PATCH 08/32] [Zellic Audit] Tonelli Shanks and Compute_c_wi (#368) * removed unnecessary log asserts * removed unnecessary if branch in tonelli shanks * removed unused imports --- bitvm/src/groth16/constants.rs | 1 + bitvm/src/groth16/offchain_checker.rs | 23 ++++++----------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/bitvm/src/groth16/constants.rs b/bitvm/src/groth16/constants.rs index 752ccff0..6b2cdaa5 100644 --- a/bitvm/src/groth16/constants.rs +++ b/bitvm/src/groth16/constants.rs @@ -33,6 +33,7 @@ pub static K: LazyLock = LazyLock::new(|| (&*T + 1_u32) / 3_u32); pub static M: LazyLock = LazyLock::new(|| &*LAMBDA / &*R); pub const D: u32 = 3; pub static MM: LazyLock = LazyLock::new(|| &*M / D); +pub static MM_INV: LazyLock = LazyLock::new(|| MM.modinv(&(&*R * &*H)).unwrap()); pub static COFACTOR_CUBIC: LazyLock = LazyLock::new(|| 3_u32.pow(S - 1) * &*T); pub static W: LazyLock = LazyLock::new(|| { diff --git a/bitvm/src/groth16/offchain_checker.rs b/bitvm/src/groth16/offchain_checker.rs index 6c59be0f..29939a40 100644 --- a/bitvm/src/groth16/offchain_checker.rs +++ b/bitvm/src/groth16/offchain_checker.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case)] -use crate::groth16::constants::{COFACTOR_CUBIC, H, K, LAMBDA, MM, R, S, T, W}; -use ark_ff::{Field, One}; +use crate::groth16::constants::{COFACTOR_CUBIC, H, K, LAMBDA, MM_INV, R, S, T, W}; +use ark_ff::Field; use num_bigint::BigUint; use num_traits::ToPrimitive; @@ -56,7 +56,6 @@ fn tonelli_shanks_cubic( ) -> ark_bn254::Fq12 { let mut r = a.pow(t.to_u64_digits()); let e = 3_u32.pow(s - 1); - let exp = 3_u32.pow(s) * &t; // compute cubic root of (a^t)^-1, say h let (mut h, cc, mut c) = ( @@ -64,13 +63,9 @@ fn tonelli_shanks_cubic( c.pow([e as u64]), c.inverse().unwrap(), ); - for i in 1..(s as i32) { - let delta = (s as i32) - i - 1; - let d = if delta < 0 { - r.pow((&exp / 3_u32.pow((-delta) as u32)).to_u64_digits()) - } else { - r.pow([3_u32.pow(delta as u32).to_u64().unwrap()]) - }; + for i in 1..s { + let delta = s - i - 1; + let d = r.pow([3_u32.pow(delta as u32).to_u64().unwrap()]); if d == cc { (h, r) = (h * c, r * c.pow([3_u64])); } else if d == cc.pow([2_u64]) { @@ -116,23 +111,17 @@ pub fn compute_c_wi(f: ark_bn254::Fq12) -> (ark_bn254::Fq12, ark_bn254::Fq12) { // r-th root of f1, say f2 let r_inv = R.modinv(&H).unwrap(); - log_assert_ne!(r_inv, BigUint::one()); let f2 = f1.pow(r_inv.to_u64_digits()); - log_assert_ne!(f2, ark_bn254::Fq12::ONE); // m'-th root of f, say f3 - let mm_inv = MM.modinv(&(&*R * &*H)).unwrap(); - log_assert_ne!(mm_inv, BigUint::one()); - let f3 = f2.pow(mm_inv.to_u64_digits()); + let f3 = f2.pow(MM_INV.to_u64_digits()); log_assert_eq!( f3.pow(&*COFACTOR_CUBIC.to_u64_digits()), ark_bn254::Fq12::ONE ); - log_assert_ne!(f3, ark_bn254::Fq12::ONE); // d-th (cubic) root, say c let c = tonelli_shanks_cubic(f3, w, S, T.clone(), K.clone()); - log_assert_ne!(c, ark_bn254::Fq12::ONE); log_assert_eq!(c.pow(LAMBDA.to_u64_digits()), f * wi); (c, wi) From e277e89220180014d96a3eb9ec06e3afde301493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eevket=20Onur=20YILMAZ?= Date: Thu, 9 Oct 2025 11:05:04 +0300 Subject: [PATCH 09/32] [Zellic Audit] fix fq6_hinted_mul_keep_elements (#339) * fix fq6_hinted_mul_keep_elements * removed unused import * fmt --- bitvm/src/bn254/fq6.rs | 281 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 249 insertions(+), 32 deletions(-) diff --git a/bitvm/src/bn254/fq6.rs b/bitvm/src/bn254/fq6.rs index adc3c887..637c12a5 100644 --- a/bitvm/src/bn254/fq6.rs +++ b/bitvm/src/bn254/fq6.rs @@ -1,6 +1,5 @@ use crate::bn254::fp254impl::Fp254Impl; use crate::bn254::fq::Fq; -use crate::bn254::fq12::Fq12; use crate::bn254::fq2::Fq2; use crate::bn254::utils::Hint; use crate::treepp::{script, Script}; @@ -218,37 +217,10 @@ impl Fq6 { // Input: [A, B] // Output: [C] where C = A x B pub fn hinted_mul( - a_depth: u32, - a: ark_bn254::Fq6, - b_depth: u32, - b: ark_bn254::Fq6, - ) -> (Script, Vec) { - Self::hinted_mul_core(a_depth, a, b_depth, b, script! {}) - } - - // Input: [A, B] - // Output: [A, B, C] where C = A x B - pub fn hinted_mul_keep_elements( - a_depth: u32, - a: ark_bn254::Fq6, - b_depth: u32, - b: ark_bn254::Fq6, - ) -> (Script, Vec) { - let preserve_scr = script! { - {Fq6::toaltstack()} - {Fq12::copy(0)} - {Fq6::fromaltstack()} - }; - - Self::hinted_mul_core(a_depth, a, b_depth, b, preserve_scr) - } - - fn hinted_mul_core( mut a_depth: u32, mut a: ark_bn254::Fq6, mut b_depth: u32, mut b: ark_bn254::Fq6, - keep_elements_scr: Script, ) -> (Script, Vec) { // The degree-6 extension on BN254 Fq2 is under the polynomial y^3 - x - 9 // Toom-Cook-3 from https://eprint.iacr.org/2006/471.pdf @@ -306,8 +278,6 @@ impl Fq6 { // compute (a-b+c)(d-e+f) = P(-1) { hinted_script3 } - { keep_elements_scr } - // compute 2b { Fq2::roll(a_depth + 8) } { Fq2::double(0) } @@ -422,6 +392,184 @@ impl Fq6 { (script, hints) } + // Input: [A, B] + // Output: [A, B, C] where C = A x B + pub fn hinted_mul_keep_elements( + mut a_depth: u32, + mut a: ark_bn254::Fq6, + mut b_depth: u32, + mut b: ark_bn254::Fq6, + ) -> (Script, Vec) { + // The degree-6 extension on BN254 Fq2 is under the polynomial y^3 - x - 9 + // Toom-Cook-3 from https://eprint.iacr.org/2006/471.pdf + if a_depth < b_depth { + (a_depth, b_depth) = (b_depth, a_depth); + (a, b) = (b, a); + } + assert_ne!(a_depth, b_depth); + let mut hints = Vec::new(); + + let (hinted_script1, hint1) = Fq2::hinted_mul(2, a.c0, 0, b.c0); + let (hinted_script2, hint2) = Fq2::hinted_mul(6, a.c0 + a.c1 + a.c2, 2, b.c0 + b.c1 + b.c2); + let (hinted_script3, hint3) = Fq2::hinted_mul(4, a.c0 - a.c1 + a.c2, 2, b.c0 - b.c1 + b.c2); + let (hinted_script4, hint4) = Fq2::hinted_mul( + 2, + a.c0 + a.c1 + a.c1 + a.c2 + a.c2 + a.c2 + a.c2, + 0, + b.c0 + b.c1 + b.c1 + b.c2 + b.c2 + b.c2 + b.c2, + ); + let (hinted_script5, hint5) = Fq2::hinted_mul(2, a.c2, 0, b.c2); + + let script = script! { + // compute ad = P(0) + { Fq2::copy(a_depth + 4) } + { Fq2::copy(b_depth + 6) } + { hinted_script1 } + + // compute a+c + { Fq2::copy(a_depth + 6) } + { Fq2::copy(a_depth + 4) } + { Fq2::add(2, 0) } + + // compute a+b+c, a-b+c + { Fq2::copy(0) } + { Fq2::copy(a_depth + 8) } + { Fq2::copy(0) } + { Fq2::add(4, 0) } + { Fq2::sub(4, 2) } + + // compute d+f + { Fq2::copy(b_depth + 10) } + { Fq2::copy(b_depth + 8) } + { Fq2::add(2, 0) } + + // compute d+e+f, d-e+f + { Fq2::copy(0) } + { Fq2::copy(b_depth + 12) } + { Fq2::copy(0) } + { Fq2::add(4, 0) } + { Fq2::sub(4, 2) } + + // compute (a+b+c)(d+e+f) = P(1) + { hinted_script2 } + + // compute (a-b+c)(d-e+f) = P(-1) + { hinted_script3 } + + // compute 2b + { Fq2::copy(a_depth + 8) } + { Fq2::double(0) } + + // compute 4c + { Fq2::copy(a_depth + 8) } + { Fq2::double(0) } + { Fq2::double(0) } + // compute a+2b+4c + { Fq2::add(2, 0) } + { Fq2::copy(a_depth + 12) } + { Fq2::add(2, 0) } + + // compute 2e + { Fq2::copy(b_depth + 10) } + { Fq2::double(0) } + + // compute 4f + { Fq2::copy(b_depth + 10) } + { Fq2::double(0) } + { Fq2::double(0) } + + // compute d+2e+4f + { Fq2::add(2, 0) } + { Fq2::copy(b_depth + 14) } + { Fq2::add(2, 0) } + + // compute (a+2b+4c)(d+2e+4f) = P(2) + { hinted_script4 } + + // compute cf = P(inf) + { Fq2::copy(a_depth + 8) } + { Fq2::copy(b_depth + 10) } + { hinted_script5 } + + // // at this point, we have v_0, v_1, v_2, v_3, v_4 + + // compute 3v_0 + { Fq2::triple(8) } + + // compute 3v_1 + { Fq2::triple(8) } + + // compute 6v_4 + { Fq2::triple(4) } + { Fq2::double(0) } + + // compute x = 3v_0 - 3v_1 - v_2 + v_3 - 12v_4 + { Fq2::copy(4) } + { Fq2::copy(4) } + { Fq2::sub(2, 0) } + { Fq2::copy(10) } + { Fq2::sub(2, 0) } + { Fq2::copy(8) } + { Fq2::add(2, 0) } + { Fq2::copy(2) } + { Fq2::double(0) } + { Fq2::sub(2, 0) } + + // compute c_0 = 6v_0 + \beta x + { Fq6::mul_fq2_by_nonresidue() } + { Fq2::copy(6) } + { Fq2::double(0) } + { Fq2::add(2, 0) } + + // compute y = -3v_0 + 6v_1 - 2v_2 - v_3 + 12v_4 + { Fq2::copy(4) } + { Fq2::double(0) } + { Fq2::copy(8) } + { Fq2::sub(2, 0) } + { Fq2::copy(12) } + { Fq2::double(0) } + { Fq2::sub(2, 0) } + { Fq2::roll(10) } + { Fq2::sub(2, 0) } + { Fq2::copy(4) } + { Fq2::double(0) } + { Fq2::add(2, 0) } + + // compute c_1 = y + \beta 6v_4 + { Fq2::copy(4) } + { Fq6::mul_fq2_by_nonresidue() } + { Fq2::add(2, 0) } + + // compute c_2 = 3v_1 - 6v_0 + 3v_2 - 6v_4 + { Fq2::roll(6) } + { Fq2::roll(8) } + { Fq2::double(0) } + { Fq2::sub(2, 0) } + { Fq2::roll(8) } + { Fq2::triple(0) } + { Fq2::add(2, 0) } + { Fq2::sub(0, 6) } + + // divide by 6 + { Fq2::roll(4) } + { Fq2::div2() } + { Fq2::div3() } + { Fq2::roll(4) } + { Fq2::div2() } + { Fq2::div3() } + { Fq2::roll(4) } + { Fq2::div2() } + { Fq2::div3() } + }; + hints.extend(hint1); + hints.extend(hint2); + hints.extend(hint3); + hints.extend(hint4); + hints.extend(hint5); + + (script, hints) + } + // input: // p.c0 (2 elements) // p.c1 (2 elements) @@ -669,13 +817,15 @@ mod test { fn test_bn254_fq6_hinted_mul() { let mut prng: ChaCha20Rng = ChaCha20Rng::seed_from_u64(0); - for _ in 0..100 { + for i in 0..100 { let a = ark_bn254::Fq6::rand(&mut prng); let b = ark_bn254::Fq6::rand(&mut prng); let c = a.mul(&b); let (hinted_mul, hints) = Fq6::hinted_mul(6, a, 0, b); - println!("Fq6::hinted_mul: {} bytes", hinted_mul.len()); + if i == 0 { + println!("Fq6::hinted_mul: {} bytes", hinted_mul.len()); + } let script = script! { for hint in hints { @@ -692,6 +842,73 @@ mod test { } } + #[test] + fn test_bn254_fq6_hinted_mul_keep_elements() { + let mut prng: ChaCha20Rng = ChaCha20Rng::seed_from_u64(0); + + for i in 0..100 { + let a = ark_bn254::Fq6::rand(&mut prng); + let b = ark_bn254::Fq6::rand(&mut prng); + let c = ark_bn254::Fq6::rand(&mut prng); + let d = a.mul(&b); + let e = b.mul(&c); + + let (hinted_mul, hints) = Fq6::hinted_mul_keep_elements(12, a, 6, b); + if i == 0 { + println!("Fq6::hinted_mul(12,6): {} bytes", hinted_mul.len()); + } + + let script = script! { + for hint in hints { + { hint.push() } + } + { Fq6::push(a) } + { Fq6::push(b) } + { Fq6::push(c) } + { hinted_mul.clone() } + { Fq6::push(d) } + { Fq6::equalverify() } + + { Fq6::push(c) } + { Fq6::equalverify() } + { Fq6::push(b) } + { Fq6::equalverify() } + { Fq6::push(a) } + { Fq6::equalverify() } + + OP_TRUE + }; + run(script); + + let (hinted_mul, hints) = Fq6::hinted_mul_keep_elements(6, b, 0, c); + if i == 0 { + println!("Fq6::hinted_mul(12,6): {} bytes", hinted_mul.len()); + } + + let script = script! { + for hint in hints { + { hint.push() } + } + { Fq6::push(a) } + { Fq6::push(b) } + { Fq6::push(c) } + { hinted_mul.clone() } + { Fq6::push(e) } + { Fq6::equalverify() } + + { Fq6::push(c) } + { Fq6::equalverify() } + { Fq6::push(b) } + { Fq6::equalverify() } + { Fq6::push(a) } + { Fq6::equalverify() } + + OP_TRUE + }; + run(script); + } + } + #[test] fn test_bn254_fq6_hinted_mul_by_01() { let mut prng: ChaCha20Rng = ChaCha20Rng::seed_from_u64(0); From 9f2a42a17a72d46c70473ee114a6ca7a7e50fffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eevket=20Onur=20YILMAZ?= Date: Thu, 9 Oct 2025 11:05:13 +0300 Subject: [PATCH 10/32] [Zellic Audit] 3.19 MSM segments drop too many elements on invalid input if NUM_PUBS > 1 (#369) * fixed num_pub>1 case * fixed num_pubs>1 case --- bitvm/src/chunk/taps_msm.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bitvm/src/chunk/taps_msm.rs b/bitvm/src/chunk/taps_msm.rs index 021c5085..591df968 100644 --- a/bitvm/src/chunk/taps_msm.rs +++ b/bitvm/src/chunk/taps_msm.rs @@ -89,9 +89,7 @@ pub(crate) fn chunk_msm( // [G1Acc, G1AccDash, 1] [G1AccDashHash, G1AccHash] OP_ELSE // [G1Acc, k] - for _ in 0..num_pubs { - {Fr::drop()} - } + {Fr::drop()} {Fq::push(ark_bn254::Fq::ONE)} {Fq::push(ark_bn254::Fq::ZERO)} // [G1Acc, Mock_G1AccDash] [G1AccDashHash, G1AccHash] @@ -141,7 +139,6 @@ pub(crate) fn chunk_hash_p( let q = ark_bn254::G1Affine::new_unchecked(qx, qy); let (add_scr, add_hints) = G1Affine::hinted_check_add(t, q); let r = (t + q).into_affine(); - let ops_script = script! { // [t] [hash_r, hash_t] { Fq2::copy(0)} From 2dea02f4f4c24df718588298cf5628c5399b86ad Mon Sep 17 00:00:00 2001 From: erray Date: Thu, 9 Oct 2025 08:11:01 +0000 Subject: [PATCH 11/32] [Zellic Audit] 3.26 Empty stack check for BLAKE3 (#370) * add empty stack check * fix comment --- bitvm/src/hash/blake3.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bitvm/src/hash/blake3.rs b/bitvm/src/hash/blake3.rs index 36ed346c..3a5e2fcb 100644 --- a/bitvm/src/hash/blake3.rs +++ b/bitvm/src/hash/blake3.rs @@ -79,6 +79,17 @@ fn blake3( stack.to_altstack(); } + stack.custom( + script!( + OP_DEPTH + { 0 } OP_EQUALVERIFY + ), + 0, + false, + 0, + "ensure that the stack is actually empty", + ); + //initialize the tables let tables = TablesVars::new(stack, use_full_tables); @@ -318,7 +329,7 @@ pub fn maximum_number_of_altstack_elements_using_blake3(message_len: usize, limb /// if the `limb_len` is small or input stacks has other elements (in the altstack) /// - If `limb_len` is not in the range [4, 32) /// - If the input doesn't unpack to a multiple of 128 nibbles with the given limb length parameter. -/// - If the stack contains elements other than the message. +/// - If the stack contains elements other than the message, script fails to execute. /// /// ## Implementation /// From 358d00f0356a1119d4c555a46a22dd1f70ce1e91 Mon Sep 17 00:00:00 2001 From: erray Date: Thu, 9 Oct 2025 08:11:29 +0000 Subject: [PATCH 12/32] extend scalar validity checks to each index (#372) --- bitvm/src/bn254/msm.rs | 12 +++++++----- bitvm/src/chunk/taps_msm.rs | 28 +++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bitvm/src/bn254/msm.rs b/bitvm/src/bn254/msm.rs index d4e3d00b..d9ea2f9c 100644 --- a/bitvm/src/bn254/msm.rs +++ b/bitvm/src/bn254/msm.rs @@ -39,7 +39,7 @@ pub fn hinted_msm_with_constant_bases_affine( let num_scalars = scalars.len(); let psm_len = all_rows.len() / num_scalars; - for (idx, (row_out, row_scr, row_hints)) in all_rows.into_iter().enumerate() { + for (idx, ((row_out, row_scr, row_hints), _)) in all_rows.into_iter().enumerate() { all_hints.extend_from_slice(&row_hints); let temp_scr = script! { @@ -275,7 +275,7 @@ fn accumulate_addition_chain_for_a_scalar_mul( pub(crate) fn g1_multi_scalar_mul( bases: Vec, scalars: Vec, -) -> Vec<(ark_bn254::G1Affine, Script, Vec)> { +) -> Vec<((ark_bn254::G1Affine, Script, Vec), usize)> { assert_eq!(bases.len(), scalars.len()); let mut prev = ark_bn254::G1Affine::identity(); let window = WINDOW_G1_MSM as usize; @@ -285,7 +285,9 @@ pub(crate) fn g1_multi_scalar_mul( let scalar_mul_res = accumulate_addition_chain_for_a_scalar_mul(prev, bases[i], scalars[i], window); prev = scalar_mul_res[scalar_mul_res.len() - 1].0; - aggregate_result_of_all_scalar_muls.extend_from_slice(&scalar_mul_res); + for x in scalar_mul_res { + aggregate_result_of_all_scalar_muls.push((x, i)); + } } aggregate_result_of_all_scalar_muls } @@ -486,11 +488,11 @@ mod test { let psm_len = all_rows.len() / num_scalars; let expected_msm = (q0 * fq0 + q1 * fq1).into_affine(); - let calculated_msm = all_rows[all_rows.len() - 1].0; + let calculated_msm = all_rows[all_rows.len() - 1].0 .0; assert_eq!(expected_msm, calculated_msm); let mut prev = ark_bn254::G1Affine::identity(); - for (idx, (row_out, row_scr, row_hints)) in all_rows.into_iter().enumerate() { + for (idx, ((row_out, row_scr, row_hints), _)) in all_rows.into_iter().enumerate() { let scr = script! { // [hints, t, scalar] for h in &row_hints { diff --git a/bitvm/src/chunk/taps_msm.rs b/bitvm/src/chunk/taps_msm.rs index 591df968..93082c2e 100644 --- a/bitvm/src/chunk/taps_msm.rs +++ b/bitvm/src/chunk/taps_msm.rs @@ -21,17 +21,16 @@ pub(crate) fn chunk_msm( assert_eq!(input_ks.len(), NUM_PUBS); let num_pubs = input_ks.len(); - let mut ks = (0..num_pubs) - .map(|_| ark_ff::BigInt::<4>::from(1u64)) - .collect::>>(); - let scalars_are_valid_elems = input_ks + let ks = input_ks .iter() - .filter(|f| **f < ark_bn254::Fr::MODULUS) - .count() - == num_pubs; - if scalars_are_valid_elems { - ks = input_ks.clone(); - } + .map(|x| { + if *x < ark_bn254::Fr::MODULUS { + *x + } else { + ark_ff::BigInt::<4>::from(1u64) + } + }) + .collect::>>(); let chunks = msm::g1_multi_scalar_mul(qs.clone(), ks.into_iter().map(|f| f.into()).collect()); @@ -39,7 +38,7 @@ pub(crate) fn chunk_msm( // [hints, G1Acc] let mut chunk_scripts = vec![]; - for (msm_tap_index, chunk) in chunks.iter().enumerate() { + for (msm_tap_index, (chunk, variable_index)) in chunks.iter().enumerate() { let ops_script = if msm_tap_index == 0 { script! { { G1Affine::push( ark_bn254::G1Affine::new_unchecked(ark_bn254::Fq::ZERO, ark_bn254::Fq::ZERO))} @@ -116,11 +115,10 @@ pub(crate) fn chunk_msm( {ops_script} // {hash_script} }; - - if scalars_are_valid_elems { - chunk_scripts.push((chunk.0, scalars_are_valid_elems, sc, chunk.2.clone())); + if input_ks[*variable_index] < ark_bn254::Fr::MODULUS { + chunk_scripts.push((chunk.0, true, sc, chunk.2.clone())); } else { - chunk_scripts.push((chunk.0, scalars_are_valid_elems, sc, vec![])); + chunk_scripts.push((chunk.0, false, sc, vec![])); } } chunk_scripts From 4fbead3951b3880b0c8b4588f07c193351506d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:17:39 +0300 Subject: [PATCH 13/32] remove fp254impl::is_field (#378) --- bitvm/src/bn254/fp254impl.rs | 21 --------------------- bitvm/src/bn254/fq.rs | 34 ---------------------------------- bitvm/src/bn254/fr.rs | 34 ---------------------------------- 3 files changed, 89 deletions(-) diff --git a/bitvm/src/bn254/fp254impl.rs b/bitvm/src/bn254/fp254impl.rs index 3662b910..3b7b4a6e 100644 --- a/bitvm/src/bn254/fp254impl.rs +++ b/bitvm/src/bn254/fp254impl.rs @@ -133,27 +133,6 @@ pub trait Fp254Impl { } } - fn is_field() -> Script { - script! { - // Each limb must not be negative - for i in 0..Self::N_LIMBS - 1 { - { i } OP_PICK - 0 OP_GREATERTHANOREQUAL OP_TOALTSTACK - } - { Self::N_LIMBS - 1 } OP_PICK - 0 OP_GREATERTHANOREQUAL - for _ in 0..Self::N_LIMBS - 1 { - OP_FROMALTSTACK OP_BOOLAND - } - OP_TOALTSTACK - - { Self::push_modulus() } - { U254::lessthan(1, 0) } - - OP_FROMALTSTACK OP_BOOLAND - } - } - #[inline] fn convert_to_le_bits_toaltstack() -> Script { U254::convert_to_le_bits_toaltstack() diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index d4f0052a..870988dd 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -662,40 +662,6 @@ mod test { } } - #[test] - fn test_is_field() { - let m = BigUint::from_str_radix(Fq::MODULUS, 16).unwrap(); - let mut prng = ChaCha20Rng::seed_from_u64(0); - - println!("Fq.is_field: {} bytes", Fq::is_field().len()); - - for _ in 0..10 { - let a: BigUint = prng.sample(RandomBits::new(254)); - let a = a.rem(&m); - - let script = script! { - { Fq::push_u32_le(&a.to_u32_digits()) } - { Fq::is_field() } - }; - run(script); - } - - let script = script! { - { Fq::push_modulus() } OP_1 OP_ADD - { Fq::is_field() } - OP_NOT - }; - run(script); - - let script = script! { - { Fq::push_modulus() } OP_1 OP_SUB - OP_NEGATE - { Fq::is_field() } - OP_NOT - }; - run(script); - } - #[test] fn test_hinted_mul() { let mut prng: ChaCha20Rng = ChaCha20Rng::seed_from_u64(0); diff --git a/bitvm/src/bn254/fr.rs b/bitvm/src/bn254/fr.rs index f9b9693a..2bcfaa33 100644 --- a/bitvm/src/bn254/fr.rs +++ b/bitvm/src/bn254/fr.rs @@ -256,38 +256,4 @@ mod test { run(script); } } - - #[test] - fn test_is_field() { - let m = BigUint::from_str_radix(Fr::MODULUS, 16).unwrap(); - let mut prng = ChaCha20Rng::seed_from_u64(0); - - println!("Fr.is_field: {} bytes", Fr::is_field().len()); - - for _ in 0..10 { - let a: BigUint = prng.sample(RandomBits::new(254)); - let a = a.rem(&m); - - let script = script! { - { Fr::push_u32_le(&a.to_u32_digits()) } - { Fr::is_field() } - }; - run(script); - } - - let script = script! { - { Fr::push_modulus() } OP_1 OP_ADD - { Fr::is_field() } - OP_NOT - }; - run(script); - - let script = script! { - { Fr::push_modulus() } OP_1 OP_SUB - OP_NEGATE - { Fr::is_field() } - OP_NOT - }; - run(script); - } } From 794c593ad9bd12c5004bce49d6a402471f90ad0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:17:46 +0300 Subject: [PATCH 14/32] Fq::mul_by_constant now works more efficiently when constant is 1 or -1 (#367) --- bitvm/src/bn254/fp254impl.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bitvm/src/bn254/fp254impl.rs b/bitvm/src/bn254/fp254impl.rs index 3b7b4a6e..75e261c6 100644 --- a/bitvm/src/bn254/fp254impl.rs +++ b/bitvm/src/bn254/fp254impl.rs @@ -4,7 +4,7 @@ use crate::bigint::U254; use crate::bn254::fq::Fq; use crate::bn254::utils::Hint; use crate::treepp::*; -use ark_ff::PrimeField; +use ark_ff::{Field, PrimeField}; use bitcoin_script::script; use num_bigint::{BigInt, BigUint}; use num_traits::Num; @@ -573,6 +573,11 @@ pub trait Fp254Impl { // TODO: Optimize by using the constant feature fn hinted_mul_by_constant(a: ark_bn254::Fq, constant: &ark_bn254::Fq) -> (Script, Vec) { + if *constant == ark_bn254::Fq::ONE { + return (script! {}, vec![]); + } else if *constant == -ark_bn254::Fq::ONE { + return (Fq::neg(0), vec![]); + } let mut hints = Vec::new(); let x = BigInt::from_str(&a.to_string()).unwrap(); let y = BigInt::from_str(&constant.to_string()).unwrap(); From 3d8e46735ec803be2df01a6fb86ea43dcec2ccb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:20:06 +0300 Subject: [PATCH 15/32] fix incorrect wrappers for tmul (#385) --- bitvm/src/bn254/fp254impl.rs | 36 ++++++++++++++++++++++++------------ bitvm/src/bn254/fq.rs | 28 +++++++++++++++++++--------- bitvm/src/bn254/utils.rs | 14 ++++++++++---- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/bitvm/src/bn254/fp254impl.rs b/bitvm/src/bn254/fp254impl.rs index 75e261c6..a2efa02e 100644 --- a/bitvm/src/bn254/fp254impl.rs +++ b/bitvm/src/bn254/fp254impl.rs @@ -556,9 +556,10 @@ pub trait Fp254Impl { let y = BigInt::from_str(&b.to_string()).unwrap(); let modulus = &Fq::modulus_as_bigint(); let q = (x * y) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -583,9 +584,10 @@ pub trait Fp254Impl { let y = BigInt::from_str(&constant.to_string()).unwrap(); let modulus = &Fq::modulus_as_bigint(); let q = (x * y) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -615,9 +617,10 @@ pub trait Fp254Impl { let y = BigInt::from_str(&b.to_string()).unwrap(); let modulus = &Fq::modulus_as_bigint(); let q = (x * y) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -653,9 +656,10 @@ pub trait Fp254Impl { let w = BigInt::from_str(&d.to_string()).unwrap(); let q = (x * z + y * w) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_2().2; let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -696,9 +700,10 @@ pub trait Fp254Impl { let w = BigInt::from_str(&d.to_string()).unwrap(); let q = (x * z + y * w) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_2_w4().2; let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -708,7 +713,7 @@ pub trait Fp254Impl { { Fq::roll(d_depth + 4) } { Fq::tmul_lc2_w4() } }; - hints.push(Hint::BigIntegerTmulLC2(q)); + hints.push(Hint::BigIntegerTmulLC2W4(q)); (script, hints) } @@ -758,9 +763,10 @@ pub trait Fp254Impl { let w2 = BigInt::from_str(&h.to_string()).unwrap(); let q = (x1 * x2 + y1 * y2 + z1 * z2 + w1 * w2) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_4().2; let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { fq_push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -802,9 +808,10 @@ pub trait Fp254Impl { let w = BigInt::from_str(&d.to_string()).unwrap(); let q = (x * z + y * w) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_2().2; let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -843,9 +850,10 @@ pub trait Fp254Impl { let w = BigInt::from_str(&d.to_string()).unwrap(); let q = (x * z + y * w) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_2_w4().2; let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -855,7 +863,7 @@ pub trait Fp254Impl { { Fq::copy(d_depth + 4) } { Fq::tmul_lc2_w4() } }; - hints.push(Hint::BigIntegerTmulLC2(q)); + hints.push(Hint::BigIntegerTmulLC2W4(q)); (script, hints) } @@ -866,8 +874,10 @@ pub trait Fp254Impl { let x = &BigInt::from_str(&a.to_string()).unwrap(); let modulus = &Fq::modulus_as_bigint(); let q = (x * x) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; + let script = script! { - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -886,11 +896,13 @@ pub trait Fp254Impl { let modulus = &Fq::modulus_as_bigint(); let y = &x.modinv(modulus).unwrap(); let q = (x * y) / modulus; + const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; + let script = script! { for _ in 0..Self::N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } - for _ in 0..Self::N_LIMBS { + for _ in 0..T_N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL // hints } // { Fq::push(ark_bn254::Fq::from_str(&y.to_string()).unwrap()) } diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index 870988dd..ca4670fd 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -63,22 +63,32 @@ impl Fq { } } - pub const fn bigint_tmul_lc_1() -> (u32, u32) { + pub const fn bigint_tmul_lc_1() -> (u32, u32, u32) { const X: u32 = ::T::N_BITS; - const Y: u32 = ::LIMB_SIZE; - (X, Y) + const Y: u32 = ::T::LIMB_SIZE; + const Z: u32 = ::T::N_LIMBS; + (X, Y, Z) } - pub const fn bigint_tmul_lc_2() -> (u32, u32) { + pub const fn bigint_tmul_lc_2() -> (u32, u32, u32) { const X: u32 = ::T::N_BITS; - const Y: u32 = ::LIMB_SIZE; - (X, Y) + const Y: u32 = ::T::LIMB_SIZE; + const Z: u32 = ::T::N_LIMBS; + (X, Y, Z) } - pub const fn bigint_tmul_lc_4() -> (u32, u32) { + pub const fn bigint_tmul_lc_2_w4() -> (u32, u32, u32) { + const X: u32 = ::T::N_BITS; + const Y: u32 = ::T::LIMB_SIZE; + const Z: u32 = ::T::N_LIMBS; + (X, Y, Z) + } + + pub const fn bigint_tmul_lc_4() -> (u32, u32, u32) { const X: u32 = ::T::N_BITS; - const Y: u32 = ::LIMB_SIZE; - (X, Y) + const Y: u32 = ::T::LIMB_SIZE; + const Z: u32 = ::T::N_LIMBS; + (X, Y, Z) } #[inline] diff --git a/bitvm/src/bn254/utils.rs b/bitvm/src/bn254/utils.rs index 5b92f09d..07fcff67 100644 --- a/bitvm/src/bn254/utils.rs +++ b/bitvm/src/bn254/utils.rs @@ -14,16 +14,19 @@ pub enum Hint { U256(num_bigint::BigInt), BigIntegerTmulLC1(num_bigint::BigInt), BigIntegerTmulLC2(num_bigint::BigInt), + BigIntegerTmulLC2W4(num_bigint::BigInt), BigIntegerTmulLC4(num_bigint::BigInt), } impl Hint { pub fn push(&self) -> Script { - const K1: (u32, u32) = Fq::bigint_tmul_lc_1(); - const K2: (u32, u32) = Fq::bigint_tmul_lc_2(); - const K4: (u32, u32) = Fq::bigint_tmul_lc_4(); + const K1: (u32, u32, u32) = Fq::bigint_tmul_lc_1(); + const K2: (u32, u32, u32) = Fq::bigint_tmul_lc_2(); + const K3: (u32, u32, u32) = Fq::bigint_tmul_lc_2_w4(); + const K4: (u32, u32, u32) = Fq::bigint_tmul_lc_4(); pub type T1 = BigIntImpl<{ K1.0 }, { K1.1 }>; pub type T2 = BigIntImpl<{ K2.0 }, { K2.1 }>; + pub type T3 = BigIntImpl<{ K3.0 }, { K3.1 }>; pub type T4 = BigIntImpl<{ K4.0 }, { K4.1 }>; match self { Hint::Fq(fq) => script! { @@ -46,8 +49,11 @@ impl Hint { Hint::BigIntegerTmulLC2(a) => script! { { T2::push_u32_le(&bigint_to_u32_limbs(a.clone(), T2::N_BITS)) } }, + Hint::BigIntegerTmulLC2W4(a) => script! { + { T3::push_u32_le(&bigint_to_u32_limbs(a.clone(), T3::N_BITS)) } + }, Hint::BigIntegerTmulLC4(a) => script! { - { T2::push_u32_le(&bigint_to_u32_limbs(a.clone(), T4::N_BITS)) } + { T4::push_u32_le(&bigint_to_u32_limbs(a.clone(), T4::N_BITS)) } }, } } From 1bf2bf5d2d4db3ec4b8f840db6600b5011e421cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:20:56 +0300 Subject: [PATCH 16/32] [Zellic Audit] 3.38 Leftover values on altstack in utils_fq12_square (#371) * drop c left in the altstack * remove if branch in utils_fq12_square since it doesnt occur --- bitvm/src/chunk/taps_mul.rs | 44 +++++++++++++------------------------ 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/bitvm/src/chunk/taps_mul.rs b/bitvm/src/chunk/taps_mul.rs index 6b2e325b..ad5ee780 100644 --- a/bitvm/src/chunk/taps_mul.rs +++ b/bitvm/src/chunk/taps_mul.rs @@ -288,7 +288,6 @@ pub(crate) fn utils_fq12_dd_mul( // Assumes input a is valid i.e (1 + a^2 J ^2) != 0 pub(crate) fn utils_fq12_square(a: ark_bn254::Fq6) -> (ark_bn254::Fq6, bool, Script, Vec) { let mut hints = vec![]; - let mock_value = ark_bn254::Fq6::ONE; // compute ab in Script let (asq_scr, ab_hints) = Fq6::hinted_square(a); @@ -299,14 +298,11 @@ pub(crate) fn utils_fq12_square(a: ark_bn254::Fq6) -> (ark_bn254::Fq6, bool, Scr let denom = ark_bn254::Fq6::ONE + a * a * beta_sq; // is input valid ? output: mock_output - let (denom_mul_c_scr, c) = if denom != ark_bn254::Fq6::ZERO { + let (denom_mul_c_scr, c) = { let c = (a + a) / denom; let res = Fq6::hinted_mul(6, denom, 0, c); hints.extend_from_slice(&res.1); (res.0, c) - } else { - let scr = Fq6::hinted_mul(6, ark_bn254::Fq6::ONE, 0, ark_bn254::Fq6::ONE).0; - (scr, mock_value) }; let mul_by_beta_sq_scr = script! { @@ -327,31 +323,21 @@ pub(crate) fn utils_fq12_square(a: ark_bn254::Fq6) -> (ark_bn254::Fq6, bool, Scr {Fq6::push(ark_bn254::Fq6::ONE)} {Fq6::add(6, 0)} // [hints, a, denom] [c] + {Fq6::fromaltstack()} + // [hints, a, denom, c] {Fq6::copy(0)} - {Fq6::is_zero()} - OP_IF - // [a, denom] [c] - {Fq6::drop()} - {Fq6::push(mock_value)} - {0} - // [a, mock_c, 0] - OP_ELSE - {Fq6::fromaltstack()} - // [hints, a, denom, c] - {Fq6::copy(0)} - // [hints, a, denom, c, c] - {Fq6::roll(12)} {Fq6::roll(12)} - // [hints, a, c, denom, c] - {denom_mul_c_scr} - // [a, c, denom_c] - {Fq6::copy(12)} - // [a, c, denom_c, a] - {Fq6::double(0)} - // [a, c, denom_c, 2a] - {Fq6::equalverify()} - // [a,c] [] - {1} - OP_ENDIF + // [hints, a, denom, c, c] + {Fq6::roll(12)} {Fq6::roll(12)} + // [hints, a, c, denom, c] + {denom_mul_c_scr} + // [a, c, denom_c] + {Fq6::copy(12)} + // [a, c, denom_c, a] + {Fq6::double(0)} + // [a, c, denom_c, 2a] + {Fq6::equalverify()} + // [a,c] [] + {1} // [a, c, 0/1] }; From 1261ddcf4848f3c74b41da9dc59e1c7cec6fe7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:21:32 +0300 Subject: [PATCH 17/32] change logic to expect hint rounded down in tmul (#384) --- bitvm/src/bn254/fq.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index ca4670fd..0cfbd63f 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -400,25 +400,22 @@ macro_rules! fp_lc_mul { } } - { T::is_positive(size_table(MOD_WIDTH) + // q was negative - N_LC * size_table(VAR_WIDTH) + N_LC) } OP_TOALTSTACK // {-q_table} {x0_table} {x1_table} {y0} {y1} {r} -> {0/1} - { T::toaltstack() } // {-q_table} {x0_table} {x1_table} {y0} {y1} -> {r} {0/1} + { T::toaltstack() } // {-q_table} {x0_table} {x1_table} {y0} {y1} -> {r} // Cleanup - for _ in 0..N_LC { { T::drop() } } // {-q_table} {x0_table} {x1_table} -> {r} {0/1} - for _ in 0..N_LC { { drop_table(VAR_WIDTH) } } // {-q_table} -> {r} {0/1} - { drop_table(MOD_WIDTH) } // -> {r} {0/1} + for _ in 0..N_LC { { T::drop() } } // {-q_table} {x0_table} {x1_table} -> {r} + for _ in 0..N_LC { { drop_table(VAR_WIDTH) } } // {-q_table} -> {r} + { drop_table(MOD_WIDTH) } // -> {r} // Correction/validation // r = if q < 0 { r + p } else { r }; assert(r < p) - { T::push_u32_le(&Fq::modulus_as_bigint().to_u32_digits().1) } // {MODULUS} -> {r} {0/1} - { T::fromaltstack() } OP_FROMALTSTACK // {MODULUS} {r} {0/1} - OP_IF { T::add_ref(1) } OP_ENDIF // {MODULUS} {-r/r} - { T::copy(0) } // {MODULUS} {-r/r} {-r/r} - { T::lessthan(0, 2) } OP_VERIFY // {-r/r} + { T::push_u32_le(&Fq::modulus_as_bigint().to_u32_digits().1) } // {MODULUS} -> {r} + { T::fromaltstack() } // {MODULUS} {r} + { T::copy(0) } // {MODULUS} {r} {r} + { T::lessthan(0, 2) } OP_VERIFY // {r} // Resize res back to N_BITS - { T::resize::() } // {r} + { T::resize::() } // {r} } } } From 880bde59401a5de8fc281d260b5a049dd287fe61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:21:55 +0300 Subject: [PATCH 18/32] change Self::N_LIMBS to T::LIMBS in tmul where it is used incorrectly (#383) --- bitvm/src/bn254/fq.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index 0cfbd63f..9cf98ed9 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -368,12 +368,12 @@ macro_rules! fp_lc_mul { OP_SWAP OP_SUB if i + j == MAIN_LOOP_START && j == 0 { - for _ in 0..Self::N_LIMBS { + for _ in 0..T::N_LIMBS { OP_NIP } - { NMUL(Self::N_LIMBS) } + { NMUL(T::N_LIMBS) } OP_DUP OP_PICK - for _ in 0..Self::N_LIMBS-1 { + for _ in 0..T::N_LIMBS-1 { OP_SWAP OP_DUP OP_PICK } From dd271c2978b2732295dcf1469bce0850ba78f096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:22:38 +0300 Subject: [PATCH 19/32] [Zellic Audit] 3.27 Blake3 script is incorrect (#390) * fix #365 Blake3 script inscorrect fix #365 Blake3 script inscorrect for more than 1,024 bytes of input or more than 32 bytes of output * add assert to blake3 compress to check final_rounds is 8 --------- Co-authored-by: lynndell | bitlayer --- bitvm/src/hash/blake3_utils.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bitvm/src/hash/blake3_utils.rs b/bitvm/src/hash/blake3_utils.rs index e9eb062b..b6da4567 100644 --- a/bitvm/src/hash/blake3_utils.rs +++ b/bitvm/src/hash/blake3_utils.rs @@ -454,8 +454,8 @@ fn init_state( for u32 in &IV[0..4] { state.push(stack.number_u32(*u32)); } - state.push(stack.number_u32(0)); state.push(stack.number_u32(counter)); + state.push(stack.number_u32(0)); state.push(stack.number_u32(block_len)); state.push(stack.number_u32(flags)); @@ -483,6 +483,9 @@ pub(crate) fn compress( //chaining value needs to be copied for multiple blocks //every time that is provided + // final_rounds must be 8, if you need more than 32 output bytes, you need to change this + assert_eq!(final_rounds, 8); + let mut state = init_state(stack, chaining, counter, block_len, flags); for _ in 0..6 { From 60325db664c86e866d85e6108e65b3e70ad0fe8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:23:19 +0300 Subject: [PATCH 20/32] change LC_BITS in tmul (#382) --- bitvm/src/bn254/fq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index 9cf98ed9..c2190020 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -122,7 +122,7 @@ macro_rules! fp_lc_mul { trait [] { const LIMB_SIZE: u32 = 29; const LCS: [bool; $LCS.len()] = $LCS; - const LC_BITS: u32 = usize::BITS - $LCS.len().leading_zeros() - 1; + const LC_BITS: u32 = usize::BITS - ($LCS.len() - 1).leading_zeros(); type U; type T; fn tmul() -> Script; From 5ee432c2db5ee1643197f9117fc24612ff76d93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:23:45 +0300 Subject: [PATCH 21/32] assert window is not 1 in tmul init_table (#381) --- bitvm/src/bn254/fq.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index c2190020..6310c5a8 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -179,8 +179,8 @@ macro_rules! fp_lc_mul { // Initialize the lookup table fn init_table(window: u32) -> Script { assert!( - (1..=6).contains(&window), - "expected 1<=window<=6; got window={}", + (2..=6).contains(&window), + "expected 2<=window<=6; got window={}", window ); script! { From b7a2229569f429674d758685f0fe93445890bf85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:24:27 +0300 Subject: [PATCH 22/32] add BigInt::double_prevent_overflow_keep_element and use it in tmul init_table (#380) --- bitvm/src/bigint/add.rs | 19 +++++++++++++++++++ bitvm/src/bn254/fq.rs | 4 +++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/bitvm/src/bigint/add.rs b/bitvm/src/bigint/add.rs index 2ed22490..3316f22a 100644 --- a/bitvm/src/bigint/add.rs +++ b/bitvm/src/bigint/add.rs @@ -144,6 +144,25 @@ impl BigIntImpl { } } + /// Double the referenced BigInt but keep the original element in its position + /// This function prevents overflow of the underlying integer types during + /// doubling operation. + pub fn double_prevent_overflow_keep_element(n: u32) -> Script { + script! { + { 1 << LIMB_SIZE } + { n + 1 } OP_PICK limb_double_without_carry OP_TOALTSTACK + for i in 0..Self::N_LIMBS - 2 { + { n + i + 3 } OP_PICK limb_double_with_carry OP_TOALTSTACK + } + OP_NIP + { n + Self::N_LIMBS } OP_PICK OP_SWAP + { limb_double_with_carry_prevent_overflow(Self::HEAD_OFFSET) } + for _ in 0..Self::N_LIMBS - 1 { + OP_FROMALTSTACK + } + } + } + /// Left shift the BigInt on top of the stack by `bits` /// /// # Note diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index 6310c5a8..ade2da8e 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -187,7 +187,9 @@ macro_rules! fp_lc_mul { for i in 2..=window { for j in 1 << (i - 1)..1 << i { if j % 2 == 0 { - { T::double_allow_overflow_keep_element( (j/2 - 1) * T::N_LIMBS ) } + // { T::copy(j/2 - 1) } + // { T::double_prevent_overflow() } + { T::double_prevent_overflow_keep_element((j/2 - 1) * T::N_LIMBS) } } else { { T::add_ref_with_top(j - 2) } } From fc89d9cbd2443320054b684069836bcf9134ae2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:25:07 +0300 Subject: [PATCH 23/32] fix limb_add and limb_double issues (#327) --- bitvm/src/bigint/add.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bitvm/src/bigint/add.rs b/bitvm/src/bigint/add.rs index 3316f22a..c9a4e413 100644 --- a/bitvm/src/bigint/add.rs +++ b/bitvm/src/bigint/add.rs @@ -253,7 +253,7 @@ impl BigIntImpl { OP_NIP { b_depth + 1 } OP_PICK - OP_ROT + OP_SWAP { limb_add_with_carry_prevent_overflow(Self::HEAD_OFFSET) } for _ in 0..Self::N_LIMBS - 1 { @@ -380,7 +380,7 @@ fn limb_add_with_carry_prevent_overflow(head_offset: u32) -> Script { OP_2SWAP // {a+b+c_nlo} {x} {a} {sign_b} {a+b+c_nlo} {x} OP_GREATERTHANOREQUAL // {a+b+c_nlo} {x} {a} {sign_b} {I:0/1} OP_2SWAP // {a+b+c_nlo} {sign_b} {I:0/1} {x} {a} - OP_GREATERTHANOREQUAL // {a+b+c_nlo} {sign_b} {I:0/1} {sign_a} + OP_GREATERTHAN // {a+b+c_nlo} {sign_b} {I:0/1} {sign_a} OP_ADD OP_ADD 1 3 OP_WITHIN OP_VERIFY // verify (sign_a, sign_b, I) is not (0, 0, 0) or (1, 1, 1) which would mean overflow } } @@ -436,7 +436,7 @@ fn limb_double_with_carry_prevent_overflow(head_offset: u32) -> Script { OP_TUCK OP_DUP OP_ADD // {a} {x} {2a+c} {2x} OP_2DUP OP_GREATERTHANOREQUAL // {a} {x} {2a+c} {2x} {L:0/1} OP_NOTIF OP_NOT OP_ENDIF OP_SUB // {a} {x} {2a+c_nlo} - OP_2DUP OP_LESSTHAN // {a} {x} {2a+c_nlo} {I:0/1} + OP_2DUP OP_LESSTHANOREQUAL // {a} {x} {2a+c_nlo} {I:0/1} OP_2SWAP // {2a+c_nlo} {I:0/1} {a} {x} OP_LESSTHAN // {2a+c_nlo} {I:0/1} {sign_a} From 56910683d47bd6f03917b436f93a3bd19d590d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:27:31 +0300 Subject: [PATCH 24/32] create BigInt::neg function for optimization purposes, use it in tmul, and add note in tmul about a case about hint (#386) --- bitvm/src/bigint/add.rs | 47 +++++++++++++++++++++++++++++++++++++++++ bitvm/src/bigint/sub.rs | 45 +++++++++++++++++++++++++++++++++++++++ bitvm/src/bn254/fq.rs | 5 ++--- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/bitvm/src/bigint/add.rs b/bitvm/src/bigint/add.rs index c9a4e413..b76dd426 100644 --- a/bitvm/src/bigint/add.rs +++ b/bitvm/src/bigint/add.rs @@ -63,6 +63,34 @@ impl BigIntImpl { } } + /// add one + pub fn add1() -> Script { + script! { + OP_1ADD // a0 ... an + { 1 << LIMB_SIZE } // a0 ... an x + OP_SWAP // a0 ... x an + for _ in 0..Self::N_LIMBS-1 { // ... x a + OP_2DUP // ... x a x a + OP_EQUAL // ... x a 0/1 + OP_TUCK // ... x 0/1 a 0/1 + OP_IF OP_NOT OP_ENDIF // ... x 0/1 a/0 + OP_TOALTSTACK // ... x 0/1 + OP_ROT // .. x 0/1 a_{i-1} + OP_ADD // .. x a' + } + // x a0 + OP_NIP // a0 + { Self::HEAD_OFFSET } // a0 y + OP_OVER // a0 y a0 + OP_EQUAL // a0 0/1 + OP_IF OP_NOT OP_ENDIF // a/0 + + for _ in 0..Self::N_LIMBS - 1 { + OP_FROMALTSTACK + } + } + } + /// Double the BigInt on top of the stack /// /// # Note @@ -556,6 +584,25 @@ mod test { } } + #[test] + fn test_add1() { + println!("U254.add1: {} bytes", U254::add1().len()); + let mut prng = ChaCha20Rng::seed_from_u64(0); + for _ in 0..100 { + let a: BigUint = prng.sample(RandomBits::new(254)); + let c: BigUint = (a.clone() + BigUint::one()).rem(BigUint::one().shl(254)); + + let script = script! { + { U254::push_u32_le(&a.to_u32_digits()) } + { U254::add1() } + { U254::push_u32_le(&c.to_u32_digits()) } + { U254::equalverify(1, 0) } + OP_TRUE + }; + run(script); + } + } + #[test] fn test_double() { println!("U254.double: {} bytes", U254::double(0).len()); diff --git a/bitvm/src/bigint/sub.rs b/bitvm/src/bigint/sub.rs index 537632aa..92cb72c4 100644 --- a/bitvm/src/bigint/sub.rs +++ b/bitvm/src/bigint/sub.rs @@ -31,6 +31,32 @@ impl BigIntImpl { } } } + + pub fn neg() -> Script { + script! { // ... a_n + { (1 << LIMB_SIZE) - 1 } // ... a_n x + + for _ in 0..Self::N_LIMBS-2 { // ... a_{i-1} a_i x + OP_TUCK // ... a_{i-1} x a_i x + OP_SWAP // ... a_{i-1} x x a_i + OP_SUB // ... a_{i-1} x x-a_i + OP_TOALTSTACK // ... a_{i-1} x + } + // a_0 a_1 x + OP_SWAP // a_0 x a_1 + OP_SUB // a_0 x-a_1 + OP_TOALTSTACK // a_0 + + { Self::HEAD_OFFSET-1 } + OP_SWAP + OP_SUB + + for _ in 0..Self::N_LIMBS-1 { + OP_FROMALTSTACK + } + { Self::add1() } + } + } } /// Compute the difference of two limbs, including the carry bit @@ -134,4 +160,23 @@ mod test { run(script); } } + + #[test] + fn test_neg() { + println!("U254.neg: {} bytes", U254::neg().len()); + let mut prng = ChaCha20Rng::seed_from_u64(0); + + let a: BigUint = prng.sample(RandomBits::new(254)); + + let script = script! { + { U254::push_zero() } + { U254::push_u32_le(&a.to_u32_digits()) } + { U254::sub(1, 0) } + { U254::push_u32_le(&a.to_u32_digits()) } + { U254::neg() } + { U254::equalverify(1, 0) } + OP_TRUE + }; + run(script); + } } diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index ade2da8e..761b1a96 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -335,9 +335,8 @@ macro_rules! fp_lc_mul { { U::lessthan(1, 0) } OP_VERIFY // {q} {x0} {x1} {y0} {y1} { U::toaltstack() } // {q} {x0} {x1} {y0} -> {y1} } // {q} -> {x0} {x1} {y0} {y1} - // Pre-compute lookup tables - { T::push_zero() } // {q} {0} -> {x0} {x1} {y0} {y1} - { T::sub(0, 1) } // {-q} -> {x0} {x1} {y0} {y1} + // Pre-compute lookup tables (q can not be 2^T::N_BITS-1 when tmul is correctly used, so neg() gives correct result) + { T::neg() } // {-q} -> {x0} {x1} {y0} {y1} { init_table(MOD_WIDTH) } // {-q_table} -> {x0} {x1} {y0} {y1} for i in 0..N_LC { { U::fromaltstack() } // {-q_table} {x0} -> {x1} {y0} {y1} From 19996d52c3224a05366a343cbf35b5e90ea9cc29 Mon Sep 17 00:00:00 2001 From: KSlashh <48985735+KSlashh@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:14:20 +0800 Subject: [PATCH 25/32] Fix altstack overflow in verify_digits for fixed-point edge case (#318) --- bitvm/src/signatures/winternitz.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bitvm/src/signatures/winternitz.rs b/bitvm/src/signatures/winternitz.rs index 61ebed28..156d7c38 100644 --- a/bitvm/src/signatures/winternitz.rs +++ b/bitvm/src/signatures/winternitz.rs @@ -469,7 +469,8 @@ impl Verifier for BruteforceVerifier { OP_DUP { -1 } OP_NUMNOTEQUAL OP_VERIFY - OP_FROMALTSTACK OP_DROP + OP_FROMALTSTACK + { -1 } OP_EQUALVERIFY // Verify the sentinel value -1 to avoid altstack overflow in the rare case where pk == hash160^n(pk). OP_TOALTSTACK } } From 04df2bd32e108dd505d4dd70cfeabb6f78510b06 Mon Sep 17 00:00:00 2001 From: erray Date: Thu, 9 Oct 2025 09:14:32 +0000 Subject: [PATCH 26/32] [Zellic Audit] Winternitz Fixes (#337) * fix checksum length * add warning for repeated hashes * fix pushing digits * fmt * add hash length checks and comment verify_test_vector's temporarily since its not compatible with the new checksum length * fix secret key generation * remove BinarysearchVerifier bound check and add test to verify that it doesn't cause a vulnerability * fix raw_witness_to_signature digit conversion --- bitvm/src/signatures/public.rs | 23 ++--- bitvm/src/signatures/utils.rs | 24 ++--- bitvm/src/signatures/winternitz.rs | 137 ++++++++++++++++++++++++++--- 3 files changed, 151 insertions(+), 33 deletions(-) diff --git a/bitvm/src/signatures/public.rs b/bitvm/src/signatures/public.rs index 894f7fd0..7dfcb367 100644 --- a/bitvm/src/signatures/public.rs +++ b/bitvm/src/signatures/public.rs @@ -1,7 +1,8 @@ use bitcoin::hex::DisplayHex; +use bitcoin::script::read_scriptint; use bitcoin_script::Script; -use super::utils::u32_to_le_bytes_minimal; +use crate::signatures::utils::bitcoin_representation; use crate::signatures::winternitz; use crate::signatures::winternitz::{ BruteforceVerifier, Converter, ListpickVerifier, Parameters, VoidConverter, Winternitz, @@ -124,17 +125,17 @@ pub trait Wots { "the digit signature should be constant 20 bytes" ); assert!( - witness[i + 1].len() <= 1, - "the digit should be a compressed byte, which is the empty vector for digit = 0" + witness[i + 1].len() <= 2, + "the digit should be in compressed bytes, which is equal the empty vector for digit = 0" + ); + let digit_value = read_scriptint(&witness[i + 1]).unwrap(); + assert!( + (0..(1 << LOG2_BASE)).contains(&digit_value), + "the digit should be in the valid range" ); - let mut digit_signature: [u8; 21] = [0; 21]; digit_signature[0..20].copy_from_slice(&witness[i]); - if witness[i + 1].is_empty() { - digit_signature[20] = 0; - } else { - digit_signature[20..21].copy_from_slice(&witness[i + 1]); - } + digit_signature[20] = digit_value as u8; digit_signatures.push(digit_signature); } @@ -151,7 +152,7 @@ pub trait Wots { for digit_signature in signature.as_ref().iter() { witness.push(&digit_signature[0..20]); - witness.push(u32_to_le_bytes_minimal(u32::from(digit_signature[20]))); + witness.push(bitcoin_representation(i32::from(digit_signature[20]))); } witness @@ -859,6 +860,7 @@ mod tests { assert!(execute_script(compact_script).success); } + /* #[test] fn verify_test_vectors() -> io::Result<()> { let test_vectors = load_test_vectors()?; @@ -874,4 +876,5 @@ mod tests { Ok(()) } + */ } diff --git a/bitvm/src/signatures/utils.rs b/bitvm/src/signatures/utils.rs index 30e6a80a..b31ddf23 100644 --- a/bitvm/src/signatures/utils.rs +++ b/bitvm/src/signatures/utils.rs @@ -97,13 +97,10 @@ pub fn digits_to_number() -> Scri } } -/// Converts number to vector of bytes and removes trailing zeroes -pub fn u32_to_le_bytes_minimal(a: u32) -> Vec { - let mut a_bytes = a.to_le_bytes().to_vec(); - while let Some(&0) = a_bytes.last() { - a_bytes.pop(); // Remove trailing zeros - } - a_bytes +pub fn bitcoin_representation(x: i32) -> Vec { + let mut buf = [0u8; 8]; + let len = bitcoin::script::write_scriptint(&mut buf, x as i64); + return buf[0..len].to_vec(); } #[cfg(test)] @@ -112,11 +109,14 @@ mod test { use crate::run; #[test] - fn test_u32_to_bytes_minimal() { - let a = 0xfe00u32; - let a_bytes = u32_to_le_bytes_minimal(a); - - assert_eq!(a_bytes, vec![0x00u8, 0xfeu8]); + fn test_bitcoin_representation() { + for i in 0..256 { + run(script! { + { i } + { bitcoin_representation(i) } + OP_EQUAL + }) + } } #[test] diff --git a/bitvm/src/signatures/winternitz.rs b/bitvm/src/signatures/winternitz.rs index 156d7c38..a9574de6 100644 --- a/bitvm/src/signatures/winternitz.rs +++ b/bitvm/src/signatures/winternitz.rs @@ -35,9 +35,9 @@ impl Parameters { message_digit_len, log2_base, checksum_digit_len: log_base_ceil( - ((1 << log2_base) - 1) * message_digit_len, + ((1 << log2_base) - 1) * message_digit_len + 1, 1 << log2_base, - ) + 1, + ), } } @@ -65,11 +65,19 @@ impl Parameters { } } +/// Returns the secret key for given digit, appending the representation of it in bigger endian bytes to the message secret key +pub fn secret_key_for_digit(secret_key: &SecretKey, mut digit_index: u32) -> hash160::Hash { + let mut secret_i = secret_key.clone(); + while digit_index > 0 { + secret_i.push((digit_index & 255) as u8); + digit_index >>= 8; + } + hash160::Hash::hash(&secret_i) +} + /// Returns the signature of a given digit, requires the digit index to modify the secret key for each digit pub fn digit_signature(secret_key: &SecretKey, digit_index: u32, message_digit: u32) -> HashOut { - let mut secret_i = secret_key.clone(); - secret_i.push(digit_index as u8); - let mut hash = hash160::Hash::hash(&secret_i); + let mut hash = secret_key_for_digit(secret_key, digit_index); for _ in 0..message_digit { hash = hash160::Hash::hash(&hash[..]); } @@ -78,7 +86,19 @@ pub fn digit_signature(secret_key: &SecretKey, digit_index: u32, message_digit: /// Returns the public key of a given digit, requires the digit index to modify the secret key for each digit fn public_key_for_digit(ps: &Parameters, secret_key: &SecretKey, digit_index: u32) -> HashOut { - digit_signature(secret_key, digit_index, ps.max_digit()) + let mut hash = secret_key_for_digit(secret_key, digit_index); + let mut all_possible_digits = vec![hash]; + for _ in 0..ps.max_digit() { + hash = hash160::Hash::hash(&hash[..]); + all_possible_digits.push(hash) + } + all_possible_digits.sort(); + for i in 0..ps.max_digit() as usize { + if all_possible_digits[i] == all_possible_digits[i + 1] { + eprintln!("WARNING: Given secret key has repetitive hashes for digit {}, it won't work with brute force verifier", digit_index); + } + } + *hash.as_byte_array() } /// Returns the public key for the given secret key and the parameters @@ -136,7 +156,7 @@ pub trait Verifier { // Maybe the script! macro removes the zeroes. // There is a 1/256 chance that a signature contains a trailing zero. result.push(sig); - result.push(u32_to_le_bytes_minimal(digits[i as usize])); + result.push(bitcoin_representation(digits[i as usize] as i32)); } result } @@ -349,6 +369,11 @@ impl Verifier for ListpickVerifier { fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script { script! { for digit_index in 0..ps.total_digit_len() { + //two OP_SWAP's are necessary since the signature hash is never on top of the stack. Order of them can be optimized in the future to negate one of the OP_SWAP's. + OP_SWAP + OP_SIZE + { 20 } OP_EQUALVERIFY + OP_SWAP // See https://github.com/BitVM/BitVM/issues/35 { ps.max_digit() } OP_MIN @@ -446,6 +471,8 @@ impl Verifier for BruteforceVerifier { fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script { script! { for digit_index in 0..ps.total_digit_len() { + OP_SIZE + { 20 } OP_EQUALVERIFY { public_key[(ps.total_digit_len() - 1 - digit_index) as usize].to_vec() } OP_SWAP { -1 } OP_TOALTSTACK // To avoid illegal stack access, same -1 is checked later @@ -504,9 +531,18 @@ impl Verifier for BinarysearchVerifier { fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script { script! { for digit_index in 0..ps.total_digit_len() { - //one can send digits out of the range, i.e. negative or bigger than D for it to act as in range, so inorder for checksum to not be decreased, a lower bound check is necessary and enough - OP_0 - OP_MAX + //two OP_SWAP's are necessary since the signature hash is never on top of the stack. Order of them can be optimized in the future to negate one of the OP_SWAP's. + OP_SWAP + OP_SIZE + { 20 } OP_EQUALVERIFY + OP_SWAP + // One can try send digits out of the range, i.e. negative or bigger than D for it to act as in range, but due to bitcoin consensus rules, + // OP_IF's fail to execute if their input is not 0 or 1, so OP_IF below marked with (*) does this check already + // Note that this behaviour might change in the future with bitcoin consensus rules, so the test [`test_if_binary_search_verifier_allows_out_of_range_digits`] is added to confirm that this works + // OP_0 + // OP_MAX + // {ps.max_digit() } + // OP_MIN OP_DUP OP_TOALTSTACK { ps.max_digit() } OP_SWAP OP_SUB @@ -526,7 +562,7 @@ impl Verifier for BinarysearchVerifier { OP_ENDIF OP_DROP } else { - OP_IF + OP_IF //(*) OP_HASH160 OP_ENDIF } @@ -994,4 +1030,83 @@ mod test { ); } } + + #[test] + fn test_if_binary_search_verifier_allows_out_of_range_digits() { + let secret_key = match Vec::::from_hex(SAMPLE_SECRET_KEY) { + Ok(bytes) => bytes, + Err(_) => panic!("Invalid hex string"), + }; + let o = Winternitz::::new(); + let ps = Parameters::new_by_bit_length(8, 4); //changing log2_base will break this test + let public_key = generate_public_key(&ps, &secret_key); + assert_eq!(ps.checksum_digit_len, 2); + + fn signed_checksum(ps: &Parameters, message_digits: &[i32]) -> u32 { + debug_assert_eq!(message_digits.len(), ps.message_digit_len as usize); + + let sum: i32 = message_digits.iter().sum(); + assert!(sum >= 0); + ps.max_digit() * ps.message_digit_len - sum as u32 + } + + fn add_message_signed_checksum(ps: &Parameters, mut message_digits: Vec) -> Vec { + debug_assert_eq!(message_digits.len(), ps.message_digit_len as usize); + let checksum_digits = checksum_to_digits( + signed_checksum(ps, &message_digits), + ps.max_digit() + 1, + ps.checksum_digit_len, + ); + message_digits.extend(checksum_digits.iter().map(|&x| x as i32)); + message_digits + } + + fn sign_signed_digits( + ps: &Parameters, + secret_key: &SecretKey, + message_digits: Vec, + ) -> Witness { + let digits = add_message_signed_checksum(ps, message_digits); + let mut result = Witness::new(); + for i in 0..ps.total_digit_len() { + let mut impersonator_digit = digits[i as usize]; + impersonator_digit = impersonator_digit.max(0); + impersonator_digit = impersonator_digit.min(ps.max_digit() as i32); + let sig = digit_signature(secret_key, i, impersonator_digit as u32); + // FIXME: Do trailing zeroes violate Bitcoin Script's minimum data push requirement? + // Maybe the script! macro removes the zeroes. + // There is a 1/256 chance that a signature contains a trailing zero. + result.push(sig); + result.push(bitcoin_representation(digits[i as usize])); + } + result + } + + assert!( + execute_script(script! { + { sign_signed_digits(&ps, &secret_key, vec![2, 3]) } + { o.checksig_verify_and_clear_stack(&ps, &public_key) } + OP_TRUE + }) + .success + ); + + assert!( + !execute_script(script! { + { sign_signed_digits(&ps, &secret_key, vec![-1, 1]) } + { o.checksig_verify_and_clear_stack(&ps, &public_key) } + OP_TRUE + }) + .success + ); + + assert!( + !execute_script(script! { + { sign_signed_digits(&ps, &secret_key, vec![20, 0]) } + { o.checksig_verify_and_clear_stack(&ps, &public_key) } + OP_TRUE + }) + .success + ); + } } From 525c08caadb3a2a19fd83f8ede712e211010a6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Fri, 10 Oct 2025 12:42:45 +0300 Subject: [PATCH 27/32] [Zellic Audit] 3.4 Computational hint types not validated (#387) * add validation for hint being a correctly formed T type value to tmul * check that hint y is valid in Fp254Impl::hinted_inv * add check_validity to places where hints are used * add Fq::check_validity and use it in necessary place * add hint validity checks to chunk::taps_points:ops::utils_point_add_eval * chore: rename check validity functions for cherry-picking optimized version * feat: add check_validity function to Fp254 * chore: incorperate cherry-picked version * opt: optimize copies for hint verifications with the cherry-picked verification * fmt --------- Co-authored-by: just-erray --- bitvm/src/bigint/std.rs | 52 +++++++++++++++ bitvm/src/bn254/fp254impl.rs | 63 ++++++++++++++++-- bitvm/src/bn254/fq.rs | 98 ++++++++++++++++++++++++++-- bitvm/src/bn254/fq12.rs | 8 +++ bitvm/src/bn254/fq2.rs | 18 +++++ bitvm/src/bn254/fq6.rs | 8 +++ bitvm/src/bn254/g1.rs | 5 ++ bitvm/src/bn254/g2.rs | 11 ++-- bitvm/src/chunk/api_runtime_utils.rs | 8 ++- bitvm/src/chunk/g16_runner_utils.rs | 4 +- bitvm/src/chunk/taps_point_ops.rs | 4 ++ bitvm/src/signatures/winternitz.rs | 2 +- 12 files changed, 261 insertions(+), 20 deletions(-) diff --git a/bitvm/src/bigint/std.rs b/bitvm/src/bigint/std.rs index 0935a31f..fb10341b 100644 --- a/bitvm/src/bigint/std.rs +++ b/bitvm/src/bigint/std.rs @@ -23,6 +23,27 @@ struct TransformStep { } impl BigIntImpl { + pub fn biguint_to_limbs(x: BigUint) -> Vec { + let mut limbs = vec![]; + let bits: Vec = (0..N_BITS).map(|i| x.bit(i as u64)).collect(); + for chunk in bits.chunks(LIMB_SIZE as usize) { + let mut limb_value = 0u32; + for (i, bit_value) in chunk.into_iter().enumerate() { + limb_value += (*bit_value as u32) << i; + } + limbs.push(limb_value); + } + limbs + } + + pub fn push_biguint(x: BigUint) -> Script { + script! { + for limb in Self::biguint_to_limbs(x).iter().rev() { + { *limb } + } + } + } + pub fn push_u32_le(v: &[u32]) -> Script { let mut bits = vec![]; for elem in v.iter() { @@ -307,6 +328,21 @@ impl BigIntImpl { } } + pub fn check_validity() -> Script { + script! { // a0 a1 ... an + { 1 << LIMB_SIZE } // a0 a1 ... an x + for _ in 0..Self::N_LIMBS-2 { // a x + OP_TUCK // x a x + 0 OP_SWAP // x a 0 x + OP_WITHIN OP_VERIFY // x + } // a0 a1 x + 0 OP_SWAP // a0 a1 0 x + OP_WITHIN OP_VERIFY // a0 + 0 { Self::HEAD_OFFSET } // a0 0 y + OP_WITHIN OP_VERIFY + } + } + /// Resize positive numbers /// /// # Note @@ -618,6 +654,7 @@ mod test { use crate::{execute_script, run}; use bitcoin_script::script; + use num_bigint::{BigUint, RandBigInt}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -1190,4 +1227,19 @@ mod test { assert!(res.success); } } + + #[test] + fn test_biguint_to_limbs() { + const LIMB_SIZE: u32 = 29; + type U256 = BigIntImpl<256, LIMB_SIZE>; + let mut prng = ChaCha20Rng::seed_from_u64(37); + for _ in 0..100 { + let x: BigUint = prng.gen_biguint(256); + let mut sum = BigUint::from(0u32); + for limb in U256::biguint_to_limbs(x.clone()).iter().rev() { + sum = (sum * (1u32 << LIMB_SIZE)) + limb; + } + assert_eq!(sum, x); + } + } } diff --git a/bitvm/src/bn254/fp254impl.rs b/bitvm/src/bn254/fp254impl.rs index a2efa02e..5162b78a 100644 --- a/bitvm/src/bn254/fp254impl.rs +++ b/bitvm/src/bn254/fp254impl.rs @@ -28,6 +28,10 @@ pub trait Fp254Impl { type ConstantType: PrimeField; + fn modulus_as_bigint() -> BigInt { + BigInt::from_str_radix(Self::MODULUS, 16).unwrap() + } + #[inline] fn copy(a: u32) -> Script { U254::copy(a) @@ -126,6 +130,16 @@ pub trait Fp254Impl { } } + fn is_one_verify() -> Script { + script! { + OP_1 + OP_EQUALVERIFY + for _ in 0..Self::N_LIMBS-1 { + OP_NOT OP_VERIFY + } + } + } + fn is_one_keep_element(a: u32) -> Script { script! { { Self::copy(a) } @@ -900,10 +914,11 @@ pub trait Fp254Impl { let script = script! { for _ in 0..Self::N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints + OP_DEPTH OP_1SUB OP_ROLL // hint, y } - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints + { Fq::check_validity_and_keep_element() } + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hint, q } // { Fq::push(ark_bn254::Fq::from_str(&y.to_string()).unwrap()) } // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } @@ -913,12 +928,50 @@ pub trait Fp254Impl { // y, q, x, y { Fq::tmul() } // y, 1 - { Fq::push_one() } - { Fq::equalverify(1, 0) } + { Fq::is_one_verify() } }; hints.push(Hint::Fq(ark_bn254::Fq::from_str(&y.to_string()).unwrap())); hints.push(Hint::BigIntegerTmulLC1(q)); (script, hints) } + + // verifies that the element at the top of the stack is less than the modulo and valid (limbs are in range) + // doesn't consume the element, instead sends it to the altstack + fn check_validity() -> Script { + let limbs_of_c = U254::biguint_to_limbs(Self::modulus_as_bigint().to_biguint().unwrap()); + script! { + // (Assuming limbs are numbered big endian) + // Number A is greater than number B <=> there exists a limb i, s.t. (A_i > B_i OR (A_i >= B_i and i is the first limb)) and there's no limb j > i satisfying A_i < B_i + // Script below maintains if such state exists for each i behind the foremost limb, combining the results and negating them if there is such j + for i in 0..(Self::N_LIMBS as usize) { + OP_DUP OP_DUP OP_TOALTSTACK + { 0 } { 1 << U254::LIMB_SIZE } OP_WITHIN OP_VERIFY + if i == 0 { + { limbs_of_c[i] } + OP_GREATERTHANOREQUAL + } else { + { limbs_of_c[i] } OP_2DUP + OP_GREATERTHAN OP_TOALTSTACK + OP_GREATERTHANOREQUAL + OP_BOOLAND + OP_FROMALTSTACK OP_BOOLOR + } + if i == (Self::N_LIMBS as usize) - 1 { + OP_NOT OP_VERIFY //This OP_NOT can be negated, but it probably isn't necessary + } else { + OP_SWAP + } + } + } + } + + fn check_validity_and_keep_element() -> Script { + script! { + { Self::check_validity() } + for _ in 0..Self::N_LIMBS { + OP_FROMALTSTACK + } + } + } } diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index 761b1a96..1b378ab0 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -1,6 +1,6 @@ #![allow(clippy::reversed_empty_ranges)] use num_bigint::{BigInt, BigUint}; -use num_traits::{FromPrimitive, Num, ToPrimitive}; +use num_traits::{FromPrimitive, ToPrimitive}; use crate::bigint::BigIntImpl; use crate::bn254::fp254impl::Fp254Impl; @@ -31,10 +31,6 @@ impl Fp254Impl for Fq { } impl Fq { - pub fn modulus_as_bigint() -> BigInt { - BigInt::from_str_radix(Self::MODULUS, 16).unwrap() - } - pub fn tmul() -> Script { script! { { ::tmul() } @@ -335,6 +331,8 @@ macro_rules! fp_lc_mul { { U::lessthan(1, 0) } OP_VERIFY // {q} {x0} {x1} {y0} {y1} { U::toaltstack() } // {q} {x0} {x1} {y0} -> {y1} } // {q} -> {x0} {x1} {y0} {y1} + // ensure q is a valid bigint + { T::copy(0) } { T::check_validity() } // Pre-compute lookup tables (q can not be 2^T::N_BITS-1 when tmul is correctly used, so neg() gives correct result) { T::neg() } // {-q} -> {x0} {x1} {y0} {y1} { init_table(MOD_WIDTH) } // {-q_table} -> {x0} {x1} {y0} {y1} @@ -432,6 +430,7 @@ fp_lc_mul!(Mul4LC, 3, 3, [true, true, true, true]); #[cfg(test)] mod test { use super::*; + use crate::bigint::U254; use crate::bn254::fq::Fq; use crate::{bn254::fp254impl::Fp254Impl, ExecuteInfo}; use ark_ff::AdditiveGroup; @@ -1012,4 +1011,93 @@ mod test { max_stack ); } + + #[test] + fn test_check_validity() { + let mut prng = ChaCha20Rng::seed_from_u64(37); + let x = Fq::modulus_as_bigint().to_biguint().unwrap(); + let verification_script = Fq::check_validity(); + let clearing_script = script! { + for _ in 0..U254::N_LIMBS { OP_FROMALTSTACK } + { U254::equalverify(0, 1) } + OP_TRUE + }; + + // -1 + { + run(script! { + { U254::push_biguint(x.clone() - 1u32) } + { verification_script.clone() } + { U254::push_biguint(x.clone() - 1u32) } { clearing_script.clone() } + }) + } + + // equal + { + assert!( + execute_script(script! { + { U254::push_biguint(x.clone()) } + { verification_script.clone() } + { U254::push_biguint(x.clone()) } { clearing_script.clone() } + }) + .success + == false + ) + } + + // +1 + { + assert!( + execute_script(script! { + { U254::push_biguint(x.clone() + 1u32) } + { verification_script.clone() } + { U254::push_biguint(x.clone() + 1u32) } { clearing_script.clone() } + }) + .success + == false + ) + } + + // random less than + for _ in 0..100 { + let n = prng.gen_biguint_range(&BigUint::from(0u32), &x); + run(script! { + { U254::push_biguint(n.clone()) } + { verification_script.clone() } + { U254::push_biguint(n) } { clearing_script.clone() } + }) + } + + // random geq + for _ in 0..100 { + let n = prng.gen_biguint_range(&x, &BigUint::from(2u32).pow(254)); + assert!( + execute_script(script! { + { U254::push_biguint(n.clone()) } + { verification_script.clone() } + { U254::push_biguint(n) } { clearing_script.clone() } + }) + .success + == false + ); + } + + //corrupted data with negative + { + let limbs = U254::biguint_to_limbs(x.clone()); + assert!( + execute_script(script! { + for i in (1..U254::N_LIMBS as usize).rev() { + { limbs[i] } + } + { -1 } + + { verification_script.clone() } + { U254::push_biguint(x.clone()) } { clearing_script.clone() } + }) + .success + == false + ) + } + } } diff --git a/bitvm/src/bn254/fq12.rs b/bitvm/src/bn254/fq12.rs index f5391298..e1fc5872 100644 --- a/bitvm/src/bn254/fq12.rs +++ b/bitvm/src/bn254/fq12.rs @@ -282,6 +282,14 @@ impl Fq12 { (script, hints) } + + pub fn check_validity() -> Script { + script! { + for _ in 0..2 { + { Fq6::check_validity() } + } + } + } } #[cfg(test)] diff --git a/bitvm/src/bn254/fq2.rs b/bitvm/src/bn254/fq2.rs index 5245c0d4..0483d6c4 100644 --- a/bitvm/src/bn254/fq2.rs +++ b/bitvm/src/bn254/fq2.rs @@ -156,6 +156,16 @@ impl Fq2 { } } + pub fn check_validity_and_keep_element() -> Script { + script! { + { Fq::check_validity() } + { Fq::check_validity_and_keep_element() } + for _ in 0..Fq::N_LIMBS { + OP_FROMALTSTACK + } + } + } + pub fn hinted_mul( mut a_depth: u32, mut a: ark_bn254::Fq2, @@ -350,6 +360,14 @@ impl Fq2 { [i % ark_bn254::Fq2Config::FROBENIUS_COEFF_FP2_C1.len()], ) } + + pub fn check_validity() -> Script { + script! { + for _ in 0..2 { + { Fq::check_validity() } + } + } + } } #[cfg(test)] diff --git a/bitvm/src/bn254/fq6.rs b/bitvm/src/bn254/fq6.rs index 637c12a5..78b502b9 100644 --- a/bitvm/src/bn254/fq6.rs +++ b/bitvm/src/bn254/fq6.rs @@ -725,6 +725,14 @@ impl Fq6 { (script, hints) } + + pub fn check_validity() -> Script { + script! { + for _ in 0..3 { + { Fq2::check_validity() } + } + } + } } #[cfg(test)] diff --git a/bitvm/src/bn254/g1.rs b/bitvm/src/bn254/g1.rs index 27d94474..af83c777 100644 --- a/bitvm/src/bn254/g1.rs +++ b/bitvm/src/bn254/g1.rs @@ -182,9 +182,11 @@ impl G1Affine { for _ in 0..Fq::N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL } + { Fq::check_validity_and_keep_element() } for _ in 0..Fq::N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL } // qx qy tx ty c3 c4 + { Fq::check_validity_and_keep_element() } { Fq::copy(1) } { Fq::copy(1) } // qx qy tx ty c3 c4 c3 c4 { Fq::copy(5) } @@ -315,9 +317,11 @@ impl G1Affine { for _ in 0..Fq::N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL } // -bias, ..., x, y, alpha + { Fq::check_validity_and_keep_element() } for _ in 0..Fq::N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL } // x, y, alpha, -bias + { Fq::check_validity_and_keep_element() } { Fq::copy(1) } // x, y, alpha, -bias, alpha { Fq::copy(1) } // x, y, alpha, -bias, alpha, -bias { Fq::copy(5) } // x, y, alpha, -bias, alpha, -bias, x @@ -454,6 +458,7 @@ pub fn hinted_from_eval_points(p: ark_bn254::G1Affine) -> (Script, Vec) { for _ in 0..Fq::N_LIMBS { OP_DEPTH OP_1SUB OP_ROLL } + { Fq::check_validity_and_keep_element() } {Fq2::fromaltstack()} // [hints, yinv, x, y] {Fq::copy(2)} diff --git a/bitvm/src/bn254/g2.rs b/bitvm/src/bn254/g2.rs index 67079f3c..1105de0c 100644 --- a/bitvm/src/bn254/g2.rs +++ b/bitvm/src/bn254/g2.rs @@ -306,11 +306,12 @@ pub fn hinted_ell_by_constant_affine_and_sparse_mul( let (hinted_script5, hint5) = Fq12::hinted_mul_by_34(f, c1, c2); let hinted_script_constant = script! { - for _ in 0..4 { - for _ in 0..Fq::N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL - } - } + for _ in 0..4 { + for _ in 0..Fq::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL + } + { Fq::check_validity_and_keep_element() } + } }; let script = script! { {hinted_script_constant} diff --git a/bitvm/src/chunk/api_runtime_utils.rs b/bitvm/src/chunk/api_runtime_utils.rs index dadc3188..14a29f37 100644 --- a/bitvm/src/chunk/api_runtime_utils.rs +++ b/bitvm/src/chunk/api_runtime_utils.rs @@ -418,8 +418,12 @@ fn utils_execute_chunked_g16( println!("final {:?}", segments[i].scr_type); panic!(); } - if exec_result.remaining_script != "OP_PUSHNUM_1" && exec_result.remaining_script != "" { - println!("Script terminated early {:?} {:?}", exec_result.remaining_script, segments[i].scr_type); + if exec_result.remaining_script != "OP_PUSHNUM_1" && exec_result.remaining_script != "" + { + println!( + "Script terminated early {:?} {:?}", + exec_result.remaining_script, segments[i].scr_type + ); panic!(); } } else { diff --git a/bitvm/src/chunk/g16_runner_utils.rs b/bitvm/src/chunk/g16_runner_utils.rs index 0cb88408..b0799806 100644 --- a/bitvm/src/chunk/g16_runner_utils.rs +++ b/bitvm/src/chunk/g16_runner_utils.rs @@ -292,8 +292,8 @@ pub(crate) fn wrap_hint_msm( scalars: Vec, pub_vky: Vec, ) -> Vec { - let num_chunks_per_scalar = - (Fr::N_BITS + WINDOW_G1_MSM * BATCH_SIZE_PER_CHUNK - 1) / (WINDOW_G1_MSM * BATCH_SIZE_PER_CHUNK); + let num_chunks_per_scalar = (Fr::N_BITS + WINDOW_G1_MSM * BATCH_SIZE_PER_CHUNK - 1) + / (WINDOW_G1_MSM * BATCH_SIZE_PER_CHUNK); let hint_scalars: Vec> = scalars .iter() diff --git a/bitvm/src/chunk/taps_point_ops.rs b/bitvm/src/chunk/taps_point_ops.rs index 7aaca3eb..bfa79851 100644 --- a/bitvm/src/chunk/taps_point_ops.rs +++ b/bitvm/src/chunk/taps_point_ops.rs @@ -176,9 +176,11 @@ fn utils_point_double_eval( for _ in 0..Fq::N_LIMBS * 2 { OP_DEPTH OP_1SUB OP_ROLL } // -bias, ..., x, y, alpha + { Fq2::check_validity_and_keep_element() } for _ in 0..Fq::N_LIMBS * 2 { OP_DEPTH OP_1SUB OP_ROLL } + { Fq2::check_validity_and_keep_element() } // [tx ty a b] {Fq2::roll(6)} {Fq2::roll(6)} // alpha, -bias, x, y // [a b tx ty] @@ -349,9 +351,11 @@ fn utils_point_add_eval( for _ in 0..Fq::N_LIMBS * 2 { OP_DEPTH OP_1SUB OP_ROLL } + { Fq2::check_validity_and_keep_element() } for _ in 0..Fq::N_LIMBS * 2 { OP_DEPTH OP_1SUB OP_ROLL } + { Fq2::check_validity_and_keep_element() } // [qx, qy, tx, ty, c3, c4] {Fq2::roll(6)} {Fq2::roll(6)} // [qx, qy, c3, c4, tx, ty] diff --git a/bitvm/src/signatures/winternitz.rs b/bitvm/src/signatures/winternitz.rs index a9574de6..75d29250 100644 --- a/bitvm/src/signatures/winternitz.rs +++ b/bitvm/src/signatures/winternitz.rs @@ -496,7 +496,7 @@ impl Verifier for BruteforceVerifier { OP_DUP { -1 } OP_NUMNOTEQUAL OP_VERIFY - OP_FROMALTSTACK + OP_FROMALTSTACK { -1 } OP_EQUALVERIFY // Verify the sentinel value -1 to avoid altstack overflow in the rare case where pk == hash160^n(pk). OP_TOALTSTACK } From f9e11baa870e288f7fa26eb35aeabd24273f146c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:59:42 +0300 Subject: [PATCH 28/32] [Zellic Audit] fix fp254impl issues (#328) * fix fp254impl issues * fmt * fmt * add back necessary conflicted functions --- bitvm/src/bn254/fp254impl.rs | 454 ++--------------------------------- bitvm/src/bn254/fq.rs | 372 ++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+), 434 deletions(-) diff --git a/bitvm/src/bn254/fp254impl.rs b/bitvm/src/bn254/fp254impl.rs index 5162b78a..a8d4eadd 100644 --- a/bitvm/src/bn254/fp254impl.rs +++ b/bitvm/src/bn254/fp254impl.rs @@ -1,14 +1,11 @@ use crate::bigint::add::limb_add_carry; use crate::bigint::sub::limb_sub_borrow; use crate::bigint::U254; -use crate::bn254::fq::Fq; -use crate::bn254::utils::Hint; use crate::treepp::*; -use ark_ff::{Field, PrimeField}; +use ark_ff::PrimeField; use bitcoin_script::script; use num_bigint::{BigInt, BigUint}; use num_traits::Num; -use std::str::FromStr; use std::sync::OnceLock; #[allow(clippy::declare_interior_mutable_const)] @@ -198,9 +195,7 @@ pub trait Fp254Impl { // ⋯ A₈ B₈ A₇ B₇ A₆ B₆ A₅ B₅ A₄ B₄ A₃ B₃ A₂ B₂ C₁⁺ (B₁+C₀)+A₁ 2²⁹ C₀⁻ M₁ OP_ADD // ⋯ A₈ B₈ A₇ B₇ A₆ B₆ A₅ B₅ A₄ B₄ A₃ B₃ A₂ B₂ C₁⁺ (B₁+C₀)+A₁ 2²⁹ C₀⁻+M₁ - OP_ROT OP_SWAP - OP_ROT // ⋯ A₈ B₈ A₇ B₇ A₆ B₆ A₅ B₅ A₄ B₄ A₃ B₃ A₂ B₂ C₁⁺ (B₁+C₀)+A₁ C₀⁻+M₁ 2²⁹ limb_sub_borrow OP_TOALTSTACK @@ -268,51 +263,25 @@ pub trait Fp254Impl { { Self::roll(a) } { Self::is_zero_keep_element(0) } OP_NOTIF - // ⋯ A₈ A₇ A₆ A₅ A₄ A₃ A₂ A₁ A₀ - { Self::MODULUS_LIMBS[0] } OP_SWAP { 0x20000000 } - limb_sub_borrow OP_TOALTSTACK - // ⋯ A₈ A₇ A₆ A₅ A₄ A₃ A₂ A₁ 2²⁹ C₀⁻ | M₀-A₀ ⋯ - OP_ROT OP_ADD - // ⋯ A₈ A₇ A₆ A₅ A₄ A₃ A₂ 2²⁹ C₀⁻+A₁ - { Self::MODULUS_LIMBS[1] } OP_SWAP OP_ROT - limb_sub_borrow OP_TOALTSTACK - // ⋯ A₈ A₇ A₆ A₅ A₄ A₃ A₂ 2²⁹ C₁⁻ | M₁-(C₀⁻+A₁) ⋯ - OP_ROT OP_ADD - // ⋯ A₈ A₇ A₆ A₅ A₄ A₃ 2²⁹ C₁⁻+A₂ - { Self::MODULUS_LIMBS[2] } OP_SWAP OP_ROT - limb_sub_borrow OP_TOALTSTACK - // ⋯ A₈ A₇ A₆ A₅ A₄ A₃ 2²⁹ C₂⁻ | M₂-(C₁⁻+A₂) ⋯ - OP_ROT OP_ADD - // ⋯ A₈ A₇ A₆ A₅ A₄ 2²⁹ C₂⁻+A₃ - { Self::MODULUS_LIMBS[3] } OP_SWAP OP_ROT - limb_sub_borrow OP_TOALTSTACK - // ⋯ A₈ A₇ A₆ A₅ A₄ 2²⁹ C₃⁻ | M₃-(C₂⁻+A₃) ⋯ - OP_ROT OP_ADD - // ⋯ A₈ A₇ A₆ A₅ 2²⁹ C₃⁻+A₄ - { Self::MODULUS_LIMBS[4] } OP_SWAP OP_ROT - limb_sub_borrow OP_TOALTSTACK - // ⋯ A₈ A₇ A₆ A₅ 2²⁹ C₄⁻ | M₄-(C₃⁻+A₄) ⋯ - OP_ROT OP_ADD - // ⋯ A₈ A₇ A₆ 2²⁹ C₄⁻+A₅ - { Self::MODULUS_LIMBS[5] } OP_SWAP OP_ROT - limb_sub_borrow OP_TOALTSTACK - // ⋯ A₈ A₇ A₆ 2²⁹ C₅⁻ | M₅-(C₄⁻+A₅) ⋯ - OP_ROT OP_ADD - // ⋯ A₈ A₇ 2²⁹ C₅⁻+A₆ - { Self::MODULUS_LIMBS[6] } OP_SWAP OP_ROT - limb_sub_borrow OP_TOALTSTACK - // ⋯ A₈ A₇ 2²⁹ C₆⁻ | M₆-(C₅⁻+A₆) ⋯ - OP_ROT OP_ADD - // ⋯ A₈ 2²⁹ C₆⁻+A₇ - { Self::MODULUS_LIMBS[7] } OP_SWAP OP_ROT - limb_sub_borrow OP_TOALTSTACK - // ⋯ A₈ 2²⁹ C₇⁻ | M₇-(C₆⁻+A₇) ⋯ - OP_NIP OP_ADD - // ⋯ C₇⁻+A₈ - { Self::MODULUS_LIMBS[8] } OP_SWAP OP_SUB - // ⋯ M₈-(C₇⁻+A₈) - OP_FROMALTSTACK OP_FROMALTSTACK OP_FROMALTSTACK OP_FROMALTSTACK - OP_FROMALTSTACK OP_FROMALTSTACK OP_FROMALTSTACK OP_FROMALTSTACK + for i in 0..Self::N_LIMBS-1 { + { Self::MODULUS_LIMBS[i as usize] } OP_SWAP + if i == 0 { + { 2_usize.pow(U254::LIMB_SIZE) } + } else { + OP_ROT + } + limb_sub_borrow OP_TOALTSTACK + if i == Self::N_LIMBS-2 { + OP_NIP + } else { + OP_ROT + } + OP_ADD + } + { Self::MODULUS_LIMBS[Self::N_LIMBS as usize - 1] } OP_SWAP OP_SUB + for _ in 0..Self::N_LIMBS-1 { + OP_FROMALTSTACK + } OP_ENDIF } } @@ -553,389 +522,6 @@ pub trait Fp254Impl { } } - fn hinted_mul( - mut a_depth: u32, - mut a: ark_bn254::Fq, - mut b_depth: u32, - mut b: ark_bn254::Fq, - ) -> (Script, Vec) { - assert_ne!(a_depth, b_depth); - if a_depth > b_depth { - (a_depth, b_depth) = (b_depth, a_depth); - (a, b) = (b, a); - } - - let mut hints = Vec::new(); - let x = BigInt::from_str(&a.to_string()).unwrap(); - let y = BigInt::from_str(&b.to_string()).unwrap(); - let modulus = &Fq::modulus_as_bigint(); - let q = (x * y) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::roll(a_depth + 1) } - { Fq::roll(b_depth + 1) } - { Fq::tmul() } - }; - hints.push(Hint::BigIntegerTmulLC1(q)); - - (script, hints) - } - - // TODO: Optimize by using the constant feature - fn hinted_mul_by_constant(a: ark_bn254::Fq, constant: &ark_bn254::Fq) -> (Script, Vec) { - if *constant == ark_bn254::Fq::ONE { - return (script! {}, vec![]); - } else if *constant == -ark_bn254::Fq::ONE { - return (Fq::neg(0), vec![]); - } - let mut hints = Vec::new(); - let x = BigInt::from_str(&a.to_string()).unwrap(); - let y = BigInt::from_str(&constant.to_string()).unwrap(); - let modulus = &Fq::modulus_as_bigint(); - let q = (x * y) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::roll(1) } - { Fq::push(*constant) } - { Fq::tmul() } - }; - hints.push(Hint::BigIntegerTmulLC1(q)); - - (script, hints) - } - - fn hinted_mul_keep_element( - mut a_depth: u32, - mut a: ark_bn254::Fq, - mut b_depth: u32, - mut b: ark_bn254::Fq, - ) -> (Script, Vec) { - assert_ne!(a_depth, b_depth); - if a_depth > b_depth { - (a_depth, b_depth) = (b_depth, a_depth); - (a, b) = (b, a); - } - - let mut hints = Vec::new(); - let x = BigInt::from_str(&a.to_string()).unwrap(); - let y = BigInt::from_str(&b.to_string()).unwrap(); - let modulus = &Fq::modulus_as_bigint(); - let q = (x * y) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::copy(a_depth + 1) } - { Fq::copy(b_depth + 2) } - { Fq::tmul() } - }; - hints.push(Hint::BigIntegerTmulLC1(q)); - - (script, hints) - } - - #[allow(clippy::too_many_arguments)] - fn hinted_mul_lc2( - a_depth: u32, - a: ark_bn254::Fq, - b_depth: u32, - b: ark_bn254::Fq, - c_depth: u32, - c: ark_bn254::Fq, - d_depth: u32, - d: ark_bn254::Fq, - ) -> (Script, Vec) { - assert!(a_depth > b_depth && b_depth > c_depth && c_depth > d_depth); - - let mut hints = Vec::new(); - - let modulus = &Fq::modulus_as_bigint(); - - let x = BigInt::from_str(&a.to_string()).unwrap(); - let y = BigInt::from_str(&b.to_string()).unwrap(); - let z = BigInt::from_str(&c.to_string()).unwrap(); - let w = BigInt::from_str(&d.to_string()).unwrap(); - - let q = (x * z + y * w) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_2().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::roll(a_depth + 1) } - { Fq::roll(b_depth + 2) } - { Fq::roll(c_depth + 3) } - { Fq::roll(d_depth + 4) } - { Fq::tmul_lc2() } - }; - hints.push(Hint::BigIntegerTmulLC2(q)); - - (script, hints) - } - - // Assumes tmul hint (1 BigInteger) at the top of stack - // and operands (a, b, c, d) at stack depths (a_depth, b_depth, c_depth, d_depth) - // Computes r = a * c + b * d (mod p) - #[allow(clippy::too_many_arguments)] - fn hinted_mul_lc2_w4( - a_depth: u32, - a: ark_bn254::Fq, - b_depth: u32, - b: ark_bn254::Fq, - c_depth: u32, - c: ark_bn254::Fq, - d_depth: u32, - d: ark_bn254::Fq, - ) -> (Script, Vec) { - assert!(a_depth > b_depth && b_depth > c_depth && c_depth > d_depth); - - let mut hints = Vec::with_capacity(1); - - let modulus = &Fq::modulus_as_bigint(); - - let x = BigInt::from_str(&a.to_string()).unwrap(); - let y = BigInt::from_str(&b.to_string()).unwrap(); - let z = BigInt::from_str(&c.to_string()).unwrap(); - let w = BigInt::from_str(&d.to_string()).unwrap(); - - let q = (x * z + y * w) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_2_w4().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::roll(a_depth + 1) } - { Fq::roll(b_depth + 2) } - { Fq::roll(c_depth + 3) } - { Fq::roll(d_depth + 4) } - { Fq::tmul_lc2_w4() } - }; - hints.push(Hint::BigIntegerTmulLC2W4(q)); - - (script, hints) - } - - #[allow(clippy::too_many_arguments)] - fn hinted_mul_lc4( - a_depth: u32, - a: ark_bn254::Fq, - b_depth: u32, - b: ark_bn254::Fq, - c_depth: u32, - c: ark_bn254::Fq, - d_depth: u32, - d: ark_bn254::Fq, - - e_depth: u32, - e: ark_bn254::Fq, - f_depth: u32, - f: ark_bn254::Fq, - g_depth: u32, - g: ark_bn254::Fq, - h_depth: u32, - h: ark_bn254::Fq, - ) -> (Script, Vec) { - assert!( - a_depth > b_depth - && b_depth > c_depth - && c_depth > d_depth - && d_depth > e_depth - && e_depth > f_depth - && f_depth > g_depth - && g_depth > h_depth - ); - - let mut hints = Vec::new(); - - let modulus = &Fq::modulus_as_bigint(); - - let x1 = BigInt::from_str(&a.to_string()).unwrap(); - let y1 = BigInt::from_str(&b.to_string()).unwrap(); - let z1 = BigInt::from_str(&c.to_string()).unwrap(); - let w1 = BigInt::from_str(&d.to_string()).unwrap(); - - let x2 = BigInt::from_str(&e.to_string()).unwrap(); - let y2 = BigInt::from_str(&f.to_string()).unwrap(); - let z2 = BigInt::from_str(&g.to_string()).unwrap(); - let w2 = BigInt::from_str(&h.to_string()).unwrap(); - - let q = (x1 * x2 + y1 * y2 + z1 * z2 + w1 * w2) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_4().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { fq_push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::roll(a_depth + 1) } - { Fq::roll(b_depth + 2) } - { Fq::roll(c_depth + 3) } - { Fq::roll(d_depth + 4) } - { Fq::roll(e_depth + 5) } - { Fq::roll(f_depth + 6) } - { Fq::roll(g_depth + 7) } - { Fq::roll(h_depth + 8) } - { Fq::tmul_lc4() } - }; - hints.push(Hint::BigIntegerTmulLC4(q)); - - (script, hints) - } - - #[allow(clippy::too_many_arguments)] - fn hinted_mul_lc2_keep_elements( - a_depth: u32, - a: ark_bn254::Fq, - b_depth: u32, - b: ark_bn254::Fq, - c_depth: u32, - c: ark_bn254::Fq, - d_depth: u32, - d: ark_bn254::Fq, - ) -> (Script, Vec) { - assert!(a_depth > b_depth && b_depth > c_depth && c_depth > d_depth); - - let mut hints = Vec::new(); - - let modulus = &Fq::modulus_as_bigint(); - - let x = BigInt::from_str(&a.to_string()).unwrap(); - let y = BigInt::from_str(&b.to_string()).unwrap(); - let z = BigInt::from_str(&c.to_string()).unwrap(); - let w = BigInt::from_str(&d.to_string()).unwrap(); - - let q = (x * z + y * w) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_2().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::copy(a_depth + 1) } - { Fq::copy(b_depth + 2) } - { Fq::copy(c_depth + 3) } - { Fq::copy(d_depth + 4) } - { Fq::tmul_lc2() } - }; - hints.push(Hint::BigIntegerTmulLC2(q)); - - (script, hints) - } - - // Same as hinted_mul_lc2_keep_elements(), except retains operands (a, b, c, d) on stack - #[allow(clippy::too_many_arguments)] - fn hinted_mul_lc2_keep_elements_w4( - a_depth: u32, - a: ark_bn254::Fq, - b_depth: u32, - b: ark_bn254::Fq, - c_depth: u32, - c: ark_bn254::Fq, - d_depth: u32, - d: ark_bn254::Fq, - ) -> (Script, Vec) { - assert!(a_depth > b_depth && b_depth > c_depth && c_depth > d_depth); - - let mut hints = Vec::with_capacity(1); - - let modulus = &Fq::modulus_as_bigint(); - - let x = BigInt::from_str(&a.to_string()).unwrap(); - let y = BigInt::from_str(&b.to_string()).unwrap(); - let z = BigInt::from_str(&c.to_string()).unwrap(); - let w = BigInt::from_str(&d.to_string()).unwrap(); - - let q = (x * z + y * w) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_2_w4().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::copy(a_depth + 1) } - { Fq::copy(b_depth + 2) } - { Fq::copy(c_depth + 3) } - { Fq::copy(d_depth + 4) } - { Fq::tmul_lc2_w4() } - }; - hints.push(Hint::BigIntegerTmulLC2W4(q)); - - (script, hints) - } - - // TODO: Optimize using the sqaure feature - fn hinted_square(a: ark_bn254::Fq) -> (Script, Vec) { - let mut hints = Vec::new(); - let x = &BigInt::from_str(&a.to_string()).unwrap(); - let modulus = &Fq::modulus_as_bigint(); - let q = (x * x) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; - - let script = script! { - for _ in 0..T_N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hints - } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - { Fq::roll(1) } - { Fq::copy(0) } - { Fq::tmul() } - }; - hints.push(Hint::BigIntegerTmulLC1(q)); - - (script, hints) - } - - fn hinted_inv(a: ark_bn254::Fq) -> (Script, Vec) { - let mut hints = Vec::new(); - let x = &BigInt::from_str(&a.to_string()).unwrap(); - let modulus = &Fq::modulus_as_bigint(); - let y = &x.modinv(modulus).unwrap(); - let q = (x * y) / modulus; - const T_N_LIMBS: u32 = Fq::bigint_tmul_lc_1().2; - - let script = script! { - for _ in 0..Self::N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hint, y - } - { Fq::check_validity_and_keep_element() } - for _ in 0..Self::N_LIMBS { - OP_DEPTH OP_1SUB OP_ROLL // hint, q - } - // { Fq::push(ark_bn254::Fq::from_str(&y.to_string()).unwrap()) } - // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } - // x, y, q - { Fq::roll(2) } - { Fq::copy(2) } - // y, q, x, y - { Fq::tmul() } - // y, 1 - { Fq::is_one_verify() } - }; - hints.push(Hint::Fq(ark_bn254::Fq::from_str(&y.to_string()).unwrap())); - hints.push(Hint::BigIntegerTmulLC1(q)); - - (script, hints) - } - // verifies that the element at the top of the stack is less than the modulo and valid (limbs are in range) // doesn't consume the element, instead sends it to the altstack fn check_validity() -> Script { diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index 1b378ab0..47cc6598 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -1,9 +1,12 @@ #![allow(clippy::reversed_empty_ranges)] +use std::str::FromStr; + use num_bigint::{BigInt, BigUint}; use num_traits::{FromPrimitive, ToPrimitive}; use crate::bigint::BigIntImpl; use crate::bn254::fp254impl::Fp254Impl; +use crate::bn254::utils::Hint; use crate::pseudo::NMUL; use crate::treepp::*; @@ -93,6 +96,375 @@ impl Fq { { Fq::push_u32_le(&BigUint::from(a).to_u32_digits()) } } } + + pub fn hinted_mul( + mut a_depth: u32, + mut a: ark_bn254::Fq, + mut b_depth: u32, + mut b: ark_bn254::Fq, + ) -> (Script, Vec) { + assert_ne!(a_depth, b_depth); + if a_depth > b_depth { + (a_depth, b_depth) = (b_depth, a_depth); + (a, b) = (b, a); + } + + let mut hints = Vec::new(); + let x = BigInt::from_str(&a.to_string()).unwrap(); + let y = BigInt::from_str(&b.to_string()).unwrap(); + let modulus = &Fq::modulus_as_bigint(); + let q = (x * y) / modulus; + + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::roll(a_depth + 1) } + { Fq::roll(b_depth + 1) } + { Fq::tmul() } + }; + hints.push(Hint::BigIntegerTmulLC1(q)); + + (script, hints) + } + + // TODO: Optimize by using the constant feature + pub fn hinted_mul_by_constant( + a: ark_bn254::Fq, + constant: &ark_bn254::Fq, + ) -> (Script, Vec) { + let mut hints = Vec::new(); + let x = BigInt::from_str(&a.to_string()).unwrap(); + let y = BigInt::from_str(&constant.to_string()).unwrap(); + let modulus = &Fq::modulus_as_bigint(); + let q = (x * y) / modulus; + + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::roll(1) } + { Fq::push(*constant) } + { Fq::tmul() } + }; + hints.push(Hint::BigIntegerTmulLC1(q)); + + (script, hints) + } + + pub fn hinted_mul_keep_element( + mut a_depth: u32, + mut a: ark_bn254::Fq, + mut b_depth: u32, + mut b: ark_bn254::Fq, + ) -> (Script, Vec) { + assert_ne!(a_depth, b_depth); + if a_depth > b_depth { + (a_depth, b_depth) = (b_depth, a_depth); + (a, b) = (b, a); + } + + let mut hints = Vec::new(); + let x = BigInt::from_str(&a.to_string()).unwrap(); + let y = BigInt::from_str(&b.to_string()).unwrap(); + let modulus = &Fq::modulus_as_bigint(); + let q = (x * y) / modulus; + + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::copy(a_depth + 1) } + { Fq::copy(b_depth + 2) } + { Fq::tmul() } + }; + hints.push(Hint::BigIntegerTmulLC1(q)); + + (script, hints) + } + + #[allow(clippy::too_many_arguments)] + pub fn hinted_mul_lc2( + a_depth: u32, + a: ark_bn254::Fq, + b_depth: u32, + b: ark_bn254::Fq, + c_depth: u32, + c: ark_bn254::Fq, + d_depth: u32, + d: ark_bn254::Fq, + ) -> (Script, Vec) { + assert!(a_depth > b_depth && b_depth > c_depth && c_depth > d_depth); + + let mut hints = Vec::new(); + + let modulus = &Fq::modulus_as_bigint(); + + let x = BigInt::from_str(&a.to_string()).unwrap(); + let y = BigInt::from_str(&b.to_string()).unwrap(); + let z = BigInt::from_str(&c.to_string()).unwrap(); + let w = BigInt::from_str(&d.to_string()).unwrap(); + + let q = (x * z + y * w) / modulus; + + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::roll(a_depth + 1) } + { Fq::roll(b_depth + 2) } + { Fq::roll(c_depth + 3) } + { Fq::roll(d_depth + 4) } + { Fq::tmul_lc2() } + }; + hints.push(Hint::BigIntegerTmulLC2(q)); + + (script, hints) + } + + // Assumes tmul hint (1 BigInteger) at the top of stack + // and operands (a, b, c, d) at stack depths (a_depth, b_depth, c_depth, d_depth) + // Computes r = a * c + b * d (mod p) + #[allow(clippy::too_many_arguments)] + pub fn hinted_mul_lc2_w4( + a_depth: u32, + a: ark_bn254::Fq, + b_depth: u32, + b: ark_bn254::Fq, + c_depth: u32, + c: ark_bn254::Fq, + d_depth: u32, + d: ark_bn254::Fq, + ) -> (Script, Vec) { + assert!(a_depth > b_depth && b_depth > c_depth && c_depth > d_depth); + + let mut hints = Vec::with_capacity(1); + + let modulus = &Fq::modulus_as_bigint(); + + let x = BigInt::from_str(&a.to_string()).unwrap(); + let y = BigInt::from_str(&b.to_string()).unwrap(); + let z = BigInt::from_str(&c.to_string()).unwrap(); + let w = BigInt::from_str(&d.to_string()).unwrap(); + + let q = (x * z + y * w) / modulus; + + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::roll(a_depth + 1) } + { Fq::roll(b_depth + 2) } + { Fq::roll(c_depth + 3) } + { Fq::roll(d_depth + 4) } + { Fq::tmul_lc2_w4() } + }; + hints.push(Hint::BigIntegerTmulLC2(q)); + + (script, hints) + } + + #[allow(clippy::too_many_arguments)] + pub fn hinted_mul_lc4( + a_depth: u32, + a: ark_bn254::Fq, + b_depth: u32, + b: ark_bn254::Fq, + c_depth: u32, + c: ark_bn254::Fq, + d_depth: u32, + d: ark_bn254::Fq, + + e_depth: u32, + e: ark_bn254::Fq, + f_depth: u32, + f: ark_bn254::Fq, + g_depth: u32, + g: ark_bn254::Fq, + h_depth: u32, + h: ark_bn254::Fq, + ) -> (Script, Vec) { + assert!( + a_depth > b_depth + && b_depth > c_depth + && c_depth > d_depth + && d_depth > e_depth + && e_depth > f_depth + && f_depth > g_depth + && g_depth > h_depth + ); + + let mut hints = Vec::new(); + + let modulus = &Fq::modulus_as_bigint(); + + let x1 = BigInt::from_str(&a.to_string()).unwrap(); + let y1 = BigInt::from_str(&b.to_string()).unwrap(); + let z1 = BigInt::from_str(&c.to_string()).unwrap(); + let w1 = BigInt::from_str(&d.to_string()).unwrap(); + + let x2 = BigInt::from_str(&e.to_string()).unwrap(); + let y2 = BigInt::from_str(&f.to_string()).unwrap(); + let z2 = BigInt::from_str(&g.to_string()).unwrap(); + let w2 = BigInt::from_str(&h.to_string()).unwrap(); + + let q = (x1 * x2 + y1 * y2 + z1 * z2 + w1 * w2) / modulus; + + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { fq_push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::roll(a_depth + 1) } + { Fq::roll(b_depth + 2) } + { Fq::roll(c_depth + 3) } + { Fq::roll(d_depth + 4) } + { Fq::roll(e_depth + 5) } + { Fq::roll(f_depth + 6) } + { Fq::roll(g_depth + 7) } + { Fq::roll(h_depth + 8) } + { Fq::tmul_lc4() } + }; + hints.push(Hint::BigIntegerTmulLC4(q)); + + (script, hints) + } + + #[allow(clippy::too_many_arguments)] + pub fn hinted_mul_lc2_keep_elements( + a_depth: u32, + a: ark_bn254::Fq, + b_depth: u32, + b: ark_bn254::Fq, + c_depth: u32, + c: ark_bn254::Fq, + d_depth: u32, + d: ark_bn254::Fq, + ) -> (Script, Vec) { + assert!(a_depth > b_depth && b_depth > c_depth && c_depth > d_depth); + + let mut hints = Vec::new(); + + let modulus = &Fq::modulus_as_bigint(); + + let x = BigInt::from_str(&a.to_string()).unwrap(); + let y = BigInt::from_str(&b.to_string()).unwrap(); + let z = BigInt::from_str(&c.to_string()).unwrap(); + let w = BigInt::from_str(&d.to_string()).unwrap(); + + let q = (x * z + y * w) / modulus; + + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::copy(a_depth + 1) } + { Fq::copy(b_depth + 2) } + { Fq::copy(c_depth + 3) } + { Fq::copy(d_depth + 4) } + { Fq::tmul_lc2() } + }; + hints.push(Hint::BigIntegerTmulLC2(q)); + + (script, hints) + } + + // Same as hinted_mul_lc2_keep_elements(), except retains operands (a, b, c, d) on stack + #[allow(clippy::too_many_arguments)] + pub fn hinted_mul_lc2_keep_elements_w4( + a_depth: u32, + a: ark_bn254::Fq, + b_depth: u32, + b: ark_bn254::Fq, + c_depth: u32, + c: ark_bn254::Fq, + d_depth: u32, + d: ark_bn254::Fq, + ) -> (Script, Vec) { + assert!(a_depth > b_depth && b_depth > c_depth && c_depth > d_depth); + + let mut hints = Vec::with_capacity(1); + + let modulus = &Fq::modulus_as_bigint(); + + let x = BigInt::from_str(&a.to_string()).unwrap(); + let y = BigInt::from_str(&b.to_string()).unwrap(); + let z = BigInt::from_str(&c.to_string()).unwrap(); + let w = BigInt::from_str(&d.to_string()).unwrap(); + + let q = (x * z + y * w) / modulus; + + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::copy(a_depth + 1) } + { Fq::copy(b_depth + 2) } + { Fq::copy(c_depth + 3) } + { Fq::copy(d_depth + 4) } + { Fq::tmul_lc2_w4() } + }; + hints.push(Hint::BigIntegerTmulLC2(q)); + + (script, hints) + } + + // TODO: Optimize using the sqaure feature + pub fn hinted_square(a: ark_bn254::Fq) -> (Script, Vec) { + let mut hints = Vec::new(); + let x = &BigInt::from_str(&a.to_string()).unwrap(); + let modulus = &Fq::modulus_as_bigint(); + let q = (x * x) / modulus; + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + { Fq::roll(1) } + { Fq::copy(0) } + { Fq::tmul() } + }; + hints.push(Hint::BigIntegerTmulLC1(q)); + + (script, hints) + } + + pub fn hinted_inv(a: ark_bn254::Fq) -> (Script, Vec) { + let mut hints = Vec::new(); + let x = &BigInt::from_str(&a.to_string()).unwrap(); + let modulus = &Fq::modulus_as_bigint(); + let y = &x.modinv(modulus).unwrap(); + let q = (x * y) / modulus; + let script = script! { + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + for _ in 0..Self::N_LIMBS { + OP_DEPTH OP_1SUB OP_ROLL // hints + } + // { Fq::push(ark_bn254::Fq::from_str(&y.to_string()).unwrap()) } + // { Fq::push(ark_bn254::Fq::from_str(&q.to_string()).unwrap()) } + // x, y, q + { Fq::roll(2) } + { Fq::copy(2) } + // y, q, x, y + { Fq::tmul() } + // y, 1 + { Fq::push_one() } + { Fq::equalverify(1, 0) } + }; + hints.push(Hint::Fq(ark_bn254::Fq::from_str(&y.to_string()).unwrap())); + hints.push(Hint::BigIntegerTmulLC1(q)); + + (script, hints) + } } pub fn bigint_to_u32_limbs(n: BigInt, n_bits: u32) -> Vec { From d2a4bb442c204dfcaa68b8afde582015c723d807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:56:27 +0300 Subject: [PATCH 29/32] Clear Part1 Report (#300) * clear part1 report * add assertion to push_u32_le * fmt * remove debug prints and improve test for limb_shr1_carry * fmt * limb_to_bits functions assert num_bits>0 * change G1Affine::roll, G2Affine::roll, G2Affine::copy parameters to in terms of Fq, and fix tests for bigint change * fmt * assert LIMB_SIZE < 31 in BigIntImpl * restore bigint/mul.rs because it is fixed in another pr * fmt * apply parameter change to double_prevent_overflow_keep_element too --- bitvm/src/bigint/add.rs | 12 +-- bitvm/src/bigint/bits.rs | 149 +++--------------------------- bitvm/src/bigint/inv.rs | 60 ++++++------ bitvm/src/bigint/mod.rs | 2 + bitvm/src/bigint/std.rs | 17 +++- bitvm/src/bn254/fq.rs | 4 +- bitvm/src/bn254/g1.rs | 5 +- bitvm/src/bn254/g2.rs | 6 +- bitvm/src/chunk/taps_point_ops.rs | 10 +- bitvm/src/hash/sha256_u4.rs | 2 +- bitvm/src/lib.rs | 1 + bitvm/src/u4/u4_add.rs | 13 ++- 12 files changed, 86 insertions(+), 195 deletions(-) diff --git a/bitvm/src/bigint/add.rs b/bitvm/src/bigint/add.rs index b76dd426..07a88f6d 100644 --- a/bitvm/src/bigint/add.rs +++ b/bitvm/src/bigint/add.rs @@ -126,15 +126,15 @@ impl BigIntImpl { { 1 << LIMB_SIZE } // Double the limb, take the result to the alt stack, and add initial carry - { n + 1 } OP_PICK limb_double_without_carry OP_TOALTSTACK + { n * Self::N_LIMBS + 1 } OP_PICK limb_double_without_carry OP_TOALTSTACK for i in 0..Self::N_LIMBS - 2 { - { n + i + 3 } OP_PICK limb_double_with_carry OP_TOALTSTACK + { n * Self::N_LIMBS + i + 3 } OP_PICK limb_double_with_carry OP_TOALTSTACK } // When we got {limb} {base} {carry} on the stack, we drop the base OP_NIP // {limb} {carry} - { n + 9 } OP_PICK { limb_double_with_carry_allow_overflow(Self::HEAD_OFFSET) } + { n * Self::N_LIMBS + Self::N_LIMBS } OP_PICK { limb_double_with_carry_allow_overflow(Self::HEAD_OFFSET) } // Take all limbs from the alt stack to the main stack for _ in 0..Self::N_LIMBS - 1 { @@ -178,12 +178,12 @@ impl BigIntImpl { pub fn double_prevent_overflow_keep_element(n: u32) -> Script { script! { { 1 << LIMB_SIZE } - { n + 1 } OP_PICK limb_double_without_carry OP_TOALTSTACK + { n * Self::N_LIMBS + 1 } OP_PICK limb_double_without_carry OP_TOALTSTACK for i in 0..Self::N_LIMBS - 2 { - { n + i + 3 } OP_PICK limb_double_with_carry OP_TOALTSTACK + { n * Self::N_LIMBS + i + 3 } OP_PICK limb_double_with_carry OP_TOALTSTACK } OP_NIP - { n + Self::N_LIMBS } OP_PICK OP_SWAP + { n * Self::N_LIMBS + Self::N_LIMBS } OP_PICK OP_SWAP { limb_double_with_carry_prevent_overflow(Self::HEAD_OFFSET) } for _ in 0..Self::N_LIMBS - 1 { OP_FROMALTSTACK diff --git a/bitvm/src/bigint/bits.rs b/bitvm/src/bigint/bits.rs index 998556a2..7f7868fe 100644 --- a/bitvm/src/bigint/bits.rs +++ b/bitvm/src/bigint/bits.rs @@ -45,44 +45,6 @@ impl BigIntImpl { { limb_to_le_bits_toaltstack(N_BITS - LIMB_SIZE * (Self::N_LIMBS - 1)) } } } - - pub fn limb_from_bytes() -> Script { - let bytes_per_limb = LIMB_SIZE.div_ceil(8); - - assert!(LIMB_SIZE > 0, "LIMB_SIZE must not be 0"); - assert!(LIMB_SIZE < 33, "LIMB_SIZE must be less than 33"); - - script! { - // This will be our sum on the stack - OP_0 - for i in 0..bytes_per_limb { - // Check that the number is a u8 - OP_SWAP - OP_DUP - { 256 } - OP_LESSTHAN - OP_VERIFY - // lshift - for _ in 0..8*i { - OP_DUP - OP_ADD - } - OP_ADD - } - } - } - - pub fn from_bytes() -> Script { - script! { - for _ in 0..Self::N_LIMBS { - { Self::limb_from_bytes() } - OP_TOALTSTACK - } - for _ in 0..Self::N_LIMBS { - OP_FROMALTSTACK - } - } - } } fn limb_to_be_bits_common(num_bits: u32) -> Script { @@ -171,16 +133,19 @@ fn limb_to_le_bits_common(num_bits: u32) -> Script { } pub fn limb_to_le_bits(num_bits: u32) -> Script { + assert!(num_bits > 0); if num_bits >= 2 { script! { { limb_to_le_bits_common(num_bits) } } } else { + // if num_bits == 1 script! {} } } pub fn limb_to_le_bits_toaltstack(num_bits: u32) -> Script { + assert!(num_bits > 0); if num_bits >= 2 { script! { { limb_to_le_bits_common(num_bits) } @@ -189,11 +154,15 @@ pub fn limb_to_le_bits_toaltstack(num_bits: u32) -> Script { } } } else { - script! {} + // if num_bits == 1 + script! { + OP_TOALTSTACK + } } } pub fn limb_to_be_bits(num_bits: u32) -> Script { + assert!(num_bits > 0); if num_bits >= 2 { script! { { limb_to_be_bits_common(num_bits) } @@ -202,11 +171,13 @@ pub fn limb_to_be_bits(num_bits: u32) -> Script { } } } else { + // if num_bits == 1 script! {} } } pub fn limb_to_be_bits_toaltstack(num_bits: u32) -> Script { + assert!(num_bits > 0); if num_bits >= 2 { script! { { limb_to_be_bits_common(num_bits) } @@ -214,6 +185,7 @@ pub fn limb_to_be_bits_toaltstack(num_bits: u32) -> Script { OP_TOALTSTACK } } else { + // if num_bits == 1 script! { OP_TOALTSTACK } @@ -304,11 +276,6 @@ mod test { }; run(script); } - - let script = script! { - 0 { limb_to_be_bits(0) } 0 OP_EQUAL - }; - run(script); } #[test] @@ -385,11 +352,6 @@ mod test { }; run(script); } - - let script = script! { - 0 { limb_to_le_bits(0) } 0 OP_EQUAL - }; - run(script); } #[test] @@ -598,93 +560,4 @@ mod test { run(script); } } - - #[test] - fn test_u29_limb_from_bytes() { - assert_eq!(U254::N_LIMBS, 9); - let script = script! { - { 0x01 } - { 0x12 } - { 0x13 } - { 0x14 } - { U254::limb_from_bytes() } - { 0x01121314 } - OP_EQUAL - }; - run(script); - - let script = script! { - { 0x00 } - { 0x01 } - { 0x13 } - { 0x14 } - { U254::limb_from_bytes() } - { 0x00011314 } - OP_EQUAL - }; - run(script); - } - #[test] - fn test_u254_from_bytes() { - assert_eq!(U254::N_LIMBS, 9); - let script = script! { - { 0x00 } - { 0x01 } - { 0x13 } - { 0x14 } - - { 0x01 } - { 0x22 } - { 0x23 } - { 0x24 } - - { 0x01 } - { 0x32 } - { 0x33 } - { 0x34 } - - { 0x01 } - { 0x42 } - { 0x43 } - { 0x44 } - - { 0x01 } - { 0x52 } - { 0x53 } - { 0x54 } - - { 0x01 } - { 0x62 } - { 0x63 } - { 0x64 } - - { 0x01 } - { 0x72 } - { 0x73 } - { 0x74 } - - { 0x01 } - { 0x82 } - { 0x83 } - { 0x84 } - - { 0x01 } - { 0x92 } - { 0x93 } - { 0x94 } - - { U254::from_bytes() } - { 0x00011314 } - { 0x01222324 } - { 0x01323334 } - { 0x01424344 } - { 0x01525354 } - { 0x01626364 } - { 0x01727374 } - { 0x01828384 } - { 0x01929394 } - { U254::equal(0, 1) } - }; - run(script); - } } diff --git a/bitvm/src/bigint/inv.rs b/bitvm/src/bigint/inv.rs index 8f683adb..abfd1b59 100644 --- a/bitvm/src/bigint/inv.rs +++ b/bitvm/src/bigint/inv.rs @@ -48,7 +48,7 @@ impl BigIntImpl { pub fn limb_shr1_carry(num_bits: u32) -> Script { let powers_of_2_script = if num_bits < 7 { script! { - for i in 0..num_bits - 1 { + for i in 1..num_bits { { 2_u32.pow(i) } } } @@ -158,37 +158,43 @@ mod test { #[test] fn test_limb_shr1_carry() { - println!("limb_shr1_carry: {} bytes", limb_shr1_carry(29).len()); - let mut prng = ChaCha20Rng::seed_from_u64(0); + for shift in 2..30 { + println!( + "limb_shr1_carry({:?}): {} bytes", + shift, + limb_shr1_carry(shift).len() + ); + let mut prng = ChaCha20Rng::seed_from_u64(0); + + for _ in 0..100 { + let mut a: u32 = prng.gen(); + a %= 1 << shift; - for _ in 0..100 { - let mut a: u32 = prng.gen(); - a %= 1 << 29; - - let script = script! { - { a } - { 0 } - { limb_shr1_carry(29) } - { a & 1 } OP_EQUALVERIFY - { a >> 1 } OP_EQUAL - }; + let script = script! { + { a } + { 0 } + { limb_shr1_carry(shift) } + { a & 1 } OP_EQUALVERIFY + { a >> 1 } OP_EQUAL + }; - run(script); - } + run(script); + } - for _ in 0..100 { - let mut a: u32 = prng.gen(); - a %= 1 << 29; + for _ in 0..100 { + let mut a: u32 = prng.gen(); + a %= 1 << shift; - let script = script! { - { a } - { 1 } - { limb_shr1_carry(29) } - { a & 1 } OP_EQUALVERIFY - { (1 << 28) | (a >> 1) } OP_EQUAL - }; + let script = script! { + { a } + { 1 } + { limb_shr1_carry(shift) } + { a & 1 } OP_EQUALVERIFY + { (1 << (shift - 1)) | (a >> 1) } OP_EQUAL + }; - run(script); + run(script); + } } } diff --git a/bitvm/src/bigint/mod.rs b/bitvm/src/bigint/mod.rs index 323d62a3..441296fa 100644 --- a/bitvm/src/bigint/mod.rs +++ b/bitvm/src/bigint/mod.rs @@ -15,6 +15,8 @@ impl BigIntImpl { pub const N_LIMBS: u32 = N_BITS.div_ceil(LIMB_SIZE); pub const HEAD: u32 = N_BITS - (Self::N_LIMBS - 1) * LIMB_SIZE; pub const HEAD_OFFSET: u32 = 1u32 << Self::HEAD; + const _ASSERTION1: () = assert!(Self::N_LIMBS > 1); + const _ASSERTION2: () = assert!(Self::LIMB_SIZE < 31); } pub type U254 = BigIntImpl<254, 29>; diff --git a/bitvm/src/bigint/std.rs b/bitvm/src/bigint/std.rs index fb10341b..b2fbef1b 100644 --- a/bitvm/src/bigint/std.rs +++ b/bitvm/src/bigint/std.rs @@ -51,6 +51,21 @@ impl BigIntImpl { bits.push((elem & (1 << i)) != 0); } } + // make sure most significant 1 lies inside the limits + let ms_one = if bits.len() > 0 { + let mut ms_one = bits.len() - 1; + while !bits[ms_one] { + if ms_one != 0 { + ms_one -= 1; + } else { + break; + } + } + ms_one + } else { + 0 + }; + assert!(ms_one < Self::N_BITS as usize); bits.resize(N_BITS as usize, false); let mut limbs = vec![]; @@ -180,7 +195,7 @@ impl BigIntImpl { a = (a + 1) * Self::N_LIMBS - 1; script! { - if a < 134 { + if a < 128 { for _ in 0..Self::N_LIMBS { { a } OP_PICK } diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index 47cc6598..76e7a9fd 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -555,9 +555,7 @@ macro_rules! fp_lc_mul { for i in 2..=window { for j in 1 << (i - 1)..1 << i { if j % 2 == 0 { - // { T::copy(j/2 - 1) } - // { T::double_prevent_overflow() } - { T::double_prevent_overflow_keep_element((j/2 - 1) * T::N_LIMBS) } + { T::double_prevent_overflow_keep_element(j/2 - 1) } } else { { T::add_ref_with_top(j - 2) } } diff --git a/bitvm/src/bn254/g1.rs b/bitvm/src/bn254/g1.rs index af83c777..32813b0c 100644 --- a/bitvm/src/bn254/g1.rs +++ b/bitvm/src/bn254/g1.rs @@ -174,7 +174,7 @@ impl G1Affine { OP_IF { G1Affine::drop() } OP_ELSE - { G1Affine::roll(1) } + { G1Affine::roll(2) } { G1Affine::is_zero_keep_element() } OP_IF { G1Affine::drop() } @@ -400,8 +400,7 @@ impl G1Affine { } } - pub fn roll(mut a: u32) -> Script { - a *= 2; + pub fn roll(a: u32) -> Script { script! { { Fq::roll(a + 1) } { Fq::roll(a + 1) } diff --git a/bitvm/src/bn254/g2.rs b/bitvm/src/bn254/g2.rs index 1105de0c..859f5e93 100644 --- a/bitvm/src/bn254/g2.rs +++ b/bitvm/src/bn254/g2.rs @@ -38,8 +38,7 @@ impl G2Affine { } } - pub fn roll(mut a: u32) -> Script { - a *= 4; + pub fn roll(a: u32) -> Script { script! { { Fq::roll(a + 3) } { Fq::roll(a + 3) } @@ -49,8 +48,7 @@ impl G2Affine { } // [ax, ay, bx, by] - pub fn copy(mut a: u32) -> Script { - a *= 4; + pub fn copy(a: u32) -> Script { script! { { Fq::copy(a + 3) } { Fq::copy(a + 3) } diff --git a/bitvm/src/chunk/taps_point_ops.rs b/bitvm/src/chunk/taps_point_ops.rs index bfa79851..c447b882 100644 --- a/bitvm/src/chunk/taps_point_ops.rs +++ b/bitvm/src/chunk/taps_point_ops.rs @@ -325,25 +325,25 @@ fn utils_point_add_eval( {drop_and_insert_zero.clone()} OP_ELSE // [t, q] - { G2Affine::roll(1) } + { G2Affine::roll(4) } { G2Affine::is_zero_keep_element() } OP_IF // t == 0 // [q, t] - {G2Affine::roll(1)} + {G2Affine::roll(4)} {drop_and_insert_zero.clone()} OP_ELSE // qx qy tx ty - {G2Affine::copy(1)} + {G2Affine::copy(4)} // qx qy tx ty qx qy { Fq2::neg(0)} // qx qy tx ty qx -qy - {G2Affine::copy(1)} + {G2Affine::copy(4)} // qx qy tx ty qx -qy tx ty {G2Affine::equal()} // q = -t ? // qx qy tx ty 0/1 OP_IF // q == -t // [q, t] - {G2Affine::roll(1)} + {G2Affine::roll(4)} // [t, q] {drop_and_insert_zero} OP_ELSE diff --git a/bitvm/src/hash/sha256_u4.rs b/bitvm/src/hash/sha256_u4.rs index 1f19d7ea..802451a8 100644 --- a/bitvm/src/hash/sha256_u4.rs +++ b/bitvm/src/hash/sha256_u4.rs @@ -279,7 +279,7 @@ pub fn sha256(num_bytes: u32) -> Script { println!("{:?}", bytes_per_chunk); println!("{:?}", padding_scripts); - let add_size = 130; + let add_size = 128; let sched_size = 128; let rrot_size = 16 * 5; let half_logic_size = 136 + 16; diff --git a/bitvm/src/lib.rs b/bitvm/src/lib.rs index 697aa948..10265cb1 100644 --- a/bitvm/src/lib.rs +++ b/bitvm/src/lib.rs @@ -166,6 +166,7 @@ fn execute_script_buf_optional_stack_limit( break; } } + let res = exec.result().unwrap(); ExecuteInfo { success: res.success, diff --git a/bitvm/src/u4/u4_add.rs b/bitvm/src/u4/u4_add.rs index fd6e7c6f..70c51b29 100644 --- a/bitvm/src/u4/u4_add.rs +++ b/bitvm/src/u4/u4_add.rs @@ -5,7 +5,6 @@ use bitcoin::opcodes::all::*; /// Pushes the table for calculating the quotient, i.e. floor(x / 16) for x < 65. i.e. 15 (max u4) * 4 (max # numbers to sum) + 4 (max carry) pub fn u4_push_quotient_table() -> Script { script! { - OP_4 for i in (0..=3).rev() { { i } OP_DUP @@ -35,13 +34,13 @@ pub fn u4_push_quotient_table_5() -> Script { /// Drop quotient table pub fn u4_drop_quotient_table() -> Script { - u4_drop(65) + u4_drop(64) } /// Pushes the table for calculating the modulo, i.e. x % 16 for x < 65. i.e. 15 (max u4) * 4 (max # numbers to sum) + 4 (max carry) pub fn u4_push_modulo_table() -> Script { script! { - for i in (0..65).rev() { + for i in (0..64).rev() { { i % 16 } } } @@ -58,7 +57,7 @@ pub fn u4_push_modulo_table_5() -> Script { /// Drops the modulo table pub fn u4_drop_modulo_table() -> Script { - u4_drop(65) + u4_drop(64) } /// Pushes both modulo and quotient tables for sums @@ -159,7 +158,7 @@ pub fn u4_add_no_table_internal(nibble_count: u32, number_count: u32) -> Script /// Requires the addition table and tables_offset to locate the table which should be equal to number of elements on top of the table including operating values pub fn u4_add_internal(nibble_count: u32, number_count: u32, tables_offset: u32) -> Script { assert!(number_count < 5); - let quotient_table_size = 65; + let quotient_table_size = 64; //extra size on the stack let mut offset_calc: i32 = 0; script! { @@ -318,7 +317,7 @@ mod tests { #[test] fn test_quotient() { - for i in 0..65 { + for i in 0..64 { let script = script! { { u4_push_quotient_table() } { i as u32 } @@ -334,7 +333,7 @@ mod tests { #[test] fn test_modulo() { - for i in 0..65 { + for i in 0..64 { let script = script! { { u4_push_modulo_table() } { i as u32 } From dda579f74eb1777cd843c61fb660e9c74a0722b2 Mon Sep 17 00:00:00 2001 From: erray Date: Thu, 30 Oct 2025 08:42:05 +0000 Subject: [PATCH 30/32] [Zellic Audit] 3.2 3.3 (#393) * feat: add check_validity function to Fp254 * feat: add validity checks to some functions in taps_ext_miller.rs as samples * add validity check to taps_mul * feat: add is_valid function to Fp254 * fix-feat: fix validity checks for precompute_p and complete validity checks for taps_ext_miller.rs * feat: fix and add validity checks in taps_msm.rs * fix: a validity check in taps_points_ops.rs * fix-feat: correct and add validity checks * fix: correct tests for test_point_ops_and_multiply_line_evals_step_1 tests according to the changed form of the input * fix: add validity checks for G1Acc in chunk_msm * opt: remove unnecessary q4 validity check * fix: validity checks of taps_ext_miller::chunk_precompute_p_from_hash * feat: add functions to analyze max stack usage of chunks * fix: some typos and delete leftovers --------- Co-authored-by: Hakkush-07 --- bitvm/src/bn254/fp254impl.rs | 36 ++++++++ bitvm/src/bn254/fq.rs | 66 ++++++++++++++ bitvm/src/bn254/g1.rs | 14 +++ bitvm/src/chunk/api.rs | 99 +++++++++++++++++++++ bitvm/src/chunk/api_runtime_utils.rs | 128 +++++++++++++++++++++++++++ bitvm/src/chunk/taps_ext_miller.rs | 37 +++++--- bitvm/src/chunk/taps_msm.rs | 16 ++-- bitvm/src/chunk/taps_mul.rs | 10 +++ bitvm/src/chunk/taps_point_ops.rs | 41 +++++++-- 9 files changed, 426 insertions(+), 21 deletions(-) diff --git a/bitvm/src/bn254/fp254impl.rs b/bitvm/src/bn254/fp254impl.rs index a8d4eadd..066574b6 100644 --- a/bitvm/src/bn254/fp254impl.rs +++ b/bitvm/src/bn254/fp254impl.rs @@ -560,4 +560,40 @@ pub trait Fp254Impl { } } } + + // finds if the element at the top of the stack is less than the modulo and valid (limbs are in range) + // consumes the element, leaves the result at the topstack + fn is_valid() -> Script { + let limbs_of_c = U254::biguint_to_limbs(Self::modulus_as_bigint().to_biguint().unwrap()); + script! { + // (Assuming limbs are numbered big endian) + // Number A is greater than number B <=> there exists a limb i, s.t. (A_i > B_i OR (A_i >= B_i and i is the first limb)) and there's no limb j > i satisfying A_i < B_i + // Script below maintains if such state exists for each i behind the foremost limb, combining the results and negating them if there is such j + for i in 0..(Self::N_LIMBS as usize) { + OP_DUP + { 0 } { 1 << U254::LIMB_SIZE } OP_WITHIN + if i == 0 { + OP_TOALTSTACK //u254 validity check + + { limbs_of_c[i] } + OP_GREATERTHANOREQUAL + } else { + OP_FROMALTSTACK OP_BOOLAND OP_TOALTSTACK //u254 validity check + + { limbs_of_c[i] } OP_2DUP + OP_GREATERTHAN OP_TOALTSTACK + OP_GREATERTHANOREQUAL + OP_BOOLAND + OP_FROMALTSTACK OP_BOOLOR + } + if i == (Self::N_LIMBS as usize) - 1 { + OP_NOT //This OP_NOT can be negated, but it probably isn't necessary + OP_FROMALTSTACK + OP_BOOLAND + } else { + OP_SWAP + } + } + } + } } diff --git a/bitvm/src/bn254/fq.rs b/bitvm/src/bn254/fq.rs index 76e7a9fd..a21fa9a6 100644 --- a/bitvm/src/bn254/fq.rs +++ b/bitvm/src/bn254/fq.rs @@ -1470,4 +1470,70 @@ mod test { ) } } + + #[test] + fn test_is_valid() { + let mut prng = ChaCha20Rng::seed_from_u64(37); + let x = Fq::modulus_as_bigint().to_biguint().unwrap(); + let verification_script = Fq::is_valid(); + + // -1 + { + run(script! { + { U254::push_biguint(x.clone() - 1u32) } + { verification_script.clone() } + }) + } + + // equal + { + run(script! { + { U254::push_biguint(x.clone()) } + { verification_script.clone() } + OP_NOT + }) + } + + // +1 + { + run(script! { + { U254::push_biguint(x.clone() + 1u32) } + { verification_script.clone() } + OP_NOT + }) + } + + // random less than + for _ in 0..100 { + let n = prng.gen_biguint_range(&BigUint::from(0u32), &x); + run(script! { + { U254::push_biguint(n.clone()) } + { verification_script.clone() } + }) + } + + // random geq + for _ in 0..100 { + let n = prng.gen_biguint_range(&x, &BigUint::from(2u32).pow(254)); + run(script! { + { U254::push_biguint(n.clone()) } + { verification_script.clone() } + OP_NOT + }); + } + + //corrupted data with negative + { + let limbs = U254::biguint_to_limbs(x.clone()); + run(script! { + for i in (1..U254::N_LIMBS as usize).rev() { + { limbs[i] } + } + { -1 } + + { verification_script.clone() } + OP_NOT + }) + } + } } diff --git a/bitvm/src/bn254/g1.rs b/bitvm/src/bn254/g1.rs index 32813b0c..2e98d281 100644 --- a/bitvm/src/bn254/g1.rs +++ b/bitvm/src/bn254/g1.rs @@ -139,6 +139,20 @@ impl G1Affine { } } + pub fn toaltstack() -> Script { + script! { + {Fq::toaltstack()} + {Fq::toaltstack()} + } + } + + pub fn fromaltstack() -> Script { + script! { + {Fq::fromaltstack()} + {Fq::fromaltstack()} + } + } + pub fn read_from_stack(witness: Vec>) -> ark_bn254::G1Affine { assert_eq!(witness.len() as u32, Fq::N_LIMBS * 2); let x: ark_bn254::Fq = diff --git a/bitvm/src/chunk/api.rs b/bitvm/src/chunk/api.rs index bafbb8a2..03efbfe7 100644 --- a/bitvm/src/chunk/api.rs +++ b/bitvm/src/chunk/api.rs @@ -352,6 +352,9 @@ mod test { use crate::chunk::api::generate_signatures_for_any_proof; + use crate::chunk::api_runtime_utils::{ + analyze_largest_segments_from_signatures, get_segments_from_assertion, + }; use crate::chunk::wrap_hasher::BLAKE3_HASH_LENGTH; use ark_bn254::Bn254; use ark_ff::UniformRand; @@ -508,6 +511,102 @@ mod test { } } + #[test] + fn test_largest_chunks() { + println!("Use mock groth16 proof"); + let vk_bytes = [ + 115, 158, 251, 51, 106, 255, 102, 248, 22, 171, 229, 158, 80, 192, 240, 217, 99, 162, + 65, 107, 31, 137, 197, 79, 11, 210, 74, 65, 65, 203, 243, 14, 123, 2, 229, 125, 198, + 247, 76, 241, 176, 116, 6, 3, 241, 1, 134, 195, 39, 5, 124, 47, 31, 43, 164, 48, 120, + 207, 150, 125, 108, 100, 48, 155, 137, 132, 16, 193, 139, 74, 179, 131, 42, 119, 25, + 185, 98, 13, 235, 118, 92, 11, 154, 142, 134, 220, 191, 220, 169, 250, 244, 104, 123, + 7, 247, 33, 178, 155, 121, 59, 75, 188, 206, 198, 182, 97, 0, 64, 231, 45, 55, 92, 100, + 17, 56, 159, 79, 13, 219, 221, 33, 39, 193, 24, 36, 58, 105, 8, 70, 206, 176, 209, 146, + 45, 201, 157, 226, 84, 213, 135, 143, 178, 156, 112, 137, 246, 123, 248, 215, 168, 51, + 95, 177, 47, 57, 29, 199, 224, 98, 48, 144, 253, 15, 201, 192, 142, 62, 143, 13, 228, + 89, 51, 58, 6, 226, 139, 99, 207, 22, 113, 215, 79, 91, 158, 166, 210, 28, 90, 218, + 111, 151, 4, 55, 230, 76, 90, 209, 149, 113, 248, 245, 50, 231, 137, 51, 157, 40, 29, + 184, 198, 201, 108, 199, 89, 67, 136, 239, 96, 216, 237, 172, 29, 84, 3, 128, 240, 2, + 218, 169, 217, 118, 179, 34, 226, 19, 227, 59, 193, 131, 108, 20, 113, 46, 170, 196, + 156, 45, 39, 151, 218, 22, 132, 250, 209, 183, 46, 249, 115, 239, 14, 176, 200, 134, + 158, 148, 139, 212, 167, 152, 205, 183, 236, 242, 176, 96, 177, 187, 184, 252, 14, 226, + 127, 127, 173, 147, 224, 220, 8, 29, 63, 73, 215, 92, 161, 110, 20, 154, 131, 23, 217, + 116, 145, 196, 19, 167, 84, 185, 16, 89, 175, 180, 110, 116, 57, 198, 237, 147, 183, + 164, 169, 220, 172, 52, 68, 175, 113, 244, 62, 104, 134, 215, 99, 132, 199, 139, 172, + 108, 143, 25, 238, 201, 128, 85, 24, 73, 30, 186, 142, 186, 201, 79, 3, 176, 185, 70, + 66, 89, 127, 188, 158, 209, 83, 17, 22, 187, 153, 8, 63, 58, 174, 236, 132, 226, 43, + 145, 97, 242, 198, 117, 105, 161, 21, 241, 23, 84, 32, 62, 155, 245, 172, 30, 78, 41, + 199, 219, 180, 149, 193, 163, 131, 237, 240, 46, 183, 186, 42, 201, 49, 249, 142, 188, + 59, 212, 26, 253, 23, 27, 205, 231, 163, 76, 179, 135, 193, 152, 110, 91, 5, 218, 67, + 204, 164, 128, 183, 221, 82, 16, 72, 249, 111, 118, 182, 24, 249, 91, 215, 215, 155, 2, + 0, 0, 0, 0, 0, 0, 0, 212, 110, 6, 228, 73, 146, 46, 184, 158, 58, 94, 4, 141, 241, 158, + 0, 175, 140, 72, 75, 52, 6, 72, 49, 112, 215, 21, 243, 151, 67, 106, 22, 158, 237, 80, + 204, 41, 128, 69, 52, 154, 189, 124, 203, 35, 107, 132, 241, 234, 31, 3, 165, 87, 58, + 10, 92, 252, 227, 214, 99, 176, 66, 118, 22, 177, 20, 120, 198, 252, 236, 7, 148, 207, + 78, 152, 132, 94, 207, 50, 243, 4, 169, 146, 240, 79, 98, 0, 212, 106, 137, 36, 193, + 21, 175, 180, 1, 26, 107, 39, 198, 89, 152, 26, 220, 138, 105, 243, 45, 63, 106, 163, + 80, 74, 253, 176, 207, 47, 52, 7, 84, 59, 151, 47, 178, 165, 112, 251, 161, + ] + .to_vec(); + let proof_bytes: Vec = [ + 162, 50, 57, 98, 3, 171, 250, 108, 49, 206, 73, 126, 25, 35, 178, 148, 35, 219, 98, 90, + 122, 177, 16, 91, 233, 215, 222, 12, 72, 184, 53, 2, 62, 166, 50, 68, 98, 171, 218, + 218, 151, 177, 133, 223, 129, 53, 114, 236, 181, 215, 223, 91, 102, 225, 52, 122, 122, + 206, 36, 122, 213, 38, 186, 170, 235, 210, 179, 221, 122, 37, 74, 38, 79, 0, 26, 94, + 59, 146, 46, 252, 70, 153, 236, 126, 194, 169, 17, 144, 100, 218, 118, 22, 99, 226, + 132, 40, 24, 248, 232, 197, 195, 220, 254, 52, 36, 248, 18, 167, 167, 206, 108, 29, + 120, 188, 18, 78, 86, 8, 121, 217, 144, 185, 122, 58, 12, 34, 44, 6, 233, 80, 177, 183, + 5, 8, 150, 74, 241, 141, 65, 150, 35, 98, 15, 150, 137, 254, 132, 167, 228, 104, 63, + 133, 11, 209, 39, 79, 138, 185, 88, 20, 242, 102, 69, 73, 243, 88, 29, 91, 127, 157, + 82, 192, 52, 95, 143, 49, 227, 83, 19, 26, 108, 63, 232, 213, 169, 64, 221, 159, 214, + 220, 246, 174, 35, 43, 143, 80, 168, 142, 29, 103, 179, 58, 235, 33, 163, 198, 255, + 188, 20, 3, 91, 47, 158, 122, 226, 201, 175, 138, 18, 24, 178, 219, 78, 12, 96, 10, 2, + 133, 35, 230, 149, 235, 206, 1, 177, 211, 245, 168, 74, 62, 25, 115, 70, 42, 38, 131, + 92, 103, 103, 176, 212, 223, 177, 242, 94, 14, + ] + .to_vec(); + let scalar = [ + 232, 255, 255, 239, 147, 245, 225, 67, 145, 112, 185, 121, 72, 232, 51, 40, 93, 88, + 129, 129, 182, 69, 80, 184, 41, 160, 49, 225, 114, 78, 100, 48, + ] + .to_vec(); + + let proof: ark_groth16::Proof = + ark_groth16::Proof::deserialize_uncompressed(&proof_bytes[..]).unwrap(); + let vk: ark_groth16::VerifyingKey = + ark_groth16::VerifyingKey::deserialize_uncompressed(&vk_bytes[..]).unwrap(); + let scalar: ark_bn254::Fr = ark_bn254::Fr::deserialize_uncompressed(&scalar[..]).unwrap(); + let scalars = [scalar]; + + println!("STEP 1 GENERATE TAPSCRIPTS"); + let secret_key: &str = "a138982ce17ac813d505a5b40b665d404e9528e7"; + let secrets = (0..NUM_PUBS + NUM_U256 + NUM_HASH) + .map(|idx| format!("{secret_key}{:04x}", idx)) + .collect::>(); + let pubkeys = get_pubkeys(secrets.clone()); + + let partial_scripts = api_generate_partial_script(&vk); + let disprove_scripts = api_generate_full_tapscripts(pubkeys, &partial_scripts); + + println!("STEP 2 GENERATE SIGNED ASSERTIONS"); + let proof_sigs = + generate_signatures(proof, scalars.to_vec(), &vk, secrets.clone()).unwrap(); + + println!("num assertion; 256-bit numbers {}", NUM_PUBS + NUM_U256); + println!("num assertion; 160-bit numbers {}", NUM_HASH); + + let proof_asserts = get_assertions_from_signature(proof_sigs); + let signed_asserts = get_signature_from_assertion(proof_asserts, secrets); + let disprove_scripts: [ScriptBuf; NUM_TAPS] = disprove_scripts.try_into().unwrap(); + + let asserts = get_assertions_from_signature(signed_asserts.clone()); + let (success, segments) = get_segments_from_assertion(asserts, vk.clone()); + if !success { + println!("invalid tapscript at segment {}", segments.len()); + } + analyze_largest_segments_from_signatures(&segments, signed_asserts, &disprove_scripts); + } + #[test] fn full_e2e_execution() { println!("Use mock groth16 proof"); diff --git a/bitvm/src/chunk/api_runtime_utils.rs b/bitvm/src/chunk/api_runtime_utils.rs index 14a29f37..f1d50882 100644 --- a/bitvm/src/chunk/api_runtime_utils.rs +++ b/bitvm/src/chunk/api_runtime_utils.rs @@ -439,6 +439,56 @@ fn utils_execute_chunked_g16( None } +/// This is a duplicate of [`utils_execute_chunked_g16`], just to analyze worst case scenarios +fn utils_analyze_largest_segments( + aux_hints: Vec>, + bc_hints: Vec