From 0965aeb1169e7ed06ed7730d65c5b76bf21805b8 Mon Sep 17 00:00:00 2001 From: Hanting Zhang Date: Sun, 7 Jan 2024 10:19:24 -0800 Subject: [PATCH] Add pasta bindings (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add pasta bindings * add tests and benches * refactor: re-organize build script --------- Co-authored-by: Hanting Zhang Co-authored-by: François Garillot --- .github/workflows/test.yml | 7 +- Cargo.toml | 12 +- benches/{msm.rs => grumpkin_msm.rs} | 1 + benches/pasta_msm.rs | 66 +++++ build.rs | 186 +++++++------ cuda/pallas.cu | 24 ++ cuda/vesta.cu | 24 ++ examples/{msm.rs => grumpkin_msm.rs} | 0 examples/pasta_msm.rs | 29 ++ src/{pippenger.cpp => grumpkin_pippenger.cpp} | 2 +- src/lib.rs | 1 + src/pasta.rs | 254 ++++++++++++++++++ src/pasta_pippenger.cpp | 26 ++ 13 files changed, 547 insertions(+), 85 deletions(-) rename benches/{msm.rs => grumpkin_msm.rs} (98%) create mode 100644 benches/pasta_msm.rs create mode 100644 cuda/pallas.cu create mode 100644 cuda/vesta.cu rename examples/{msm.rs => grumpkin_msm.rs} (100%) create mode 100644 examples/pasta_msm.rs rename src/{pippenger.cpp => grumpkin_pippenger.cpp} (99%) create mode 100644 src/pasta.rs create mode 100644 src/pasta_pippenger.cpp diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f165dd..ce8438d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,8 +38,11 @@ jobs: with: command: test - - name: Run msm example - run: cargo run --release --example msm + - name: Run grumpkin_msm example + run: cargo run --release --example grumpkin_msm + + - name: Run pasta_msm example + run: cargo run --release --example pasta_msm - name: Check benches build run: cargo check --benches \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index c5a3b9a..0b1e2c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,10 @@ include = [ default = [] # Compile in portable mode, without ISA extensions. # Binary can be executed on all systems. -portable = [ "blst/portable" ] +portable = [ "blst/portable", "semolina/portable" ] # Enable ADX even if the host CPU doesn't support it. # Binary can be executed on Broadwell+ and Ryzen+ systems. -force-adx = [ "blst/force-adx" ] +force-adx = [ "blst/force-adx", "semolina/force-adx" ] cuda-mobile = [] # Build with __MSM_SORT_DONT_IMPLEMENT__ to prevent redefining # symbols that breaks compilation during linking. @@ -32,8 +32,10 @@ dont-implement-sort = [] [dependencies] blst = "~0.3.11" +semolina = "~0.1.3" sppark = "~0.1.2" halo2curves = { version = "0.5.0" } +pasta_curves = { git = "https://github.com/lurk-lab/pasta_curves", branch = "dev", version = ">=0.3.1, <=0.5", features = ["repr-c"] } rand = "^0" rand_chacha = "^0" rayon = "1.5" @@ -46,5 +48,9 @@ which = "^4.0" criterion = { version = "0.3", features = [ "html_reports" ] } [[bench]] -name = "msm" +name = "grumpkin_msm" +harness = false + +[[bench]] +name = "pasta_msm" harness = false diff --git a/benches/msm.rs b/benches/grumpkin_msm.rs similarity index 98% rename from benches/msm.rs rename to benches/grumpkin_msm.rs index 9f577ae..1575f48 100644 --- a/benches/msm.rs +++ b/benches/grumpkin_msm.rs @@ -1,6 +1,7 @@ // Copyright Supranational LLC // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 +#![allow(unused_mut)] use criterion::{criterion_group, criterion_main, Criterion}; use grumpkin_msm::utils::{gen_points, gen_scalars}; diff --git a/benches/pasta_msm.rs b/benches/pasta_msm.rs new file mode 100644 index 0000000..f77610f --- /dev/null +++ b/benches/pasta_msm.rs @@ -0,0 +1,66 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#![allow(unused_mut)] + +use criterion::{criterion_group, criterion_main, Criterion}; +use grumpkin_msm::pasta::utils::{gen_points, gen_scalars}; + +#[cfg(feature = "cuda")] +use grumpkin_msm::cuda_available; + +fn criterion_benchmark(c: &mut Criterion) { + let bench_npow: usize = std::env::var("BENCH_NPOW") + .unwrap_or("17".to_string()) + .parse() + .unwrap(); + let npoints: usize = 1 << bench_npow; + + // println!("generating {} random points, just hang on...", npoints); + let mut points = gen_points(npoints); + let mut scalars = gen_scalars(npoints); + + #[cfg(feature = "cuda")] + { + unsafe { grumpkin_msm::CUDA_OFF = true }; + } + + let mut group = c.benchmark_group("CPU"); + group.sample_size(10); + + group.bench_function(format!("2**{} points", bench_npow), |b| { + b.iter(|| { + let _ = grumpkin_msm::pasta::pallas(&points, &scalars); + }) + }); + + group.finish(); + + #[cfg(feature = "cuda")] + if unsafe { cuda_available() } { + unsafe { grumpkin_msm::CUDA_OFF = false }; + + const EXTRA: usize = 5; + let bench_npow = bench_npow + EXTRA; + let npoints: usize = 1 << bench_npow; + + while points.len() < npoints { + points.append(&mut points.clone()); + } + scalars.append(&mut gen_scalars(npoints - scalars.len())); + + let mut group = c.benchmark_group("GPU"); + group.sample_size(20); + + group.bench_function(format!("2**{} points", bench_npow), |b| { + b.iter(|| { + let _ = grumpkin_msm::pasta::pallas(&points, &scalars); + }) + }); + + group.finish(); + } +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/build.rs b/build.rs index 3abf511..3a6f625 100644 --- a/build.rs +++ b/build.rs @@ -2,105 +2,133 @@ use std::env; use std::path::PathBuf; fn main() { - // account for cross-compilation [by examining environment variable] let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - // Set CXX environment variable to choose alternative C compiler. - // Optimization level depends on whether or not --release is passed - // or implied. + compile_source( + "grumpkin_pippenger.cpp", + "__BLST_PORTABLE__", + "grumpkin_msm", + &target_arch, + ); + compile_source( + "pasta_pippenger.cpp", + "__PASTA_PORTABLE__", + "pasta_msm", + &target_arch, + ); + + if cfg!(target_os = "windows") && !cfg!(target_env = "msvc") { + return; + } + + if cuda_available() { + let mut implement_sort: bool = true; + compile_cuda("cuda/bn254.cu", "bn256_msm_cuda", implement_sort); + implement_sort = false; + compile_cuda("cuda/grumpkin.cu", "grumpkin_msm_cuda", implement_sort); + compile_cuda("cuda/pallas.cu", "pallas_msm_cuda", implement_sort); + compile_cuda("cuda/vesta.cu", "vesta_msm_cuda", implement_sort); + println!("cargo:rerun-if-changed=cuda"); + } + println!("cargo:rerun-if-env-changed=NVCC"); +} + +fn compile_source( + file_name: &str, + def: &str, + output_name: &str, + target_arch: &str, +) { let mut cc = cc::Build::new(); cc.cpp(true); let c_src_dir = PathBuf::from("src"); - let files = vec![c_src_dir.join("pippenger.cpp")]; - let mut cc_def = None; + let file = c_src_dir.join(file_name); + let cc_def = determine_cc_def(target_arch, def); - match (cfg!(feature = "portable"), cfg!(feature = "force-adx")) { - (true, false) => { - println!("Compiling in portable mode without ISA extensions"); - cc_def = Some("__BLST_PORTABLE__"); - } - (false, true) => { - if target_arch.eq("x86_64") { - println!("Enabling ADX support via `force-adx` feature"); - cc_def = Some("__ADX__"); - } else { - println!("`force-adx` is ignored for non-x86_64 targets"); - } - } - (false, false) => { - #[cfg(target_arch = "x86_64")] - if target_arch.eq("x86_64") && std::is_x86_feature_detected!("adx") - { - println!("Enabling ADX because it was detected on the host"); - cc_def = Some("__ADX__"); - } - } - (true, true) => panic!( - "Cannot compile with both `portable` and `force-adx` features" - ), + common_build_configurations(&mut cc); + if let Some(cc_def) = cc_def { + cc.define(&cc_def, None); } + if let Some(include) = env::var_os("DEP_BLST_C_SRC") { + cc.include(include); + } + if let Some(include) = env::var_os("DEP_SEMOLINA_C_INCLUDE") { + cc.include(include); + } + if let Some(include) = env::var_os("DEP_SPPARK_ROOT") { + cc.include(include); + } + cc.file(file).compile(output_name); +} - cc.flag_if_supported("-mno-avx") // avoid costly transitions +fn common_build_configurations(cc: &mut cc::Build) { + cc.flag_if_supported("-mno-avx") .flag_if_supported("-fno-builtin") .flag_if_supported("-std=c++11") .flag_if_supported("-Wno-unused-command-line-argument"); if !cfg!(debug_assertions) { cc.define("NDEBUG", None); } - if let Some(def) = cc_def { - cc.define(def, None); +} + +fn determine_cc_def(target_arch: &str, default_def: &str) -> Option { + match (cfg!(feature = "portable"), cfg!(feature = "force-adx")) { + (true, false) => Some(default_def.to_string()), + (false, true) if target_arch == "x86_64" => Some("__ADX__".to_string()), + (false, false) + if target_arch == "x86_64" + && std::is_x86_feature_detected!("adx") => + { + Some("__ADX__".to_string()) + } + (true, true) => panic!( + "Cannot compile with both `portable` and `force-adx` features" + ), + _ => None, + } +} + +fn cuda_available() -> bool { + match env::var("NVCC") { + Ok(var) => which::which(var).is_ok(), + Err(_) => which::which("nvcc").is_ok(), } +} + +fn compile_cuda(file_name: &str, output_name: &str, implement_sort: bool) { + let mut nvcc = cc::Build::new(); + nvcc.cuda(true); + nvcc.flag("-arch=sm_80"); + nvcc.flag("-gencode").flag("arch=compute_70,code=sm_70"); + nvcc.flag("-t0"); + #[cfg(not(target_env = "msvc"))] + nvcc.flag("-Xcompiler").flag("-Wno-unused-function"); + nvcc.define("TAKE_RESPONSIBILITY_FOR_ERROR_MESSAGE", None); + #[cfg(feature = "cuda-mobile")] + nvcc.define("NTHREADS", "128"); + + if let Some(def) = determine_cc_def( + &env::var("CARGO_CFG_TARGET_ARCH").unwrap(), + "__CUDA_PORTABLE__", + ) { + nvcc.define(&def, None); + } + if let Some(include) = env::var_os("DEP_BLST_C_SRC") { - cc.include(include); + nvcc.include(include); } - if let Some(include) = env::var_os("DEP_SPPARK_ROOT") { - cc.include(include); + if let Some(include) = env::var_os("DEP_SEMOLINA_C_INCLUDE") { + nvcc.include(include); } - cc.files(&files).compile("grumpkin_msm"); - - if cfg!(target_os = "windows") && !cfg!(target_env = "msvc") { - return; + if let Some(include) = env::var_os("DEP_SPPARK_ROOT") { + nvcc.include(include); } - // Detect if there is CUDA compiler and engage "cuda" feature accordingly - let nvcc = match env::var("NVCC") { - Ok(var) => which::which(var), - Err(_) => which::which("nvcc"), - }; - if nvcc.is_ok() { - let mut nvcc = cc::Build::new(); - nvcc.cuda(true); - nvcc.flag("-arch=sm_80"); - nvcc.flag("-gencode").flag("arch=compute_70,code=sm_70"); - nvcc.flag("-t0"); - #[cfg(not(target_env = "msvc"))] - nvcc.flag("-Xcompiler").flag("-Wno-unused-function"); - nvcc.define("TAKE_RESPONSIBILITY_FOR_ERROR_MESSAGE", None); - #[cfg(feature = "cuda-mobile")] - nvcc.define("NTHREADS", "128"); - if let Some(def) = cc_def { - nvcc.define(def, None); - } - if let Some(include) = env::var_os("DEP_BLST_C_SRC") { - nvcc.include(include); - } - if let Some(include) = env::var_os("DEP_SPPARK_ROOT") { - nvcc.include(include); - } - #[cfg(not(feature = "dont-implement-sort"))] - nvcc.clone().file("cuda/bn254.cu").compile("bn256_msm_cuda"); - #[cfg(feature = "dont-implement-sort")] - nvcc.clone() - .define("__MSM_SORT_DONT_IMPLEMENT__", None) - .file("cuda/bn254.cu") - .compile("bn256_msm_cuda"); + if implement_sort { + nvcc.file(file_name).compile(output_name); + } else { nvcc.define("__MSM_SORT_DONT_IMPLEMENT__", None) - .file("cuda/grumpkin.cu") - .compile("grumpkin_msm_cuda"); - - println!("cargo:rerun-if-changed=cuda"); - println!("cargo:rerun-if-env-changed=CXXFLAGS"); - println!("cargo:rustc-cfg=feature=\"cuda\""); + .file(file_name) + .compile(output_name); } - println!("cargo:rerun-if-env-changed=NVCC"); } diff --git a/cuda/pallas.cu b/cuda/pallas.cu new file mode 100644 index 0000000..f3897bb --- /dev/null +++ b/cuda/pallas.cu @@ -0,0 +1,24 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include + +#include + +typedef jacobian_t point_t; +typedef xyzz_t bucket_t; +typedef bucket_t::affine_t affine_t; +typedef vesta_t scalar_t; + +#include + +#ifndef __CUDA_ARCH__ +extern "C" +RustError cuda_pippenger_pallas(point_t *out, const affine_t points[], size_t npoints, + const scalar_t scalars[]) +{ return mult_pippenger(out, points, npoints, scalars); } +#endif diff --git a/cuda/vesta.cu b/cuda/vesta.cu new file mode 100644 index 0000000..a926c6d --- /dev/null +++ b/cuda/vesta.cu @@ -0,0 +1,24 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include + +#include + +typedef jacobian_t point_t; +typedef xyzz_t bucket_t; +typedef bucket_t::affine_t affine_t; +typedef pallas_t scalar_t; + +#include + +#ifndef __CUDA_ARCH__ +extern "C" +RustError cuda_pippenger_vesta(point_t *out, const affine_t points[], size_t npoints, + const scalar_t scalars[]) +{ return mult_pippenger(out, points, npoints, scalars); } +#endif diff --git a/examples/msm.rs b/examples/grumpkin_msm.rs similarity index 100% rename from examples/msm.rs rename to examples/grumpkin_msm.rs diff --git a/examples/pasta_msm.rs b/examples/pasta_msm.rs new file mode 100644 index 0000000..a682c35 --- /dev/null +++ b/examples/pasta_msm.rs @@ -0,0 +1,29 @@ +#[cfg(feature = "cuda")] +use grumpkin_msm::cuda_available; + +use grumpkin_msm::pasta::utils::{ + gen_points, gen_scalars, naive_multiscalar_mul, +}; +use pasta_curves::group::Curve; + +fn main() { + let bench_npow: usize = std::env::var("BENCH_NPOW") + .unwrap_or("17".to_string()) + .parse() + .unwrap(); + let npoints: usize = 1 << bench_npow; + + println!("generating {} random points, just hang on...", npoints); + let points = gen_points(npoints); + let scalars = gen_scalars(npoints); + + #[cfg(feature = "cuda")] + if unsafe { cuda_available() } { + unsafe { grumpkin_msm::CUDA_OFF = false }; + } + + let res = grumpkin_msm::pasta::pallas(&points, &scalars).to_affine(); + let native = naive_multiscalar_mul(&points, &scalars); + assert_eq!(res, native); + println!("success!") +} diff --git a/src/pippenger.cpp b/src/grumpkin_pippenger.cpp similarity index 99% rename from src/pippenger.cpp rename to src/grumpkin_pippenger.cpp index f6f7a11..6971cb2 100644 --- a/src/pippenger.cpp +++ b/src/grumpkin_pippenger.cpp @@ -23,4 +23,4 @@ void mult_pippenger_grumpkin(jacobian_t& ret, size_t npoints, const fp_t scalars[]) { mult_pippenger>(ret, points, npoints, scalars, true, &da_pool); -} +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 64bbea4..16fb22a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ #![allow(improper_ctypes)] #![allow(unused)] +pub mod pasta; pub mod utils; extern crate blst; diff --git a/src/pasta.rs b/src/pasta.rs new file mode 100644 index 0000000..c69f6be --- /dev/null +++ b/src/pasta.rs @@ -0,0 +1,254 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +#![allow(improper_ctypes)] +#![allow(unused)] + +extern crate semolina; + +use pasta_curves::pallas; + +#[cfg(feature = "cuda")] +use crate::{cuda, cuda_available, CUDA_OFF}; + +extern "C" { + fn mult_pippenger_pallas( + out: *mut pallas::Point, + points: *const pallas::Affine, + npoints: usize, + scalars: *const pallas::Scalar, + is_mont: bool, + ); +} + +pub fn pallas( + points: &[pallas::Affine], + scalars: &[pallas::Scalar], +) -> pallas::Point { + let npoints = points.len(); + assert_eq!(npoints, scalars.len(), "length mismatch"); + + #[cfg(feature = "cuda")] + if npoints >= 1 << 16 && unsafe { !CUDA_OFF && cuda_available() } { + extern "C" { + fn cuda_pippenger_pallas( + out: *mut pallas::Point, + points: *const pallas::Affine, + npoints: usize, + scalars: *const pallas::Scalar, + is_mont: bool, + ) -> cuda::Error; + + } + let mut ret = pallas::Point::default(); + let err = unsafe { + cuda_pippenger_pallas( + &mut ret, + &points[0], + npoints, + &scalars[0], + true, + ) + }; + assert!(err.code == 0, "{}", String::from(err)); + + return ret; + } + let mut ret = pallas::Point::default(); + unsafe { + mult_pippenger_pallas(&mut ret, &points[0], npoints, &scalars[0], true) + }; + ret +} + +use pasta_curves::vesta; + +extern "C" { + fn mult_pippenger_vesta( + out: *mut vesta::Point, + points: *const vesta::Affine, + npoints: usize, + scalars: *const vesta::Scalar, + is_mont: bool, + ); +} + +pub fn vesta( + points: &[vesta::Affine], + scalars: &[vesta::Scalar], +) -> vesta::Point { + let npoints = points.len(); + assert_eq!(npoints, scalars.len(), "length mismatch"); + + #[cfg(feature = "cuda")] + if npoints >= 1 << 16 && unsafe { !CUDA_OFF && cuda_available() } { + extern "C" { + fn cuda_pippenger_vesta( + out: *mut vesta::Point, + points: *const vesta::Affine, + npoints: usize, + scalars: *const vesta::Scalar, + is_mont: bool, + ) -> cuda::Error; + + } + let mut ret = vesta::Point::default(); + let err = unsafe { + cuda_pippenger_vesta( + &mut ret, + &points[0], + npoints, + &scalars[0], + true, + ) + }; + assert!(err.code == 0, "{}", String::from(err)); + + return ret; + } + let mut ret = vesta::Point::default(); + unsafe { + mult_pippenger_vesta(&mut ret, &points[0], npoints, &scalars[0], true) + }; + ret +} + +pub mod utils { + use std::{ + mem::transmute, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, + }; + + use pasta_curves::{ + arithmetic::CurveExt, + group::{ff::Field, Curve}, + pallas, + }; + use rand::{RngCore, SeedableRng}; + use rand_chacha::ChaCha20Rng; + use rayon::iter::{ + IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator, + }; + + pub fn gen_points(npoints: usize) -> Vec { + let ret = vec![pallas::Affine::default(); npoints]; + + let mut rnd = vec![0u8; 32 * npoints]; + ChaCha20Rng::from_entropy().fill_bytes(&mut rnd); + + let n_workers = rayon::current_num_threads(); + let work = AtomicUsize::new(0); + rayon::scope(|s| { + for _ in 0..n_workers { + s.spawn(|_| { + let hash = pallas::Point::hash_to_curve("foobar"); + + let mut stride = 1024; + let mut tmp = vec![pallas::Point::default(); stride]; + + loop { + let work = work.fetch_add(stride, Ordering::Relaxed); + if work >= npoints { + break; + } + if work + stride > npoints { + stride = npoints - work; + unsafe { tmp.set_len(stride) }; + } + for (i, point) in + tmp.iter_mut().enumerate().take(stride) + { + let off = (work + i) * 32; + *point = hash(&rnd[off..off + 32]); + } + #[allow(mutable_transmutes)] + pallas::Point::batch_normalize(&tmp, unsafe { + transmute::< + &[pallas::Affine], + &mut [pallas::Affine], + >( + &ret[work..work + stride] + ) + }); + } + }) + } + }); + + ret + } + + pub fn gen_scalars(npoints: usize) -> Vec { + let ret = + Arc::new(Mutex::new(vec![pallas::Scalar::default(); npoints])); + + let n_workers = rayon::current_num_threads(); + let work = Arc::new(AtomicUsize::new(0)); + + rayon::scope(|s| { + for _ in 0..n_workers { + let ret_clone = Arc::clone(&ret); + let work_clone = Arc::clone(&work); + + s.spawn(move |_| { + let mut rng = ChaCha20Rng::from_entropy(); + loop { + let work = work_clone.fetch_add(1, Ordering::Relaxed); + if work >= npoints { + break; + } + let mut ret = ret_clone.lock().unwrap(); + ret[work] = pallas::Scalar::random(&mut rng); + } + }); + } + }); + + Arc::try_unwrap(ret).unwrap().into_inner().unwrap() + } + + pub fn naive_multiscalar_mul( + points: &[pallas::Affine], + scalars: &[pallas::Scalar], + ) -> pallas::Affine { + let ret: pallas::Point = points + .par_iter() + .zip_eq(scalars.par_iter()) + .map(|(p, s)| p * s) + .sum(); + + ret.to_affine() + } +} + +#[cfg(test)] +mod tests { + use pasta_curves::group::Curve; + + use crate::pasta::{ + pallas, + utils::{gen_points, gen_scalars, naive_multiscalar_mul}, + }; + + #[test] + fn it_works() { + #[cfg(not(debug_assertions))] + const NPOINTS: usize = 128 * 1024; + #[cfg(debug_assertions)] + const NPOINTS: usize = 8 * 1024; + + let points = gen_points(NPOINTS); + let scalars = gen_scalars(NPOINTS); + + let naive = naive_multiscalar_mul(&points, &scalars); + println!("{:?}", naive); + + let ret = pallas(&points, &scalars).to_affine(); + println!("{:?}", ret); + + assert_eq!(ret, naive); + } +} diff --git a/src/pasta_pippenger.cpp b/src/pasta_pippenger.cpp new file mode 100644 index 0000000..5bd1af6 --- /dev/null +++ b/src/pasta_pippenger.cpp @@ -0,0 +1,26 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +static thread_pool_t da_pool; + +extern "C" +void mult_pippenger_pallas(jacobian_t& ret, + const xyzz_t::affine_t points[], + size_t npoints, const vesta_t scalars[], bool mont) +{ mult_pippenger>(ret, points, npoints, scalars, mont, + &da_pool); +} + +extern "C" +void mult_pippenger_vesta(jacobian_t& ret, + const xyzz_t::affine_t points[], + size_t npoints, const pallas_t scalars[], bool mont) +{ mult_pippenger>(ret, points, npoints, scalars, mont, + &da_pool); +}