diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c098f46..e758c619 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,16 @@ jobs: echo '--- test serde-secret' echo cargo test --release --features=serde-secret + echo '--- test no_std' + echo + echo 'set -e' > ulimit-s + echo 'export RUST_MIN_STACK=$(($1 * 1024)); shift' >> ulimit-s + echo 'exec "$@"' >> ulimit-s + triple=`rustc -vV | awk '/host:/ {print $2}' | tr 'a-z-' 'A-Z_'` + stack_size=`[ $OSTYPE = "msys" ] && echo 65 || echo 56` + env BLST_TEST_NO_STD= \ + CARGO_TARGET_${triple}_RUNNER="bash ulimit-s $stack_size" \ + cargo test --release if [ `uname -s` = "Linux" ]; then echo '--- test wasm32-wasi' echo diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs index 94f18d5d..29fd2127 100644 --- a/bindings/rust/build.rs +++ b/bindings/rust/build.rs @@ -26,7 +26,7 @@ fn assembly(file_vec: &mut Vec, base_dir: &Path, _arch: &str) { } fn main() { - if let Some(_) = env::var_os("CARGO_FEATURE_SERDE_SECRET") { + if env::var("CARGO_FEATURE_SERDE_SECRET").is_ok() { println!( "cargo:warning=blst: non-production feature serde-secret enabled" ); @@ -35,8 +35,12 @@ fn main() { // account for cross-compilation [by examining environment variables] let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_no_std = target_os.eq("none") + || target_os.eq("unknown") + || target_os.eq("uefi") + || env::var("BLST_TEST_NO_STD").is_ok(); - if target_os.ne("none") && env::var("BLST_TEST_NO_STD").is_err() { + if !target_no_std { println!("cargo:rustc-cfg=feature=\"std\""); if target_arch.eq("wasm32") { println!("cargo:rustc-cfg=feature=\"no-threads\""); @@ -167,8 +171,11 @@ fn main() { .flag_if_supported("-fno-builtin") .flag_if_supported("-Wno-unused-function") .flag_if_supported("-Wno-unused-command-line-argument"); - if target_arch.eq("wasm32") { - cc.flag_if_supported("-ffreestanding"); + if target_arch.eq("wasm32") || target_no_std { + if env::var("CARGO_CFG_TARGET_ENV").unwrap().ne("msvc") { + cc.flag("-ffreestanding"); + } + cc.define("SCRATCH_LIMIT", "(45 * 1024)"); } if !cfg!(debug_assertions) { cc.opt_level(2); diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index fae679a4..1c3a83c9 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -2,17 +2,24 @@ // 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)] +extern crate alloc; + +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; use core::any::Any; use core::mem::MaybeUninit; use core::ptr; -use core::sync::atomic::*; -use std::sync::{mpsc::channel, Arc}; use zeroize::Zeroize; +#[cfg(feature = "std")] +use std::sync::{atomic::*, mpsc::channel, Arc}; + #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -22,7 +29,7 @@ trait ThreadPoolExt { F: FnOnce() + Send + 'any; } -#[cfg(not(feature = "no-threads"))] +#[cfg(all(not(feature = "no-threads"), feature = "std"))] mod mt { use super::*; use core::mem::transmute; @@ -57,7 +64,7 @@ mod mt { } } -#[cfg(feature = "no-threads")] +#[cfg(all(feature = "no-threads", feature = "std"))] mod mt { use super::*; @@ -148,6 +155,22 @@ impl blst_fp12 { } } + #[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 { @@ -473,6 +496,7 @@ pub fn uniq(msgs: &[&[u8]]) -> bool { true } +#[cfg(feature = "std")] pub fn print_bytes(bytes: &[u8], name: &str) { print!("{} ", name); for b in bytes.iter() { @@ -1009,6 +1033,44 @@ macro_rules! sig_variant_impl { Ok(sig) } + #[cfg(not(feature = "std"))] + pub fn verify( + &self, + sig_groupcheck: bool, + msg: &[u8], + dst: &[u8], + aug: &[u8], + pk: &PublicKey, + pk_validate: bool, + ) -> BLST_ERROR { + if sig_groupcheck { + match self.validate(false) { + Err(err) => return err, + _ => (), + } + } + if pk_validate { + match pk.validate() { + Err(err) => return err, + _ => (), + } + } + unsafe { + $verify( + &pk.point, + &self.point, + $hash_or_encode, + msg.as_ptr(), + msg.len(), + dst.as_ptr(), + dst.len(), + aug.as_ptr(), + aug.len(), + ) + } + } + + #[cfg(feature = "std")] pub fn verify( &self, sig_groupcheck: bool, @@ -1028,6 +1090,7 @@ macro_rules! sig_variant_impl { ) } + #[cfg(feature = "std")] pub fn aggregate_verify( &self, sig_groupcheck: bool, @@ -1110,6 +1173,7 @@ macro_rules! sig_variant_impl { // pks are assumed to be verified for proof of possession, // which implies that they are already group-checked + #[cfg(feature = "std")] pub fn fast_aggregate_verify( &self, sig_groupcheck: bool, @@ -1131,6 +1195,7 @@ macro_rules! sig_variant_impl { ) } + #[cfg(feature = "std")] pub fn fast_aggregate_verify_pre_aggregated( &self, sig_groupcheck: bool, @@ -1142,6 +1207,7 @@ macro_rules! sig_variant_impl { } // https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407 + #[cfg(feature = "std")] pub fn verify_multiple_aggregate_signatures( msgs: &[&[u8]], dst: &[u8], @@ -1467,7 +1533,7 @@ macro_rules! sig_variant_impl { } #[test] - fn test_sign() { + fn test_sign_n_verify() { let ikm: [u8; 32] = [ 0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a, 0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56, @@ -1487,6 +1553,7 @@ macro_rules! sig_variant_impl { } #[test] + #[cfg(feature = "std")] fn test_aggregate() { let num_msgs = 10; let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; @@ -1559,6 +1626,7 @@ macro_rules! sig_variant_impl { } #[test] + #[cfg(feature = "std")] fn test_multiple_agg_sigs() { let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; let num_pks_per_sig = 10; @@ -1900,8 +1968,12 @@ pub mod min_sig { ); } +#[cfg(feature = "std")] include!("pippenger.rs"); +#[cfg(not(feature = "std"))] +include!("pippenger-no_std.rs"); + #[cfg(test)] mod fp12_test { use super::*; diff --git a/bindings/rust/src/pippenger-no_std.rs b/bindings/rust/src/pippenger-no_std.rs new file mode 100644 index 00000000..42311b2a --- /dev/null +++ b/bindings/rust/src/pippenger-no_std.rs @@ -0,0 +1,146 @@ +// 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, + ) => { + 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 p: [*const $point; 2] = [&points[0], ptr::null()]; + unsafe { $to_affines(&mut ret.points[0], &p[0], npoints) }; + ret + } + + pub fn mult(&self, scalars: &[u8], nbits: usize) -> $point { + let npoints = self.points.len(); + let nbytes = (nbits + 7) / 8; + + if scalars.len() < nbytes * npoints { + panic!("scalars length mismatch"); + } + + let p: [*const $point_affine; 2] = + [&self.points[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); + scratch.set_len(scratch.capacity()); + $multi_scalar_mult( + &mut ret, + &p[0], + npoints, + &s[0], + nbits, + &mut scratch[0], + ); + } + ret + } + + pub fn add(&self) -> $point { + let npoints = self.points.len(); + + let p: [*const _; 2] = [&self.points[0], ptr::null()]; + let mut ret = <$point>::default(); + unsafe { $add(&mut ret, &p[0], npoints) }; + + ret + } + } + + #[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_scalar, + blst_p1_generator, + blst_p1_mult, + blst_p1s_add, +); + +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_scalar, + blst_p2_generator, + blst_p2_mult, + blst_p2s_add, +); diff --git a/bindings/rust/src/pippenger-test_mod.rs b/bindings/rust/src/pippenger-test_mod.rs new file mode 100644 index 00000000..3e6eb6da --- /dev/null +++ b/bindings/rust/src/pippenger-test_mod.rs @@ -0,0 +1,80 @@ +// 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); + } + } + + 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/bindings/rust/src/pippenger.rs b/bindings/rust/src/pippenger.rs index 30c2f9ef..50c8347b 100644 --- a/bindings/rust/src/pippenger.rs +++ b/bindings/rust/src/pippenger.rs @@ -319,74 +319,20 @@ macro_rules! pippenger_mult_impl { } #[cfg(test)] - 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); - } - } - - 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()); - } - } + 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,