From 0adfaabad56c9141989e7035b6ecd7264a319ba9 Mon Sep 17 00:00:00 2001 From: Thomas Marchand Date: Tue, 15 Oct 2024 13:14:27 +0100 Subject: [PATCH] feat: fast const pow (#336) # Implement efficient pow2 and pow10 using const arrays ## Pull Request type Please check the type of change your PR introduces: - [ ] Bugfix - [x] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no API changes) - [ ] Build-related changes - [ ] Documentation content changes - [ ] Other (please describe): ## What is the current behavior? Currently, the implementation of pow2 and pow10 functions uses if/else branches, which is less efficient than using lookup tables. Issue Number: N/A ## What is the new behavior? - Implement pow2 and pow10 using const arrays for efficient lookup - Update keccak code to use the new pow2 implementation - Significant gas usage reduction (e.g., keccak test gas usage reduced from 2,399,388 to 1,289,988) ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information This PR takes advantage of Cairo 2.7.0's reintroduction of "`dw` like hardcoded function results" via const arrays to implement efficient lookup tables for pow2 and pow10. These functions are commonly used in various projects, including Alexandria for bitshifting and in DeFi applications for handling ERC20 decimals. I introduced a u128 version (should be the one used in most usecases), and a u256 version for both pow2 and pow10. The u256 version of pow2 uses the u128 one. The new implementation significantly reduces gas usage. For example, I updated keccak implementation to use this new function and the `test_bytes_keccak` test saw a reduction in estimated gas usage from 2,399,388 to 1,289,988. closes https://github.com/keep-starknet-strange/alexandria/issues/323 --- Scarb.lock | 3 + packages/bytes/src/utils.cairo | 270 +-------- packages/math/src/armstrong_number.cairo | 2 +- packages/math/src/const_pow.cairo | 565 +++++++++++++++++++ packages/math/src/karatsuba.cairo | 14 +- packages/math/src/lib.cairo | 5 +- packages/math/src/tests.cairo | 1 + packages/math/src/tests/const_pow_test.cairo | 92 +++ packages/merkle_tree/Scarb.toml | 5 +- packages/merkle_tree/src/storage_proof.cairo | 18 +- 10 files changed, 684 insertions(+), 291 deletions(-) create mode 100644 packages/math/src/const_pow.cairo create mode 100644 packages/math/src/tests/const_pow_test.cairo diff --git a/Scarb.lock b/Scarb.lock index ee9a715a..0cd56aae 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -50,6 +50,9 @@ version = "0.2.1" [[package]] name = "alexandria_merkle_tree" version = "0.1.0" +dependencies = [ + "alexandria_math", +] [[package]] name = "alexandria_numeric" diff --git a/packages/bytes/src/utils.cairo b/packages/bytes/src/utils.cairo index 0283fe68..acc6b422 100644 --- a/packages/bytes/src/utils.cairo +++ b/packages/bytes/src/utils.cairo @@ -1,4 +1,5 @@ use alexandria_bytes::{Bytes, BytesTrait}; +use alexandria_math::const_pow::pow2; use alexandria_data_structures::array_ext::ArrayTraitExt; use core::fmt::{Debug, Display, Formatter, Error}; use core::integer::u128_byte_reverse; @@ -94,7 +95,7 @@ fn keccak_add_uint128_be(ref keccak_input: Array::, value: u128, value_size keccak_input.append(reversed_value.try_into().unwrap()); } else { let (high, low) = DivRem::div_rem( - reversed_value, u128_fast_pow2(64).try_into().expect('Division by 0') + reversed_value, pow2(64).try_into().expect('Division by 0') ); keccak_input.append(low.try_into().unwrap()); keccak_input.append(high.try_into().unwrap()); @@ -209,7 +210,7 @@ pub fn u128_split(value: u128, value_size: usize, left_size: usize) -> (u128, u1 if left_size == 0 { (0, value) } else { - let power = u128_fast_pow2((value_size - left_size) * 8); + let power = pow2((value_size - left_size) * 8); DivRem::div_rem(value, power.try_into().expect('Division by 0')) } } @@ -253,7 +254,7 @@ pub fn read_sub_u128(value: u128, value_size: usize, offset: usize, size: usize) pub fn u128_join(left: u128, right: u128, right_size: usize) -> u128 { let left_size = u128_bytes_len(left); assert(left_size + right_size <= 16, 'left shift overflow'); - let shift = u128_fast_pow2(right_size * 8); + let shift = pow2(right_size * 8); left * shift + right } @@ -295,266 +296,3 @@ fn u128_bytes_len(value: u128) -> usize { 16_usize } } - -/// return 2^exp where exp in [0, 127] -fn u128_fast_pow2(exp: usize) -> u128 { - assert(exp <= 127, 'invalid exp'); - - if exp == 0_usize { - 1_u128 - } else if exp == 1_usize { - 2_u128 - } else if exp == 2_usize { - 4_u128 - } else if exp == 3_usize { - 8_u128 - } else if exp == 4_usize { - 16_u128 - } else if exp == 5_usize { - 32_u128 - } else if exp == 6_usize { - 64_u128 - } else if exp == 7_usize { - 128_u128 - } else if exp == 8_usize { - 256_u128 - } else if exp == 9_usize { - 512_u128 - } else if exp == 10_usize { - 1024_u128 - } else if exp == 11_usize { - 2048_u128 - } else if exp == 12_usize { - 4096_u128 - } else if exp == 13_usize { - 8192_u128 - } else if exp == 14_usize { - 16384_u128 - } else if exp == 15_usize { - 32768_u128 - } else if exp == 16_usize { - 65536_u128 - } else if exp == 17_usize { - 131072_u128 - } else if exp == 18_usize { - 262144_u128 - } else if exp == 19_usize { - 524288_u128 - } else if exp == 20_usize { - 1048576_u128 - } else if exp == 21_usize { - 2097152_u128 - } else if exp == 22_usize { - 4194304_u128 - } else if exp == 23_usize { - 8388608_u128 - } else if exp == 24_usize { - 16777216_u128 - } else if exp == 25_usize { - 33554432_u128 - } else if exp == 26_usize { - 67108864_u128 - } else if exp == 27_usize { - 134217728_u128 - } else if exp == 28_usize { - 268435456_u128 - } else if exp == 29_usize { - 536870912_u128 - } else if exp == 30_usize { - 1073741824_u128 - } else if exp == 31_usize { - 2147483648_u128 - } else if exp == 32_usize { - 4294967296_u128 - } else if exp == 33_usize { - 8589934592_u128 - } else if exp == 34_usize { - 17179869184_u128 - } else if exp == 35_usize { - 34359738368_u128 - } else if exp == 36_usize { - 68719476736_u128 - } else if exp == 37_usize { - 137438953472_u128 - } else if exp == 38_usize { - 274877906944_u128 - } else if exp == 39_usize { - 549755813888_u128 - } else if exp == 40_usize { - 1099511627776_u128 - } else if exp == 41_usize { - 2199023255552_u128 - } else if exp == 42_usize { - 4398046511104_u128 - } else if exp == 43_usize { - 8796093022208_u128 - } else if exp == 44_usize { - 17592186044416_u128 - } else if exp == 45_usize { - 35184372088832_u128 - } else if exp == 46_usize { - 70368744177664_u128 - } else if exp == 47_usize { - 140737488355328_u128 - } else if exp == 48_usize { - 281474976710656_u128 - } else if exp == 49_usize { - 562949953421312_u128 - } else if exp == 50_usize { - 1125899906842624_u128 - } else if exp == 51_usize { - 2251799813685248_u128 - } else if exp == 52_usize { - 4503599627370496_u128 - } else if exp == 53_usize { - 9007199254740992_u128 - } else if exp == 54_usize { - 18014398509481984_u128 - } else if exp == 55_usize { - 36028797018963968_u128 - } else if exp == 56_usize { - 72057594037927936_u128 - } else if exp == 57_usize { - 144115188075855872_u128 - } else if exp == 58_usize { - 288230376151711744_u128 - } else if exp == 59_usize { - 576460752303423488_u128 - } else if exp == 60_usize { - 1152921504606846976_u128 - } else if exp == 61_usize { - 2305843009213693952_u128 - } else if exp == 62_usize { - 4611686018427387904_u128 - } else if exp == 63_usize { - 9223372036854775808_u128 - } else if exp == 64_usize { - 18446744073709551616_u128 - } else if exp == 65_usize { - 36893488147419103232_u128 - } else if exp == 66_usize { - 73786976294838206464_u128 - } else if exp == 67_usize { - 147573952589676412928_u128 - } else if exp == 68_usize { - 295147905179352825856_u128 - } else if exp == 69_usize { - 590295810358705651712_u128 - } else if exp == 70_usize { - 1180591620717411303424_u128 - } else if exp == 71_usize { - 2361183241434822606848_u128 - } else if exp == 72_usize { - 4722366482869645213696_u128 - } else if exp == 73_usize { - 9444732965739290427392_u128 - } else if exp == 74_usize { - 18889465931478580854784_u128 - } else if exp == 75_usize { - 37778931862957161709568_u128 - } else if exp == 76_usize { - 75557863725914323419136_u128 - } else if exp == 77_usize { - 151115727451828646838272_u128 - } else if exp == 78_usize { - 302231454903657293676544_u128 - } else if exp == 79_usize { - 604462909807314587353088_u128 - } else if exp == 80_usize { - 1208925819614629174706176_u128 - } else if exp == 81_usize { - 2417851639229258349412352_u128 - } else if exp == 82_usize { - 4835703278458516698824704_u128 - } else if exp == 83_usize { - 9671406556917033397649408_u128 - } else if exp == 84_usize { - 19342813113834066795298816_u128 - } else if exp == 85_usize { - 38685626227668133590597632_u128 - } else if exp == 86_usize { - 77371252455336267181195264_u128 - } else if exp == 87_usize { - 154742504910672534362390528_u128 - } else if exp == 88_usize { - 309485009821345068724781056_u128 - } else if exp == 89_usize { - 618970019642690137449562112_u128 - } else if exp == 90_usize { - 1237940039285380274899124224_u128 - } else if exp == 91_usize { - 2475880078570760549798248448_u128 - } else if exp == 92_usize { - 4951760157141521099596496896_u128 - } else if exp == 93_usize { - 9903520314283042199192993792_u128 - } else if exp == 94_usize { - 19807040628566084398385987584_u128 - } else if exp == 95_usize { - 39614081257132168796771975168_u128 - } else if exp == 96_usize { - 79228162514264337593543950336_u128 - } else if exp == 97_usize { - 158456325028528675187087900672_u128 - } else if exp == 98_usize { - 316912650057057350374175801344_u128 - } else if exp == 99_usize { - 633825300114114700748351602688_u128 - } else if exp == 100_usize { - 1267650600228229401496703205376_u128 - } else if exp == 101_usize { - 2535301200456458802993406410752_u128 - } else if exp == 102_usize { - 5070602400912917605986812821504_u128 - } else if exp == 103_usize { - 10141204801825835211973625643008_u128 - } else if exp == 104_usize { - 20282409603651670423947251286016_u128 - } else if exp == 105_usize { - 40564819207303340847894502572032_u128 - } else if exp == 106_usize { - 81129638414606681695789005144064_u128 - } else if exp == 107_usize { - 162259276829213363391578010288128_u128 - } else if exp == 108_usize { - 324518553658426726783156020576256_u128 - } else if exp == 109_usize { - 649037107316853453566312041152512_u128 - } else if exp == 110_usize { - 1298074214633706907132624082305024_u128 - } else if exp == 111_usize { - 2596148429267413814265248164610048_u128 - } else if exp == 112_usize { - 5192296858534827628530496329220096_u128 - } else if exp == 113_usize { - 10384593717069655257060992658440192_u128 - } else if exp == 114_usize { - 20769187434139310514121985316880384_u128 - } else if exp == 115_usize { - 41538374868278621028243970633760768_u128 - } else if exp == 116_usize { - 83076749736557242056487941267521536_u128 - } else if exp == 117_usize { - 166153499473114484112975882535043072_u128 - } else if exp == 118_usize { - 332306998946228968225951765070086144_u128 - } else if exp == 119_usize { - 664613997892457936451903530140172288_u128 - } else if exp == 120_usize { - 1329227995784915872903807060280344576_u128 - } else if exp == 121_usize { - 2658455991569831745807614120560689152_u128 - } else if exp == 122_usize { - 5316911983139663491615228241121378304_u128 - } else if exp == 123_usize { - 10633823966279326983230456482242756608_u128 - } else if exp == 124_usize { - 21267647932558653966460912964485513216_u128 - } else if exp == 125_usize { - 42535295865117307932921825928971026432_u128 - } else if exp == 126_usize { - 85070591730234615865843651857942052864_u128 - } else { - 170141183460469231731687303715884105728_u128 - } -} diff --git a/packages/math/src/armstrong_number.cairo b/packages/math/src/armstrong_number.cairo index 9d25314e..d7189571 100644 --- a/packages/math/src/armstrong_number.cairo +++ b/packages/math/src/armstrong_number.cairo @@ -15,7 +15,7 @@ pub fn is_armstrong_number(mut num: u128) -> bool { } let lastDigit = num % 10; - let sum = pow(lastDigit, digits); + let sum = pow(lastDigit, digits.into()); num = num / 10; if sum > original_num { break false; diff --git a/packages/math/src/const_pow.cairo b/packages/math/src/const_pow.cairo new file mode 100644 index 00000000..9f76b3db --- /dev/null +++ b/packages/math/src/const_pow.cairo @@ -0,0 +1,565 @@ +/// Calculate 2 raised to the power of the given exponent +/// using a pre-computed lookup table +/// # Arguments +/// * `exponent` - The exponent to raise 2 to +/// # Returns +/// * `u256` - The result of 2^exponent +/// # Panics +/// * If `exponent` is greater than 255 (out of the supported range) +pub fn pow2_u256(exponent: u32) -> u256 { + if exponent < 128 { + pow2(exponent).into() + } else { + u256 { low: 0, high: pow2(exponent - 128), } + } +} + +/// Calculate 2 raised to the power of the given exponent +/// using a pre-computed lookup table +/// # Arguments +/// * `exponent` - The exponent to raise 2 to +/// # Returns +/// * `u128` - The result of 2^exponent +/// # Panics +/// * If `exponent` is greater than 127 (out of the supported range) +pub fn pow2(exponent: u32) -> u128 { + let hardcoded_results: [u128; 128] = [ + 0x1, + 0x2, + 0x4, + 0x8, + 0x10, + 0x20, + 0x40, + 0x80, + 0x100, + 0x200, + 0x400, + 0x800, + 0x1000, + 0x2000, + 0x4000, + 0x8000, + 0x10000, + 0x20000, + 0x40000, + 0x80000, + 0x100000, + 0x200000, + 0x400000, + 0x800000, + 0x1000000, + 0x2000000, + 0x4000000, + 0x8000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000, + 0x100000000, + 0x200000000, + 0x400000000, + 0x800000000, + 0x1000000000, + 0x2000000000, + 0x4000000000, + 0x8000000000, + 0x10000000000, + 0x20000000000, + 0x40000000000, + 0x80000000000, + 0x100000000000, + 0x200000000000, + 0x400000000000, + 0x800000000000, + 0x1000000000000, + 0x2000000000000, + 0x4000000000000, + 0x8000000000000, + 0x10000000000000, + 0x20000000000000, + 0x40000000000000, + 0x80000000000000, + 0x100000000000000, + 0x200000000000000, + 0x400000000000000, + 0x800000000000000, + 0x1000000000000000, + 0x2000000000000000, + 0x4000000000000000, + 0x8000000000000000, + 0x10000000000000000, + 0x20000000000000000, + 0x40000000000000000, + 0x80000000000000000, + 0x100000000000000000, + 0x200000000000000000, + 0x400000000000000000, + 0x800000000000000000, + 0x1000000000000000000, + 0x2000000000000000000, + 0x4000000000000000000, + 0x8000000000000000000, + 0x10000000000000000000, + 0x20000000000000000000, + 0x40000000000000000000, + 0x80000000000000000000, + 0x100000000000000000000, + 0x200000000000000000000, + 0x400000000000000000000, + 0x800000000000000000000, + 0x1000000000000000000000, + 0x2000000000000000000000, + 0x4000000000000000000000, + 0x8000000000000000000000, + 0x10000000000000000000000, + 0x20000000000000000000000, + 0x40000000000000000000000, + 0x80000000000000000000000, + 0x100000000000000000000000, + 0x200000000000000000000000, + 0x400000000000000000000000, + 0x800000000000000000000000, + 0x1000000000000000000000000, + 0x2000000000000000000000000, + 0x4000000000000000000000000, + 0x8000000000000000000000000, + 0x10000000000000000000000000, + 0x20000000000000000000000000, + 0x40000000000000000000000000, + 0x80000000000000000000000000, + 0x100000000000000000000000000, + 0x200000000000000000000000000, + 0x400000000000000000000000000, + 0x800000000000000000000000000, + 0x1000000000000000000000000000, + 0x2000000000000000000000000000, + 0x4000000000000000000000000000, + 0x8000000000000000000000000000, + 0x10000000000000000000000000000, + 0x20000000000000000000000000000, + 0x40000000000000000000000000000, + 0x80000000000000000000000000000, + 0x100000000000000000000000000000, + 0x200000000000000000000000000000, + 0x400000000000000000000000000000, + 0x800000000000000000000000000000, + 0x1000000000000000000000000000000, + 0x2000000000000000000000000000000, + 0x4000000000000000000000000000000, + 0x8000000000000000000000000000000, + 0x10000000000000000000000000000000, + 0x20000000000000000000000000000000, + 0x40000000000000000000000000000000, + 0x80000000000000000000000000000000 + ]; + *hardcoded_results.span()[exponent] +} + +/// Calculate 2 raised to the power of the given exponent +/// using a pre-computed lookup table +/// # Arguments +/// * `exponent` - The exponent to raise 2 to +/// # Returns +/// * `felt252` - The result of 2^exponent +/// # Panics +/// * If `exponent` is greater than 251 (out of the supported range) +pub fn pow2_felt252(exponent: u32) -> felt252 { + let hardcoded_results: [felt252; 251] = [ + 0x1, + 0x2, + 0x4, + 0x8, + 0x10, + 0x20, + 0x40, + 0x80, + 0x100, + 0x200, + 0x400, + 0x800, + 0x1000, + 0x2000, + 0x4000, + 0x8000, + 0x10000, + 0x20000, + 0x40000, + 0x80000, + 0x100000, + 0x200000, + 0x400000, + 0x800000, + 0x1000000, + 0x2000000, + 0x4000000, + 0x8000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000, + 0x100000000, + 0x200000000, + 0x400000000, + 0x800000000, + 0x1000000000, + 0x2000000000, + 0x4000000000, + 0x8000000000, + 0x10000000000, + 0x20000000000, + 0x40000000000, + 0x80000000000, + 0x100000000000, + 0x200000000000, + 0x400000000000, + 0x800000000000, + 0x1000000000000, + 0x2000000000000, + 0x4000000000000, + 0x8000000000000, + 0x10000000000000, + 0x20000000000000, + 0x40000000000000, + 0x80000000000000, + 0x100000000000000, + 0x200000000000000, + 0x400000000000000, + 0x800000000000000, + 0x1000000000000000, + 0x2000000000000000, + 0x4000000000000000, + 0x8000000000000000, + 0x10000000000000000, + 0x20000000000000000, + 0x40000000000000000, + 0x80000000000000000, + 0x100000000000000000, + 0x200000000000000000, + 0x400000000000000000, + 0x800000000000000000, + 0x1000000000000000000, + 0x2000000000000000000, + 0x4000000000000000000, + 0x8000000000000000000, + 0x10000000000000000000, + 0x20000000000000000000, + 0x40000000000000000000, + 0x80000000000000000000, + 0x100000000000000000000, + 0x200000000000000000000, + 0x400000000000000000000, + 0x800000000000000000000, + 0x1000000000000000000000, + 0x2000000000000000000000, + 0x4000000000000000000000, + 0x8000000000000000000000, + 0x10000000000000000000000, + 0x20000000000000000000000, + 0x40000000000000000000000, + 0x80000000000000000000000, + 0x100000000000000000000000, + 0x200000000000000000000000, + 0x400000000000000000000000, + 0x800000000000000000000000, + 0x1000000000000000000000000, + 0x2000000000000000000000000, + 0x4000000000000000000000000, + 0x8000000000000000000000000, + 0x10000000000000000000000000, + 0x20000000000000000000000000, + 0x40000000000000000000000000, + 0x80000000000000000000000000, + 0x100000000000000000000000000, + 0x200000000000000000000000000, + 0x400000000000000000000000000, + 0x800000000000000000000000000, + 0x1000000000000000000000000000, + 0x2000000000000000000000000000, + 0x4000000000000000000000000000, + 0x8000000000000000000000000000, + 0x10000000000000000000000000000, + 0x20000000000000000000000000000, + 0x40000000000000000000000000000, + 0x80000000000000000000000000000, + 0x100000000000000000000000000000, + 0x200000000000000000000000000000, + 0x400000000000000000000000000000, + 0x800000000000000000000000000000, + 0x1000000000000000000000000000000, + 0x2000000000000000000000000000000, + 0x4000000000000000000000000000000, + 0x8000000000000000000000000000000, + 0x10000000000000000000000000000000, + 0x20000000000000000000000000000000, + 0x40000000000000000000000000000000, + 0x80000000000000000000000000000000, + 0x100000000000000000000000000000000, + 0x200000000000000000000000000000000, + 0x400000000000000000000000000000000, + 0x800000000000000000000000000000000, + 0x1000000000000000000000000000000000, + 0x2000000000000000000000000000000000, + 0x4000000000000000000000000000000000, + 0x8000000000000000000000000000000000, + 0x10000000000000000000000000000000000, + 0x20000000000000000000000000000000000, + 0x40000000000000000000000000000000000, + 0x80000000000000000000000000000000000, + 0x100000000000000000000000000000000000, + 0x200000000000000000000000000000000000, + 0x400000000000000000000000000000000000, + 0x800000000000000000000000000000000000, + 0x1000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000000000 + ]; + *hardcoded_results.span()[exponent] +} + +/// Calculate 10 raised to the power of the given exponent +/// using a pre-computed lookup table +/// # Arguments +/// * `exponent` - The exponent to raise 10 to +/// # Returns +/// * `u128` - The result of 10^exponent +/// # Panics +/// * If `exponent` is greater than 38 (out of the supported range) +pub fn pow10(exponent: u32) -> u128 { + let hardcoded_results: [u128; 38] = [ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000, + 100000000000000000000, + 1000000000000000000000, + 10000000000000000000000, + 100000000000000000000000, + 1000000000000000000000000, + 10000000000000000000000000, + 100000000000000000000000000, + 1000000000000000000000000000, + 10000000000000000000000000000, + 100000000000000000000000000000, + 1000000000000000000000000000000, + 10000000000000000000000000000000, + 100000000000000000000000000000000, + 1000000000000000000000000000000000, + 10000000000000000000000000000000000, + 100000000000000000000000000000000000, + 1000000000000000000000000000000000000, + 10000000000000000000000000000000000000 + ]; + *hardcoded_results.span()[exponent] +} + +/// Calculate 10 raised to the power of the given exponent +/// using a pre-computed lookup table +/// # Arguments +/// * `exponent` - The exponent to raise 10 to +/// # Returns +/// * `u128` - The result of 10^exponent +/// # Panics +/// * If `exponent` is greater than 77 (out of the supported range) +pub fn pow10_u256(exponent: u32) -> u256 { + let hardcoded_results: [u256; 77] = [ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000, + 100000000000000000000, + 1000000000000000000000, + 10000000000000000000000, + 100000000000000000000000, + 1000000000000000000000000, + 10000000000000000000000000, + 100000000000000000000000000, + 1000000000000000000000000000, + 10000000000000000000000000000, + 100000000000000000000000000000, + 1000000000000000000000000000000, + 10000000000000000000000000000000, + 100000000000000000000000000000000, + 1000000000000000000000000000000000, + 10000000000000000000000000000000000, + 100000000000000000000000000000000000, + 1000000000000000000000000000000000000, + 10000000000000000000000000000000000000, + 100000000000000000000000000000000000000, + 1000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000000000000000000000, + 100000000000000000000000000000000000000000000000000000000000000000000000000, + 1000000000000000000000000000000000000000000000000000000000000000000000000000, + 10000000000000000000000000000000000000000000000000000000000000000000000000000 + ]; + *hardcoded_results.span()[exponent] +} diff --git a/packages/math/src/karatsuba.cairo b/packages/math/src/karatsuba.cairo index a35a05c1..8c4a9c6e 100644 --- a/packages/math/src/karatsuba.cairo +++ b/packages/math/src/karatsuba.cairo @@ -1,6 +1,6 @@ //! # Karatsuba Multiplication. use core::cmp::max; -use super::{pow, count_digits_of_base}; +use super::{count_digits_of_base, const_pow::pow10}; /// Algorithm to multiply two numbers in O(n^1.6) running time /// # Arguments @@ -14,7 +14,9 @@ pub fn multiply(x: u128, y: u128) -> u128 { } let max_digit_counts = max(count_digits_of_base(x, 10), count_digits_of_base(y, 10)); + // the digits amount has to let middle_idx = div_half_ceil(max_digit_counts); + // free type conversion let (x1, x0) = split_number(x, middle_idx); let (y1, y0) = split_number(y, middle_idx); @@ -22,15 +24,15 @@ pub fn multiply(x: u128, y: u128) -> u128 { let z1 = multiply(x1, y1); let z2 = multiply(x0 + x1, y0 + y1); - return z0 + (z2 - z0 - z1) * pow(10, middle_idx) + z1 * pow(10, 2 * middle_idx); + return z0 + (z2 - z0 - z1) * pow10(middle_idx) + z1 * pow10(2 * middle_idx); } /// Helper function for 'multiply', divides an integer in half and rounds up strictly. /// # Arguments /// * `num` - The current value to be divided. /// # Returns -/// * `u128` - Half (rounded up) of num. -fn div_half_ceil(num: u128) -> u128 { +/// * `u32` - Half (rounded up) of num. +fn div_half_ceil(num: u32) -> u32 { if num % 2 != 0 { (num + 1) % 2 } else { @@ -44,7 +46,7 @@ fn div_half_ceil(num: u128) -> u128 { /// * `split_idx` - Index at which the number will be split /// # Returns /// * `(u128, u128)` -tuple representing the split number. -fn split_number(num: u128, split_idx: u128) -> (u128, u128) { - let divisor = pow(10, split_idx); +fn split_number(num: u128, split_idx: u32) -> (u128, u128) { + let divisor = pow10(split_idx); (num / divisor, num % divisor) } diff --git a/packages/math/src/lib.cairo b/packages/math/src/lib.cairo index 142f5c02..3f309bfb 100644 --- a/packages/math/src/lib.cairo +++ b/packages/math/src/lib.cairo @@ -3,6 +3,7 @@ pub mod armstrong_number; pub mod bip340; pub mod bitmap; pub mod collatz_sequence; +pub mod const_pow; pub mod ed25519; pub mod extended_euclidean_algorithm; pub mod fast_power; @@ -54,8 +55,8 @@ pub fn pow, +Mul, +Div, +Rem, +PartialEq, +Into, + /// * `num` - The number to count the digits of. /// * `base` - Base in which to count the digits. /// # Returns -/// * `felt252` - The number of digits in num of base -fn count_digits_of_base(mut num: u128, base: u128) -> u128 { +/// * `u32` - The number of digits in num of base +fn count_digits_of_base(mut num: u128, base: u128) -> u32 { let mut res = 0; while (num != 0) { num = num / base; diff --git a/packages/math/src/tests.cairo b/packages/math/src/tests.cairo index 97185775..0e3dbe51 100644 --- a/packages/math/src/tests.cairo +++ b/packages/math/src/tests.cairo @@ -3,6 +3,7 @@ mod armstrong_number_test; mod bip340_test; mod bitmap_test; mod collatz_sequence_test; +mod const_pow_test; mod ed25519_test; mod extended_euclidean_algorithm_test; mod fast_power_test; diff --git a/packages/math/src/tests/const_pow_test.cairo b/packages/math/src/tests/const_pow_test.cairo new file mode 100644 index 00000000..35284ba3 --- /dev/null +++ b/packages/math/src/tests/const_pow_test.cairo @@ -0,0 +1,92 @@ +use alexandria_math::const_pow::{pow2, pow2_u256, pow10, pow10_u256}; + +#[test] +#[available_gas(1000000000)] +fn pow2_test() { + assert_eq!(pow2(0), 1, "2^0 should be 1"); + assert_eq!(pow2(1), 2, "2^1 should be 2"); + assert_eq!(pow2(2), 4, "2^2 should be 4"); + assert_eq!(pow2(3), 8, "2^3 should be 8"); + assert_eq!(pow2(10), 1024, "2^10 should be 1024"); + assert_eq!(pow2(63), 0x8000000000000000, "2^63 should be 0x8000000000000000"); + assert_eq!( + pow2(127), + 0x80000000000000000000000000000000, + "2^127 should be 0x80000000000000000000000000000000" + ); +} + +#[test] +#[available_gas(1000000000)] +fn pow2_u256_test() { + assert_eq!(pow2_u256(0), 1, "2^0 should be 1"); + assert_eq!(pow2_u256(1), 2, "2^1 should be 2"); + assert_eq!( + pow2_u256(128), + 340282366920938463463374607431768211456, + "2^128 should be 340282366920938463463374607431768211456" + ); + assert_eq!( + pow2_u256(255), + 57896044618658097711785492504343953926634992332820282019728792003956564819968, + "2^255 should be 57896044618658097711785492504343953926634992332820282019728792003956564819968" + ); +} + +#[test] +#[available_gas(1000000000)] +fn pow10_test() { + assert_eq!(pow10(0), 1, "10^0 should be 1"); + assert_eq!(pow10(1), 10, "10^1 should be 10"); + assert_eq!(pow10(2), 100, "10^2 should be 100"); + assert_eq!(pow10(9), 1000000000, "10^9 should be 1000000000"); + assert_eq!(pow10(18), 1000000000000000000, "10^18 should be 1000000000000000000"); + assert_eq!( + pow10(37), + 10000000000000000000000000000000000000, + "10^37 should be 10000000000000000000000000000000000000" + ); +} + +#[test] +#[available_gas(1000000000)] +fn pow10_u256_test() { + assert_eq!(pow10_u256(0), 1, "10^0 should be 1"); + assert_eq!(pow10_u256(1), 10, "10^1 should be 10"); + assert_eq!(pow10_u256(2), 100, "10^2 should be 100"); + assert_eq!( + pow10_u256(38), + 100000000000000000000000000000000000000, + "10^38 should be 100000000000000000000000000000000000000" + ); + assert_eq!( + pow10_u256(76), + 10000000000000000000000000000000000000000000000000000000000000000000000000000, + "10^76 should be 10000000000000000000000000000000000000000000000000000000000000000000000000000" + ); +} + +#[test] +#[should_panic] +fn pow2_out_of_range_test() { + pow2(128); +} + +#[test] +#[should_panic] +fn pow2_u256_out_of_range_test() { + pow2_u256(256); +} + +#[test] +#[should_panic] +fn pow10_out_of_range_test() { + pow10(38); +} + +#[test] +#[should_panic] +fn pow10_u256_out_of_range_test() { + pow10_u256(77); +} + diff --git a/packages/merkle_tree/Scarb.toml b/packages/merkle_tree/Scarb.toml index 29fa0ab8..5e3f4b58 100644 --- a/packages/merkle_tree/Scarb.toml +++ b/packages/merkle_tree/Scarb.toml @@ -8,5 +8,8 @@ edition = "2023_11" [tool] fmt.workspace = true +[dependencies] +alexandria_math = { path = "../math" } + [dev-dependencies] -cairo_test.workspace = true \ No newline at end of file +cairo_test.workspace = true diff --git a/packages/merkle_tree/src/storage_proof.cairo b/packages/merkle_tree/src/storage_proof.cairo index a4623168..55cf79dc 100644 --- a/packages/merkle_tree/src/storage_proof.cairo +++ b/packages/merkle_tree/src/storage_proof.cairo @@ -1,6 +1,7 @@ use core::hash::HashStateTrait; use core::pedersen::PedersenTrait; use core::poseidon::PoseidonTrait; +use alexandria_math::const_pow::pow2_felt252; #[derive(Drop, Serde)] pub struct BinaryNode { @@ -131,7 +132,7 @@ fn traverse(expected_path: felt252, proof: Array) -> (felt252, felt252 let mut expected_hash = node_hash(@TrieNode::Edge(leaf)); let value = leaf.child; let mut path = leaf.path; - let mut path_length_pow2 = pow(2, leaf.length); + let mut path_length_pow2 = pow2_felt252(leaf.length.into()); loop { match nodes.pop_back() { @@ -149,7 +150,7 @@ fn traverse(expected_path: felt252, proof: Array) -> (felt252, felt252 TrieNode::Edge(edge_node) => { assert(expected_hash == *edge_node.child, 'invalid node hash'); path += *edge_node.path * path_length_pow2; - path_length_pow2 *= pow(2, *edge_node.length); + path_length_pow2 *= pow2_felt252((*edge_node.length).into()); } } expected_hash = node_hash(node); @@ -169,19 +170,6 @@ fn node_hash(node: @TrieNode) -> felt252 { } } -// TODO: replace with lookup table once array constants are available in Cairo -fn pow(x: felt252, n: u8) -> felt252 { - if n == 0 { - 1 - } else if n == 1 { - x - } else if (n & 1) == 1 { - x * pow(x * x, n / 2) - } else { - pow(x * x, n / 2) - } -} - #[inline(always)] fn pedersen_hash(a: felt252, b: felt252) -> felt252 { PedersenTrait::new(a).update(b).finalize()