Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions bindings/rust/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn assembly(file_vec: &mut Vec<PathBuf>, 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"
);
Expand All @@ -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\"");
Expand Down Expand Up @@ -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);
Expand Down
82 changes: 77 additions & 5 deletions bindings/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should define features in Cargo.toml. Even though it will work like that, it is not easily discoverable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a conscious choice. There is no reason to make it discoverable. If user attempts to compile on a bare-metal platform it's simply engaged in the build script.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why should blst be unlike any other ordinary Rust crate? There is an established pattern in the ecosystem how this things are done, what is the point of deviating from it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without having this feature defined in Cargo.toml and set by default you essentially compile no_std version for ALL PLATFORMS. Users will have to set std feature to avoid this, but due to "conscious choice" they will have a hard time knowing it exists in the first place.

You can probably define it from within build.rs but you've literally fighting the best practices in the ecosystem for the sake of confusing users.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you essentially compile no_std version for ALL PLATFORMS.

No. build.rs enables std feature on ALL PLATFORMS but bare-metal ones.

Users will have to set std feature to avoid this,

No. Users won't have to do anything. Nor would they have to do anything special when using blst as dependency on a bare-metal platform.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They may want to have no threads either because for their use case threading makes more sense on higher level

And they can do that. Nobody actually did to my knowledge, defaulting to multi-threading appears to be generally appreciated.

and blst would just cause quadratic number of threads only making things worse.

This is not true. blst uses a private fixed-sized threadpool.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blst is about what actually works, not what might work.

What I'm trying to say is blst can't possibly know and doesn't really need to know what works and what doesn't. I have given a few examples like support for threads where you think there isn't such support and forcing std on users that can, but don't want to depend on it. Both of those are not possible to achieve with current design of the library.

Simply offering two features enabled by default (std and threads) allows any imaginable supported platform will work under full control of the developer using the library.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And what I'm saying is that if we don't know something, we make no assumptions and don't really want users to make such assumptions either. [As effectively reflected in README.md.] But as already said, it's not right spot to discuss this. Why-not-this-way is off-topic in this specific context. Open an issue or choose another channel to communicate with supranational.

Copy link
Contributor

@huitseeker huitseeker Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They may want to have no threads either because for their use case threading makes more sense on higher level

And they can do that. Nobody actually did to my knowledge, defaulting to multi-threading appears to be generally appreciated.

For the record (just to volunteer relevant info, I don’t have a horse in this race), MystenLabs/fastcrypto#176

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking to replace manual thread pool management with rayon, which inherits thread pool from outer context and that way you can change number of threads from the outside (or not bother and use global default thread pool), but didn't have time to do the change and bench it.

#![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};

Expand All @@ -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;
Expand Down Expand Up @@ -57,7 +64,7 @@ mod mt {
}
}

#[cfg(feature = "no-threads")]
#[cfg(all(feature = "no-threads", feature = "std"))]
mod mt {
use super::*;

Expand Down Expand Up @@ -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::<blst_fp12>::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 {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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,
Expand All @@ -1028,6 +1090,7 @@ macro_rules! sig_variant_impl {
)
}

#[cfg(feature = "std")]
pub fn aggregate_verify(
&self,
sig_groupcheck: bool,
Expand Down Expand Up @@ -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,
Expand All @@ -1131,6 +1195,7 @@ macro_rules! sig_variant_impl {
)
}

#[cfg(feature = "std")]
pub fn fast_aggregate_verify_pre_aggregated(
&self,
sig_groupcheck: bool,
Expand All @@ -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],
Expand Down Expand Up @@ -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,
Expand All @@ -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_";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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::*;
Expand Down
146 changes: 146 additions & 0 deletions bindings/rust/src/pippenger-no_std.rs
Original file line number Diff line number Diff line change
@@ -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<I: SliceIndex<[$point_affine]>> Index<I> for $points {
type Output = I::Output;

#[inline]
fn index(&self, i: I) -> &Self::Output {
&self.points[i]
}
}
impl<I: SliceIndex<[$point_affine]>> IndexMut<I> 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<u64> =
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,
);
Loading