This is a partial fork of the code from arkworks-rs/algebra and arkworks-rs/curves.
We fork the popular elliptic curves BLS12_381
, BLS12_377
, BW6_761
,
ED_ON_BLS12_381_BANDERSNATCH
and ED_ON_BLS12_377
in a way which allows
delegating some of the most computationally expensive operations to some user
defined hooks.
We also provide forks of the models BW6
and BLS12
to avoid the point
preparation before the hooks calls during pairing operations. Therefore, we
redefine the elliptic curve sub-groups G2
for both models as thin wrappers
around the affine points and move the point preparation procedure to the
user defined hook.
Be aware that, while in the hook context, any usage of functions which may re-enter into the same hook with the same value, may cause an infinite loop.
See Known Limitations sections.
The following usage example is extracted from the hooks provided by substrate-curves project.
The project provides a set of ready to use CurveHooks
implementations compatible with
Substrate
host functions to jump from wasm32 computational domain into the native host.
The motivation is:
- native target is typically more efficient that wasm32.
- wasm32 is single thread while in the native target we can hopefully leverage
the Arkworks
parallel
feature.
Note that Substrate elliptic curves host functions take and return raw byte arrays representing SCALE encoded values.
use ark_bls12_377_ext::CurveHooks;
use ark_ec::{pairing::Pairing, CurveConfig}
use ark_scale::{
ark_serialize::{Compress, Validate},
scale::{Decode, Encode},
};
use sp_crypto_ec_utils::bls12_377_ops;
const SCALE_USAGE: u8 = ark_scale::make_usage(Compress::No, Validate::No);
type ArkScale<T> = ark_scale::ArkScale<T, SCALE_USAGE>;
type ArkScaleProjective<T> = ark_scale::hazmat::ArkScaleProjective<T>;
#[derive(Copy, Clone)]
pub struct HostHooks;
type Bls12_377 = ark_bls12_377_ext::Bls12_377<HostHooks>;
type G1Affine = ark_bls12_377_ext::g1::G1Affine<HostHooks>;
type G1Config = ark_bls12_377_ext::g1::Config<HostHooks>;
type G2Affine = ark_bls12_377_ext::g2::G2Affine<HostHooks>;
type G2Config = ark_bls12_377_ext::g2::Config<HostHooks>;
impl CurveHooks for HostHooks {
fn bls12_377_multi_miller_loop(
g1: impl Iterator<Item = <Bls12_377 as Pairing>::G1Prepared>,
g2: impl Iterator<Item = <Bls12_377 as Pairing>::G2Prepared>,
) -> Result<<Bls12_377 as Pairing>::TargetField, ()> {
let g1 = ArkScale::from(g1.collect::<Vec<_>>()).encode();
let g2 = ArkScale::from(g2.collect::<Vec<_>>()).encode();
let res = bls12_377_ops::bls12_377_multi_miller_loop(g1, g2).unwrap_or_default();
let res = ArkScale::<<Bls12_377 as Pairing>::TargetField>::decode(&mut res.as_slice());
res.map(|v| v.0).map_err(|_| ())
}
fn bls12_377_final_exponentiation(
target: <Bls12_377 as Pairing>::TargetField,
) -> Result<<Bls12_377 as Pairing>::TargetField, ()> {
let target = ArkScale::from(target).encode();
let res = bls12_377_ops::bls12_377_final_exponentiation(target).unwrap_or_default();
let res = ArkScale::<<Bls12_377 as Pairing>::TargetField>::decode(&mut res.as_slice());
res.map(|v| v.0).map_err(|_| ())
}
fn bls12_377_msm_g1(
bases: &[G1Affine],
scalars: &[<G1Config as CurveConfig>::ScalarField],
) -> Result<G1Projective, ()> {
let bases = ArkScale::from(bases).encode();
let scalars = ArkScale::from(scalars).encode();
let res = bls12_377_ops::bls12_377_msm_g1(bases, scalars).unwrap_or_default();
let res = ArkScaleProjective::<G1Projective>::decode(&mut res.as_slice());
res.map(|v| v.0).map_err(|_| ())
}
fn bls12_377_msm_g2(
bases: &[G2Affine],
scalars: &[<G2Config as CurveConfig>::ScalarField],
) -> Result<G2Projective, ()> {
let bases = ArkScale::from(bases).encode();
let scalars = ArkScale::from(scalars).encode();
let res = bls12_377_ops::bls12_377_msm_g2(bases, scalars).unwrap_or_default();
let res = ArkScaleProjective::<G2Projective>::decode(&mut res.as_slice());
res.map(|v| v.0).map_err(|_| ())
}
fn bls12_377_mul_projective_g1(
base: &G1Projective,
scalar: &[u64],
) -> Result<G1Projective, ()> {
let base = ArkScaleProjective::from(base).encode();
let scalar = ArkScale::from(scalar).encode();
let res = bls12_377_ops::bls12_377_mul_projective_g1(base, scalar).unwrap_or_default();
let res = ArkScaleProjective::<G1Projective>::decode(&mut res.as_slice());
res.map(|v| v.0).map_err(|_| ())
}
fn bls12_377_mul_projective_g2(
base: &G2Projective,
scalar: &[u64],
) -> Result<G2Projective, ()> {
let base = ArkScaleProjective::from(base).encode();
let scalar = ArkScale::from(scalar).encode();
let res = bls12_377_ops::bls12_377_mul_projective_g2(base, scalar).unwrap_or_default();
let res = ArkScaleProjective::<G2Projective>::decode(&mut res.as_slice());
res.map(|v| v.0).map_err(|_| ())
}
}
For more working refer to Ark Substrate examples project.
We are aware of hooks re-entrancy issues when using point checked deserialization in projective multiplication hooks.
In particular, if you serialize and deserialize (with point checking) the input point in one of the projective multiplication hooks then we end up re-entering the multiplication hook with the same value as a consequence of the internally performed check.
The following invocation flow applies:
- Validation of deserialized value.
- Check if point is in the correct subgroup.
- Jump into the
TECurveConfig
for the check. - Calls the "custom" (defined by this crate) implementation of
mul_affine
which callsmul_projective
. - Goto 1
So pay special attention to the actions in your CurveHooks
implementations.
If you encounter any other way to trigger the open, please file an issue.