Skip to content

Commit

Permalink
turn some more deserialization functions into generic
Browse files Browse the repository at this point in the history
  • Loading branch information
jayz22 committed Sep 16, 2024
1 parent 191abe8 commit 274db73
Showing 1 changed file with 83 additions and 112 deletions.
195 changes: 83 additions & 112 deletions soroban-env-host/src/crypto/bls12_381.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
};
use ark_bls12_381::{
Bls12_381, Fq, Fq12, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective,
g2::Config as G2Config, g1::Config as G1Config,
};
use ark_ec::{
hashing::{
Expand Down Expand Up @@ -136,34 +137,19 @@ impl Host {
Ok(pt.is_in_correct_subgroup_assuming_on_curve())
}

pub(crate) fn g1_affine_deserialize_from_bytesobj(
pub(crate) fn affine_deserialize<const EXPECTED_SIZE: usize, P: SWCurveConfig>(
&self,
bo: BytesObject,
ct_curve: ContractCostType,
subgroup_check: bool,
) -> Result<G1Affine, HostError> {
let msg: &str = "G1";
let pt: G1Affine = self.visit_obj(bo, |bytes: &ScBytes| {
self.validate_point_encoding::<G1_SERIALIZED_SIZE>(&bytes, msg)?;
// `CanonicalDeserialize` of `Affine<P>` calls into
// `P::deserialize_with_mode`, where `P` is `arc_bls12_381::g1::Config`, the
// core logic is in `arc_bls12_381::curves::util::read_g1_uncompressed`.
//
// The `arc_bls12_381` lib already expects the input to be serialized in
// big-endian order (aligning with the common standard and contrary
// to ark::serialize's convention),
//
// i.e. `input = be_bytes(X) || be_bytes(Y)` and the
// most-significant three bits of X are flags:
//
// `bits(X) = [compression_flag, infinity_flag, sort_flag, bit_3, .. bit_383]`
//
// internally when deserializing `Fp`, the flag bits are masked off
// to get `X: Fp`. The Y however, does not have the top bits masked off
// so it is possible for Y to exceed 381 bits. I've checked all over and
// didn't find that being an invalid condition, so we will leave them as is.
self.deserialize_uncompessed_no_validate(&bytes, 2, msg)
ct_subgroup: ContractCostType,
msg: &str
) -> Result<Affine<P>, HostError> {
let pt: Affine<P> = self.visit_obj(bo, |bytes: &ScBytes| {
self.validate_point_encoding::<EXPECTED_SIZE>(&bytes, msg)?;
self.deserialize_uncompessed_no_validate(&bytes, 4, msg)
})?;
if !self.check_point_is_on_curve(&pt, &ContractCostType::Bls12381G1CheckPointOnCurve)? {
if !self.check_point_is_on_curve(&pt, &ct_curve)? {
return Err(self.err(
ScErrorType::Crypto,
ScErrorCode::InvalidInput,
Expand All @@ -174,7 +160,7 @@ impl Host {
if subgroup_check
&& !self.check_point_is_in_subgroup(
&pt,
&ContractCostType::Bls12381G1CheckPointInSubgroup,
&ct_subgroup,
)?
{
return Err(self.err(
Expand All @@ -187,55 +173,55 @@ impl Host {
Ok(pt)
}

pub(crate) fn g1_affine_deserialize_from_bytesobj(
&self,
bo: BytesObject,
subgroup_check: bool,
) -> Result<G1Affine, HostError> {
// `CanonicalDeserialize` of `Affine<P>` calls into
// `P::deserialize_with_mode`, where `P` is `arc_bls12_381::g1::Config`, the
// core logic is in `arc_bls12_381::curves::util::read_g1_uncompressed`.
//
// The `arc_bls12_381` lib already expects the input to be serialized in
// big-endian order (aligning with the common standard and contrary
// to ark::serialize's convention),
//
// i.e. `input = be_bytes(X) || be_bytes(Y)` and the
// most-significant three bits of X are flags:
//
// `bits(X) = [compression_flag, infinity_flag, sort_flag, bit_3, .. bit_383]`
//
// internally when deserializing `Fp`, the flag bits are masked off
// to get `X: Fp`. The Y however, does not have the top bits masked off
// so it is possible for Y to exceed 381 bits. I've checked all over and
// didn't find that being an invalid condition, so we will leave them as is.
self.affine_deserialize::<G1_SERIALIZED_SIZE, G1Config>(bo, ContractCostType::Bls12381G1CheckPointOnCurve, subgroup_check, ContractCostType::Bls12381G1CheckPointInSubgroup, "G1")
}

pub(crate) fn g2_affine_deserialize_from_bytesobj(
&self,
bo: BytesObject,
subgroup_check: bool,
) -> Result<G2Affine, HostError> {
let msg: &str = "G2";
let pt: G2Affine = self.visit_obj(bo, |bytes: &ScBytes| {
self.validate_point_encoding::<G2_SERIALIZED_SIZE>(&bytes, msg)?;
// `CanonicalDeserialize` of `Affine<P>` calls into
// `P::deserialize_with_mode`, where `P` is `arc_bls12_381::g2::Config`, the
// core logic is in `arc_bls12_381::curves::util::read_g2_uncompressed`.
//
// The `arc_bls12_381` lib already expects the input to be serialized in
// big-endian order (aligning with the common standard and contrary
// to ark::serialize's convention),
//
// i.e. `input = be_bytes(X) || be_bytes(Y)` and the
// most-significant three bits of X are flags:
//
// `bits(X) = [compression_flag, infinity_flag, sort_flag, bit_3, .. bit_383]`
//
// internally when deserializing `Fp`, the flag bits are masked off
// to get `X: Fp`. The Y however, does not have the top bits masked off
// so it is possible for Y to exceed 381 bits. I've checked all over and
// didn't find that being an invalid condition, so we will leave them as is.
self.deserialize_uncompessed_no_validate(&bytes, 4, msg)
})?;
if !self.check_point_is_on_curve(&pt, &ContractCostType::Bls12381G2CheckPointOnCurve)? {
return Err(self.err(
ScErrorType::Crypto,
ScErrorCode::InvalidInput,
format!("bls12-381 {}: point not on curve", msg).as_str(),
&[],
));
}
if subgroup_check
&& !self.check_point_is_in_subgroup(
&pt,
&ContractCostType::Bls12381G2CheckPointInSubgroup,
)?
{
return Err(self.err(
ScErrorType::Crypto,
ScErrorCode::InvalidInput,
format!("bls12-381 {}: point not in the correct subgroup", msg).as_str(),
&[],
));
}
Ok(pt)
// `CanonicalDeserialize` of `Affine<P>` calls into
// `P::deserialize_with_mode`, where `P` is `arc_bls12_381::g2::Config`, the
// core logic is in `arc_bls12_381::curves::util::read_g2_uncompressed`.
//
// The `arc_bls12_381` lib already expects the input to be serialized in
// big-endian order (aligning with the common standard and contrary
// to ark::serialize's convention),
//
// i.e. `input = be_bytes(X) || be_bytes(Y)` and the
// most-significant three bits of X are flags:
//
// `bits(X) = [compression_flag, infinity_flag, sort_flag, bit_3, .. bit_383]`
//
// internally when deserializing `Fp`, the flag bits are masked off
// to get `X: Fp`. The Y however, does not have the top bits masked off
// so it is possible for Y to exceed 381 bits. I've checked all over and
// didn't find that being an invalid condition, so we will leave them as is.
self.affine_deserialize::<G2_SERIALIZED_SIZE, G2Config>(bo, ContractCostType::Bls12381G2CheckPointOnCurve, subgroup_check, ContractCostType::Bls12381G2CheckPointInSubgroup, "G2")

}

pub(crate) fn g1_projective_into_affine(
Expand Down Expand Up @@ -336,64 +322,49 @@ impl Host {
self.map_err(U256Val::try_from_val(self, &u))
}

pub(crate) fn fp_deserialize_from_bytesobj(&self, bo: BytesObject) -> Result<Fq, HostError> {
let expected_size = FP_SERIALIZED_SIZE;
pub(crate) fn field_element_deserialize<const EXPECTED_SIZE: usize, T: CanonicalDeserialize>(&self, bo: BytesObject, units_of_fp: u64, msg: &str) -> Result<T, HostError> {
self.visit_obj(bo, |bytes: &ScBytes| {
if bytes.len() != expected_size {
if bytes.len() != EXPECTED_SIZE {
return Err(self.err(
ScErrorType::Crypto,
ScErrorCode::InvalidInput,
"bls12-381 field element (Fp): invalid input length to deserialize",
format!("bls12-381 field element {}: invalid input length to deserialize", msg).as_str(),
&[
Val::from_u32(bytes.len() as u32).into(),
Val::from_u32(expected_size as u32).into(),
Val::from_u32(EXPECTED_SIZE as u32).into(),
],
));
}
// `CanonicalDeserialize for Fp<P, N>` assumes input bytes in
// little-endian order, with the highest bits being empty flags.
// thus we must first reverse the bytes before passing them in.
// there is no other check for Fp besides the length check.
self.charge_budget(ContractCostType::MemCpy, Some(FP_SERIALIZED_SIZE as u64))?;
let mut buf = [0u8; FP_SERIALIZED_SIZE];
self.charge_budget(ContractCostType::MemCpy, Some(EXPECTED_SIZE as u64))?;
let mut buf = [0u8; EXPECTED_SIZE];
buf.copy_from_slice(bytes);
buf.reverse();
self.deserialize_uncompessed_no_validate(&buf, 1, "Fp")
self.deserialize_uncompessed_no_validate(&buf, units_of_fp, msg)
})
}

pub(crate) fn fp_deserialize_from_bytesobj(&self, bo: BytesObject) -> Result<Fq, HostError> {
// `CanonicalDeserialize for Fp<P, N>` assumes input bytes in
// little-endian order, with the highest bits being empty flags.
// thus we must first reverse the bytes before passing them in.
// there is no other check for Fp besides the length check.
self.field_element_deserialize::<FP_SERIALIZED_SIZE, Fq>(bo, 1, "Fp")
}

pub(crate) fn fp2_deserialize_from_bytesobj(&self, bo: BytesObject) -> Result<Fq2, HostError> {
let expected_size = FP2_SERIALIZED_SIZE;
self.visit_obj(bo, |bytes: &ScBytes| {
if bytes.len() != expected_size {
return Err(self.err(
ScErrorType::Crypto,
ScErrorCode::InvalidInput,
"bls12-381 quadradic extension field element (Fp2): invalid input length to deserialize",
&[
Val::from_u32(bytes.len() as u32).into(),
Val::from_u32(expected_size as u32).into(),
],
));
}
// `CanonicalDeserialize for QuadExtField<P>` reads the first chunk,
// deserialize it into `Fp` as c0. Then repeat for c1. The
// deserialization for `Fp` follows same rules as above, where the
// bytes are expected in little-endian, with the highest bits being
// empty flags. There is no check involved.
//
// This is entirely reversed from the [standard](https://github.com/zcash/librustzcash/blob/6e0364cd42a2b3d2b958a54771ef51a8db79dd29/pairing/src/bls12_381/README.md#serialization)
// we have adopted. This is the input format we provide:
//
// `input = be_bytes(c1) || be_bytes(c0)`
//
// So we just need to reverse our input.
self.charge_budget(ContractCostType::MemCpy, Some(FP2_SERIALIZED_SIZE as u64))?;
let mut buf = [0u8; FP2_SERIALIZED_SIZE];
buf.copy_from_slice(&bytes);
buf.reverse();
self.deserialize_uncompessed_no_validate(&buf, 2, "Fp2")
})
// `CanonicalDeserialize for QuadExtField<P>` reads the first chunk,
// deserialize it into `Fp` as c0. Then repeat for c1. The
// deserialization for `Fp` follows same rules as above, where the
// bytes are expected in little-endian, with the highest bits being
// empty flags. There is no check involved.
//
// This is entirely reversed from the [standard](https://github.com/zcash/librustzcash/blob/6e0364cd42a2b3d2b958a54771ef51a8db79dd29/pairing/src/bls12_381/README.md#serialization)
// we have adopted. This is the input format we provide:
//
// `input = be_bytes(c1) || be_bytes(c0)`
//
// So we just need to reverse our input.
self.field_element_deserialize::<FP2_SERIALIZED_SIZE, Fq2>(bo, 2, "Fp2")
}

pub(crate) fn g1_vec_from_vecobj(&self, vp: VecObject) -> Result<Vec<G1Affine>, HostError> {
Expand Down

0 comments on commit 274db73

Please sign in to comment.