From 698959c42bbea1c71ba6807cbe4cb38cfbdf778d Mon Sep 17 00:00:00 2001 From: Rodrigo Ferreira Date: Wed, 7 Aug 2024 17:52:58 -0300 Subject: [PATCH] Rust binding of final exp hint (#141) --- .github/workflows/hydra.yml | 5 +- .gitignore | 8 +- hydra/hints/multi_miller_witness.py | 17 +- hydra/poseidon_transcript.py | 4 +- tools/{hades_binding => garaga_rs}/Cargo.toml | 16 +- .../src/bls12_381_final_exp_witness.rs | 111 +++++++++ .../garaga_rs/src/bn254_final_exp_witness.rs | 214 ++++++++++++++++++ tools/garaga_rs/src/lib.rs | 132 +++++++++++ tools/hades_binding/src/lib.rs | 50 ---- tools/make/setup.sh | 4 +- 10 files changed, 483 insertions(+), 78 deletions(-) rename tools/{hades_binding => garaga_rs}/Cargo.toml (64%) create mode 100644 tools/garaga_rs/src/bls12_381_final_exp_witness.rs create mode 100644 tools/garaga_rs/src/bn254_final_exp_witness.rs create mode 100644 tools/garaga_rs/src/lib.rs delete mode 100644 tools/hades_binding/src/lib.rs diff --git a/.github/workflows/hydra.yml b/.github/workflows/hydra.yml index c7efc182..bc7a0bb5 100644 --- a/.github/workflows/hydra.yml +++ b/.github/workflows/hydra.yml @@ -36,9 +36,10 @@ jobs: - name: Compile Rust bindings with maturin run: | source venv/bin/activate - cd tools/hades_binding + cd tools/garaga_rs + cargo test maturin develop --release - name: Run pytest run: | source venv/bin/activate - pytest -n auto \ No newline at end of file + pytest -n auto diff --git a/.gitignore b/.gitignore index 07f5cccf..aec237ba 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,8 @@ venv tools/gnark/main tools/gnark/bls12_381/cairo_test/main -tools/hades_binding/target/ -tools/hades_binding/Cargo.lock -tools/hades_binding/sys +tools/garaga_rs/target/ +tools/garaga_rs/Cargo.lock +tools/garaga_rs/sys -src/cairo/target/ \ No newline at end of file +src/cairo/target/ diff --git a/hydra/hints/multi_miller_witness.py b/hydra/hints/multi_miller_witness.py index aba4cd94..41c0ee44 100644 --- a/hydra/hints/multi_miller_witness.py +++ b/hydra/hints/multi_miller_witness.py @@ -1,23 +1,24 @@ import math +from hydra.algebra import PyFelt from hydra.definitions import CURVES, CurveID, G1Point, G2Point from hydra.hints.bls import get_root_and_scaling_factor_bls from hydra.hints.tower_backup import E12 from tools.gnark_cli import GnarkCLI - +import garaga_rs def get_final_exp_witness(curve_id: int, f: E12) -> tuple[E12, E12]: """ Returns the witness for the final exponentiation step. """ - if curve_id == CurveID.BN254.value: - c, wi = find_c_e12(f, get_27th_bn254_root()) - return c, wi - elif curve_id == CurveID.BLS12_381.value: - c, wi = get_root_and_scaling_factor_bls(f) - return c, wi - else: + if curve_id != CurveID.BN254.value and curve_id != CurveID.BLS12_381.value: raise ValueError(f"Curve ID {curve_id} not supported") + curve = CURVES[curve_id] + f_values = f.value_coeffs + c_values, wi_values = garaga_rs.get_final_exp_witness(curve_id, f_values) + c = E12([PyFelt(v, curve.p) for v in c_values], curve_id) + wi = E12([PyFelt(v, curve.p) for v in wi_values], curve_id) + return c, wi def get_lambda(curve_id: CurveID) -> int: diff --git a/hydra/poseidon_transcript.py b/hydra/poseidon_transcript.py index 66f838e4..ba43eb3b 100644 --- a/hydra/poseidon_transcript.py +++ b/hydra/poseidon_transcript.py @@ -1,4 +1,4 @@ -import hades_binding +import garaga_rs from starkware.cairo.common.poseidon_utils import PoseidonParams from starkware.cairo.common.poseidon_utils import ( hades_permutation as hades_permutation_slow, # #only for testing times @@ -10,7 +10,7 @@ def hades_permutation(s0: int, s1: int, s2: int) -> tuple[int, int, int]: - r0, r1, r2 = hades_binding.hades_permutation( + r0, r1, r2 = garaga_rs.hades_permutation( (s0 % STARK).to_bytes(32, "big"), (s1 % STARK).to_bytes(32, "big"), (s2 % STARK).to_bytes(32, "big"), diff --git a/tools/hades_binding/Cargo.toml b/tools/garaga_rs/Cargo.toml similarity index 64% rename from tools/hades_binding/Cargo.toml rename to tools/garaga_rs/Cargo.toml index ce6d7508..a92481c6 100644 --- a/tools/hades_binding/Cargo.toml +++ b/tools/garaga_rs/Cargo.toml @@ -1,20 +1,16 @@ [package] -name = "hades_binding" +name = "garaga_rs" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -pyo3 = { version = "0.15", features = ["extension-module"] } +pyo3 = { version = "0.15", features = ["extension-module", "num-bigint"] } num-bigint = "0.4" -num-traits = "0.2" -sha2 = "0.10" -num-integer = "0.1" -lazy_static = "1.4.0" - - - +ark-bn254 = "0.4" +ark-bls12-381 = "0.4" +ark-ff = "0.4" lambdaworks-crypto = { git = "https://github.com/lambdaclass/lambdaworks.git" } -lambdaworks-math = { git = "https://github.com/lambdaclass/lambdaworks.git" } \ No newline at end of file +lambdaworks-math = { git = "https://github.com/lambdaclass/lambdaworks.git" } diff --git a/tools/garaga_rs/src/bls12_381_final_exp_witness.rs b/tools/garaga_rs/src/bls12_381_final_exp_witness.rs new file mode 100644 index 00000000..1b63dd71 --- /dev/null +++ b/tools/garaga_rs/src/bls12_381_final_exp_witness.rs @@ -0,0 +1,111 @@ +use ark_bls12_381::Fq12; +use ark_ff::Field; + +/// Takes a miller loop output and returns root, shift such that +/// root ** lam = shift * mlo, if and only if mlo ** h == 1. +pub fn get_final_exp_witness(mlo: Fq12) -> (Fq12, Fq12) { + let shift = mlo.pow(H3_S); + let root = (shift * mlo).pow(E); + return (root, shift); +} + +const H3_S: [u64; 68] = [ + 0x3546cefb808890f0, 0x641330f2e1e842d7, 0x87f5d9df187f516a, 0x859c07e6ea022bc0, + 0x9528051fe44630de, 0xea11d7f897145eb9, 0x889bb4506a0322fb, 0x0d646b1efe54084a, + 0xc97e07cf5b584781, 0xb935ad200614b088, 0x62fc282e6b69e55e, 0x1aa363aed49c65ac, + 0x67b40a59e5b8738e, 0x6d10d4d358c7ef08, 0xffdebea143aa81a2, 0x3a3bdc3a328d93a7, + 0x7388a43206c95a9c, 0x2818509193ed294f, 0x9f45587930e7c088, 0x60ad8d281c783b30, + 0x847e843a73a904fd, 0x45812c8ae65aab68, 0xc08229ab91464915, 0x3206d41e1ba53f66, + 0xfc5966c331ec72e2, 0x044826c4e7af7613, 0x2407cbb0a82ed97e, 0x7a4b4c3e69046f62, + 0x5f2b1a013f759119, 0x6364ce56ec1689ef, 0x45f668f6c66a15f2, 0xc3b31ca6b881c438, + 0xb1517b99f1ca7257, 0x22b0d2c6267403d9, 0x9d15e60372cc338d, 0x4b812aacfeb0b422, + 0x2fff766f9193bd25, 0x000ff7ccf94859dd, 0xd11b9d21c74d5e11, 0x193efcb76e4075b4, + 0x3dc5d3624b226d9c, 0xa28eab2d6f10d623, 0x6d82b81d18973336, 0x4d3cb074cbfb327f, + 0xaccd414c69208cfc, 0xc73ef082a325fa4f, 0x02355b9525fc9508, 0xd0d1d3c39399eeb0, + 0xfcd8f64eecb22f47, 0x48b77abab9c1250f, 0xee2d2507d12c6c89, 0xbf76bd8efed7c907, + 0xd356fac2fb717b68, 0x49d83d768ec95330, 0x074f36284b55f60f, 0x519ebde1c30350fa, + 0xabd1ec4ee31955ee, 0x4c061913753ece72, 0xd59968daee6b65c8, 0x2bd228194946730a, + 0x386e734e82cf750c, 0x368f7530de524d17, 0x4baa54ea85455a82, 0x38974a9b00eab788, + 0xfadb7f89cb2c5de9, 0xfdd66cdf6b27a928, 0xe4b592105c07d08e, 0x00000000004c6694, +]; + +const E: [u64; 67] = [ + 0x00b6e3fefaaf6c23, 0xba50f7521d495337, 0x8fa40e48bef9c692, 0xd0cabc76f961074d, + 0x805168994fad7512, 0x6aa1c8c9776871c3, 0x2a68a4a67f55a88e, 0x9b798d4030a925b3, + 0xbd017f6f55031685, 0x5941c1d1cc14bb00, 0x8cab8b6b547ac1b1, 0xe8ba0fa4956270d9, + 0x402e2ea408490f83, 0xe18f6141e75e6151, 0x46944b9ccc52a999, 0x2d1a56bba3476aeb, + 0x2884afb4610957be, 0x0c7f1ca9e35adf3a, 0x25a8b21e58a3059d, 0x23e0efdaa215a695, + 0xbe22bd36eae8284d, 0x4941f4877fe7756e, 0xed8552dabf485b53, 0x3d6f68b235ee04c1, + 0xfba66f18e3f07980, 0x9f0afd39b4ffa61a, 0x770409ca39863bc3, 0x1ec1adbd1d7fd6b0, + 0x27919600415dabdb, 0xf2e39dc4a2ef8538, 0x2c1761964cb5c1f1, 0x7cc854792a8a9c52, + 0x10e35f0ec2b3fc7a, 0xaf9e06161c9a4afc, 0x6835191eddcb197f, 0x851646957b8a14f5, + 0x23fb3f871de4d8e5, 0x0b41c4306d335cb6, 0x03c5be4d379339f3, 0x7dc912d6e44a7fab, + 0x06d087dcd3618e42, 0x64e6c398c568675b, 0x5893e10ca8e48731, 0xe9d43d3b7ad9af62, + 0x67bcb0d7498e2482, 0xacaab294577b5f17, 0xf82b66fd5a502089, 0x7ffac9d4f6359297, + 0x2c27b262ebbdcb0c, 0xf1bd69c020b89006, 0x987f26d6b7de5b55, 0x7a07bc5b60e1ed38, + 0x362f49e4dd57cf6c, 0xd5d230c454517eb4, 0x7b93fc9ccc5cd0d8, 0x89def3e07ff5e35a, + 0xa70dcd395814cc0c, 0xb18f69bd487a02af, 0x066c5b338d11e220, 0xa8bba4f42f4d8974, + 0x75e528ac2412d477, 0xd46fda1e16fb9588, 0xfc4e91d7468d4790, 0x8a72692b8d18d58e, + 0x22142ac1801c949d, 0xcb940d75d40a0772, 0x00000000004ea48c, +]; + +#[cfg(test)] +mod tests { + use num_bigint::BigInt; + use super::{H3_S, E}; + + #[test] + fn test_hardcoded_consts() { + let x = -BigInt::from(0xD201000000010000u64); // CURVES[CurveID.BLS12_381.value].x + let k = 12; + let r = x.pow(4) - x.pow(2) + 1; + let q: BigInt = (&x - BigInt::from(1)).pow(2) / 3 * &r + &x; + let h = (q.pow(k) - 1) / &r; + + let lam = -&x + &q; + let m = &lam / &r; + + let p = BigInt::from(5044125407647214251u64); + let h3 = &h / (27 * &p); + + let e = lam.modinv(&h3).unwrap(); + let s = ((&p * 27 - 1) * h3.modinv(&(&p * 27)).unwrap()) % (&p * 27); + + let h3_s = &h3 * &s; + + assert_eq!(h % (27 * &p), BigInt::from(0)); + assert_eq!(m, 3 * p.pow(2)); + + assert_eq!(gcd(&BigInt::from(3), &h3), BigInt::from(1)); + assert_eq!(gcd(&p.pow(2), &h3), BigInt::from(1)); + assert_eq!(gcd(&p, &h3), BigInt::from(1)); + assert_eq!(gcd(&p, &(27 * &h3)), BigInt::from(1)); + assert_eq!(gcd(&BigInt::from(27), &(&p * &h3)), BigInt::from(1)); + + assert_eq!((q.pow(3) - 1) % 27, BigInt::from(0)); + + assert_eq!(H3_S, to_words_le(&h3_s).as_slice()); + assert_eq!(E, to_words_le(&e).as_slice()); + } + + fn gcd(a: &BigInt, b: &BigInt) -> BigInt { + let mut a = a.clone(); + let mut b = b.clone(); + while b != BigInt::from(0) { + (a, b) = (b.clone(), a % b); + } + return a; + } + + fn to_words_le(bigint: &BigInt) -> Vec { + let (sign, bytes) = bigint.to_bytes_le(); + assert!(sign != num_bigint::Sign::Minus); + let mut words = Vec::with_capacity((bytes.len() + 7) / 8); + for chunk in bytes.chunks(8) { + let mut word = [0u8; 8]; + word[..chunk.len()].copy_from_slice(chunk); + words.push(u64::from_le_bytes(word)); + } + return words; + } +} diff --git a/tools/garaga_rs/src/bn254_final_exp_witness.rs b/tools/garaga_rs/src/bn254_final_exp_witness.rs new file mode 100644 index 00000000..2fc0f7ca --- /dev/null +++ b/tools/garaga_rs/src/bn254_final_exp_witness.rs @@ -0,0 +1,214 @@ +use ark_bn254::{Fq, Fq2, Fq6, Fq12}; +use ark_ff::{Field, One, Zero}; +use std::str::FromStr; + +/// Algorithm 5: Algorithm for computing λ residues over BN curve +/// Input: Output of a Miller loop f +/// Output: (c, wi) such that c ** λ = f * wi +pub fn get_final_exp_witness(f: Fq12) -> (Fq12, Fq12) { + // fixed 27-th root of unity + let w = get_27th_root(); + // case 1: f ** ((q ** k - 1) / 3) = 1 + let mut c = f; + let mut ws = Fq12::one(); + if c.pow(EXP) != Fq12::one() { + // case 2: (f * w) ** ((q ** k - 1) / 3) = 1 + c *= w; + ws *= w; + if c.pow(EXP) != Fq12::one() { + // case 3: (f * w * w) ** ((q ** k - 1) / 3) = 1 + c *= w; + ws *= w; + } + } + // c <- f ** (r′ * m′′) + c = c.pow(R_M_D_INV); + // c <- c ** (1 / 3) (by using modified Tonelli-Shanks 4) + return (find_cube_root(c, w), ws); +} + +fn get_27th_root() -> Fq12 { + Fq12::new( + Fq6::new(Fq2::zero(), Fq2::zero(), Fq2::new( + Fq::from_str("8204864362109909869166472767738877274689483185363591877943943203703805152849").unwrap(), + Fq::from_str("17912368812864921115467448876996876278487602260484145953989158612875588124088").unwrap(), + )), + Fq6::zero(), + ) +} + +/// Algorithm 4: Modified Tonelli-Shanks for cube roots +/// Input: Cube residue a, cube non residue w +/// Output: x such that x^3 = a +fn find_cube_root(a: Fq12, w: Fq12) -> Fq12 { + let a_inv = a.inverse().unwrap(); + let we = w.pow(EXP0); + let mut x = a.pow(EXP0); + let mut t = pow_3_ord(x.pow([3]) * a_inv); + while t != 0 { + x *= we; + t = pow_3_ord(x.pow([3]) * a_inv); + } + return x; +} + +fn pow_3_ord(a: Fq12) -> usize { + let mut a = a; + let mut t = 0; + while a != Fq12::one() { + a = a.pow([3]); + t += 1; + } + return t; +} + +// (q ** k - 1) / 3 +const EXP: [u64; 48] = [ + 0xeb46f64643825060, 0xc09504ce57838ff3, 0xb6973b1dfda111a7, 0x9e6a6b1d46fc408c, + 0x745bdaf039c199f6, 0xe9f65a41395df713, 0x4dcd3d267739953c, 0x9f49699c7d2e3b27, + 0xb189f37c0ecd514e, 0x55aa926463b3f1ad, 0x6030fad438f67304, 0x1dc6e7821edb8a5c, + 0x3fabe2a396c821ee, 0xce442caa65704817, 0xac5266c00ed4ded7, 0x53aa9ef14c0ae51f, + 0x133df7ebbc224e97, 0x88ce9faea263de92, 0x8c4be6bdd2b88017, 0x628d5a19e9c247d9, + 0xa93bf3094d3d5518, 0x3939f77b19cd8e05, 0x3c85c4759d907006, 0xf47559371ceb7cb4, + 0x9868d7443cc60fe8, 0x591589f02cf8ecb7, 0x680fa342f7100bba, 0xb44b431aae371e85, + 0x99625bea8196289d, 0xa38d36e079b35749, 0x08d38b7277eb44ec, 0xb5de835af494b061, + 0x370bd1df6206ad8e, 0xf755226d1fb5139f, 0xedafa93168993756, 0x5b43e8559e471ed9, + 0xe84ed08d6375382d, 0x9b99a5c06b47a88a, 0x19e45304da068978, 0x12aff3b863cdce2f, + 0xb0178e622c3aaf92, 0x19e6b3b6373de8df, 0xeb4cec3eff8e12f1, 0xc3fc152a73114859, + 0xd516d062f8015f36, 0x6440dd3153897c68, 0x73924a5d67a5d259, 0x00000002fae42e49, +]; + +// (s + 1) / 3 +const EXP0: [u64; 48] = [ + 0x7102a0d331e861cb, 0x1a187b6ff0473e38, 0xcddfacdb2f51d13f, 0x483cd48f4e7b1ed5, + 0xd4e6f5255778f2bd, 0x83ecae026a6bc6c7, 0x911a907caf15187d, 0xe9747f2bb8c8d2c8, + 0x069354deab370302, 0x61fcd603b7d741d7, 0xcaac7b1157716c8e, 0xb540417698d8b945, + 0x6aa78d2280d80141, 0x4a028665203390e4, 0x6eadb7f42679a970, 0x586e9d9728be087c, + 0xedbfecbce10ac08a, 0x1807a7196e4f8cfb, 0xdf452e78ceea638f, 0xb7cc58aba05c8766, + 0xb0ef41e3e66a915f, 0x3b02259c43537709, 0x31a623b881185000, 0x4b6ca47d4cec46fd, + 0xd63cc59a3b23c7b3, 0x7513c2bd0b25a9f3, 0xdded9dc01c1d09ea, 0xcdc9e60a783aee2a, + 0x9d62752e9c80d218, 0x944799bc7649033b, 0xed5d2b1733d94e67, 0x19b2e86baa3e6558, + 0xe5982437ae4c1964, 0xffadd1de1d9e6905, 0x253f6514cafc3174, 0x58b6a9ca483b85e2, + 0xe2ad95f2460dd2ac, 0x8a80f32d0d746e89, 0xe483b739118e76de, 0x4c8b41ea6282e1b5, + 0x81c7fbcabf448b3e, 0x4ccfa7d757611b96, 0x2528c66125e8d14b, 0x6612d16063139a62, + 0xd87c1aae55098844, 0x628724a303180e16, 0x33b015b79b8ae1dd, 0x000000001c41570c, +]; + +// r_inv * m_dash_inv +const R_M_D_INV: [u64; 88] = [ + 0x5269cbffa1de7d17, 0x752a442f05402918, 0xadc220e391c8b45d, 0x0dbe15d89c0dfb4d, + 0x2ced6e1be7dd5e8a, 0x575712a5d3d521dc, 0xcadc50ae726c1eb2, 0x7c7bb95af32e875b, + 0x66671e4a6d4f9732, 0x2e1513eddd68cb99, 0x35ec1147bc833f2d, 0x813c5551bdfcedbb, + 0xafb52c74db67694d, 0xa94f43e174bd3f6d, 0xf176b9f29acc032e, 0x983092561ca6e1fe, + 0xeaae5e50cd083d65, 0x814477de35d5a366, 0x2fcc4cffc1b1e3fd, 0x03fa0ce334fb3fe4, + 0x66540780fb9ac8f7, 0xb9f1db69db43907c, 0xffa31ad233044010, 0x2a2eb529ca2226e2, + 0x29bf149a2f7e4c09, 0x1b51c305bd849dc1, 0x18fc937dbfa56566, 0xbbf6da52bb8e5703, + 0x332d30ae50878d68, 0xf2902f5ff575a178, 0x0be45ffc9c011320, 0xd7f46d0e2bdbb46d, + 0xcc76c1af25c63a3e, 0x41b9be93546909bd, 0xe82c0ad99b03cea7, 0x3d71429a78379fac, + 0x17bc76ca22e89651, 0x46da9fc09404fe5b, 0x5b7add89effe054b, 0x2a55051203606c83, + 0x5ae677a94d11680d, 0x8818c3dfb091ea0e, 0xe7ca63d528ef0ce3, 0x6540ab5b17bc3e4e, + 0xee17fccf70b3bfa6, 0x4d269f99b85e3861, 0x0f439a8ede245648, 0x3017de195816c113, + 0x2ac5abaa37e5d43c, 0x3e90bdbb16953cd0, 0x8da22490e8158422, 0xb1d0af2d2bce56e5, + 0x40939a400333329d, 0x87c9c0b6aa231e84, 0x22003c8e41d77534, 0xa6e0d349cc2a3430, + 0x410c9e004cd770fa, 0xde4616b947f58692, 0x9f9af1729273af7a, 0x0dab3d19889c8dfc, + 0xd9c2c4c7f76c2cdf, 0x16e443a7d20359d8, 0x7b1baced23deb8fe, 0xf06f91873a3f53f9, + 0x2fe9cbacad4165e5, 0x1c42aec2ef6eabb7, 0x6b68c8c486218334, 0x1f5191d84e6f1ce7, + 0xbcf4530ad9ba18f0, 0xe3a540d126a882d9, 0xe8c30bdcb670e368, 0x244c1c4ce26fa40e, + 0x766d84f873c7479e, 0xb5bb0451361c8bb1, 0xdb3cad9e79c4cec4, 0xb3a3c26108159c65, + 0xc73f5e0555a15603, 0x49059e6d30de40ae, 0x2d3521471b552560, 0x709c2cfe852f9c82, + 0x4a4d30b72e03a39e, 0xf3b7af30db17e951, 0x871fb7dfdaac5fa6, 0x1fe6c8220475c036, + 0x458467c2fd0c5f61, 0xcde087ba1c0a1e85, 0xd1e99cd525608c71, 0x0000000000000040, +]; + +#[cfg(test)] +mod tests { + use ark_bn254::{Fq, Fr, Fq12}; + use ark_ff::{BigInteger, Field, One, PrimeField}; + use num_bigint::BigInt; + use super::{EXP, EXP0, R_M_D_INV, get_27th_root}; + + #[test] + fn test_hardcoded_consts() { + let r = to_bigint(&Fr::MODULUS); + let q = to_bigint(&Fq::MODULUS); + let x = BigInt::from(0x44E992B44A6909F1u64); // CURVES[CurveID.BN254.value].x + + let w = get_27th_root(); + assert!(w.pow([27]) == Fq12::one(), "root_27th**27 should be one"); + assert!(w.pow([9]) != Fq12::one(), "root_27th**9 should not be one"); + + let exp = (q.pow(12) - 1) / 3; + + let h = (q.pow(12) - 1) / &r; // = 3^3 · l # where gcd(l, 3) = 1 + assert!(gcd(&r, &h) == BigInt::from(1)); + + let r_inv = r.modinv(&h).unwrap(); + + let base = 3; + let (k, l) = decompose_scalar_into_b_powers_and_remainder(&h, base); + assert!(base.pow(k) * &l == h, "3^k * l should be h"); + assert!(&h % (base.pow(k)) == BigInt::from(0), "h should be a multiple of 3^k"); + assert!(gcd(&l, &BigInt::from(base)) == BigInt::from(1), "l should be coprime with 3"); + + let lam: BigInt = 6 * &x + 2 + &q - q.pow(2) + q.pow(3); // https://eprint.iacr.org/2008/096.pdf See section 4 for BN curves. + + assert!(&lam % &r == BigInt::from(0), "λ should be a multiple of r. See section 4.2.2"); + let m = &lam / &r; + let d = gcd(&m, &h); + + assert!(&m % &d == BigInt::from(0), "m should be a multiple of d"); + let m_dash = &m / &d; // m' = m/d + assert!(&m_dash % &h != BigInt::from(0), "m/d should not divide h. See section 4.2.2 Theorem 2."); + assert!(&d * &r * &m_dash == lam, "incorrect parameters"); // sanity check + assert!(gcd(&lam, &(q.pow(12) - BigInt::from(1))) == &d * &r); + assert!(gcd(&m_dash, &(q.pow(12) - BigInt::from(1))) == BigInt::from(1), "m_dash should be coprime with q**12 - 1 'by construction'. See 4.3.2 computing m-th root"); + let m_d_inv = m_dash.modinv(&h).unwrap(); + + let r_m_d_inv = &r_inv * &m_d_inv; + + let (_, s) = decompose_scalar_into_b_powers_and_remainder(&(q.pow(12) - 1), 3); + let exp0 = (&s + 1) / 3; + + assert_eq!(EXP, to_words_le(&exp).as_slice()); + assert_eq!(EXP0, to_words_le(&exp0).as_slice()); + assert_eq!(R_M_D_INV, to_words_le(&r_m_d_inv).as_slice()); + } + + fn decompose_scalar_into_b_powers_and_remainder(scalar: &BigInt, b: usize) -> (u32, BigInt) { + // Decompose scalar into b^k * l, where l is not divisible by b. + let mut k: u32 = 0; + let mut l: BigInt = scalar.clone(); + while &l % b == BigInt::from(0) { + l /= b; + k += 1; + } + assert!(&l % b != BigInt::from(0), "l should not be divisible by b"); + assert!(*scalar == b.pow(k) * &l, "scalar should be the product of b^k * l"); + return (k, l); + } + + fn gcd(a: &BigInt, b: &BigInt) -> BigInt { + let mut a = a.clone(); + let mut b = b.clone(); + while b != BigInt::from(0) { + (a, b) = (b.clone(), a % b); + } + return a; + } + + fn to_words_le(bigint: &BigInt) -> Vec { + let (sign, bytes) = bigint.to_bytes_le(); + assert!(sign != num_bigint::Sign::Minus); + let mut words = Vec::with_capacity((bytes.len() + 7) / 8); + for chunk in bytes.chunks(8) { + let mut word = [0u8; 8]; + word[..chunk.len()].copy_from_slice(chunk); + words.push(u64::from_le_bytes(word)); + } + return words; + } + + fn to_bigint(v: &ark_ff::BigInt<4>) -> BigInt { + return BigInt::from_bytes_be(num_bigint::Sign::Plus, &v.to_bytes_be()); + } +} diff --git a/tools/garaga_rs/src/lib.rs b/tools/garaga_rs/src/lib.rs new file mode 100644 index 00000000..75d83872 --- /dev/null +++ b/tools/garaga_rs/src/lib.rs @@ -0,0 +1,132 @@ +pub mod bn254_final_exp_witness; +pub mod bls12_381_final_exp_witness; + +use ark_ff::PrimeField; +use num_bigint::BigUint; +use lambdaworks_crypto::hash::poseidon::{starknet::PoseidonCairoStark252, Poseidon}; +use lambdaworks_math::{ + field::{ + element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, + }, + traits::ByteConversion, +}; +use pyo3::{ + types::{PyBytes, PyList, PyTuple}, + {prelude::*, wrap_pyfunction}, +}; + +#[pymodule] +fn garaga_rs(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(get_final_exp_witness, m)?)?; + m.add_function(wrap_pyfunction!(hades_permutation, m)?)?; + Ok(()) +} + +#[pyfunction] +fn get_final_exp_witness(py: Python, curve_id: usize, py_list: &PyList) -> PyResult { + let f_0: BigUint = py_list[0].extract()?; + let f_1: BigUint = py_list[1].extract()?; + let f_2: BigUint = py_list[2].extract()?; + let f_3: BigUint = py_list[3].extract()?; + let f_4: BigUint = py_list[4].extract()?; + let f_5: BigUint = py_list[5].extract()?; + let f_6: BigUint = py_list[6].extract()?; + let f_7: BigUint = py_list[7].extract()?; + let f_8: BigUint = py_list[8].extract()?; + let f_9: BigUint = py_list[9].extract()?; + let f_10: BigUint = py_list[10].extract()?; + let f_11: BigUint = py_list[11].extract()?; + + if curve_id == 0 { // BN254 + use ark_bn254::{Fq, Fq2, Fq6, Fq12}; + let f = Fq12::new( + Fq6::new( + Fq2::new(Fq::from(f_0), Fq::from(f_1)), + Fq2::new(Fq::from(f_2), Fq::from(f_3)), + Fq2::new(Fq::from(f_4), Fq::from(f_5)), + ), + Fq6::new( + Fq2::new(Fq::from(f_6), Fq::from(f_7)), + Fq2::new(Fq::from(f_8), Fq::from(f_9)), + Fq2::new(Fq::from(f_10), Fq::from(f_11)), + ), + ); + let (c, wi) = bn254_final_exp_witness::get_final_exp_witness(f); + fn to(v: Fq12) -> [BigUint; 12] { + [ + BigUint::from(v.c0.c0.c0.into_bigint()), BigUint::from(v.c0.c0.c1.into_bigint()), + BigUint::from(v.c0.c1.c0.into_bigint()), BigUint::from(v.c0.c1.c1.into_bigint()), + BigUint::from(v.c0.c2.c0.into_bigint()), BigUint::from(v.c0.c2.c1.into_bigint()), + BigUint::from(v.c1.c0.c0.into_bigint()), BigUint::from(v.c1.c0.c1.into_bigint()), + BigUint::from(v.c1.c1.c0.into_bigint()), BigUint::from(v.c1.c1.c1.into_bigint()), + BigUint::from(v.c1.c2.c0.into_bigint()), BigUint::from(v.c1.c2.c1.into_bigint()), + ] + } + let py_tuple = PyTuple::new(py, [PyList::new(py, to(c)), PyList::new(py, to(wi))]); + return Ok(py_tuple.into()); + } + + if curve_id == 1 { // BLS12_381 + use ark_bls12_381::{Fq, Fq2, Fq6, Fq12}; + let f = Fq12::new( + Fq6::new( + Fq2::new(Fq::from(f_0), Fq::from(f_1)), + Fq2::new(Fq::from(f_2), Fq::from(f_3)), + Fq2::new(Fq::from(f_4), Fq::from(f_5)), + ), + Fq6::new( + Fq2::new(Fq::from(f_6), Fq::from(f_7)), + Fq2::new(Fq::from(f_8), Fq::from(f_9)), + Fq2::new(Fq::from(f_10), Fq::from(f_11)), + ), + ); + let (c, wi) = bls12_381_final_exp_witness::get_final_exp_witness(f); + fn to(v: Fq12) -> [BigUint; 12] { + [ + BigUint::from(v.c0.c0.c0.into_bigint()), BigUint::from(v.c0.c0.c1.into_bigint()), + BigUint::from(v.c0.c1.c0.into_bigint()), BigUint::from(v.c0.c1.c1.into_bigint()), + BigUint::from(v.c0.c2.c0.into_bigint()), BigUint::from(v.c0.c2.c1.into_bigint()), + BigUint::from(v.c1.c0.c0.into_bigint()), BigUint::from(v.c1.c0.c1.into_bigint()), + BigUint::from(v.c1.c1.c0.into_bigint()), BigUint::from(v.c1.c1.c1.into_bigint()), + BigUint::from(v.c1.c2.c0.into_bigint()), BigUint::from(v.c1.c2.c1.into_bigint()), + ] + } + let py_tuple = PyTuple::new(py, [PyList::new(py, to(c)), PyList::new(py, to(wi))]); + return Ok(py_tuple.into()); + } + + panic!("Curve ID {} not supported", curve_id); +} + +#[pyfunction] +fn hades_permutation( + py: Python, + py_value_1: &PyBytes, + py_value_2: &PyBytes, + py_value_3: &PyBytes, +) -> PyResult { + let byte_slice_1: &[u8] = py_value_1.as_bytes(); + let byte_slice_2: &[u8] = py_value_2.as_bytes(); + let byte_slice_3: &[u8] = py_value_3.as_bytes(); + + let mut state: Vec> = vec![ + FieldElement::::from_bytes_be(byte_slice_1) + .expect("Unable to convert first param from bytes to FieldElement"), + FieldElement::::from_bytes_be(byte_slice_2) + .expect("Unable to convert second param from bytes to FieldElement"), + FieldElement::::from_bytes_be(byte_slice_3) + .expect("Unable to convert third param from bytes to FieldElement"), + ]; + + PoseidonCairoStark252::hades_permutation(&mut state); + + let py_tuple = PyTuple::new( + py, + state.iter().map(|fe| { + let fe_bytes = fe.to_bytes_be(); + PyBytes::new(py, &fe_bytes) + }), + ); + + Ok(py_tuple.into()) +} diff --git a/tools/hades_binding/src/lib.rs b/tools/hades_binding/src/lib.rs deleted file mode 100644 index 64fe7135..00000000 --- a/tools/hades_binding/src/lib.rs +++ /dev/null @@ -1,50 +0,0 @@ -use lambdaworks_crypto::hash::poseidon::{starknet::PoseidonCairoStark252, Poseidon}; -use lambdaworks_math::{ - field::{ - element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField, - }, - traits::ByteConversion, -}; -use pyo3::{ - types::{PyBytes, PyTuple}, - {prelude::*, wrap_pyfunction}, -}; - -#[pymodule] -fn hades_binding(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(hades_permutation, m)?)?; - Ok(()) -} - -#[pyfunction] -fn hades_permutation( - py: Python, - py_value_1: &PyBytes, - py_value_2: &PyBytes, - py_value_3: &PyBytes, -) -> PyResult { - let byte_slice_1: &[u8] = py_value_1.as_bytes(); - let byte_slice_2: &[u8] = py_value_2.as_bytes(); - let byte_slice_3: &[u8] = py_value_3.as_bytes(); - - let mut state: Vec> = vec![ - FieldElement::::from_bytes_be(byte_slice_1) - .expect("Unable to convert first param from bytes to FieldElement"), - FieldElement::::from_bytes_be(byte_slice_2) - .expect("Unable to convert second param from bytes to FieldElement"), - FieldElement::::from_bytes_be(byte_slice_3) - .expect("Unable to convert third param from bytes to FieldElement"), - ]; - - PoseidonCairoStark252::hades_permutation(&mut state); - - let py_tuple = PyTuple::new( - py, - state.iter().map(|fe| { - let fe_bytes = fe.to_bytes_be(); - PyBytes::new(py, &fe_bytes) - }), - ); - - Ok(py_tuple.into()) -} diff --git a/tools/make/setup.sh b/tools/make/setup.sh index d1acd962..cb246562 100755 --- a/tools/make/setup.sh +++ b/tools/make/setup.sh @@ -55,8 +55,8 @@ python3.10 tests/gen_inputs.py echo "compiling Gnark..." make go -echo "Compiling hades_binding Rust extension..." -cd tools/hades_binding +echo "Compiling garaga_rs Rust extension..." +cd tools/garaga_rs maturin develop --release cd ../../