Skip to content

Commit

Permalink
Elaborate on panic conditions of map-to-curve; turn them into templates
Browse files Browse the repository at this point in the history
  • Loading branch information
jayz22 committed Sep 16, 2024
1 parent f5e68d4 commit a83cefb
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 85 deletions.
8 changes: 4 additions & 4 deletions soroban-env-host/benches/common/cost_types/bls12_381.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ impl HostCostMeasurement for Bls12381MapFpToG1Measure {

fn new_random_case(_host: &Host, rng: &mut StdRng, _input: u64) -> Bls12381MapFpToG1Sample {
let fp = Fq::rand(rng);
Bls12381MapFpToG1Sample(fp)
Bls12381MapFpToG1Sample(fp, Bls12381MapFpToG1)
}
}

Expand All @@ -242,7 +242,7 @@ impl HostCostMeasurement for Bls12381HashToG1Measure {
.to_vec();
let mut msg = vec![0u8; len as usize];
rng.fill(msg.as_mut_slice());
Bls12381HashToG1Sample(domain, msg)
Bls12381HashToG1Sample(domain, msg, Bls12381HashToG1)
}
}

Expand Down Expand Up @@ -308,7 +308,7 @@ impl HostCostMeasurement for Bls12381MapFp2ToG2Measure {

fn new_random_case(_host: &Host, rng: &mut StdRng, _input: u64) -> Bls12381MapFp2ToG2Sample {
let fp2 = Fq2::rand(rng);
Bls12381MapFp2ToG2Sample(fp2)
Bls12381MapFp2ToG2Sample(fp2, Bls12381MapFp2ToG2)
}
}

Expand All @@ -325,7 +325,7 @@ impl HostCostMeasurement for Bls12381HashToG2Measure {
.to_vec();
let mut msg = vec![0u8; len as usize];
rng.fill(msg.as_mut_slice());
Bls12381HashToG2Sample(domain, msg)
Bls12381HashToG2Sample(domain, msg, Bls12381HashToG2)
}
}

Expand Down
28 changes: 16 additions & 12 deletions soroban-env-host/src/cost_runner/cost_types/bls12_381.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ pub struct Bls12381G1MulSample(pub G1Affine, pub Fr);
#[derive(Clone)]
pub struct Bls12381G1MsmSample(pub Vec<G1Affine>, pub Vec<Fr>);
#[derive(Clone)]
pub struct Bls12381MapFpToG1Sample(pub Fq);
pub struct Bls12381MapFpToG1Sample(pub Fq, pub ContractCostType);
#[derive(Clone)]
pub struct Bls12381HashToG1Sample(pub Vec<u8>, pub Vec<u8>);
pub struct Bls12381HashToG1Sample(pub Vec<u8>, pub Vec<u8>, pub ContractCostType);
#[derive(Clone)]
pub struct Bls12381G2ProjectiveToAffineSample(pub G2Projective);
#[derive(Clone)]
Expand All @@ -65,9 +65,9 @@ pub struct Bls12381G2MulSample(pub G2Affine, pub Fr);
#[derive(Clone)]
pub struct Bls12381G2MsmSample(pub Vec<G2Affine>, pub Vec<Fr>);
#[derive(Clone)]
pub struct Bls12381MapFp2ToG2Sample(pub Fq2);
pub struct Bls12381MapFp2ToG2Sample(pub Fq2, pub ContractCostType);
#[derive(Clone)]
pub struct Bls12381HashToG2Sample(pub Vec<u8>, pub Vec<u8>);
pub struct Bls12381HashToG2Sample(pub Vec<u8>, pub Vec<u8>, pub ContractCostType);
#[derive(Clone)]
pub struct Bls12381PairingSample(pub Vec<G1Affine>, pub Vec<G2Affine>);
#[derive(Clone)]
Expand Down Expand Up @@ -122,10 +122,11 @@ impl_const_cost_runner_for_bls_consume_sample!(
impl_const_cost_runner_for_bls_consume_sample!(
Bls12381MapFpToG1Run,
Bls12381MapFpToG1,
map_fp_to_g1_internal,
map_to_curve,
Bls12381MapFpToG1Sample,
G1Affine,
fq
fq,
ty
);

impl_const_cost_runner_for_bls_consume_sample!(
Expand Down Expand Up @@ -157,10 +158,11 @@ impl_const_cost_runner_for_bls_consume_sample!(
impl_const_cost_runner_for_bls_consume_sample!(
Bls12381MapFp2ToG2Run,
Bls12381MapFp2ToG2,
map_fp2_to_g2_internal,
map_to_curve,
Bls12381MapFp2ToG2Sample,
G2Affine,
fq2
fq2,
ty
);
impl_const_cost_runner_for_bls_consume_sample!(
Bls12381FrFromU256Run,
Expand All @@ -182,20 +184,22 @@ impl_const_cost_runner_for_bls_consume_sample!(
impl_lin_cost_runner_for_bls_deref_sample!(
Bls12381HashToG1Run,
Bls12381HashToG1,
hash_to_g1_internal,
hash_to_curve,
Bls12381HashToG1Sample,
G1Affine,
domain,
msg
msg,
ty
);
impl_lin_cost_runner_for_bls_deref_sample!(
Bls12381HashToG2Run,
Bls12381HashToG2,
hash_to_g2_internal,
hash_to_curve,
Bls12381HashToG2Sample,
G2Affine,
domain,
msg
msg,
ty
);

impl_lin_cost_runner_for_bls_deref_sample!(
Expand Down
143 changes: 79 additions & 64 deletions soroban-env-host/src/crypto/bls12_381.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@ use crate::{
U256Small, U256Val, Val, VecObject, U256,
};
use ark_bls12_381::{
g1, g2, Bls12_381, Fq, Fq12, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective,
Bls12_381, Fq, Fq12, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective,
};
use ark_ec::{
hashing::{
curve_maps::wb::WBMap,
curve_maps::wb::{WBConfig, WBMap},
map_to_curve_hasher::{MapToCurve, MapToCurveBasedHasher},
HashToCurve,
},
pairing::{Pairing, PairingOutput},
scalar_mul::variable_base::VariableBaseMSM,
short_weierstrass::{Affine, Projective, SWCurveConfig},
CurveGroup,
}, pairing::{Pairing, PairingOutput}, scalar_mul::variable_base::VariableBaseMSM, short_weierstrass::{Affine, Projective, SWCurveConfig}, AffineRepr, CurveGroup
};
use ark_ff::{field_hashers::DefaultFieldHasher, BigInteger, Field, PrimeField};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
Expand Down Expand Up @@ -475,55 +471,6 @@ impl Host {
Ok(G1Projective::msm_unchecked(points, scalars))
}

pub(crate) fn map_fp_to_g1_internal(&self, fp: Fq) -> Result<G1Affine, HostError> {
self.charge_budget(ContractCostType::Bls12381MapFpToG1, None)?;
let mapper = WBMap::<g1::Config>::new().map_err(|e| {
self.err(
ScErrorType::Crypto,
ScErrorCode::InternalError,
format!("hash-to-curve error {e}").as_str(),
&[],
)
})?;
mapper.map_to_curve(fp).map_err(|e| {
self.err(
ScErrorType::Crypto,
ScErrorCode::InternalError,
format!("hash-to-curve error {e}").as_str(),
&[],
)
})
}

pub(crate) fn hash_to_g1_internal(
&self,
domain: &[u8],
msg: &[u8],
) -> Result<G1Affine, HostError> {
self.charge_budget(ContractCostType::Bls12381HashToG1, Some(msg.len() as u64))?;
let g1_mapper = MapToCurveBasedHasher::<
Projective<g1::Config>,
DefaultFieldHasher<Sha256, 128>,
WBMap<g1::Config>,
>::new(domain)
.map_err(|e| {
self.err(
ScErrorType::Crypto,
ScErrorCode::InternalError,
format!("hash-to-curve error {e}").as_str(),
&[],
)
})?;
g1_mapper.hash(msg.as_ref()).map_err(|e| {
self.err(
ScErrorType::Crypto,
ScErrorCode::InternalError,
format!("hash-to-curve error {e}").as_str(),
&[],
)
})
}

pub(crate) fn g2_vec_from_vecobj(&self, vp: VecObject) -> Result<Vec<G2Affine>, HostError> {
let len: u32 = self.vec_len(vp)?.into();
self.charge_budget(
Expand Down Expand Up @@ -580,16 +527,48 @@ impl Host {
Ok(G2Projective::msm_unchecked(points, scalars))
}

pub(crate) fn map_fp2_to_g2_internal(&self, fp: Fq2) -> Result<G2Affine, HostError> {
self.charge_budget(ContractCostType::Bls12381MapFp2ToG2, None)?;
let mapper = WBMap::<g2::Config>::new().map_err(|e| {
pub(crate) fn map_to_curve<P: WBConfig>(&self, fp: <Affine<P> as AffineRepr>::BaseField, ty: ContractCostType) -> Result<Affine<P>, HostError> {
self.charge_budget(ty, None)?;

// The `WBMap<g2::Config>::new()` first calls
// `P::ISOGENY_MAP.apply(GENERATOR)` which returns error if the result
// point is not on curve. This should not happen if the map constants
// have been correctly defined. otherwise it would be an internal error
// since it's a bug in the library implementation.
//
// Then it returns `WBMap`, which wraps a `SWUMap<P>` where P is the
// `ark_bls12_381::curves::g2_swu_iso::SwuIsoConfig`.
//
// Potential panic condition: `SWUMap::new().unwrap()`
//
// The `SWUMap::new()` function performs some validation on the static
// parameters `ZETA`, `COEFF_A`, `COEFF_B`, all of which are statically
// defined in `ark_bls12_381::curves::g1_swu_iso` and `g2_swu_iso`.
// Realistically this panic cannot occur, otherwise it will panic every
// time including during tests
let mapper = WBMap::<P>::new().map_err(|e| {
self.err(
ScErrorType::Crypto,
ScErrorCode::InternalError,
format!("hash-to-curve error {e}").as_str(),
&[],
)
})?;

// The `SWUMap::map_to_curve` function contains several panic conditions
// 1. assert!(!div3.is_zero())
// 2. gx1.sqrt().expect()
// 3. zeta_gx1.sqrt().expect()
// 4. assert!(point_on_curve.is_on_curve())
//
// While all of these should theoretically just be debug assertions that
// can't happen if the map parameters are correctly defined (several of
// these have recently been downgraded to debug_assert, e.g see
// https://github.com/arkworks-rs/algebra/pull/659#discussion_r1450808159),
// we cannot guaruantee with 100% confidence these panics will never
// happen.
//
// Otherwise, this function should never Err.
mapper.map_to_curve(fp).map_err(|e| {
self.err(
ScErrorType::Crypto,
Expand All @@ -600,16 +579,24 @@ impl Host {
})
}

pub(crate) fn hash_to_g2_internal(
pub(crate) fn hash_to_curve<P: WBConfig>(
&self,
domain: &[u8],
msg: &[u8],
) -> Result<G2Affine, HostError> {
self.charge_budget(ContractCostType::Bls12381HashToG2, Some(msg.len() as u64))?;
ty: &ContractCostType
) -> Result<Affine<P>, HostError> {
self.charge_budget(*ty, Some(msg.len() as u64))?;

// The `new` function here constructs a DefaultFieldHasher and a WBMap.
// - The DefaultFieldHasher::new() function creates an ExpanderXmd with
// Sha256. This cannot fail or panic.
// - Construction of WBMap follows the exact same analysis as map_to_curve
// function earlier.
// This function cannot realistically produce an error or panic.
let mapper = MapToCurveBasedHasher::<
Projective<g2::Config>,
Projective<P>,
DefaultFieldHasher<Sha256, 128>,
WBMap<g2::Config>,
WBMap<P>,
>::new(domain)
.map_err(|e| {
self.err(
Expand All @@ -619,6 +606,34 @@ impl Host {
&[],
)
})?;

// `ark_ec::hashing::map_to_curve_hasher::MapToCurveBasedHasher::hash`
// contains the following calls
// - `DefaultFieldHasher::hash_to_field`
// - `SWUMap::map_to_curve`
// - `clear_cofactor`. This cannot fail or panic.
//
// `hash_to_field` calls the ExpanderXmd::expand function, there are two
// assertions on the length of bytes produced by the hash function. Both
// of these cannot happen because the output size can be computed
// analytically. Let's use G2:
// - `block_size = 384 (Fp bit size) + 128 (security padding) / 8 = 64`
// - `len_in_bytes = 2 (number of elements to produce) * 2 (extention
// degree of Fp2) * 64 (block_size) = 256`
// - `ell = 256 (len_in_bytes) / 32 (sha256 output size) = 8`
//
// # Assertion #1. ell <= 255, which is saying the expander cannot expand
// up to a certain length. in our case ell == 8.
// # Assertion #2. len_in_bytes < 2^16, which is clearly true as well.
//
// The rest is just hashing, dividing bytes into element size, and
// producing field elements from bytes. None of these can panic or
// error.
//
// The only panic conditions we cannot 100% exclude comes from
// `map_to_curve`, see previous analysis.
//
// This function should not Err.
mapper.hash(msg.as_ref()).map_err(|e| {
self.err(
ScErrorType::Crypto,
Expand Down
10 changes: 5 additions & 5 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2978,7 +2978,7 @@ impl VmCallerEnv for Host {
fp: BytesObject,
) -> Result<BytesObject, HostError> {
let fp = self.fp_deserialize_from_bytesobj(fp)?;
let g1 = self.map_fp_to_g1_internal(fp)?;
let g1 = self.map_to_curve(fp, ContractCostType::Bls12381MapFpToG1)?;
self.g1_affine_serialize_uncompressed(g1)
}

Expand All @@ -3002,7 +3002,7 @@ impl VmCallerEnv for Host {
&[],
));
}
self.hash_to_g1_internal(dst.as_slice(), msg.as_slice())
self.hash_to_curve(dst.as_slice(), msg.as_slice(), &ContractCostType::Bls12381HashToG1)
})
})?;
self.g1_affine_serialize_uncompressed(g1)
Expand Down Expand Up @@ -3070,8 +3070,8 @@ impl VmCallerEnv for Host {
fp2: BytesObject,
) -> Result<BytesObject, HostError> {
let fp2 = self.fp2_deserialize_from_bytesobj(fp2)?;
let g2 = self.map_fp2_to_g2_internal(fp2)?;
self.g2_affine_serialize_uncompressed(g2)
let g2 = self.map_to_curve(fp2, ContractCostType::Bls12381MapFp2ToG2)?;
self.g2_affine_serialize_uncompressed(&g2)
}

fn bls12_381_hash_to_g2(
Expand All @@ -3094,7 +3094,7 @@ impl VmCallerEnv for Host {
&[],
));
}
self.hash_to_g2_internal(dst.as_slice(), msg.as_slice())
self.hash_to_curve(dst.as_slice(), msg.as_slice(), &ContractCostType::Bls12381HashToG2)
})
})?;
self.g2_affine_serialize_uncompressed(g2)
Expand Down

0 comments on commit a83cefb

Please sign in to comment.