From 274db73a13facf0cfd8044462a86e8d51784b304 Mon Sep 17 00:00:00 2001 From: Jay Geng Date: Mon, 16 Sep 2024 10:48:07 -0400 Subject: [PATCH] turn some more deserialization functions into generic --- soroban-env-host/src/crypto/bls12_381.rs | 195 ++++++++++------------- 1 file changed, 83 insertions(+), 112 deletions(-) diff --git a/soroban-env-host/src/crypto/bls12_381.rs b/soroban-env-host/src/crypto/bls12_381.rs index c6554c698..23f83da26 100644 --- a/soroban-env-host/src/crypto/bls12_381.rs +++ b/soroban-env-host/src/crypto/bls12_381.rs @@ -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::{ @@ -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( &self, bo: BytesObject, + ct_curve: ContractCostType, subgroup_check: bool, - ) -> Result { - let msg: &str = "G1"; - let pt: G1Affine = self.visit_obj(bo, |bytes: &ScBytes| { - self.validate_point_encoding::(&bytes, msg)?; - // `CanonicalDeserialize` of `Affine

` 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, HostError> { + let pt: Affine

= self.visit_obj(bo, |bytes: &ScBytes| { + self.validate_point_encoding::(&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, @@ -174,7 +160,7 @@ impl Host { if subgroup_check && !self.check_point_is_in_subgroup( &pt, - &ContractCostType::Bls12381G1CheckPointInSubgroup, + &ct_subgroup, )? { return Err(self.err( @@ -187,55 +173,55 @@ impl Host { Ok(pt) } + pub(crate) fn g1_affine_deserialize_from_bytesobj( + &self, + bo: BytesObject, + subgroup_check: bool, + ) -> Result { + // `CanonicalDeserialize` of `Affine

` 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::(bo, ContractCostType::Bls12381G1CheckPointOnCurve, subgroup_check, ContractCostType::Bls12381G1CheckPointInSubgroup, "G1") + } + pub(crate) fn g2_affine_deserialize_from_bytesobj( &self, bo: BytesObject, subgroup_check: bool, ) -> Result { - let msg: &str = "G2"; - let pt: G2Affine = self.visit_obj(bo, |bytes: &ScBytes| { - self.validate_point_encoding::(&bytes, msg)?; - // `CanonicalDeserialize` of `Affine

` 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

` 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::(bo, ContractCostType::Bls12381G2CheckPointOnCurve, subgroup_check, ContractCostType::Bls12381G2CheckPointInSubgroup, "G2") + } pub(crate) fn g1_projective_into_affine( @@ -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 { - let expected_size = FP_SERIALIZED_SIZE; + pub(crate) fn field_element_deserialize(&self, bo: BytesObject, units_of_fp: u64, msg: &str) -> Result { 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` 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 { + // `CanonicalDeserialize for Fp` 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::(bo, 1, "Fp") + } + pub(crate) fn fp2_deserialize_from_bytesobj(&self, bo: BytesObject) -> Result { - 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

` 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

` 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::(bo, 2, "Fp2") } pub(crate) fn g1_vec_from_vecobj(&self, vp: VecObject) -> Result, HostError> {