diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..338b84a72 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "vroom"] + path = vroom + url = https://github.com/SimonLangowski/VROOM.git +[submodule "blst-src"] + path = blst-src + url = https://github.com/supranational/blst.git diff --git a/Cargo.lock b/Cargo.lock index 6b075b097..2e1cc6172 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,11 +235,12 @@ dependencies = [ [[package]] name = "blst" version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" dependencies = [ "cc", "glob", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", "threadpool", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 2c316dd22..5c59d8e53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,10 @@ repository = "https://github.com/Chia-Network/chia_rs" [workspace] members = ["crates/*", "crates/*/fuzz", "wasm", "wheel"] +[patch.crates-io] +# Use local blst (with optional VROOM pairing) so only one native lib is linked +blst = { path = "./crates/blst" } + [workspace.package] version = "0.38.1" @@ -142,7 +146,9 @@ chia-protocol-fuzz = { path = "./crates/chia-protocol/fuzz", version = "0.38.1" chia-puzzle-types-fuzz = { path = "./crates/chia-puzzle-types/fuzz", version = "0.38.1" } clvm-traits-fuzz = { path = "./crates/clvm-traits/fuzz", version = "0.38.1" } clvm-utils-fuzz = { path = "./crates/clvm-utils/fuzz", version = "0.38.1" } -blst = { version = "0.3.16", features = ["portable"] } +# BLS backend. When feature "vroom" is enabled (e.g. via chia-bls), pairing uses +# VROOM's implementation (vroom/ submodule). Otherwise standard blst from blst-src. +blst = { path = "./crates/blst", default-features = false, features = ["portable"] } clvmr = "0.17.1" clvm-fuzzing = "0.17.0" syn = "2.0.101" diff --git a/README.md b/README.md index f9455abec..17f871007 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,20 @@ A collection of Rust crates for working with the Chia blockchain. There are also - [Python](https://www.python.org/downloads/) 3.9 or higher installed. - The [Rust toolchain](https://rustup.rs/) must be installed. +After cloning, initialize submodules (required for the optional **vroom** BLS backend): + +```bash +git submodule update --init --recursive +``` + +### Optional: VROOM backend for pairing + +To use [VROOM](https://github.com/SimonLangowski/VROOM)’s pairing implementation (AVX512 RNS optimizations) where supported, build with the `vroom` feature. All other BLS operations (keygen, sign, verify, aggregation) use the standard blst implementation; only pairing uses VROOM when this feature is enabled. + +```bash +cargo build --features vroom # or add vroom to default features in your crate +``` + ## Unit Tests To run the unit tests for the whole workspace: diff --git a/blst-src b/blst-src new file mode 160000 index 000000000..dafa98f74 --- /dev/null +++ b/blst-src @@ -0,0 +1 @@ +Subproject commit dafa98f749869f7ab63ab31074626b7dfc70b5b3 diff --git a/crates/blst/Cargo.toml b/crates/blst/Cargo.toml new file mode 100644 index 000000000..5035f068d --- /dev/null +++ b/crates/blst/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "blst" +version = "0.3.16" +authors = ["sean-sn "] +edition = "2018" +license = "Apache-2.0" +description = "Bindings for blst BLS12-381 library (with optional VROOM pairing)" +repository = "https://github.com/supranational/blst" +categories = ["cryptography"] +keywords = ["crypto", "bls", "signature", "asm", "wasm"] +links = "blst" + +[features] +default = [] +portable = [] +force-adx = [] +no-threads = [] +serde-secret = ["serde"] +# Use VROOM's pairing implementation where supported (AVX512 RNS optimizations) +vroom = [] + +[build-dependencies] +cc = "1.0" +[target.'cfg(target_env = "msvc")'.build-dependencies] +glob = "0.3" + +[dependencies] +zeroize = { version = "^1.1", features = ["zeroize_derive"] } +serde = { version = "1.0.152", optional = true } + +[target.'cfg(not(any(target_arch="wasm32", target_os="none", target_os="unknown", target_os="uefi")))'.dependencies] +threadpool = "^1.8.1" + +[dev-dependencies] +rand = "0.8" +rand_chacha = "0.3" diff --git a/crates/blst/build.rs b/crates/blst/build.rs new file mode 100644 index 000000000..f5f5380b9 --- /dev/null +++ b/crates/blst/build.rs @@ -0,0 +1,256 @@ +#![allow(unused_imports)] + +extern crate cc; + +use std::env; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +fn copy_dir_all(src: &Path, dst: &Path) -> io::Result<()> { + fs::create_dir_all(dst)?; + for entry in fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + let dst_path = dst.join(entry.file_name()); + if ty.is_dir() { + copy_dir_all(&entry.path(), &dst_path)?; + } else { + fs::copy(entry.path(), dst_path)?; + } + } + Ok(()) +} + +fn assembly( + file_vec: &mut Vec, + base_dir: &Path, + _arch: &str, + _is_msvc: bool, +) { + #[cfg(target_env = "msvc")] + if _is_msvc { + let sfx = match _arch { + "x86_64" => "x86_64", + "aarch64" => "armv8", + _ => "unknown", + }; + let files = + glob::glob(&format!("{}/win64/*-{}.asm", base_dir.display(), sfx)) + .expect("unable to collect assembly files"); + for file in files { + file_vec.push(file.unwrap()); + } + return; + } + + file_vec.push(base_dir.join("assembly.S")); +} + +fn main() { + if env::var("CARGO_FEATURE_SERDE_SECRET").is_ok() { + println!( + "cargo:warning=blst: non-production feature serde-secret enabled" + ); + } + + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_family = env::var("CARGO_CFG_TARGET_FAMILY").unwrap_or_default(); + + let target_no_std = target_os.eq("none") + || (target_os.eq("unknown") && target_arch.eq("wasm32")) + || target_os.eq("uefi") + || env::var("BLST_TEST_NO_STD").is_ok(); + + if !target_no_std { + println!("cargo:rustc-cfg=feature=\"std\""); + if target_arch.eq("wasm32") || target_os.eq("unknown") { + println!("cargo:rustc-cfg=feature=\"no-threads\""); + } + } + println!("cargo:rerun-if-env-changed=BLST_TEST_NO_STD"); + + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let workspace_root = manifest_dir.parent().and_then(|p| p.parent()); + + if Path::new("libblst.a").exists() { + println!("cargo:rustc-link-search=."); + println!("cargo:rustc-link-lib=blst"); + println!("cargo:rerun-if-changed=libblst.a"); + return; + } + + let mut blst_base_dir = manifest_dir.join("blst"); + if !blst_base_dir.exists() { + blst_base_dir = workspace_root + .map(|r| r.join("blst-src")) + .unwrap_or_else(|| { + manifest_dir + .parent() + .and_then(|dir| dir.parent()) + .expect("can't access workspace root or blst repo") + .join("blst-src") + }); + } + + // When vroom feature is enabled, build from a staging copy that uses VROOM's pairing.c + let use_vroom = env::var("CARGO_FEATURE_VROOM").is_ok(); + if use_vroom { + if let Some(ws) = workspace_root { + let vroom_pairing = ws.join("vroom").join("blst").join("pairing.c"); + let blst_src = ws.join("blst-src"); + if vroom_pairing.exists() && blst_src.exists() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let staging = out_dir.join("blst_vroom_build"); + let staging_src = staging.join("src"); + let staging_build = staging.join("build"); + + println!("cargo:warning=blst: building with VROOM pairing (vroom/blst/pairing.c)"); + fs::create_dir_all(&staging_src).expect("create staging src"); + fs::create_dir_all(&staging_build).expect("create staging build"); + + copy_dir_all(&blst_src.join("src"), &staging_src) + .expect("copy blst src to staging"); + copy_dir_all(&blst_src.join("build"), &staging_build) + .expect("copy blst build to staging"); + fs::copy(&vroom_pairing, staging_src.join("pairing.c")) + .expect("copy VROOM pairing.c"); + + println!("cargo:rerun-if-changed={}", vroom_pairing.display()); + blst_base_dir = staging; + } + } + } + + if !blst_base_dir.exists() { + panic!( + "blst source not found at {} (set vroom feature and init submodules, or add blst-src)", + blst_base_dir.display() + ); + } + println!("Using blst source directory {}", blst_base_dir.display()); + + if target_os.eq("uefi") && env::var("CC").is_err() { + match std::process::Command::new("clang").arg("--version").output() { + Ok(_) => env::set_var("CC", "clang"), + Err(_) => {} + } + } + + if target_env.eq("sgx") && env::var("CC").is_err() { + if let Ok(out) = std::process::Command::new("clang").arg("--version").output() { + let version = String::from_utf8(out.stdout).unwrap_or_default(); + if let Some(x) = version.find("clang version ") { + let x = x + 14; + let y = version[x..].find('.').unwrap_or(0); + if version[x..x + y].parse::().unwrap_or(0) >= 11 { + env::set_var("CC", "clang"); + } + } + } + } + + if target_env.eq("msvc") + && env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap().eq("32") + && env::var("CC").is_err() + { + if let Ok(out) = + std::process::Command::new("clang-cl").args(["-m32", "--version"]).output() + { + if String::from_utf8(out.stdout).unwrap_or_default() + .contains("Target: i386-pc-windows-msvc") + { + env::set_var("CC", "clang-cl"); + } + } + } + + let mut cc = cc::Build::new(); + + let c_src_dir = blst_base_dir.join("src"); + println!("cargo:rerun-if-changed={}", c_src_dir.display()); + let mut file_vec = vec![c_src_dir.join("server.c")]; + + if target_arch.eq("x86_64") || target_arch.eq("aarch64") { + let asm_dir = blst_base_dir.join("build"); + println!("cargo:rerun-if-changed={}", asm_dir.display()); + assembly( + &mut file_vec, + &asm_dir, + &target_arch, + cc.get_compiler().is_like_msvc(), + ); + } else { + cc.define("__BLST_NO_ASM__", None); + } + match (cfg!(feature = "portable"), cfg!(feature = "force-adx")) { + (true, false) => { + if target_arch.eq("x86_64") && target_env.eq("sgx") { + panic!("'portable' is not supported on SGX target"); + } + println!("cargo:warning=blst: compiling in portable mode"); + cc.define("__BLST_PORTABLE__", None); + } + (false, true) => { + if target_arch.eq("x86_64") { + cc.define("__ADX__", None); + } + } + (false, false) => { + if target_arch.eq("x86_64") { + if target_env.eq("sgx") { + cc.define("__ADX__", None); + } else if env::var("CARGO_ENCODED_RUSTFLAGS") + .unwrap_or_default() + .contains("target-cpu=") + { + let feat_list = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default(); + let features: Vec<_> = feat_list.split(',').collect(); + if !features.contains(&"ssse3") { + cc.define("__BLST_PORTABLE__", None); + } else if features.contains(&"adx") { + cc.define("__ADX__", None); + } + } else { + #[cfg(target_arch = "x86_64")] + if std::is_x86_feature_detected!("adx") { + cc.define("__ADX__", None); + } + } + } + } + (true, true) => panic!("Cannot compile with both `portable` and `force-adx` features"), + } + if target_env.eq("msvc") && cc.get_compiler().is_like_msvc() { + cc.flag("-Zl"); + } + cc.flag_if_supported("-mno-avx") + .flag_if_supported("-fno-builtin") + .flag_if_supported("-Wno-unused-function") + .flag_if_supported("-Wno-unused-command-line-argument"); + if target_arch.eq("wasm32") || target_family.is_empty() { + cc.flag("-ffreestanding"); + } + if target_arch.eq("wasm32") || target_no_std { + cc.define("SCRATCH_LIMIT", "(45 * 1024)"); + } + if target_env.eq("sgx") { + cc.flag_if_supported("-mlvi-hardening"); + cc.define("__SGX_LVI_HARDENING__", None); + cc.define("__BLST_NO_CPUID__", None); + cc.define("__ELF__", None); + cc.define("SCRATCH_LIMIT", "(45 * 1024)"); + } + if !cfg!(debug_assertions) { + cc.opt_level(2); + } + cc.files(&file_vec).compile("blst"); + + let bindings = blst_base_dir.join("bindings"); + if bindings.exists() { + println!("cargo:BINDINGS={}", bindings.to_string_lossy()); + } + println!("cargo:C_SRC={}", c_src_dir.to_string_lossy()); +} diff --git a/crates/blst/rustfmt.toml b/crates/blst/rustfmt.toml new file mode 100644 index 000000000..df99c6919 --- /dev/null +++ b/crates/blst/rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 diff --git a/crates/blst/src/bindings.rs b/crates/blst/src/bindings.rs new file mode 100644 index 000000000..53dc8ed6b --- /dev/null +++ b/crates/blst/src/bindings.rs @@ -0,0 +1,1415 @@ +/* automatically generated by rust-bindgen 0.65.1 */ + +#[repr(u32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum BLST_ERROR { + BLST_SUCCESS = 0, + BLST_BAD_ENCODING = 1, + BLST_POINT_NOT_ON_CURVE = 2, + BLST_POINT_NOT_IN_GROUP = 3, + BLST_AGGR_TYPE_MISMATCH = 4, + BLST_VERIFY_FAIL = 5, + BLST_PK_IS_INFINITY = 6, + BLST_BAD_SCALAR = 7, +} +pub type byte = u8; +pub type limb_t = u64; +#[repr(C)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Zeroize)] +#[zeroize(drop)] +pub struct blst_scalar { + pub b: [byte; 32usize], +} +#[test] +fn bindgen_test_layout_blst_scalar() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(blst_scalar)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(blst_scalar)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).b) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_scalar), + "::", + stringify!(b) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct blst_fr { + pub l: [limb_t; 4usize], +} +#[test] +fn bindgen_test_layout_blst_fr() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(blst_fr)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_fr)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).l) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_fr), + "::", + stringify!(l) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct blst_fp { + pub l: [limb_t; 6usize], +} +#[test] +fn bindgen_test_layout_blst_fp() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(blst_fp)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_fp)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).l) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_fp), + "::", + stringify!(l) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct blst_fp2 { + pub fp: [blst_fp; 2usize], +} +#[test] +fn bindgen_test_layout_blst_fp2() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 96usize, + concat!("Size of: ", stringify!(blst_fp2)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_fp2)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).fp) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_fp2), + "::", + stringify!(fp) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct blst_fp6 { + pub fp2: [blst_fp2; 3usize], +} +#[test] +fn bindgen_test_layout_blst_fp6() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 288usize, + concat!("Size of: ", stringify!(blst_fp6)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_fp6)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).fp2) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_fp6), + "::", + stringify!(fp2) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone, Eq)] +pub struct blst_fp12 { + pub fp6: [blst_fp6; 2usize], +} +#[test] +fn bindgen_test_layout_blst_fp12() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 576usize, + concat!("Size of: ", stringify!(blst_fp12)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_fp12)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).fp6) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_fp12), + "::", + stringify!(fp6) + ) + ); +} +extern "C" { + pub fn blst_scalar_from_uint32(out: *mut blst_scalar, a: *const u32); +} +extern "C" { + pub fn blst_uint32_from_scalar(out: *mut u32, a: *const blst_scalar); +} +extern "C" { + pub fn blst_scalar_from_uint64(out: *mut blst_scalar, a: *const u64); +} +extern "C" { + pub fn blst_uint64_from_scalar(out: *mut u64, a: *const blst_scalar); +} +extern "C" { + pub fn blst_scalar_from_bendian(out: *mut blst_scalar, a: *const byte); +} +extern "C" { + pub fn blst_bendian_from_scalar(out: *mut byte, a: *const blst_scalar); +} +extern "C" { + pub fn blst_scalar_from_lendian(out: *mut blst_scalar, a: *const byte); +} +extern "C" { + pub fn blst_lendian_from_scalar(out: *mut byte, a: *const blst_scalar); +} +extern "C" { + pub fn blst_scalar_fr_check(a: *const blst_scalar) -> bool; +} +extern "C" { + pub fn blst_sk_check(a: *const blst_scalar) -> bool; +} +extern "C" { + pub fn blst_sk_add_n_check( + out: *mut blst_scalar, + a: *const blst_scalar, + b: *const blst_scalar, + ) -> bool; +} +extern "C" { + pub fn blst_sk_sub_n_check( + out: *mut blst_scalar, + a: *const blst_scalar, + b: *const blst_scalar, + ) -> bool; +} +extern "C" { + pub fn blst_sk_mul_n_check( + out: *mut blst_scalar, + a: *const blst_scalar, + b: *const blst_scalar, + ) -> bool; +} +extern "C" { + pub fn blst_sk_inverse(out: *mut blst_scalar, a: *const blst_scalar); +} +extern "C" { + pub fn blst_scalar_from_le_bytes(out: *mut blst_scalar, in_: *const byte, len: usize) -> bool; +} +extern "C" { + pub fn blst_scalar_from_be_bytes(out: *mut blst_scalar, in_: *const byte, len: usize) -> bool; +} +extern "C" { + pub fn blst_fr_add(ret: *mut blst_fr, a: *const blst_fr, b: *const blst_fr); +} +extern "C" { + pub fn blst_fr_sub(ret: *mut blst_fr, a: *const blst_fr, b: *const blst_fr); +} +extern "C" { + pub fn blst_fr_mul_by_3(ret: *mut blst_fr, a: *const blst_fr); +} +extern "C" { + pub fn blst_fr_lshift(ret: *mut blst_fr, a: *const blst_fr, count: usize); +} +extern "C" { + pub fn blst_fr_rshift(ret: *mut blst_fr, a: *const blst_fr, count: usize); +} +extern "C" { + pub fn blst_fr_mul(ret: *mut blst_fr, a: *const blst_fr, b: *const blst_fr); +} +extern "C" { + pub fn blst_fr_sqr(ret: *mut blst_fr, a: *const blst_fr); +} +extern "C" { + pub fn blst_fr_cneg(ret: *mut blst_fr, a: *const blst_fr, flag: bool); +} +extern "C" { + pub fn blst_fr_eucl_inverse(ret: *mut blst_fr, a: *const blst_fr); +} +extern "C" { + pub fn blst_fr_inverse(ret: *mut blst_fr, a: *const blst_fr); +} +extern "C" { + pub fn blst_fr_from_uint64(ret: *mut blst_fr, a: *const u64); +} +extern "C" { + pub fn blst_uint64_from_fr(ret: *mut u64, a: *const blst_fr); +} +extern "C" { + pub fn blst_fr_from_scalar(ret: *mut blst_fr, a: *const blst_scalar); +} +extern "C" { + pub fn blst_scalar_from_fr(ret: *mut blst_scalar, a: *const blst_fr); +} +extern "C" { + pub fn blst_fp_add(ret: *mut blst_fp, a: *const blst_fp, b: *const blst_fp); +} +extern "C" { + pub fn blst_fp_sub(ret: *mut blst_fp, a: *const blst_fp, b: *const blst_fp); +} +extern "C" { + pub fn blst_fp_mul_by_3(ret: *mut blst_fp, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_mul_by_8(ret: *mut blst_fp, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_lshift(ret: *mut blst_fp, a: *const blst_fp, count: usize); +} +extern "C" { + pub fn blst_fp_mul(ret: *mut blst_fp, a: *const blst_fp, b: *const blst_fp); +} +extern "C" { + pub fn blst_fp_sqr(ret: *mut blst_fp, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_cneg(ret: *mut blst_fp, a: *const blst_fp, flag: bool); +} +extern "C" { + pub fn blst_fp_eucl_inverse(ret: *mut blst_fp, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_inverse(ret: *mut blst_fp, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_sqrt(ret: *mut blst_fp, a: *const blst_fp) -> bool; +} +extern "C" { + pub fn blst_fp_from_uint32(ret: *mut blst_fp, a: *const u32); +} +extern "C" { + pub fn blst_uint32_from_fp(ret: *mut u32, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_from_uint64(ret: *mut blst_fp, a: *const u64); +} +extern "C" { + pub fn blst_uint64_from_fp(ret: *mut u64, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_from_bendian(ret: *mut blst_fp, a: *const byte); +} +extern "C" { + pub fn blst_bendian_from_fp(ret: *mut byte, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_from_lendian(ret: *mut blst_fp, a: *const byte); +} +extern "C" { + pub fn blst_lendian_from_fp(ret: *mut byte, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp2_add(ret: *mut blst_fp2, a: *const blst_fp2, b: *const blst_fp2); +} +extern "C" { + pub fn blst_fp2_sub(ret: *mut blst_fp2, a: *const blst_fp2, b: *const blst_fp2); +} +extern "C" { + pub fn blst_fp2_mul_by_3(ret: *mut blst_fp2, a: *const blst_fp2); +} +extern "C" { + pub fn blst_fp2_mul_by_8(ret: *mut blst_fp2, a: *const blst_fp2); +} +extern "C" { + pub fn blst_fp2_lshift(ret: *mut blst_fp2, a: *const blst_fp2, count: usize); +} +extern "C" { + pub fn blst_fp2_mul(ret: *mut blst_fp2, a: *const blst_fp2, b: *const blst_fp2); +} +extern "C" { + pub fn blst_fp2_sqr(ret: *mut blst_fp2, a: *const blst_fp2); +} +extern "C" { + pub fn blst_fp2_cneg(ret: *mut blst_fp2, a: *const blst_fp2, flag: bool); +} +extern "C" { + pub fn blst_fp2_eucl_inverse(ret: *mut blst_fp2, a: *const blst_fp2); +} +extern "C" { + pub fn blst_fp2_inverse(ret: *mut blst_fp2, a: *const blst_fp2); +} +extern "C" { + pub fn blst_fp2_sqrt(ret: *mut blst_fp2, a: *const blst_fp2) -> bool; +} +extern "C" { + pub fn blst_fp12_sqr(ret: *mut blst_fp12, a: *const blst_fp12); +} +extern "C" { + pub fn blst_fp12_cyclotomic_sqr(ret: *mut blst_fp12, a: *const blst_fp12); +} +extern "C" { + pub fn blst_fp12_mul(ret: *mut blst_fp12, a: *const blst_fp12, b: *const blst_fp12); +} +extern "C" { + pub fn blst_fp12_mul_by_xy00z0( + ret: *mut blst_fp12, + a: *const blst_fp12, + xy00z0: *const blst_fp6, + ); +} +extern "C" { + pub fn blst_fp12_conjugate(a: *mut blst_fp12); +} +extern "C" { + pub fn blst_fp12_inverse(ret: *mut blst_fp12, a: *const blst_fp12); +} +extern "C" { + pub fn blst_fp12_frobenius_map(ret: *mut blst_fp12, a: *const blst_fp12, n: usize); +} +extern "C" { + pub fn blst_fp12_is_equal(a: *const blst_fp12, b: *const blst_fp12) -> bool; +} +extern "C" { + pub fn blst_fp12_is_one(a: *const blst_fp12) -> bool; +} +extern "C" { + pub fn blst_fp12_in_group(a: *const blst_fp12) -> bool; +} +extern "C" { + pub fn blst_fp12_one() -> *const blst_fp12; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, Eq)] +pub struct blst_p1 { + pub x: blst_fp, + pub y: blst_fp, + pub z: blst_fp, +} +#[test] +fn bindgen_test_layout_blst_p1() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 144usize, + concat!("Size of: ", stringify!(blst_p1)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_p1)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).x) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_p1), + "::", + stringify!(x) + ) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).y) as usize - ptr as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(blst_p1), + "::", + stringify!(y) + ) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).z) as usize - ptr as usize }, + 96usize, + concat!( + "Offset of field: ", + stringify!(blst_p1), + "::", + stringify!(z) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, Eq)] +pub struct blst_p1_affine { + pub x: blst_fp, + pub y: blst_fp, +} +#[test] +fn bindgen_test_layout_blst_p1_affine() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 96usize, + concat!("Size of: ", stringify!(blst_p1_affine)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_p1_affine)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).x) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_p1_affine), + "::", + stringify!(x) + ) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).y) as usize - ptr as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(blst_p1_affine), + "::", + stringify!(y) + ) + ); +} +extern "C" { + pub fn blst_p1_add(out: *mut blst_p1, a: *const blst_p1, b: *const blst_p1); +} +extern "C" { + pub fn blst_p1_add_or_double(out: *mut blst_p1, a: *const blst_p1, b: *const blst_p1); +} +extern "C" { + pub fn blst_p1_add_affine(out: *mut blst_p1, a: *const blst_p1, b: *const blst_p1_affine); +} +extern "C" { + pub fn blst_p1_add_or_double_affine( + out: *mut blst_p1, + a: *const blst_p1, + b: *const blst_p1_affine, + ); +} +extern "C" { + pub fn blst_p1_double(out: *mut blst_p1, a: *const blst_p1); +} +extern "C" { + pub fn blst_p1_mult(out: *mut blst_p1, p: *const blst_p1, scalar: *const byte, nbits: usize); +} +extern "C" { + pub fn blst_p1_cneg(p: *mut blst_p1, cbit: bool); +} +extern "C" { + pub fn blst_p1_to_affine(out: *mut blst_p1_affine, in_: *const blst_p1); +} +extern "C" { + pub fn blst_p1_from_affine(out: *mut blst_p1, in_: *const blst_p1_affine); +} +extern "C" { + pub fn blst_p1_on_curve(p: *const blst_p1) -> bool; +} +extern "C" { + pub fn blst_p1_in_g1(p: *const blst_p1) -> bool; +} +extern "C" { + pub fn blst_p1_is_equal(a: *const blst_p1, b: *const blst_p1) -> bool; +} +extern "C" { + pub fn blst_p1_is_inf(a: *const blst_p1) -> bool; +} +extern "C" { + pub fn blst_p1_generator() -> *const blst_p1; +} +extern "C" { + pub fn blst_p1_affine_on_curve(p: *const blst_p1_affine) -> bool; +} +extern "C" { + pub fn blst_p1_affine_in_g1(p: *const blst_p1_affine) -> bool; +} +extern "C" { + pub fn blst_p1_affine_is_equal(a: *const blst_p1_affine, b: *const blst_p1_affine) -> bool; +} +extern "C" { + pub fn blst_p1_affine_is_inf(a: *const blst_p1_affine) -> bool; +} +extern "C" { + pub fn blst_p1_affine_generator() -> *const blst_p1_affine; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, Eq)] +pub struct blst_p2 { + pub x: blst_fp2, + pub y: blst_fp2, + pub z: blst_fp2, +} +#[test] +fn bindgen_test_layout_blst_p2() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 288usize, + concat!("Size of: ", stringify!(blst_p2)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_p2)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).x) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_p2), + "::", + stringify!(x) + ) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).y) as usize - ptr as usize }, + 96usize, + concat!( + "Offset of field: ", + stringify!(blst_p2), + "::", + stringify!(y) + ) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).z) as usize - ptr as usize }, + 192usize, + concat!( + "Offset of field: ", + stringify!(blst_p2), + "::", + stringify!(z) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, Eq)] +pub struct blst_p2_affine { + pub x: blst_fp2, + pub y: blst_fp2, +} +#[test] +fn bindgen_test_layout_blst_p2_affine() { + const UNINIT: ::core::mem::MaybeUninit = ::core::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::core::mem::size_of::(), + 192usize, + concat!("Size of: ", stringify!(blst_p2_affine)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(blst_p2_affine)) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).x) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(blst_p2_affine), + "::", + stringify!(x) + ) + ); + assert_eq!( + unsafe { ::core::ptr::addr_of!((*ptr).y) as usize - ptr as usize }, + 96usize, + concat!( + "Offset of field: ", + stringify!(blst_p2_affine), + "::", + stringify!(y) + ) + ); +} +extern "C" { + pub fn blst_p2_add(out: *mut blst_p2, a: *const blst_p2, b: *const blst_p2); +} +extern "C" { + pub fn blst_p2_add_or_double(out: *mut blst_p2, a: *const blst_p2, b: *const blst_p2); +} +extern "C" { + pub fn blst_p2_add_affine(out: *mut blst_p2, a: *const blst_p2, b: *const blst_p2_affine); +} +extern "C" { + pub fn blst_p2_add_or_double_affine( + out: *mut blst_p2, + a: *const blst_p2, + b: *const blst_p2_affine, + ); +} +extern "C" { + pub fn blst_p2_double(out: *mut blst_p2, a: *const blst_p2); +} +extern "C" { + pub fn blst_p2_mult(out: *mut blst_p2, p: *const blst_p2, scalar: *const byte, nbits: usize); +} +extern "C" { + pub fn blst_p2_cneg(p: *mut blst_p2, cbit: bool); +} +extern "C" { + pub fn blst_p2_to_affine(out: *mut blst_p2_affine, in_: *const blst_p2); +} +extern "C" { + pub fn blst_p2_from_affine(out: *mut blst_p2, in_: *const blst_p2_affine); +} +extern "C" { + pub fn blst_p2_on_curve(p: *const blst_p2) -> bool; +} +extern "C" { + pub fn blst_p2_in_g2(p: *const blst_p2) -> bool; +} +extern "C" { + pub fn blst_p2_is_equal(a: *const blst_p2, b: *const blst_p2) -> bool; +} +extern "C" { + pub fn blst_p2_is_inf(a: *const blst_p2) -> bool; +} +extern "C" { + pub fn blst_p2_generator() -> *const blst_p2; +} +extern "C" { + pub fn blst_p2_affine_on_curve(p: *const blst_p2_affine) -> bool; +} +extern "C" { + pub fn blst_p2_affine_in_g2(p: *const blst_p2_affine) -> bool; +} +extern "C" { + pub fn blst_p2_affine_is_equal(a: *const blst_p2_affine, b: *const blst_p2_affine) -> bool; +} +extern "C" { + pub fn blst_p2_affine_is_inf(a: *const blst_p2_affine) -> bool; +} +extern "C" { + pub fn blst_p2_affine_generator() -> *const blst_p2_affine; +} +extern "C" { + pub fn blst_p1s_to_affine( + dst: *mut blst_p1_affine, + points: *const *const blst_p1, + npoints: usize, + ); +} +extern "C" { + pub fn blst_p1s_add(ret: *mut blst_p1, points: *const *const blst_p1_affine, npoints: usize); +} +extern "C" { + pub fn blst_p1s_mult_wbits_precompute_sizeof(wbits: usize, npoints: usize) -> usize; +} +extern "C" { + pub fn blst_p1s_mult_wbits_precompute( + table: *mut blst_p1_affine, + wbits: usize, + points: *const *const blst_p1_affine, + npoints: usize, + ); +} +extern "C" { + pub fn blst_p1s_mult_wbits_scratch_sizeof(npoints: usize) -> usize; +} +extern "C" { + pub fn blst_p1s_mult_wbits( + ret: *mut blst_p1, + table: *const blst_p1_affine, + wbits: usize, + npoints: usize, + scalars: *const *const byte, + nbits: usize, + scratch: *mut limb_t, + ); +} +extern "C" { + pub fn blst_p1s_mult_pippenger_scratch_sizeof(npoints: usize) -> usize; +} +extern "C" { + pub fn blst_p1s_mult_pippenger( + ret: *mut blst_p1, + points: *const *const blst_p1_affine, + npoints: usize, + scalars: *const *const byte, + nbits: usize, + scratch: *mut limb_t, + ); +} +extern "C" { + pub fn blst_p1s_tile_pippenger( + ret: *mut blst_p1, + points: *const *const blst_p1_affine, + npoints: usize, + scalars: *const *const byte, + nbits: usize, + scratch: *mut limb_t, + bit0: usize, + window: usize, + ); +} +extern "C" { + pub fn blst_p2s_to_affine( + dst: *mut blst_p2_affine, + points: *const *const blst_p2, + npoints: usize, + ); +} +extern "C" { + pub fn blst_p2s_add(ret: *mut blst_p2, points: *const *const blst_p2_affine, npoints: usize); +} +extern "C" { + pub fn blst_p2s_mult_wbits_precompute_sizeof(wbits: usize, npoints: usize) -> usize; +} +extern "C" { + pub fn blst_p2s_mult_wbits_precompute( + table: *mut blst_p2_affine, + wbits: usize, + points: *const *const blst_p2_affine, + npoints: usize, + ); +} +extern "C" { + pub fn blst_p2s_mult_wbits_scratch_sizeof(npoints: usize) -> usize; +} +extern "C" { + pub fn blst_p2s_mult_wbits( + ret: *mut blst_p2, + table: *const blst_p2_affine, + wbits: usize, + npoints: usize, + scalars: *const *const byte, + nbits: usize, + scratch: *mut limb_t, + ); +} +extern "C" { + pub fn blst_p2s_mult_pippenger_scratch_sizeof(npoints: usize) -> usize; +} +extern "C" { + pub fn blst_p2s_mult_pippenger( + ret: *mut blst_p2, + points: *const *const blst_p2_affine, + npoints: usize, + scalars: *const *const byte, + nbits: usize, + scratch: *mut limb_t, + ); +} +extern "C" { + pub fn blst_p2s_tile_pippenger( + ret: *mut blst_p2, + points: *const *const blst_p2_affine, + npoints: usize, + scalars: *const *const byte, + nbits: usize, + scratch: *mut limb_t, + bit0: usize, + window: usize, + ); +} +extern "C" { + pub fn blst_map_to_g1(out: *mut blst_p1, u: *const blst_fp, v: *const blst_fp); +} +extern "C" { + pub fn blst_map_to_g2(out: *mut blst_p2, u: *const blst_fp2, v: *const blst_fp2); +} +extern "C" { + pub fn blst_encode_to_g1( + out: *mut blst_p1, + msg: *const byte, + msg_len: usize, + DST: *const byte, + DST_len: usize, + aug: *const byte, + aug_len: usize, + ); +} +extern "C" { + pub fn blst_hash_to_g1( + out: *mut blst_p1, + msg: *const byte, + msg_len: usize, + DST: *const byte, + DST_len: usize, + aug: *const byte, + aug_len: usize, + ); +} +extern "C" { + pub fn blst_encode_to_g2( + out: *mut blst_p2, + msg: *const byte, + msg_len: usize, + DST: *const byte, + DST_len: usize, + aug: *const byte, + aug_len: usize, + ); +} +extern "C" { + pub fn blst_hash_to_g2( + out: *mut blst_p2, + msg: *const byte, + msg_len: usize, + DST: *const byte, + DST_len: usize, + aug: *const byte, + aug_len: usize, + ); +} +extern "C" { + pub fn blst_p1_serialize(out: *mut byte, in_: *const blst_p1); +} +extern "C" { + pub fn blst_p1_compress(out: *mut byte, in_: *const blst_p1); +} +extern "C" { + pub fn blst_p1_affine_serialize(out: *mut byte, in_: *const blst_p1_affine); +} +extern "C" { + pub fn blst_p1_affine_compress(out: *mut byte, in_: *const blst_p1_affine); +} +extern "C" { + pub fn blst_p1_uncompress(out: *mut blst_p1_affine, in_: *const byte) -> BLST_ERROR; +} +extern "C" { + pub fn blst_p1_deserialize(out: *mut blst_p1_affine, in_: *const byte) -> BLST_ERROR; +} +extern "C" { + pub fn blst_p2_serialize(out: *mut byte, in_: *const blst_p2); +} +extern "C" { + pub fn blst_p2_compress(out: *mut byte, in_: *const blst_p2); +} +extern "C" { + pub fn blst_p2_affine_serialize(out: *mut byte, in_: *const blst_p2_affine); +} +extern "C" { + pub fn blst_p2_affine_compress(out: *mut byte, in_: *const blst_p2_affine); +} +extern "C" { + pub fn blst_p2_uncompress(out: *mut blst_p2_affine, in_: *const byte) -> BLST_ERROR; +} +extern "C" { + pub fn blst_p2_deserialize(out: *mut blst_p2_affine, in_: *const byte) -> BLST_ERROR; +} +extern "C" { + pub fn blst_keygen( + out_SK: *mut blst_scalar, + IKM: *const byte, + IKM_len: usize, + info: *const byte, + info_len: usize, + ); +} +extern "C" { + pub fn blst_sk_to_pk_in_g1(out_pk: *mut blst_p1, SK: *const blst_scalar); +} +extern "C" { + pub fn blst_sign_pk_in_g1(out_sig: *mut blst_p2, hash: *const blst_p2, SK: *const blst_scalar); +} +extern "C" { + pub fn blst_sk_to_pk_in_g2(out_pk: *mut blst_p2, SK: *const blst_scalar); +} +extern "C" { + pub fn blst_sign_pk_in_g2(out_sig: *mut blst_p1, hash: *const blst_p1, SK: *const blst_scalar); +} +extern "C" { + pub fn blst_miller_loop( + ret: *mut blst_fp12, + Q: *const blst_p2_affine, + P: *const blst_p1_affine, + ); +} +extern "C" { + pub fn blst_miller_loop_n( + ret: *mut blst_fp12, + Qs: *const *const blst_p2_affine, + Ps: *const *const blst_p1_affine, + n: usize, + ); +} +extern "C" { + pub fn blst_final_exp(ret: *mut blst_fp12, f: *const blst_fp12); +} +extern "C" { + pub fn blst_precompute_lines(Qlines: *mut blst_fp6, Q: *const blst_p2_affine); +} +extern "C" { + pub fn blst_miller_loop_lines( + ret: *mut blst_fp12, + Qlines: *const blst_fp6, + P: *const blst_p1_affine, + ); +} +extern "C" { + pub fn blst_fp12_finalverify(gt1: *const blst_fp12, gt2: *const blst_fp12) -> bool; +} +#[repr(C)] +#[repr(align(1))] +#[derive(Debug, Default)] +pub struct blst_pairing { + pub _bindgen_opaque_blob: [u8; 0usize], +} +#[test] +fn bindgen_test_layout_blst_pairing() { + assert_eq!( + ::core::mem::size_of::(), + 0usize, + concat!("Size of: ", stringify!(blst_pairing)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(blst_pairing)) + ); +} +extern "C" { + pub fn blst_pairing_sizeof() -> usize; +} +extern "C" { + pub fn blst_pairing_init( + new_ctx: *mut blst_pairing, + hash_or_encode: bool, + DST: *const byte, + DST_len: usize, + ); +} +extern "C" { + pub fn blst_pairing_get_dst(ctx: *const blst_pairing) -> *const byte; +} +extern "C" { + pub fn blst_pairing_commit(ctx: *mut blst_pairing); +} +extern "C" { + pub fn blst_pairing_aggregate_pk_in_g2( + ctx: *mut blst_pairing, + PK: *const blst_p2_affine, + signature: *const blst_p1_affine, + msg: *const byte, + msg_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_chk_n_aggr_pk_in_g2( + ctx: *mut blst_pairing, + PK: *const blst_p2_affine, + pk_grpchk: bool, + signature: *const blst_p1_affine, + sig_grpchk: bool, + msg: *const byte, + msg_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_mul_n_aggregate_pk_in_g2( + ctx: *mut blst_pairing, + PK: *const blst_p2_affine, + sig: *const blst_p1_affine, + scalar: *const byte, + nbits: usize, + msg: *const byte, + msg_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_chk_n_mul_n_aggr_pk_in_g2( + ctx: *mut blst_pairing, + PK: *const blst_p2_affine, + pk_grpchk: bool, + sig: *const blst_p1_affine, + sig_grpchk: bool, + scalar: *const byte, + nbits: usize, + msg: *const byte, + msg_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_aggregate_pk_in_g1( + ctx: *mut blst_pairing, + PK: *const blst_p1_affine, + signature: *const blst_p2_affine, + msg: *const byte, + msg_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_chk_n_aggr_pk_in_g1( + ctx: *mut blst_pairing, + PK: *const blst_p1_affine, + pk_grpchk: bool, + signature: *const blst_p2_affine, + sig_grpchk: bool, + msg: *const byte, + msg_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_mul_n_aggregate_pk_in_g1( + ctx: *mut blst_pairing, + PK: *const blst_p1_affine, + sig: *const blst_p2_affine, + scalar: *const byte, + nbits: usize, + msg: *const byte, + msg_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_chk_n_mul_n_aggr_pk_in_g1( + ctx: *mut blst_pairing, + PK: *const blst_p1_affine, + pk_grpchk: bool, + sig: *const blst_p2_affine, + sig_grpchk: bool, + scalar: *const byte, + nbits: usize, + msg: *const byte, + msg_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_merge(ctx: *mut blst_pairing, ctx1: *const blst_pairing) -> BLST_ERROR; +} +extern "C" { + pub fn blst_pairing_finalverify(ctx: *const blst_pairing, gtsig: *const blst_fp12) -> bool; +} +extern "C" { + pub fn blst_aggregate_in_g1( + out: *mut blst_p1, + in_: *const blst_p1, + zwire: *const byte, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_aggregate_in_g2( + out: *mut blst_p2, + in_: *const blst_p2, + zwire: *const byte, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_aggregated_in_g1(out: *mut blst_fp12, signature: *const blst_p1_affine); +} +extern "C" { + pub fn blst_aggregated_in_g2(out: *mut blst_fp12, signature: *const blst_p2_affine); +} +extern "C" { + pub fn blst_core_verify_pk_in_g1( + pk: *const blst_p1_affine, + signature: *const blst_p2_affine, + hash_or_encode: bool, + msg: *const byte, + msg_len: usize, + DST: *const byte, + DST_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub fn blst_core_verify_pk_in_g2( + pk: *const blst_p2_affine, + signature: *const blst_p1_affine, + hash_or_encode: bool, + msg: *const byte, + msg_len: usize, + DST: *const byte, + DST_len: usize, + aug: *const byte, + aug_len: usize, + ) -> BLST_ERROR; +} +extern "C" { + pub static BLS12_381_G1: blst_p1_affine; +} +extern "C" { + pub static BLS12_381_NEG_G1: blst_p1_affine; +} +extern "C" { + pub static BLS12_381_G2: blst_p2_affine; +} +extern "C" { + pub static BLS12_381_NEG_G2: blst_p2_affine; +} +extern "C" { + pub fn blst_fr_ct_bfly(x0: *mut blst_fr, x1: *mut blst_fr, twiddle: *const blst_fr); +} +extern "C" { + pub fn blst_fr_gs_bfly(x0: *mut blst_fr, x1: *mut blst_fr, twiddle: *const blst_fr); +} +extern "C" { + pub fn blst_fr_to(ret: *mut blst_fr, a: *const blst_fr); +} +extern "C" { + pub fn blst_fr_from(ret: *mut blst_fr, a: *const blst_fr); +} +extern "C" { + pub fn blst_fp_to(ret: *mut blst_fp, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_from(ret: *mut blst_fp, a: *const blst_fp); +} +extern "C" { + pub fn blst_fp_is_square(a: *const blst_fp) -> bool; +} +extern "C" { + pub fn blst_fp2_is_square(a: *const blst_fp2) -> bool; +} +extern "C" { + pub fn blst_p1_from_jacobian(out: *mut blst_p1, in_: *const blst_p1); +} +extern "C" { + pub fn blst_p2_from_jacobian(out: *mut blst_p2, in_: *const blst_p2); +} +extern "C" { + pub fn blst_sk_to_pk2_in_g1( + out: *mut byte, + out_pk: *mut blst_p1_affine, + SK: *const blst_scalar, + ); +} +extern "C" { + pub fn blst_sign_pk2_in_g1( + out: *mut byte, + out_sig: *mut blst_p2_affine, + hash: *const blst_p2, + SK: *const blst_scalar, + ); +} +extern "C" { + pub fn blst_sk_to_pk2_in_g2( + out: *mut byte, + out_pk: *mut blst_p2_affine, + SK: *const blst_scalar, + ); +} +extern "C" { + pub fn blst_sign_pk2_in_g2( + out: *mut byte, + out_sig: *mut blst_p1_affine, + hash: *const blst_p1, + SK: *const blst_scalar, + ); +} +#[repr(C)] +#[repr(align(1))] +#[derive(Debug, Default)] +pub struct blst_uniq { + pub _bindgen_opaque_blob: [u8; 0usize], +} +#[test] +fn bindgen_test_layout_blst_uniq() { + assert_eq!( + ::core::mem::size_of::(), + 0usize, + concat!("Size of: ", stringify!(blst_uniq)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(blst_uniq)) + ); +} +extern "C" { + pub fn blst_uniq_sizeof(n_nodes: usize) -> usize; +} +extern "C" { + pub fn blst_uniq_init(tree: *mut blst_uniq); +} +extern "C" { + pub fn blst_uniq_test(tree: *mut blst_uniq, msg: *const byte, len: usize) -> bool; +} +extern "C" { + pub fn blst_expand_message_xmd( + out: *mut byte, + out_len: usize, + msg: *const byte, + msg_len: usize, + DST: *const byte, + DST_len: usize, + ); +} +extern "C" { + pub fn blst_p1_unchecked_mult( + out: *mut blst_p1, + p: *const blst_p1, + scalar: *const byte, + nbits: usize, + ); +} +extern "C" { + pub fn blst_p2_unchecked_mult( + out: *mut blst_p2, + p: *const blst_p2, + scalar: *const byte, + nbits: usize, + ); +} +extern "C" { + pub fn blst_pairing_raw_aggregate( + ctx: *mut blst_pairing, + q: *const blst_p2_affine, + p: *const blst_p1_affine, + ); +} +extern "C" { + pub fn blst_pairing_as_fp12(ctx: *mut blst_pairing) -> *mut blst_fp12; +} +extern "C" { + pub fn blst_bendian_from_fp12(out: *mut byte, a: *const blst_fp12); +} +extern "C" { + pub fn blst_keygen_v3( + out_SK: *mut blst_scalar, + IKM: *const byte, + IKM_len: usize, + info: *const byte, + info_len: usize, + ); +} +extern "C" { + pub fn blst_keygen_v4_5( + out_SK: *mut blst_scalar, + IKM: *const byte, + IKM_len: usize, + salt: *const byte, + salt_len: usize, + info: *const byte, + info_len: usize, + ); +} +extern "C" { + pub fn blst_keygen_v5( + out_SK: *mut blst_scalar, + IKM: *const byte, + IKM_len: usize, + salt: *const byte, + salt_len: usize, + info: *const byte, + info_len: usize, + ); +} +extern "C" { + pub fn blst_derive_master_eip2333(out_SK: *mut blst_scalar, IKM: *const byte, IKM_len: usize); +} +extern "C" { + pub fn blst_derive_child_eip2333( + out_SK: *mut blst_scalar, + SK: *const blst_scalar, + child_index: u32, + ); +} +extern "C" { + pub fn blst_scalar_from_hexascii(out: *mut blst_scalar, hex: *const byte); +} +extern "C" { + pub fn blst_fr_from_hexascii(ret: *mut blst_fr, hex: *const byte); +} +extern "C" { + pub fn blst_fp_from_hexascii(ret: *mut blst_fp, hex: *const byte); +} +extern "C" { + pub fn blst_p1_sizeof() -> usize; +} +extern "C" { + pub fn blst_p1_affine_sizeof() -> usize; +} +extern "C" { + pub fn blst_p2_sizeof() -> usize; +} +extern "C" { + pub fn blst_p2_affine_sizeof() -> usize; +} +extern "C" { + pub fn blst_fp12_sizeof() -> usize; +} +extern "C" { + pub fn blst_fp_from_le_bytes(ret: *mut blst_fp, in_: *const byte, len: usize); +} +extern "C" { + pub fn blst_fp_from_be_bytes(ret: *mut blst_fp, in_: *const byte, len: usize); +} +extern "C" { + pub fn blst_sha256(out: *mut byte, msg: *const byte, msg_len: usize); +} +#[test] +fn bindgen_test_normal_types() { + // from "Rust for Rustaceans" by Jon Gjengset + fn is_normal() {} + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); +} diff --git a/crates/blst/src/lib.rs b/crates/blst/src/lib.rs new file mode 100644 index 000000000..6105afb65 --- /dev/null +++ b/crates/blst/src/lib.rs @@ -0,0 +1,2395 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unexpected_cfgs)] + +extern crate alloc; + +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; +use core::any::Any; +use core::mem::{transmute, MaybeUninit}; +use core::ptr; +use zeroize::Zeroize; + +#[cfg(feature = "std")] +use std::sync::{atomic::*, mpsc::sync_channel, Arc}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[cfg(feature = "std")] +trait ThreadPoolExt { + fn joined_execute<'any, F>(&self, job: F) + where + F: FnOnce() + Send + 'any; +} + +#[cfg(all(not(feature = "no-threads"), feature = "std"))] +mod mt { + use super::*; + use std::sync::{Mutex, Once}; + use threadpool::ThreadPool; + + pub fn da_pool() -> ThreadPool { + static INIT: Once = Once::new(); + static mut POOL: *const Mutex = ptr::null(); + + INIT.call_once(|| { + let pool = Mutex::new(ThreadPool::default()); + unsafe { POOL = transmute::, *const _>(Box::new(pool)) }; + }); + unsafe { (*POOL).lock().unwrap().clone() } + } + + type Thunk<'any> = Box; + + impl ThreadPoolExt for ThreadPool { + fn joined_execute<'scope, F>(&self, job: F) + where + F: FnOnce() + Send + 'scope, + { + // Bypass 'lifetime limitations by brute force. It works, + // because we explicitly join the threads... + self.execute(unsafe { + transmute::, Thunk<'static>>(Box::new(job)) + }) + } + } +} + +#[cfg(all(feature = "no-threads", feature = "std"))] +mod mt { + use super::*; + + pub struct EmptyPool {} + + pub fn da_pool() -> EmptyPool { + EmptyPool {} + } + + impl EmptyPool { + pub fn max_count(&self) -> usize { + 1 + } + } + + impl ThreadPoolExt for EmptyPool { + fn joined_execute<'scope, F>(&self, job: F) + where + F: FnOnce() + Send + 'scope, + { + job() + } + } +} + +include!("bindings.rs"); + +impl PartialEq for blst_p1 { + fn eq(&self, other: &Self) -> bool { + unsafe { blst_p1_is_equal(self, other) } + } +} + +impl PartialEq for blst_p1_affine { + fn eq(&self, other: &Self) -> bool { + unsafe { blst_p1_affine_is_equal(self, other) } + } +} + +impl PartialEq for blst_p2 { + fn eq(&self, other: &Self) -> bool { + unsafe { blst_p2_is_equal(self, other) } + } +} + +impl PartialEq for blst_p2_affine { + fn eq(&self, other: &Self) -> bool { + unsafe { blst_p2_affine_is_equal(self, other) } + } +} + +impl Default for blst_fp12 { + fn default() -> Self { + unsafe { *blst_fp12_one() } + } +} + +impl PartialEq for blst_fp12 { + fn eq(&self, other: &Self) -> bool { + unsafe { blst_fp12_is_equal(self, other) } + } +} + +impl core::ops::Mul for blst_fp12 { + type Output = Self; + + fn mul(self, other: Self) -> Self { + let mut out = MaybeUninit::::uninit(); + unsafe { + blst_fp12_mul(out.as_mut_ptr(), &self, &other); + out.assume_init() + } + } +} + +impl core::ops::MulAssign for blst_fp12 { + fn mul_assign(&mut self, other: Self) { + unsafe { blst_fp12_mul(self, self, &other) } + } +} + +impl blst_fp12 { + pub fn miller_loop(q: &blst_p2_affine, p: &blst_p1_affine) -> Self { + let mut out = MaybeUninit::::uninit(); + unsafe { + blst_miller_loop(out.as_mut_ptr(), q, p); + out.assume_init() + } + } + + #[cfg(not(feature = "std"))] + pub fn miller_loop_n(q: &[blst_p2_affine], p: &[blst_p1_affine]) -> Self { + let n_elems = q.len(); + if n_elems != p.len() || n_elems == 0 { + panic!("inputs' lengths mismatch"); + } + let qs: [*const _; 2] = [&q[0], ptr::null()]; + let ps: [*const _; 2] = [&p[0], ptr::null()]; + let mut out = MaybeUninit::::uninit(); + unsafe { + blst_miller_loop_n(out.as_mut_ptr(), &qs[0], &ps[0], n_elems); + out.assume_init() + } + } + + #[cfg(feature = "std")] + pub fn miller_loop_n(q: &[blst_p2_affine], p: &[blst_p1_affine]) -> Self { + let n_elems = q.len(); + if n_elems != p.len() || n_elems == 0 { + panic!("inputs' lengths mismatch"); + } + + let pool = mt::da_pool(); + + let mut n_workers = pool.max_count(); + if n_workers == 1 { + let qs: [*const _; 2] = [&q[0], ptr::null()]; + let ps: [*const _; 2] = [&p[0], ptr::null()]; + let mut out = MaybeUninit::::uninit(); + unsafe { + blst_miller_loop_n(out.as_mut_ptr(), &qs[0], &ps[0], n_elems); + return out.assume_init(); + } + } + + let counter = Arc::new(AtomicUsize::new(0)); + let stride = core::cmp::min((n_elems + n_workers - 1) / n_workers, 16); + n_workers = core::cmp::min((n_elems + stride - 1) / stride, n_workers); + let (tx, rx) = sync_channel(n_workers); + for _ in 0..n_workers { + let tx = tx.clone(); + let counter = counter.clone(); + + pool.joined_execute(move || { + let mut acc = blst_fp12::default(); + let mut tmp = MaybeUninit::::uninit(); + let mut qs: [*const _; 2] = [ptr::null(), ptr::null()]; + let mut ps: [*const _; 2] = [ptr::null(), ptr::null()]; + + loop { + let work = counter.fetch_add(stride, Ordering::Relaxed); + if work >= n_elems { + break; + } + let n = core::cmp::min(n_elems - work, stride); + qs[0] = &q[work]; + ps[0] = &p[work]; + unsafe { + blst_miller_loop_n(tmp.as_mut_ptr(), &qs[0], &ps[0], n); + acc *= tmp.assume_init(); + } + } + + tx.send(acc).expect("disaster"); + }); + } + + let mut acc = rx.recv().unwrap(); + for _ in 1..n_workers { + acc *= rx.recv().unwrap(); + } + + acc + } + + pub fn final_exp(&self) -> Self { + let mut out = MaybeUninit::::uninit(); + unsafe { + blst_final_exp(out.as_mut_ptr(), self); + out.assume_init() + } + } + + pub fn in_group(&self) -> bool { + unsafe { blst_fp12_in_group(self) } + } + + pub fn finalverify(a: &Self, b: &Self) -> bool { + unsafe { blst_fp12_finalverify(a, b) } + } + + pub fn to_bendian(&self) -> [u8; 48 * 12] { + let mut out = MaybeUninit::<[u8; 48 * 12]>::uninit(); + unsafe { + blst_bendian_from_fp12(out.as_mut_ptr() as *mut u8, self); + out.assume_init() + } + } +} + +impl blst_scalar { + pub fn hash_to(msg: &[u8], dst: &[u8]) -> Option { + unsafe { + let mut out = ::default(); + let mut elem = [0u8; 48]; + blst_expand_message_xmd( + elem.as_mut_ptr(), + elem.len(), + msg.as_ptr(), + msg.len(), + dst.as_ptr(), + dst.len(), + ); + if blst_scalar_from_be_bytes(&mut out, elem.as_ptr(), elem.len()) { + Some(out) + } else { + None + } + } + } +} + +#[derive(Debug)] +pub struct Pairing { + v: Box<[u64]>, +} + +impl Pairing { + pub fn new(hash_or_encode: bool, dst: &[u8]) -> Self { + let v: Vec = vec![0; unsafe { blst_pairing_sizeof() } / 8]; + let mut obj = Self { + v: v.into_boxed_slice(), + }; + obj.init(hash_or_encode, dst); + obj + } + + pub fn init(&mut self, hash_or_encode: bool, dst: &[u8]) { + unsafe { + blst_pairing_init( + self.ctx(), + hash_or_encode, + dst.as_ptr(), + dst.len(), + ) + } + } + fn ctx(&mut self) -> *mut blst_pairing { + self.v.as_mut_ptr() as *mut blst_pairing + } + fn const_ctx(&self) -> *const blst_pairing { + self.v.as_ptr() as *const blst_pairing + } + + pub fn aggregate( + &mut self, + pk: &dyn Any, + pk_validate: bool, + sig: &dyn Any, + sig_groupcheck: bool, + msg: &[u8], + aug: &[u8], + ) -> BLST_ERROR { + if pk.is::() { + unsafe { + blst_pairing_chk_n_aggr_pk_in_g1( + self.ctx(), + match pk.downcast_ref::() { + Some(pk) => pk, + None => ptr::null(), + }, + pk_validate, + match sig.downcast_ref::() { + Some(sig) => sig, + None => ptr::null(), + }, + sig_groupcheck, + msg.as_ptr(), + msg.len(), + aug.as_ptr(), + aug.len(), + ) + } + } else if pk.is::() { + unsafe { + blst_pairing_chk_n_aggr_pk_in_g2( + self.ctx(), + match pk.downcast_ref::() { + Some(pk) => pk, + None => ptr::null(), + }, + pk_validate, + match sig.downcast_ref::() { + Some(sig) => sig, + None => ptr::null(), + }, + sig_groupcheck, + msg.as_ptr(), + msg.len(), + aug.as_ptr(), + aug.len(), + ) + } + } else { + panic!("whaaaa?") + } + } + + #[allow(clippy::too_many_arguments)] + pub fn mul_n_aggregate( + &mut self, + pk: &dyn Any, + pk_validate: bool, + sig: &dyn Any, + sig_groupcheck: bool, + scalar: &[u8], + nbits: usize, + msg: &[u8], + aug: &[u8], + ) -> BLST_ERROR { + if pk.is::() { + unsafe { + blst_pairing_chk_n_mul_n_aggr_pk_in_g1( + self.ctx(), + match pk.downcast_ref::() { + Some(pk) => pk, + None => ptr::null(), + }, + pk_validate, + match sig.downcast_ref::() { + Some(sig) => sig, + None => ptr::null(), + }, + sig_groupcheck, + scalar.as_ptr(), + nbits, + msg.as_ptr(), + msg.len(), + aug.as_ptr(), + aug.len(), + ) + } + } else if pk.is::() { + unsafe { + blst_pairing_chk_n_mul_n_aggr_pk_in_g2( + self.ctx(), + match pk.downcast_ref::() { + Some(pk) => pk, + None => ptr::null(), + }, + pk_validate, + match sig.downcast_ref::() { + Some(sig) => sig, + None => ptr::null(), + }, + sig_groupcheck, + scalar.as_ptr(), + nbits, + msg.as_ptr(), + msg.len(), + aug.as_ptr(), + aug.len(), + ) + } + } else { + panic!("whaaaa?") + } + } + + pub fn aggregated(gtsig: &mut blst_fp12, sig: &dyn Any) { + if sig.is::() { + unsafe { + blst_aggregated_in_g1( + gtsig, + sig.downcast_ref::().unwrap(), + ) + } + } else if sig.is::() { + unsafe { + blst_aggregated_in_g2( + gtsig, + sig.downcast_ref::().unwrap(), + ) + } + } else { + panic!("whaaaa?") + } + } + + pub fn commit(&mut self) { + unsafe { blst_pairing_commit(self.ctx()) } + } + + pub fn merge(&mut self, ctx1: &Self) -> BLST_ERROR { + unsafe { blst_pairing_merge(self.ctx(), ctx1.const_ctx()) } + } + + pub fn finalverify(&self, gtsig: Option<&blst_fp12>) -> bool { + unsafe { + blst_pairing_finalverify( + self.const_ctx(), + match gtsig { + Some(gtsig) => gtsig, + None => ptr::null(), + }, + ) + } + } + + pub fn raw_aggregate(&mut self, q: &blst_p2_affine, p: &blst_p1_affine) { + unsafe { blst_pairing_raw_aggregate(self.ctx(), q, p) } + } + + pub fn as_fp12(&mut self) -> blst_fp12 { + unsafe { *blst_pairing_as_fp12(self.ctx()) } + } +} + +pub fn uniq(msgs: &[&[u8]]) -> bool { + let n_elems = msgs.len(); + + if n_elems == 1 { + return true; + } else if n_elems == 2 { + return msgs[0] != msgs[1]; + } + + let mut v: Vec = vec![0; unsafe { blst_uniq_sizeof(n_elems) } / 8]; + let ctx = v.as_mut_ptr() as *mut blst_uniq; + + unsafe { blst_uniq_init(ctx) }; + + for msg in msgs.iter() { + if !unsafe { blst_uniq_test(ctx, msg.as_ptr(), msg.len()) } { + return false; + } + } + + true +} + +#[cfg(feature = "std")] +pub fn print_bytes(bytes: &[u8], name: &str) { + print!("{} ", name); + for b in bytes.iter() { + print!("{:02x}", b); + } + println!(); +} + +macro_rules! sig_variant_impl { + ( + $name:expr, + $pk:ty, + $pk_aff:ty, + $sig:ty, + $sig_aff:ty, + $sk_to_pk:ident, + $hash_or_encode:expr, + $hash_or_encode_to:ident, + $sign:ident, + $pk_eq:ident, + $sig_eq:ident, + $verify:ident, + $pk_in_group:ident, + $pk_to_aff:ident, + $pk_from_aff:ident, + $pk_ser:ident, + $pk_comp:ident, + $pk_deser:ident, + $pk_uncomp:ident, + $pk_comp_size:expr, + $pk_ser_size:expr, + $sig_in_group:ident, + $sig_to_aff:ident, + $sig_from_aff:ident, + $sig_ser:ident, + $sig_comp:ident, + $sig_deser:ident, + $sig_uncomp:ident, + $sig_comp_size:expr, + $sig_ser_size:expr, + $pk_add_or_dbl:ident, + $pk_add_or_dbl_aff:ident, + $pk_cneg:ident, + $sig_add_or_dbl:ident, + $sig_add_or_dbl_aff:ident, + $pk_is_inf:ident, + $sig_is_inf:ident, + $sig_aggr_in_group:ident, + ) => { + /// Secret Key + #[repr(transparent)] + #[derive(Default, Debug, Clone, Zeroize)] + #[zeroize(drop)] + pub struct SecretKey { + value: blst_scalar, + } + + impl SecretKey { + /// Deterministically generate a secret key from key material + pub fn key_gen( + ikm: &[u8], + key_info: &[u8], + ) -> Result { + if ikm.len() < 32 { + return Err(BLST_ERROR::BLST_BAD_ENCODING); + } + let mut sk = SecretKey::default(); + unsafe { + blst_keygen( + &mut sk.value, + ikm.as_ptr(), + ikm.len(), + key_info.as_ptr(), + key_info.len(), + ); + } + Ok(sk) + } + + pub fn key_gen_v3( + ikm: &[u8], + key_info: &[u8], + ) -> Result { + if ikm.len() < 32 { + return Err(BLST_ERROR::BLST_BAD_ENCODING); + } + let mut sk = SecretKey::default(); + unsafe { + blst_keygen_v3( + &mut sk.value, + ikm.as_ptr(), + ikm.len(), + key_info.as_ptr(), + key_info.len(), + ); + } + Ok(sk) + } + + pub fn key_gen_v4_5( + ikm: &[u8], + salt: &[u8], + info: &[u8], + ) -> Result { + if ikm.len() < 32 { + return Err(BLST_ERROR::BLST_BAD_ENCODING); + } + let mut sk = SecretKey::default(); + unsafe { + blst_keygen_v4_5( + &mut sk.value, + ikm.as_ptr(), + ikm.len(), + salt.as_ptr(), + salt.len(), + info.as_ptr(), + info.len(), + ); + } + Ok(sk) + } + + pub fn key_gen_v5( + ikm: &[u8], + salt: &[u8], + info: &[u8], + ) -> Result { + if ikm.len() < 32 { + return Err(BLST_ERROR::BLST_BAD_ENCODING); + } + let mut sk = SecretKey::default(); + unsafe { + blst_keygen_v5( + &mut sk.value, + ikm.as_ptr(), + ikm.len(), + salt.as_ptr(), + salt.len(), + info.as_ptr(), + info.len(), + ); + } + Ok(sk) + } + + pub fn derive_master_eip2333( + ikm: &[u8], + ) -> Result { + if ikm.len() < 32 { + return Err(BLST_ERROR::BLST_BAD_ENCODING); + } + let mut sk = SecretKey::default(); + unsafe { + blst_derive_master_eip2333( + &mut sk.value, + ikm.as_ptr(), + ikm.len(), + ); + } + Ok(sk) + } + + pub fn derive_child_eip2333(&self, child_index: u32) -> Self { + let mut sk = SecretKey::default(); + unsafe { + blst_derive_child_eip2333( + &mut sk.value, + &self.value, + child_index, + ); + } + sk + } + + // sk_to_pk + pub fn sk_to_pk(&self) -> PublicKey { + // TODO - would the user like the serialized/compressed pk as well? + let mut pk_aff = PublicKey::default(); + //let mut pk_ser = [0u8; $pk_ser_size]; + + unsafe { + $sk_to_pk( + //pk_ser.as_mut_ptr(), + ptr::null_mut(), + &mut pk_aff.point, + &self.value, + ); + } + pk_aff + } + + // Sign + pub fn sign( + &self, + msg: &[u8], + dst: &[u8], + aug: &[u8], + ) -> Signature { + // TODO - would the user like the serialized/compressed sig as well? + let mut q = <$sig>::default(); + let mut sig_aff = <$sig_aff>::default(); + //let mut sig_ser = [0u8; $sig_ser_size]; + unsafe { + $hash_or_encode_to( + &mut q, + msg.as_ptr(), + msg.len(), + dst.as_ptr(), + dst.len(), + aug.as_ptr(), + aug.len(), + ); + $sign(ptr::null_mut(), &mut sig_aff, &q, &self.value); + } + Signature { point: sig_aff } + } + + // TODO - formally speaking application is entitled to have + // ultimate control over secret key storage, which means that + // corresponding serialization/deserialization subroutines + // should accept reference to where to store the result, as + // opposite to returning one. + + // serialize + pub fn serialize(&self) -> [u8; 32] { + let mut sk_out = [0; 32]; + unsafe { + blst_bendian_from_scalar(sk_out.as_mut_ptr(), &self.value); + } + sk_out + } + + // deserialize + pub fn deserialize(sk_in: &[u8]) -> Result { + let mut sk = blst_scalar::default(); + if sk_in.len() != 32 { + return Err(BLST_ERROR::BLST_BAD_ENCODING); + } + unsafe { + blst_scalar_from_bendian(&mut sk, sk_in.as_ptr()); + if !blst_sk_check(&sk) { + return Err(BLST_ERROR::BLST_BAD_ENCODING); + } + } + Ok(Self { value: sk }) + } + + pub fn to_bytes(&self) -> [u8; 32] { + SecretKey::serialize(&self) + } + + pub fn from_bytes(sk_in: &[u8]) -> Result { + SecretKey::deserialize(sk_in) + } + } + + #[cfg(feature = "serde-secret")] + impl Serialize for SecretKey { + fn serialize( + &self, + ser: S, + ) -> Result { + let bytes = zeroize::Zeroizing::new(self.serialize()); + ser.serialize_bytes(bytes.as_ref()) + } + } + + #[cfg(feature = "serde-secret")] + impl<'de> Deserialize<'de> for SecretKey { + fn deserialize>( + deser: D, + ) -> Result { + let bytes: &[u8] = Deserialize::deserialize(deser)?; + Self::deserialize(bytes).map_err(|e| { + ::custom(format!("{:?}", e)) + }) + } + } + + // From traits are not provided to discourage duplication + // of the secret key material. + impl<'a> From<&'a SecretKey> for &'a blst_scalar { + fn from(sk: &'a SecretKey) -> Self { + unsafe { + transmute::<&SecretKey, Self>(sk) + } + } + } + + impl<'a> core::convert::TryFrom<&'a blst_scalar> for &'a SecretKey { + type Error = BLST_ERROR; + + fn try_from(sk: &'a blst_scalar) -> Result { + unsafe { + if !blst_sk_check(sk) { + return Err(BLST_ERROR::BLST_BAD_ENCODING); + } + Ok(transmute::<&blst_scalar, Self>(sk)) + } + } + } + + #[repr(transparent)] + #[derive(Default, Debug, Clone, Copy)] + pub struct PublicKey { + point: $pk_aff, + } + + impl PublicKey { + // Core operations + + // key_validate + pub fn validate(&self) -> Result<(), BLST_ERROR> { + unsafe { + if $pk_is_inf(&self.point) { + return Err(BLST_ERROR::BLST_PK_IS_INFINITY); + } + if !$pk_in_group(&self.point) { + return Err(BLST_ERROR::BLST_POINT_NOT_IN_GROUP); + } + } + Ok(()) + } + + pub fn key_validate(key: &[u8]) -> Result { + let pk = PublicKey::from_bytes(key)?; + pk.validate()?; + Ok(pk) + } + + pub fn from_aggregate(agg_pk: &AggregatePublicKey) -> Self { + let mut pk_aff = <$pk_aff>::default(); + unsafe { + $pk_to_aff(&mut pk_aff, &agg_pk.point); + } + Self { point: pk_aff } + } + + // Serdes + + pub fn compress(&self) -> [u8; $pk_comp_size] { + let mut pk_comp = [0u8; $pk_comp_size]; + unsafe { + $pk_comp(pk_comp.as_mut_ptr(), &self.point); + } + pk_comp + } + + pub fn serialize(&self) -> [u8; $pk_ser_size] { + let mut pk_out = [0u8; $pk_ser_size]; + unsafe { + $pk_ser(pk_out.as_mut_ptr(), &self.point); + } + pk_out + } + + pub fn uncompress(pk_comp: &[u8]) -> Result { + if pk_comp.len() == $pk_comp_size && (pk_comp[0] & 0x80) != 0 { + let mut pk = <$pk_aff>::default(); + let err = unsafe { $pk_uncomp(&mut pk, pk_comp.as_ptr()) }; + if err != BLST_ERROR::BLST_SUCCESS { + return Err(err); + } + Ok(Self { point: pk }) + } else { + Err(BLST_ERROR::BLST_BAD_ENCODING) + } + } + + pub fn deserialize(pk_in: &[u8]) -> Result { + if (pk_in.len() == $pk_ser_size && (pk_in[0] & 0x80) == 0) + || (pk_in.len() == $pk_comp_size && (pk_in[0] & 0x80) != 0) + { + let mut pk = <$pk_aff>::default(); + let err = unsafe { $pk_deser(&mut pk, pk_in.as_ptr()) }; + if err != BLST_ERROR::BLST_SUCCESS { + return Err(err); + } + Ok(Self { point: pk }) + } else { + Err(BLST_ERROR::BLST_BAD_ENCODING) + } + } + + pub fn from_bytes(pk_in: &[u8]) -> Result { + PublicKey::deserialize(pk_in) + } + + pub fn to_bytes(&self) -> [u8; $pk_comp_size] { + self.compress() + } + } + + // Trait for equality comparisons which are equivalence relations. + // + // This means, that in addition to a == b and a != b being strict + // inverses, the equality must be reflexive, symmetric and transitive. + impl Eq for PublicKey {} + + impl PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + unsafe { $pk_eq(&self.point, &other.point) } + } + } + + #[cfg(feature = "serde")] + impl Serialize for PublicKey { + fn serialize( + &self, + ser: S, + ) -> Result { + ser.serialize_bytes(&self.serialize()) + } + } + + #[cfg(feature = "serde")] + impl<'de> Deserialize<'de> for PublicKey { + fn deserialize>( + deser: D, + ) -> Result { + let bytes: &[u8] = Deserialize::deserialize(deser)?; + Self::deserialize(&bytes).map_err(|e| { + ::custom(format!("{:?}", e)) + }) + } + } + + impl From for $pk_aff { + fn from(pk: PublicKey) -> Self { + pk.point + } + } + + impl<'a> From<&'a PublicKey> for &'a $pk_aff { + fn from(pk: &'a PublicKey) -> Self { + &pk.point + } + } + + impl From<$pk_aff> for PublicKey { + fn from(point: $pk_aff) -> Self { + Self { point } + } + } + + #[repr(transparent)] + #[derive(Debug, Clone, Copy)] + pub struct AggregatePublicKey { + point: $pk, + } + + impl AggregatePublicKey { + pub fn from_public_key(pk: &PublicKey) -> Self { + let mut agg_pk = <$pk>::default(); + unsafe { + $pk_from_aff(&mut agg_pk, &pk.point); + } + Self { point: agg_pk } + } + + pub fn to_public_key(&self) -> PublicKey { + let mut pk = <$pk_aff>::default(); + unsafe { + $pk_to_aff(&mut pk, &self.point); + } + PublicKey { point: pk } + } + + // Aggregate + pub fn aggregate( + pks: &[&PublicKey], + pks_validate: bool, + ) -> Result { + if pks.len() == 0 { + return Err(BLST_ERROR::BLST_AGGR_TYPE_MISMATCH); + } + if pks_validate { + pks[0].validate()?; + } + let mut agg_pk = AggregatePublicKey::from_public_key(pks[0]); + for s in pks.iter().skip(1) { + if pks_validate { + s.validate()?; + } + unsafe { + $pk_add_or_dbl_aff( + &mut agg_pk.point, + &agg_pk.point, + &s.point, + ); + } + } + Ok(agg_pk) + } + + pub fn aggregate_with_randomness( + pks: &[PublicKey], + randomness: &[u8], + nbits: usize, + pks_groupcheck: bool, + ) -> Result { + if pks.len() == 0 { + return Err(BLST_ERROR::BLST_AGGR_TYPE_MISMATCH); + } + if pks_groupcheck { + pks.validate()?; + } + Ok(pks.mult(randomness, nbits)) + } + + pub fn aggregate_serialized( + pks: &[&[u8]], + pks_validate: bool, + ) -> Result { + // TODO - threading + if pks.len() == 0 { + return Err(BLST_ERROR::BLST_AGGR_TYPE_MISMATCH); + } + let mut pk = if pks_validate { + PublicKey::key_validate(pks[0])? + } else { + PublicKey::from_bytes(pks[0])? + }; + let mut agg_pk = AggregatePublicKey::from_public_key(&pk); + for s in pks.iter().skip(1) { + pk = if pks_validate { + PublicKey::key_validate(s)? + } else { + PublicKey::from_bytes(s)? + }; + unsafe { + $pk_add_or_dbl_aff( + &mut agg_pk.point, + &agg_pk.point, + &pk.point, + ); + } + } + Ok(agg_pk) + } + + pub fn add_aggregate(&mut self, agg_pk: &AggregatePublicKey) { + unsafe { + $pk_add_or_dbl(&mut self.point, &self.point, &agg_pk.point); + } + } + + pub fn sub_aggregate(&mut self, agg_pk: &AggregatePublicKey) { + unsafe { + let mut tmp = agg_pk.clone(); + $pk_cneg(&mut tmp.point, true); + $pk_add_or_dbl(&mut self.point, &self.point, &tmp.point); + } + } + + pub fn add_public_key( + &mut self, + pk: &PublicKey, + pk_validate: bool, + ) -> Result<(), BLST_ERROR> { + if pk_validate { + pk.validate()?; + } + unsafe { + $pk_add_or_dbl_aff(&mut self.point, &self.point, &pk.point); + } + Ok(()) + } + } + + impl From for $pk { + fn from(pk: AggregatePublicKey) -> Self { + pk.point + } + } + + impl<'a> From<&'a AggregatePublicKey> for &'a $pk { + fn from(pk: &'a AggregatePublicKey) -> Self { + &pk.point + } + } + + impl From<$pk> for AggregatePublicKey { + fn from(point: $pk) -> Self { + Self { point } + } + } + + #[repr(transparent)] + #[derive(Debug, Clone, Copy)] + pub struct Signature { + point: $sig_aff, + } + + impl Signature { + // sig_infcheck, check for infinity, is a way to avoid going + // into resource-consuming verification. Passing 'false' is + // always cryptographically safe, but application might want + // to guard against obviously bogus individual[!] signatures. + pub fn validate( + &self, + sig_infcheck: bool, + ) -> Result<(), BLST_ERROR> { + unsafe { + if sig_infcheck && $sig_is_inf(&self.point) { + return Err(BLST_ERROR::BLST_PK_IS_INFINITY); + } + if !$sig_in_group(&self.point) { + return Err(BLST_ERROR::BLST_POINT_NOT_IN_GROUP); + } + } + Ok(()) + } + + pub fn sig_validate( + sig: &[u8], + sig_infcheck: bool, + ) -> Result { + let sig = Signature::from_bytes(sig)?; + sig.validate(sig_infcheck)?; + Ok(sig) + } + + pub fn verify( + &self, + sig_groupcheck: bool, + msg: &[u8], + dst: &[u8], + aug: &[u8], + pk: &PublicKey, + pk_validate: bool, + ) -> BLST_ERROR { + let aug_msg = [aug, msg].concat(); + self.aggregate_verify( + sig_groupcheck, + &[aug_msg.as_slice()], + dst, + &[pk], + pk_validate, + ) + } + + #[cfg(not(feature = "std"))] + pub fn aggregate_verify( + &self, + sig_groupcheck: bool, + msgs: &[&[u8]], + dst: &[u8], + pks: &[&PublicKey], + pks_validate: bool, + ) -> BLST_ERROR { + let n_elems = pks.len(); + if n_elems == 0 || msgs.len() != n_elems { + return BLST_ERROR::BLST_VERIFY_FAIL; + } + + let mut pairing = Pairing::new($hash_or_encode, dst); + + let err = pairing.aggregate( + &pks[0].point, + pks_validate, + &self.point, + sig_groupcheck, + &msgs[0], + &[], + ); + if err != BLST_ERROR::BLST_SUCCESS { + return err; + } + + for i in 1..n_elems { + let err = pairing.aggregate( + &pks[i].point, + pks_validate, + &unsafe { ptr::null::<$sig_aff>().as_ref() }, + false, + &msgs[i], + &[], + ); + if err != BLST_ERROR::BLST_SUCCESS { + return err; + } + } + + pairing.commit(); + + if pairing.finalverify(None) { + BLST_ERROR::BLST_SUCCESS + } else { + BLST_ERROR::BLST_VERIFY_FAIL + } + } + + #[cfg(feature = "std")] + pub fn aggregate_verify( + &self, + sig_groupcheck: bool, + msgs: &[&[u8]], + dst: &[u8], + pks: &[&PublicKey], + pks_validate: bool, + ) -> BLST_ERROR { + let n_elems = pks.len(); + if n_elems == 0 || msgs.len() != n_elems { + return BLST_ERROR::BLST_VERIFY_FAIL; + } + + // TODO - check msg uniqueness? + + let pool = mt::da_pool(); + let counter = Arc::new(AtomicUsize::new(0)); + let valid = Arc::new(AtomicBool::new(true)); + let n_workers = core::cmp::min(pool.max_count(), n_elems); + let (tx, rx) = sync_channel(n_workers); + for _ in 0..n_workers { + let tx = tx.clone(); + let counter = counter.clone(); + let valid = valid.clone(); + + pool.joined_execute(move || { + let mut pairing = Pairing::new($hash_or_encode, dst); + + while valid.load(Ordering::Relaxed) { + let work = counter.fetch_add(1, Ordering::Relaxed); + if work >= n_elems { + break; + } + if pairing.aggregate( + &pks[work].point, + pks_validate, + &unsafe { ptr::null::<$sig_aff>().as_ref() }, + false, + &msgs[work], + &[], + ) != BLST_ERROR::BLST_SUCCESS + { + valid.store(false, Ordering::Relaxed); + break; + } + } + if valid.load(Ordering::Relaxed) { + pairing.commit(); + } + tx.send(pairing).expect("disaster"); + }); + } + + if sig_groupcheck && valid.load(Ordering::Relaxed) { + match self.validate(false) { + Err(_err) => valid.store(false, Ordering::Relaxed), + _ => (), + } + } + + let mut gtsig = blst_fp12::default(); + if valid.load(Ordering::Relaxed) { + Pairing::aggregated(&mut gtsig, &self.point); + } + + let mut acc = rx.recv().unwrap(); + for _ in 1..n_workers { + acc.merge(&rx.recv().unwrap()); + } + + if valid.load(Ordering::Relaxed) + && acc.finalverify(Some(>sig)) + { + BLST_ERROR::BLST_SUCCESS + } else { + BLST_ERROR::BLST_VERIFY_FAIL + } + } + + // pks are assumed to be verified for proof of possession, + // which implies that they are already group-checked + pub fn fast_aggregate_verify( + &self, + sig_groupcheck: bool, + msg: &[u8], + dst: &[u8], + pks: &[&PublicKey], + ) -> BLST_ERROR { + let agg_pk = match AggregatePublicKey::aggregate(pks, false) { + Ok(agg_sig) => agg_sig, + Err(err) => return err, + }; + let pk = agg_pk.to_public_key(); + self.aggregate_verify( + sig_groupcheck, + &[msg], + dst, + &[&pk], + false, + ) + } + + pub fn fast_aggregate_verify_pre_aggregated( + &self, + sig_groupcheck: bool, + msg: &[u8], + dst: &[u8], + pk: &PublicKey, + ) -> BLST_ERROR { + self.aggregate_verify(sig_groupcheck, &[msg], dst, &[pk], false) + } + + // https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407 + #[cfg(feature = "std")] + #[allow(clippy::too_many_arguments)] + pub fn verify_multiple_aggregate_signatures( + msgs: &[&[u8]], + dst: &[u8], + pks: &[&PublicKey], + pks_validate: bool, + sigs: &[&Signature], + sigs_groupcheck: bool, + rands: &[blst_scalar], + rand_bits: usize, + ) -> BLST_ERROR { + let n_elems = pks.len(); + if n_elems == 0 + || msgs.len() != n_elems + || sigs.len() != n_elems + || rands.len() != n_elems + { + return BLST_ERROR::BLST_VERIFY_FAIL; + } + + // TODO - check msg uniqueness? + + let pool = mt::da_pool(); + let counter = Arc::new(AtomicUsize::new(0)); + let valid = Arc::new(AtomicBool::new(true)); + let n_workers = core::cmp::min(pool.max_count(), n_elems); + let (tx, rx) = sync_channel(n_workers); + for _ in 0..n_workers { + let tx = tx.clone(); + let counter = counter.clone(); + let valid = valid.clone(); + + pool.joined_execute(move || { + let mut pairing = Pairing::new($hash_or_encode, dst); + + // TODO - engage multi-point mul-n-add for larger + // amount of inputs... + while valid.load(Ordering::Relaxed) { + let work = counter.fetch_add(1, Ordering::Relaxed); + if work >= n_elems { + break; + } + + if pairing.mul_n_aggregate( + &pks[work].point, + pks_validate, + &sigs[work].point, + sigs_groupcheck, + &rands[work].b, + rand_bits, + msgs[work], + &[], + ) != BLST_ERROR::BLST_SUCCESS + { + valid.store(false, Ordering::Relaxed); + break; + } + } + if valid.load(Ordering::Relaxed) { + pairing.commit(); + } + tx.send(pairing).expect("disaster"); + }); + } + + let mut acc = rx.recv().unwrap(); + for _ in 1..n_workers { + acc.merge(&rx.recv().unwrap()); + } + + if valid.load(Ordering::Relaxed) && acc.finalverify(None) { + BLST_ERROR::BLST_SUCCESS + } else { + BLST_ERROR::BLST_VERIFY_FAIL + } + } + + #[cfg(not(feature = "std"))] + #[allow(clippy::too_many_arguments)] + pub fn verify_multiple_aggregate_signatures( + msgs: &[&[u8]], + dst: &[u8], + pks: &[&PublicKey], + pks_validate: bool, + sigs: &[&Signature], + sigs_groupcheck: bool, + rands: &[blst_scalar], + rand_bits: usize, + ) -> BLST_ERROR { + let n_elems = pks.len(); + if n_elems == 0 + || msgs.len() != n_elems + || sigs.len() != n_elems + || rands.len() != n_elems + { + return BLST_ERROR::BLST_VERIFY_FAIL; + } + + // TODO - check msg uniqueness? + + let mut pairing = Pairing::new($hash_or_encode, dst); + + for i in 0..n_elems { + let err = pairing.mul_n_aggregate( + &pks[i].point, + pks_validate, + &sigs[i].point, + sigs_groupcheck, + &rands[i].b, + rand_bits, + msgs[i], + &[], + ); + if err != BLST_ERROR::BLST_SUCCESS { + return err; + } + } + + pairing.commit(); + + if pairing.finalverify(None) { + BLST_ERROR::BLST_SUCCESS + } else { + BLST_ERROR::BLST_VERIFY_FAIL + } + } + + pub fn from_aggregate(agg_sig: &AggregateSignature) -> Self { + let mut sig_aff = <$sig_aff>::default(); + unsafe { + $sig_to_aff(&mut sig_aff, &agg_sig.point); + } + Self { point: sig_aff } + } + + pub fn compress(&self) -> [u8; $sig_comp_size] { + let mut sig_comp = [0; $sig_comp_size]; + unsafe { + $sig_comp(sig_comp.as_mut_ptr(), &self.point); + } + sig_comp + } + + pub fn serialize(&self) -> [u8; $sig_ser_size] { + let mut sig_out = [0; $sig_ser_size]; + unsafe { + $sig_ser(sig_out.as_mut_ptr(), &self.point); + } + sig_out + } + + pub fn uncompress(sig_comp: &[u8]) -> Result { + if sig_comp.len() == $sig_comp_size && (sig_comp[0] & 0x80) != 0 + { + let mut sig = <$sig_aff>::default(); + let err = + unsafe { $sig_uncomp(&mut sig, sig_comp.as_ptr()) }; + if err != BLST_ERROR::BLST_SUCCESS { + return Err(err); + } + Ok(Self { point: sig }) + } else { + Err(BLST_ERROR::BLST_BAD_ENCODING) + } + } + + pub fn deserialize(sig_in: &[u8]) -> Result { + if (sig_in.len() == $sig_ser_size && (sig_in[0] & 0x80) == 0) + || (sig_in.len() == $sig_comp_size + && (sig_in[0] & 0x80) != 0) + { + let mut sig = <$sig_aff>::default(); + let err = unsafe { $sig_deser(&mut sig, sig_in.as_ptr()) }; + if err != BLST_ERROR::BLST_SUCCESS { + return Err(err); + } + Ok(Self { point: sig }) + } else { + Err(BLST_ERROR::BLST_BAD_ENCODING) + } + } + + pub fn from_bytes(sig_in: &[u8]) -> Result { + Signature::deserialize(sig_in) + } + + pub fn to_bytes(&self) -> [u8; $sig_comp_size] { + self.compress() + } + + pub fn subgroup_check(&self) -> bool { + unsafe { $sig_in_group(&self.point) } + } + } + + // Trait for equality comparisons which are equivalence relations. + // + // This means, that in addition to a == b and a != b being strict + // inverses, the equality must be reflexive, symmetric and transitive. + impl Eq for Signature {} + + impl PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + unsafe { $sig_eq(&self.point, &other.point) } + } + } + + #[cfg(feature = "serde")] + impl Serialize for Signature { + fn serialize( + &self, + ser: S, + ) -> Result { + ser.serialize_bytes(&self.serialize()) + } + } + + #[cfg(feature = "serde")] + impl<'de> Deserialize<'de> for Signature { + fn deserialize>( + deser: D, + ) -> Result { + let bytes: &[u8] = Deserialize::deserialize(deser)?; + Self::deserialize(&bytes).map_err(|e| { + ::custom(format!("{:?}", e)) + }) + } + } + + impl From for $sig_aff { + fn from(sig: Signature) -> Self { + sig.point + } + } + + impl<'a> From<&'a Signature> for &'a $sig_aff { + fn from(sig: &'a Signature) -> Self { + &sig.point + } + } + + impl From<$sig_aff> for Signature { + fn from(point: $sig_aff) -> Self { + Self { point } + } + } + + #[repr(transparent)] + #[derive(Debug, Clone, Copy)] + pub struct AggregateSignature { + point: $sig, + } + + impl AggregateSignature { + pub fn validate(&self) -> Result<(), BLST_ERROR> { + unsafe { + if !$sig_aggr_in_group(&self.point) { + return Err(BLST_ERROR::BLST_POINT_NOT_IN_GROUP); + } + } + Ok(()) + } + + pub fn from_signature(sig: &Signature) -> Self { + let mut agg_sig = <$sig>::default(); + unsafe { + $sig_from_aff(&mut agg_sig, &sig.point); + } + Self { point: agg_sig } + } + + pub fn to_signature(&self) -> Signature { + let mut sig = <$sig_aff>::default(); + unsafe { + $sig_to_aff(&mut sig, &self.point); + } + Signature { point: sig } + } + + // Aggregate + pub fn aggregate( + sigs: &[&Signature], + sigs_groupcheck: bool, + ) -> Result { + if sigs.len() == 0 { + return Err(BLST_ERROR::BLST_AGGR_TYPE_MISMATCH); + } + if sigs_groupcheck { + // We can't actually judge if input is individual or + // aggregated signature, so we can't enforce infinity + // check. + sigs[0].validate(false)?; + } + let mut agg_sig = AggregateSignature::from_signature(sigs[0]); + for s in sigs.iter().skip(1) { + if sigs_groupcheck { + s.validate(false)?; + } + unsafe { + $sig_add_or_dbl_aff( + &mut agg_sig.point, + &agg_sig.point, + &s.point, + ); + } + } + Ok(agg_sig) + } + + pub fn aggregate_with_randomness( + sigs: &[Signature], + randomness: &[u8], + nbits: usize, + sigs_groupcheck: bool, + ) -> Result { + if sigs.len() == 0 { + return Err(BLST_ERROR::BLST_AGGR_TYPE_MISMATCH); + } + if sigs_groupcheck { + sigs.validate()?; + } + Ok(sigs.mult(randomness, nbits)) + } + + pub fn aggregate_serialized( + sigs: &[&[u8]], + sigs_groupcheck: bool, + ) -> Result { + // TODO - threading + if sigs.len() == 0 { + return Err(BLST_ERROR::BLST_AGGR_TYPE_MISMATCH); + } + let mut sig = if sigs_groupcheck { + Signature::sig_validate(sigs[0], false)? + } else { + Signature::from_bytes(sigs[0])? + }; + let mut agg_sig = AggregateSignature::from_signature(&sig); + for s in sigs.iter().skip(1) { + sig = if sigs_groupcheck { + Signature::sig_validate(s, false)? + } else { + Signature::from_bytes(s)? + }; + unsafe { + $sig_add_or_dbl_aff( + &mut agg_sig.point, + &agg_sig.point, + &sig.point, + ); + } + } + Ok(agg_sig) + } + + pub fn add_aggregate(&mut self, agg_sig: &AggregateSignature) { + unsafe { + $sig_add_or_dbl( + &mut self.point, + &self.point, + &agg_sig.point, + ); + } + } + + pub fn add_signature( + &mut self, + sig: &Signature, + sig_groupcheck: bool, + ) -> Result<(), BLST_ERROR> { + if sig_groupcheck { + sig.validate(false)?; + } + unsafe { + $sig_add_or_dbl_aff( + &mut self.point, + &self.point, + &sig.point, + ); + } + Ok(()) + } + + pub fn subgroup_check(&self) -> bool { + unsafe { $sig_aggr_in_group(&self.point) } + } + } + + impl From for $sig { + fn from(sig: AggregateSignature) -> Self { + sig.point + } + } + + impl<'a> From<&'a AggregateSignature> for &'a $sig { + fn from(sig: &'a AggregateSignature) -> Self { + &sig.point + } + } + + impl From<$sig> for AggregateSignature { + fn from(point: $sig) -> Self { + Self { point } + } + } + + impl MultiPoint for [PublicKey] { + type Output = AggregatePublicKey; + + fn mult(&self, scalars: &[u8], nbits: usize) -> Self::Output { + Self::Output { + point: unsafe { transmute::<&[_], &[$pk_aff]>(self) } + .mult(scalars, nbits), + } + } + + fn add(&self) -> Self::Output { + Self::Output { + point: unsafe { transmute::<&[_], &[$pk_aff]>(self) } + .add(), + } + } + + fn validate(&self) -> Result<(), BLST_ERROR> { + unsafe { transmute::<&[_], &[$pk_aff]>(self) }.validate() + } + } + + impl MultiPoint for [Signature] { + type Output = AggregateSignature; + + fn mult(&self, scalars: &[u8], nbits: usize) -> Self::Output { + Self::Output { + point: unsafe { transmute::<&[_], &[$sig_aff]>(self) } + .mult(scalars, nbits), + } + } + + fn add(&self) -> Self::Output { + Self::Output { + point: unsafe { transmute::<&[_], &[$sig_aff]>(self) } + .add(), + } + } + + fn validate(&self) -> Result<(), BLST_ERROR> { + unsafe { transmute::<&[_], &[$sig_aff]>(self) }.validate() + } + } + + #[cfg(test)] + mod tests { + use super::*; + use rand::{RngCore, SeedableRng}; + use rand_chacha::ChaCha20Rng; + + // Testing only - do not use for production + pub fn gen_random_key( + rng: &mut rand_chacha::ChaCha20Rng, + ) -> SecretKey { + let mut ikm = [0u8; 32]; + rng.fill_bytes(&mut ikm); + + let mut sk = ::default(); + unsafe { + blst_keygen(&mut sk, ikm.as_ptr(), 32, ptr::null(), 0); + } + SecretKey { value: sk } + } + + #[test] + fn test_sign_n_verify() { + for _ in 0..100 { + let ikm: [u8; 32] = [ + 0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a, + 0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56, + 0x60, 0x5b, 0xb0, 0x56, 0xed, 0xfe, 0x2b, 0x60, 0xa6, 0x3c, + 0x48, 0x99, + ]; + + let sk = SecretKey::key_gen(&ikm, &[]).unwrap(); + let pk = sk.sk_to_pk(); + + let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; + let msg = b"hello foo"; + let sig = sk.sign(msg, dst, &[]); + + let err = sig.verify(true, msg, dst, &[], &pk, true); + assert_eq!(err, BLST_ERROR::BLST_SUCCESS); + } + } + + #[test] + fn test_aggregate() { + for _ in 0..100 { + let num_msgs = 10; + let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; + + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + + let sks: Vec<_> = + (0..num_msgs).map(|_| gen_random_key(&mut rng)).collect(); + let pks = + sks.iter().map(|sk| sk.sk_to_pk()).collect::>(); + let pks_refs: Vec<&PublicKey> = + pks.iter().map(|pk| pk).collect(); + let pks_rev: Vec<&PublicKey> = + pks.iter().rev().map(|pk| pk).collect(); + + let pk_comp = pks[0].compress(); + let pk_uncomp = PublicKey::uncompress(&pk_comp); + assert_eq!(pk_uncomp.is_ok(), true); + + let mut msgs: Vec> = vec![vec![]; num_msgs]; + for i in 0..num_msgs { + let msg_len = (rng.next_u64() & 0x3F) + 1; + msgs[i] = vec![0u8; msg_len as usize]; + rng.fill_bytes(&mut msgs[i]); + } + + let msgs_refs: Vec<&[u8]> = + msgs.iter().map(|m| m.as_slice()).collect(); + + let sigs = sks + .iter() + .zip(msgs.iter()) + .map(|(sk, m)| (sk.sign(m, dst, &[]))) + .collect::>(); + + let mut errs = sigs + .iter() + .zip(msgs.iter()) + .zip(pks.iter()) + .map(|((s, m), pk)| (s.verify(true, m, dst, &[], pk, true))) + .collect::>(); + assert_eq!(errs, vec![BLST_ERROR::BLST_SUCCESS; num_msgs]); + + // Swap message/public key pairs to create bad signature + errs = sigs + .iter() + .zip(msgs.iter()) + .zip(pks.iter().rev()) + .map(|((s, m), pk)| (s.verify(true, m, dst, &[], pk, true))) + .collect::>(); + assert_ne!(errs, vec![BLST_ERROR::BLST_SUCCESS; num_msgs]); + + let sig_refs = + sigs.iter().map(|s| s).collect::>(); + let agg = match AggregateSignature::aggregate(&sig_refs, true) { + Ok(agg) => agg, + Err(err) => panic!("aggregate failure: {:?}", err), + }; + + let agg_sig = agg.to_signature(); + let mut result = agg_sig + .aggregate_verify(false, &msgs_refs, dst, &pks_refs, false); + assert_eq!(result, BLST_ERROR::BLST_SUCCESS); + + // Swap message/public key pairs to create bad signature + result = agg_sig + .aggregate_verify(false, &msgs_refs, dst, &pks_rev, false); + assert_ne!(result, BLST_ERROR::BLST_SUCCESS); + } + } + + #[test] + fn test_multiple_agg_sigs() { + for _ in 0..100 { + let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + let num_pks_per_sig = 10; + let num_sigs = 10; + + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + + let mut msgs: Vec> = vec![vec![]; num_sigs]; + let mut sigs: Vec = Vec::with_capacity(num_sigs); + let mut pks: Vec = Vec::with_capacity(num_sigs); + let mut rands: Vec = Vec::with_capacity(num_sigs); + for i in 0..num_sigs { + // Create public keys + let sks_i: Vec<_> = (0..num_pks_per_sig) + .map(|_| gen_random_key(&mut rng)) + .collect(); + + let pks_i = sks_i + .iter() + .map(|sk| sk.sk_to_pk()) + .collect::>(); + let pks_refs_i: Vec<&PublicKey> = + pks_i.iter().map(|pk| pk).collect(); + + // Create random message for pks to all sign + let msg_len = (rng.next_u64() & 0x3F) + 1; + msgs[i] = vec![0u8; msg_len as usize]; + rng.fill_bytes(&mut msgs[i]); + + // Generate signature for each key pair + let sigs_i = sks_i + .iter() + .map(|sk| sk.sign(&msgs[i], dst, &[])) + .collect::>(); + + // Test each current single signature + let errs = sigs_i + .iter() + .zip(pks_i.iter()) + .map(|(s, pk)| { + (s.verify(true, &msgs[i], dst, &[], pk, true)) + }) + .collect::>(); + assert_eq!( + errs, + vec![BLST_ERROR::BLST_SUCCESS; num_pks_per_sig] + ); + + let sig_refs_i = + sigs_i.iter().map(|s| s).collect::>(); + let agg_i = + match AggregateSignature::aggregate(&sig_refs_i, false) + { + Ok(agg_i) => agg_i, + Err(err) => panic!("aggregate failure: {:?}", err), + }; + + // Test current aggregate signature + sigs.push(agg_i.to_signature()); + let mut result = sigs[i].fast_aggregate_verify( + false, + &msgs[i], + dst, + &pks_refs_i, + ); + assert_eq!(result, BLST_ERROR::BLST_SUCCESS); + + // negative test + if i != 0 { + result = sigs[i - 1].fast_aggregate_verify( + false, + &msgs[i], + dst, + &pks_refs_i, + ); + assert_ne!(result, BLST_ERROR::BLST_SUCCESS); + } + + // aggregate public keys and push into vec + let agg_pk_i = + match AggregatePublicKey::aggregate(&pks_refs_i, false) + { + Ok(agg_pk_i) => agg_pk_i, + Err(err) => panic!("aggregate failure: {:?}", err), + }; + pks.push(agg_pk_i.to_public_key()); + + // Test current aggregate signature with aggregated pks + result = sigs[i].fast_aggregate_verify_pre_aggregated( + false, &msgs[i], dst, &pks[i], + ); + assert_eq!(result, BLST_ERROR::BLST_SUCCESS); + + // negative test + if i != 0 { + result = sigs[i - 1] + .fast_aggregate_verify_pre_aggregated( + false, &msgs[i], dst, &pks[i], + ); + assert_ne!(result, BLST_ERROR::BLST_SUCCESS); + } + + // create random values + let mut vals = [0u64; 4]; + vals[0] = rng.next_u64(); + while vals[0] == 0 { + // Reject zero as it is used for multiplication. + vals[0] = rng.next_u64(); + } + let mut rand_i = MaybeUninit::::uninit(); + unsafe { + blst_scalar_from_uint64( + rand_i.as_mut_ptr(), + vals.as_ptr(), + ); + rands.push(rand_i.assume_init()); + } + } + + let msgs_refs: Vec<&[u8]> = + msgs.iter().map(|m| m.as_slice()).collect(); + let sig_refs = + sigs.iter().map(|s| s).collect::>(); + let pks_refs: Vec<&PublicKey> = + pks.iter().map(|pk| pk).collect(); + + let msgs_rev: Vec<&[u8]> = + msgs.iter().rev().map(|m| m.as_slice()).collect(); + let sig_rev = + sigs.iter().rev().map(|s| s).collect::>(); + let pks_rev: Vec<&PublicKey> = + pks.iter().rev().map(|pk| pk).collect(); + + let mut result = + Signature::verify_multiple_aggregate_signatures( + &msgs_refs, dst, &pks_refs, false, &sig_refs, true, + &rands, 64, + ); + assert_eq!(result, BLST_ERROR::BLST_SUCCESS); + + // negative tests (use reverse msgs, pks, and sigs) + result = Signature::verify_multiple_aggregate_signatures( + &msgs_rev, dst, &pks_refs, false, &sig_refs, true, &rands, + 64, + ); + assert_ne!(result, BLST_ERROR::BLST_SUCCESS); + + result = Signature::verify_multiple_aggregate_signatures( + &msgs_refs, dst, &pks_rev, false, &sig_refs, true, &rands, + 64, + ); + assert_ne!(result, BLST_ERROR::BLST_SUCCESS); + + result = Signature::verify_multiple_aggregate_signatures( + &msgs_refs, dst, &pks_refs, false, &sig_rev, true, &rands, + 64, + ); + assert_ne!(result, BLST_ERROR::BLST_SUCCESS); + } + } + + #[test] + fn test_serialization() { + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + + let sk = gen_random_key(&mut rng); + let sk2 = gen_random_key(&mut rng); + + let pk = sk.sk_to_pk(); + let pk_comp = pk.compress(); + let pk_ser = pk.serialize(); + + let pk_uncomp = PublicKey::uncompress(&pk_comp); + assert_eq!(pk_uncomp.is_ok(), true); + assert_eq!(pk_uncomp.unwrap(), pk); + + let pk_deser = PublicKey::deserialize(&pk_ser); + assert_eq!(pk_deser.is_ok(), true); + assert_eq!(pk_deser.unwrap(), pk); + + let pk2 = sk2.sk_to_pk(); + let pk_comp2 = pk2.compress(); + let pk_ser2 = pk2.serialize(); + + let pk_uncomp2 = PublicKey::uncompress(&pk_comp2); + assert_eq!(pk_uncomp2.is_ok(), true); + assert_eq!(pk_uncomp2.unwrap(), pk2); + + let pk_deser2 = PublicKey::deserialize(&pk_ser2); + assert_eq!(pk_deser2.is_ok(), true); + assert_eq!(pk_deser2.unwrap(), pk2); + + assert_ne!(pk, pk2); + assert_ne!(pk_uncomp.unwrap(), pk2); + assert_ne!(pk_deser.unwrap(), pk2); + assert_ne!(pk_uncomp2.unwrap(), pk); + assert_ne!(pk_deser2.unwrap(), pk); + } + + #[cfg(feature = "serde")] + #[test] + fn test_serde() { + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + + // generate a sk, pk, and sig, and make sure it signs + let sk = gen_random_key(&mut rng); + let pk = sk.sk_to_pk(); + let sig = sk.sign(b"asdf", b"qwer", b"zxcv"); + assert_eq!( + sig.verify(true, b"asdf", b"qwer", b"zxcv", &pk, true), + BLST_ERROR::BLST_SUCCESS + ); + + // roundtrip through serde + let pk_ser = + rmp_serde::encode::to_vec_named(&pk).expect("ser pk"); + let sig_ser = + rmp_serde::encode::to_vec_named(&sig).expect("ser sig"); + let pk_des: PublicKey = + rmp_serde::decode::from_slice(&pk_ser).expect("des pk"); + let sig_des: Signature = + rmp_serde::decode::from_slice(&sig_ser).expect("des sig"); + + // check that we got back the right things + assert_eq!(pk, pk_des); + assert_eq!(sig, sig_des); + assert_eq!( + sig.verify(true, b"asdf", b"qwer", b"zxcv", &pk_des, true), + BLST_ERROR::BLST_SUCCESS + ); + assert_eq!( + sig_des.verify(true, b"asdf", b"qwer", b"zxcv", &pk, true), + BLST_ERROR::BLST_SUCCESS + ); + assert_eq!(sk.sign(b"asdf", b"qwer", b"zxcv"), sig_des); + + #[cfg(feature = "serde-secret")] + if true { + let sk_ser = + rmp_serde::encode::to_vec_named(&sk).expect("ser sk"); + let sk_des: SecretKey = + rmp_serde::decode::from_slice(&sk_ser).expect("des sk"); + // BLS signatures are deterministic, so this establishes + // that sk == sk_des + assert_eq!(sk_des.sign(b"asdf", b"qwer", b"zxcv"), sig); + } + } + + #[test] + fn test_multi_point() { + for _ in 0..100 { + let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + let num_pks = 13; + + let seed = [0u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + + // Create public keys + let sks: Vec<_> = + (0..num_pks).map(|_| gen_random_key(&mut rng)).collect(); + + let pks = + sks.iter().map(|sk| sk.sk_to_pk()).collect::>(); + let pks_refs: Vec<&PublicKey> = + pks.iter().map(|pk| pk).collect(); + + // Create random message for pks to all sign + let msg_len = (rng.next_u64() & 0x3F) + 1; + let mut msg = vec![0u8; msg_len as usize]; + rng.fill_bytes(&mut msg); + + // Generate signature for each key pair + let sigs = sks + .iter() + .map(|sk| sk.sign(&msg, dst, &[])) + .collect::>(); + let sigs_refs: Vec<&Signature> = + sigs.iter().map(|s| s).collect(); + + // create random values + let mut rands: Vec = Vec::with_capacity(8 * num_pks); + for _ in 0..num_pks { + let mut r = rng.next_u64(); + while r == 0 { + // Reject zero as it is used for multiplication. + r = rng.next_u64(); + } + rands.extend_from_slice(&r.to_le_bytes()); + } + + // Sanity test each current single signature + let errs = sigs + .iter() + .zip(pks.iter()) + .map(|(s, pk)| (s.verify(true, &msg, dst, &[], pk, true))) + .collect::>(); + assert_eq!(errs, vec![BLST_ERROR::BLST_SUCCESS; num_pks]); + + // sanity test aggregated signature + let agg_pk = AggregatePublicKey::aggregate(&pks_refs, false) + .unwrap() + .to_public_key(); + let agg_sig = AggregateSignature::aggregate(&sigs_refs, false) + .unwrap() + .to_signature(); + let err = agg_sig.verify(true, &msg, dst, &[], &agg_pk, true); + assert_eq!(err, BLST_ERROR::BLST_SUCCESS); + + // test multi-point aggregation using add + let agg_pk = pks.add().to_public_key(); + let agg_sig = sigs.add().to_signature(); + let err = agg_sig.verify(true, &msg, dst, &[], &agg_pk, true); + assert_eq!(err, BLST_ERROR::BLST_SUCCESS); + + // test multi-point aggregation using mult + let agg_pk = pks.mult(&rands, 64).to_public_key(); + let agg_sig = sigs.mult(&rands, 64).to_signature(); + let err = agg_sig.verify(true, &msg, dst, &[], &agg_pk, true); + assert_eq!(err, BLST_ERROR::BLST_SUCCESS); + } + } + } + }; +} + +pub mod min_pk { + use super::*; + + sig_variant_impl!( + "MinPk", + blst_p1, + blst_p1_affine, + blst_p2, + blst_p2_affine, + blst_sk_to_pk2_in_g1, + true, + blst_hash_to_g2, + blst_sign_pk2_in_g1, + blst_p1_affine_is_equal, + blst_p2_affine_is_equal, + blst_core_verify_pk_in_g1, + blst_p1_affine_in_g1, + blst_p1_to_affine, + blst_p1_from_affine, + blst_p1_affine_serialize, + blst_p1_affine_compress, + blst_p1_deserialize, + blst_p1_uncompress, + 48, + 96, + blst_p2_affine_in_g2, + blst_p2_to_affine, + blst_p2_from_affine, + blst_p2_affine_serialize, + blst_p2_affine_compress, + blst_p2_deserialize, + blst_p2_uncompress, + 96, + 192, + blst_p1_add_or_double, + blst_p1_add_or_double_affine, + blst_p1_cneg, + blst_p2_add_or_double, + blst_p2_add_or_double_affine, + blst_p1_affine_is_inf, + blst_p2_affine_is_inf, + blst_p2_in_g2, + ); +} + +pub mod min_sig { + use super::*; + + sig_variant_impl!( + "MinSig", + blst_p2, + blst_p2_affine, + blst_p1, + blst_p1_affine, + blst_sk_to_pk2_in_g2, + true, + blst_hash_to_g1, + blst_sign_pk2_in_g2, + blst_p2_affine_is_equal, + blst_p1_affine_is_equal, + blst_core_verify_pk_in_g2, + blst_p2_affine_in_g2, + blst_p2_to_affine, + blst_p2_from_affine, + blst_p2_affine_serialize, + blst_p2_affine_compress, + blst_p2_deserialize, + blst_p2_uncompress, + 96, + 192, + blst_p1_affine_in_g1, + blst_p1_to_affine, + blst_p1_from_affine, + blst_p1_affine_serialize, + blst_p1_affine_compress, + blst_p1_deserialize, + blst_p1_uncompress, + 48, + 96, + blst_p2_add_or_double, + blst_p2_add_or_double_affine, + blst_p2_cneg, + blst_p1_add_or_double, + blst_p1_add_or_double_affine, + blst_p2_affine_is_inf, + blst_p1_affine_is_inf, + blst_p1_in_g1, + ); +} + +pub trait MultiPoint { + type Output; + + fn mult(&self, scalars: &[u8], nbits: usize) -> Self::Output; + fn add(&self) -> Self::Output; + fn validate(&self) -> Result<(), BLST_ERROR> { + Err(BLST_ERROR::BLST_POINT_NOT_IN_GROUP) + } +} + +#[cfg(feature = "std")] +include!("pippenger.rs"); + +#[cfg(not(feature = "std"))] +include!("pippenger-no_std.rs"); + +#[cfg(test)] +mod fp12_test { + use super::*; + use rand::{RngCore, SeedableRng}; + use rand_chacha::ChaCha20Rng; + + #[test] + fn miller_loop_n() { + const npoints: usize = 97; + const nbits: usize = 64; + const nbytes: usize = (nbits + 7) / 8; + + for _ in 0..100 { + let mut scalars = Box::new([0u8; nbytes * npoints]); + ChaCha20Rng::from_entropy().fill_bytes(scalars.as_mut()); + + let mut p1s: Vec = Vec::with_capacity(npoints); + let mut p2s: Vec = Vec::with_capacity(npoints); + + unsafe { + p1s.set_len(npoints); + p2s.set_len(npoints); + + for i in 0..npoints { + blst_p1_mult( + &mut p1s[i], + blst_p1_generator(), + &scalars[i * nbytes], + 32, + ); + blst_p2_mult( + &mut p2s[i], + blst_p2_generator(), + &scalars[i * nbytes + 4], + 32, + ); + } + } + + let ps = p1_affines::from(&p1s); + let qs = p2_affines::from(&p2s); + + let mut naive = blst_fp12::default(); + for i in 0..npoints { + naive *= blst_fp12::miller_loop(&qs[i], &ps[i]); + } + + assert_eq!( + naive, + blst_fp12::miller_loop_n(qs.as_slice(), ps.as_slice()) + ); + } + } +} + +#[cfg(test)] +mod sk_test { + use super::*; + use rand::{RngCore, SeedableRng}; + use rand_chacha::ChaCha20Rng; + + #[test] + fn inverse() { + let mut bytes = [0u8; 64]; + ChaCha20Rng::from_entropy().fill_bytes(bytes.as_mut()); + + let mut sk = blst_scalar::default(); + let mut p1 = blst_p1::default(); + let mut p2 = blst_p2::default(); + + unsafe { + blst_scalar_from_be_bytes(&mut sk, bytes.as_ptr(), bytes.len()); + + blst_p1_mult(&mut p1, blst_p1_generator(), sk.b.as_ptr(), 255); + blst_sk_inverse(&mut sk, &sk); + blst_p1_mult(&mut p1, &p1, sk.b.as_ptr(), 255); + + blst_p2_mult(&mut p2, blst_p2_generator(), sk.b.as_ptr(), 255); + blst_sk_inverse(&mut sk, &sk); + blst_p2_mult(&mut p2, &p2, sk.b.as_ptr(), 255); + } + + assert_eq!(p1, unsafe { *blst_p1_generator() }); + assert_eq!(p2, unsafe { *blst_p2_generator() }); + } +} diff --git a/crates/blst/src/pippenger-no_std.rs b/crates/blst/src/pippenger-no_std.rs new file mode 100644 index 000000000..10f48bece --- /dev/null +++ b/crates/blst/src/pippenger-no_std.rs @@ -0,0 +1,179 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use core::ops::{Index, IndexMut}; +use core::slice::SliceIndex; + +macro_rules! pippenger_mult_impl { + ( + $points:ident, + $point:ty, + $point_affine:ty, + $to_affines:ident, + $scratch_sizeof:ident, + $multi_scalar_mult:ident, + $tile_mult:ident, + $add_or_double:ident, + $double:ident, + $test_mod:ident, + $generator:ident, + $mult:ident, + $add:ident, + $is_inf:ident, + $in_group:ident, + ) => { + pub struct $points { + points: Vec<$point_affine>, + } + + impl> Index for $points { + type Output = I::Output; + + #[inline] + fn index(&self, i: I) -> &Self::Output { + &self.points[i] + } + } + impl> IndexMut for $points { + #[inline] + fn index_mut(&mut self, i: I) -> &mut Self::Output { + &mut self.points[i] + } + } + + impl $points { + #[inline] + pub fn as_slice(&self) -> &[$point_affine] { + self.points.as_slice() + } + + pub fn from(points: &[$point]) -> Self { + let npoints = points.len(); + let mut ret = Self { + points: Vec::with_capacity(npoints), + }; + #[allow(clippy::uninit_vec)] + unsafe { ret.points.set_len(npoints) }; + + let p: [*const $point; 2] = [&points[0], ptr::null()]; + unsafe { $to_affines(&mut ret.points[0], &p[0], npoints) }; + ret + } + + #[inline] + pub fn mult(&self, scalars: &[u8], nbits: usize) -> $point { + self.as_slice().mult(scalars, nbits) + } + + #[inline] + pub fn add(&self) -> $point { + self.as_slice().add() + } + } + + impl MultiPoint for [$point_affine] { + type Output = $point; + + fn mult(&self, scalars: &[u8], nbits: usize) -> $point { + let npoints = self.len(); + let nbytes = (nbits + 7) / 8; + + if scalars.len() < nbytes * npoints { + panic!("scalars length mismatch"); + } + + let p: [*const $point_affine; 2] = [&self[0], ptr::null()]; + let s: [*const u8; 2] = [&scalars[0], ptr::null()]; + + let mut ret = <$point>::default(); + unsafe { + let mut scratch: Vec = + Vec::with_capacity($scratch_sizeof(npoints) / 8); + #[allow(clippy::uninit_vec)] + scratch.set_len(scratch.capacity()); + $multi_scalar_mult( + &mut ret, + &p[0], + npoints, + &s[0], + nbits, + &mut scratch[0], + ); + } + ret + } + + fn add(&self) -> $point { + let npoints = self.len(); + + let p: [*const _; 2] = [&self[0], ptr::null()]; + let mut ret = <$point>::default(); + unsafe { $add(&mut ret, &p[0], npoints) }; + + ret + } + + fn validate(&self) -> Result<(), BLST_ERROR> { + for i in 0..self.len() { + if unsafe { $is_inf(&self[i]) } { + return Err(BLST_ERROR::BLST_PK_IS_INFINITY); + } + if !unsafe { $in_group(&self[i]) } { + return Err(BLST_ERROR::BLST_POINT_NOT_IN_GROUP); + } + } + Ok(()) + } + } + + #[cfg(test)] + pippenger_test_mod!( + $test_mod, + $points, + $point, + $add_or_double, + $generator, + $mult, + ); + }; +} + +#[cfg(test)] +include!("pippenger-test_mod.rs"); + +pippenger_mult_impl!( + p1_affines, + blst_p1, + blst_p1_affine, + blst_p1s_to_affine, + blst_p1s_mult_pippenger_scratch_sizeof, + blst_p1s_mult_pippenger, + blst_p1s_tile_pippenger, + blst_p1_add_or_double, + blst_p1_double, + p1_multi_point, + blst_p1_generator, + blst_p1_mult, + blst_p1s_add, + blst_p1_affine_is_inf, + blst_p1_affine_in_g1, +); + +pippenger_mult_impl!( + p2_affines, + blst_p2, + blst_p2_affine, + blst_p2s_to_affine, + blst_p2s_mult_pippenger_scratch_sizeof, + blst_p2s_mult_pippenger, + blst_p2s_tile_pippenger, + blst_p2_add_or_double, + blst_p2_double, + p2_multi_point, + blst_p2_generator, + blst_p2_mult, + blst_p2s_add, + blst_p2_affine_is_inf, + blst_p2_affine_in_g2, +); diff --git a/crates/blst/src/pippenger-test_mod.rs b/crates/blst/src/pippenger-test_mod.rs new file mode 100644 index 000000000..4874a12ee --- /dev/null +++ b/crates/blst/src/pippenger-test_mod.rs @@ -0,0 +1,85 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +macro_rules! pippenger_test_mod { + ( + $test_mod:ident, + $points:ident, + $point:ty, + $add_or_double:ident, + $generator:ident, + $mult:ident, + ) => { + mod $test_mod { + use super::*; + use rand::{RngCore, SeedableRng}; + use rand_chacha::ChaCha20Rng; + + #[test] + fn test_mult() { + const npoints: usize = 2000; + const nbits: usize = 160; + const nbytes: usize = (nbits + 7) / 8; + + let mut scalars = Box::new([0u8; nbytes * npoints]); + ChaCha20Rng::from_seed([0u8; 32]).fill_bytes(scalars.as_mut()); + + let mut points: Vec<$point> = Vec::with_capacity(npoints); + unsafe { points.set_len(points.capacity()) }; + + let mut naive = <$point>::default(); + for i in 0..npoints { + unsafe { + let mut t = <$point>::default(); + $mult( + &mut points[i], + $generator(), + &scalars[i * nbytes], + core::cmp::min(32, nbits), + ); + $mult(&mut t, &points[i], &scalars[i * nbytes], nbits); + $add_or_double(&mut naive, &naive, &t); + } + if i < 27 { + let points = $points::from(&points[0..i + 1]); + assert_eq!(naive, points.mult(scalars.as_ref(), nbits)); + } + } + + let points = $points::from(&points); + + assert_eq!(naive, points.mult(scalars.as_ref(), nbits)); + } + + #[test] + fn test_add() { + const npoints: usize = 2000; + const nbits: usize = 32; + const nbytes: usize = (nbits + 7) / 8; + + let mut scalars = Box::new([0u8; nbytes * npoints]); + ChaCha20Rng::from_seed([0u8; 32]).fill_bytes(scalars.as_mut()); + + let mut points: Vec<$point> = Vec::with_capacity(npoints); + unsafe { points.set_len(points.capacity()) }; + + let mut naive = <$point>::default(); + for i in 0..npoints { + unsafe { + $mult( + &mut points[i], + $generator(), + &scalars[i * nbytes], + 32, + ); + $add_or_double(&mut naive, &naive, &points[i]); + } + } + + let points = $points::from(&points); + assert_eq!(naive, points.add()); + } + } + }; +} diff --git a/crates/blst/src/pippenger.rs b/crates/blst/src/pippenger.rs new file mode 100644 index 000000000..6e940f7e5 --- /dev/null +++ b/crates/blst/src/pippenger.rs @@ -0,0 +1,548 @@ +// Copyright Supranational LLC +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use core::num::Wrapping; +use core::ops::{Index, IndexMut}; +use core::slice::SliceIndex; +use std::sync::Barrier; + +struct tile { + x: usize, + dx: usize, + y: usize, + dy: usize, +} + +// Minimalist core::cell::Cell stand-in, but with Sync marker, which +// makes it possible to pass it to multiple threads. It works, because +// *here* each Cell is written only once and by just one thread. +#[repr(transparent)] +struct Cell { + value: T, +} +unsafe impl Sync for Cell {} +impl Cell { + pub fn as_ptr(&self) -> *mut T { + &self.value as *const T as *mut T + } +} + +macro_rules! pippenger_mult_impl { + ( + $points:ident, + $point:ty, + $point_affine:ty, + $to_affines:ident, + $scratch_sizeof:ident, + $multi_scalar_mult:ident, + $tile_mult:ident, + $add_or_double:ident, + $double:ident, + $test_mod:ident, + $generator:ident, + $mult:ident, + $add:ident, + $is_inf:ident, + $in_group:ident, + $from_affine:ident, + ) => { + pub struct $points { + points: Vec<$point_affine>, + } + + impl> Index for $points { + type Output = I::Output; + + #[inline] + fn index(&self, i: I) -> &Self::Output { + &self.points[i] + } + } + impl> IndexMut for $points { + #[inline] + fn index_mut(&mut self, i: I) -> &mut Self::Output { + &mut self.points[i] + } + } + + impl $points { + #[inline] + pub fn as_slice(&self) -> &[$point_affine] { + self.points.as_slice() + } + + pub fn from(points: &[$point]) -> Self { + let npoints = points.len(); + let mut ret = Self { + points: Vec::with_capacity(npoints), + }; + unsafe { ret.points.set_len(npoints) }; + + let pool = mt::da_pool(); + let ncpus = pool.max_count(); + if ncpus < 2 || npoints < 768 { + let p: [*const $point; 2] = [&points[0], ptr::null()]; + unsafe { $to_affines(&mut ret.points[0], &p[0], npoints) }; + return ret; + } + + let mut nslices = (npoints + 511) / 512; + nslices = core::cmp::min(nslices, ncpus); + let wg = Arc::new((Barrier::new(2), AtomicUsize::new(nslices))); + + let (mut delta, mut rem) = + (npoints / nslices + 1, Wrapping(npoints % nslices)); + let mut x = 0usize; + while x < npoints { + let out = &mut ret.points[x]; + let inp = &points[x]; + + delta -= (rem == Wrapping(0)) as usize; + rem -= Wrapping(1); + x += delta; + + let wg = wg.clone(); + pool.joined_execute(move || { + let p: [*const $point; 2] = [inp, ptr::null()]; + unsafe { $to_affines(out, &p[0], delta) }; + if wg.1.fetch_sub(1, Ordering::AcqRel) == 1 { + wg.0.wait(); + } + }); + } + wg.0.wait(); + + ret + } + + #[inline] + pub fn mult(&self, scalars: &[u8], nbits: usize) -> $point { + self.as_slice().mult(scalars, nbits) + } + + #[inline] + pub fn add(&self) -> $point { + self.as_slice().add() + } + } + + impl MultiPoint for [$point_affine] { + type Output = $point; + + fn mult(&self, scalars: &[u8], nbits: usize) -> $point { + let npoints = self.len(); + let nbytes = (nbits + 7) / 8; + + if scalars.len() < nbytes * npoints { + panic!("scalars length mismatch"); + } + + let pool = mt::da_pool(); + let ncpus = pool.max_count(); + if ncpus < 2 { + let p: [*const $point_affine; 2] = [&self[0], ptr::null()]; + let s: [*const u8; 2] = [&scalars[0], ptr::null()]; + + unsafe { + let mut scratch: Vec = + Vec::with_capacity($scratch_sizeof(npoints) / 8); + #[allow(clippy::uninit_vec)] + scratch.set_len(scratch.capacity()); + let mut ret = <$point>::default(); + $multi_scalar_mult( + &mut ret, + &p[0], + npoints, + &s[0], + nbits, + &mut scratch[0], + ); + return ret; + } + } + + if npoints < 32 { + let counter = Arc::new(AtomicUsize::new(0)); + let n_workers = core::cmp::min(ncpus, npoints); + let (tx, rx) = sync_channel(n_workers); + for _ in 0..n_workers { + let tx = tx.clone(); + let counter = counter.clone(); + + pool.joined_execute(move || { + let mut acc = <$point>::default(); + let mut tmp = <$point>::default(); + let mut first = true; + + loop { + let work = + counter.fetch_add(1, Ordering::Relaxed); + if work >= npoints { + break; + } + + unsafe { + $from_affine(&mut tmp, &self[work]); + let scalar = &scalars[nbytes * work]; + if first { + $mult(&mut acc, &tmp, scalar, nbits); + first = false; + } else { + $mult(&mut tmp, &tmp, scalar, nbits); + $add_or_double(&mut acc, &acc, &tmp); + } + } + } + + tx.send(acc).expect("disaster"); + }); + } + + let mut ret = rx.recv().expect("disaster"); + for _ in 1..n_workers { + let p = rx.recv().expect("disaster"); + unsafe { $add_or_double(&mut ret, &ret, &p) }; + } + + return ret; + } + + let (nx, ny, window) = + breakdown(nbits, pippenger_window_size(npoints), ncpus); + + // |grid[]| holds "coordinates" and place for result + let mut grid: Vec<(tile, Cell<$point>)> = + Vec::with_capacity(nx * ny); + #[allow(clippy::uninit_vec)] + unsafe { grid.set_len(grid.capacity()) }; + let dx = npoints / nx; + let mut y = window * (ny - 1); + let mut total = 0usize; + + while total < nx { + grid[total].0.x = total * dx; + grid[total].0.dx = dx; + grid[total].0.y = y; + grid[total].0.dy = nbits - y; + total += 1; + } + grid[total - 1].0.dx = npoints - grid[total - 1].0.x; + while y != 0 { + y -= window; + for i in 0..nx { + grid[total].0.x = grid[i].0.x; + grid[total].0.dx = grid[i].0.dx; + grid[total].0.y = y; + grid[total].0.dy = window; + total += 1; + } + } + let grid = &grid[..]; + + let points = &self[..]; + let sz = unsafe { $scratch_sizeof(0) / 8 }; + + let mut row_sync: Vec = Vec::with_capacity(ny); + row_sync.resize_with(ny, Default::default); + let row_sync = Arc::new(row_sync); + let counter = Arc::new(AtomicUsize::new(0)); + let n_workers = core::cmp::min(ncpus, total); + let (tx, rx) = sync_channel(n_workers); + for _ in 0..n_workers { + let tx = tx.clone(); + let counter = counter.clone(); + let row_sync = row_sync.clone(); + + pool.joined_execute(move || { + let mut scratch = vec![0u64; sz << (window - 1)]; + let mut p: [*const $point_affine; 2] = + [ptr::null(), ptr::null()]; + let mut s: [*const u8; 2] = [ptr::null(), ptr::null()]; + + loop { + let work = counter.fetch_add(1, Ordering::Relaxed); + if work >= total { + break; + } + let x = grid[work].0.x; + let y = grid[work].0.y; + + p[0] = &points[x]; + s[0] = &scalars[x * nbytes]; + unsafe { + $tile_mult( + grid[work].1.as_ptr(), + &p[0], + grid[work].0.dx, + &s[0], + nbits, + &mut scratch[0], + y, + window, + ); + } + if row_sync[y / window] + .fetch_add(1, Ordering::AcqRel) + == nx - 1 + { + tx.send(y).expect("disaster"); + } + } + }); + } + + let mut ret = <$point>::default(); + let mut rows = vec![false; ny]; + let mut row = 0usize; + for _ in 0..ny { + let mut y = rx.recv().unwrap(); + rows[y / window] = true; + while grid[row].0.y == y { + while row < total && grid[row].0.y == y { + unsafe { + $add_or_double( + &mut ret, + &ret, + grid[row].1.as_ptr(), + ); + } + row += 1; + } + if y == 0 { + break; + } + for _ in 0..window { + unsafe { $double(&mut ret, &ret) }; + } + y -= window; + if !rows[y / window] { + break; + } + } + } + ret + } + + fn add(&self) -> $point { + let npoints = self.len(); + + let pool = mt::da_pool(); + let ncpus = pool.max_count(); + if ncpus < 2 || npoints < 384 { + let p: [*const _; 2] = [&self[0], ptr::null()]; + let mut ret = <$point>::default(); + unsafe { $add(&mut ret, &p[0], npoints) }; + return ret; + } + + let counter = Arc::new(AtomicUsize::new(0)); + let nchunks = (npoints + 255) / 256; + let chunk = npoints / nchunks + 1; + let n_workers = core::cmp::min(ncpus, nchunks); + let (tx, rx) = sync_channel(n_workers); + for _ in 0..n_workers { + let tx = tx.clone(); + let counter = counter.clone(); + + pool.joined_execute(move || { + let mut acc = <$point>::default(); + let mut chunk = chunk; + let mut p: [*const _; 2] = [ptr::null(), ptr::null()]; + + loop { + let work = + counter.fetch_add(chunk, Ordering::Relaxed); + if work >= npoints { + break; + } + p[0] = &self[work]; + if work + chunk > npoints { + chunk = npoints - work; + } + unsafe { + let mut t = MaybeUninit::<$point>::uninit(); + $add(t.as_mut_ptr(), &p[0], chunk); + $add_or_double(&mut acc, &acc, t.as_ptr()); + }; + } + tx.send(acc).expect("disaster"); + }); + } + + let mut ret = rx.recv().unwrap(); + for _ in 1..n_workers { + unsafe { + $add_or_double(&mut ret, &ret, &rx.recv().unwrap()) + }; + } + + ret + } + + fn validate(&self) -> Result<(), BLST_ERROR> { + fn check(point: &$point_affine) -> Result<(), BLST_ERROR> { + if unsafe { $is_inf(point) } { + return Err(BLST_ERROR::BLST_PK_IS_INFINITY); + } + if !unsafe { $in_group(point) } { + return Err(BLST_ERROR::BLST_POINT_NOT_IN_GROUP); + } + Ok(()) + } + + let npoints = self.len(); + + let pool = mt::da_pool(); + let n_workers = core::cmp::min(npoints, pool.max_count()); + if n_workers < 2 { + for i in 0..npoints { + check(&self[i])? + } + return Ok(()) + } + + let counter = Arc::new(AtomicUsize::new(0)); + let valid = Arc::new(AtomicBool::new(true)); + let wg = + Arc::new((Barrier::new(2), AtomicUsize::new(n_workers))); + + for _ in 0..n_workers { + let counter = counter.clone(); + let valid = valid.clone(); + let wg = wg.clone(); + + pool.joined_execute(move || { + while valid.load(Ordering::Relaxed) { + let work = counter.fetch_add(1, Ordering::Relaxed); + if work >= npoints { + break; + } + + if check(&self[work]).is_err() { + valid.store(false, Ordering::Relaxed); + break; + } + } + + if wg.1.fetch_sub(1, Ordering::AcqRel) == 1 { + wg.0.wait(); + } + }); + } + + wg.0.wait(); + + if valid.load(Ordering::Relaxed) { + return Ok(()); + } else { + return Err(BLST_ERROR::BLST_POINT_NOT_IN_GROUP); + } + } + } + + #[cfg(test)] + pippenger_test_mod!( + $test_mod, + $points, + $point, + $add_or_double, + $generator, + $mult, + ); + }; +} + +#[cfg(test)] +include!("pippenger-test_mod.rs"); + +pippenger_mult_impl!( + p1_affines, + blst_p1, + blst_p1_affine, + blst_p1s_to_affine, + blst_p1s_mult_pippenger_scratch_sizeof, + blst_p1s_mult_pippenger, + blst_p1s_tile_pippenger, + blst_p1_add_or_double, + blst_p1_double, + p1_multi_point, + blst_p1_generator, + blst_p1_mult, + blst_p1s_add, + blst_p1_affine_is_inf, + blst_p1_affine_in_g1, + blst_p1_from_affine, +); + +pippenger_mult_impl!( + p2_affines, + blst_p2, + blst_p2_affine, + blst_p2s_to_affine, + blst_p2s_mult_pippenger_scratch_sizeof, + blst_p2s_mult_pippenger, + blst_p2s_tile_pippenger, + blst_p2_add_or_double, + blst_p2_double, + p2_multi_point, + blst_p2_generator, + blst_p2_mult, + blst_p2s_add, + blst_p2_affine_is_inf, + blst_p2_affine_in_g2, + blst_p2_from_affine, +); + +fn num_bits(l: usize) -> usize { + 8 * core::mem::size_of_val(&l) - l.leading_zeros() as usize +} + +fn breakdown( + nbits: usize, + window: usize, + ncpus: usize, +) -> (usize, usize, usize) { + let mut nx: usize; + let mut wnd: usize; + + if nbits > window * ncpus { + nx = 1; + wnd = num_bits(ncpus / 4); + if (window + wnd) > 18 { + wnd = window - wnd; + } else { + wnd = (nbits / window + ncpus - 1) / ncpus; + if (nbits / (window + 1) + ncpus - 1) / ncpus < wnd { + wnd = window + 1; + } else { + wnd = window; + } + } + } else { + nx = 2; + wnd = window - 2; + while (nbits / wnd + 1) * nx < ncpus { + nx += 1; + wnd = window - num_bits(3 * nx / 2); + } + nx -= 1; + wnd = window - num_bits(3 * nx / 2); + } + let ny = nbits / wnd + 1; + wnd = nbits / ny + 1; + + (nx, ny, wnd) +} + +fn pippenger_window_size(npoints: usize) -> usize { + let wbits = num_bits(npoints); + + if wbits > 13 { + return wbits - 4; + } + if wbits > 5 { + return wbits - 3; + } + 2 +} diff --git a/crates/chia-bls/Cargo.toml b/crates/chia-bls/Cargo.toml index 266d7bf32..8ad2814a3 100644 --- a/crates/chia-bls/Cargo.toml +++ b/crates/chia-bls/Cargo.toml @@ -15,6 +15,8 @@ workspace = true py-bindings = ["dep:pyo3", "dep:chia_py_streamable_macro", "chia-traits/py-bindings"] arbitrary = ["dep:arbitrary"] serde = ["dep:serde", "dep:chia-serde"] +# Use VROOM for pairing (AVX512 RNS) where supported; fall back to standard blst otherwise +vroom = ["blst/vroom"] [dependencies] chia-traits = { workspace = true } @@ -22,7 +24,7 @@ chia-sha2 = { workspace = true } chia_py_streamable_macro = { workspace = true, optional = true } sha2 = { workspace = true } hkdf = { workspace = true } -blst = { workspace = true } +blst = { workspace = true, features = ["portable"] } hex = { workspace = true } thiserror = { workspace = true } pyo3 = { workspace = true, features = ["multiple-pymethods"], optional = true } diff --git a/crates/chia-bls/src/signature.rs b/crates/chia-bls/src/signature.rs index 5003e064d..e206d2f16 100644 --- a/crates/chia-bls/src/signature.rs +++ b/crates/chia-bls/src/signature.rs @@ -116,6 +116,8 @@ impl Signature { } } + /// Single pairing e(sig, pk) in GT. When the `vroom` feature is enabled, + /// uses VROOM's pairing implementation (AVX512 RNS optimizations). pub fn pair(&self, other: &PublicKey) -> GTElement { let ans = unsafe { let mut ans = MaybeUninit::::uninit(); @@ -125,6 +127,7 @@ impl Signature { blst_p1_to_affine(aff1.as_mut_ptr(), &raw const other.0); blst_p2_to_affine(aff2.as_mut_ptr(), &raw const self.0); + // With feature "vroom", blst is built from vroom/blst/pairing.c so these are VROOM blst_miller_loop(ans.as_mut_ptr(), &aff2.assume_init(), &aff1.assume_init()); blst_final_exp(ans.as_mut_ptr(), ans.as_ptr()); ans.assume_init() diff --git a/vroom b/vroom new file mode 160000 index 000000000..31e98bbf7 --- /dev/null +++ b/vroom @@ -0,0 +1 @@ +Subproject commit 31e98bbf7f32e1345299d4276dd6a5bc44dc0b69