From 17e2f6de40465093c632c49cf6a42ffd0919656b Mon Sep 17 00:00:00 2001 From: Plamen Hristov Date: Wed, 27 Dec 2023 22:33:03 +0200 Subject: [PATCH 1/5] Added add, mul and mulexp --- soroban-env-host/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/soroban-env-host/Cargo.toml b/soroban-env-host/Cargo.toml index 1cf4fd894..1bf101e3a 100644 --- a/soroban-env-host/Cargo.toml +++ b/soroban-env-host/Cargo.toml @@ -62,6 +62,7 @@ sha3 = "=0.10.8" # protocol release when we can safely bump the minimum curve25519-dalek version # in stellar-core as well. curve25519-dalek = { version = ">=4.1.1, <=4.1.2", default-features = false, features = ["digest"]} +blst = "0.3.11" [target.'cfg(not(target_family = "wasm"))'.dependencies] tracy-client = { version = "=0.15.2", features = ["enable", "timer-fallback"], default-features = false, optional = true } From ece960ee9ca59302438ec9446e30a48079aa8a21 Mon Sep 17 00:00:00 2001 From: Plamen Hristov Date: Wed, 10 Apr 2024 19:09:20 +0300 Subject: [PATCH 2/5] added all bls12-381 primitives --- Cargo.lock | 52 +++ soroban-env-common/env.json | 64 ++++ soroban-env-host/src/host.rs | 120 +++++++ soroban-env-host/src/host/bls.rs | 582 +++++++++++++++++++++++++++++++ soroban-env-host/src/test/bls.rs | 0 5 files changed, 818 insertions(+) create mode 100644 soroban-env-host/src/host/bls.rs create mode 100644 soroban-env-host/src/test/bls.rs diff --git a/Cargo.lock b/Cargo.lock index 70d2e55ff..2dc2dd13b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blst" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94087b935a822949d3291a9989ad2b2051ea141eda0fd4e478a75f6aa3e604b" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -548,6 +560,12 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "group" version = "0.13.0" @@ -898,6 +916,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "num_enum" version = "0.7.1" @@ -1402,6 +1430,7 @@ version = "21.0.0" dependencies = [ "arbitrary", "backtrace", + "blst", "bytes-lit", "curve25519-dalek", "ecdsa", @@ -1658,6 +1687,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.3.30" @@ -2087,3 +2125,17 @@ name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json index ec4355825..53af00b21 100644 --- a/soroban-env-common/env.json +++ b/soroban-env-common/env.json @@ -2022,6 +2022,70 @@ "return": "Void", "docs": "Verifies the `signature` using an ECDSA secp256r1 `public_key` on a 32-byte `msg_digest`. Warning: The `msg_digest` must be produced by a secure cryptographic hash function on the message, otherwise the attacker can potentially forge signatures. The `public_key` is expected to be 65 bytes in length, representing a SEC-1 encoded point in uncompressed format. The `signature` is the ECDSA signature `(r, s)` serialized as fixed-size big endian scalar values, both `r`, `s` must be non-zero and `s` must be in the lower range. ", "min_supported_protocol": 21 + }, + { + "export": "3", + "name": "bls_g1_add", + "args": [ + { + "name": "point1", + "type": "BytesObject" + }, + { + "name": "point2", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Adds two BLS12-381 G1 points given in bytes format and returns the resulting G1 point in bytes format." + }, + { + "export": "4", + "name": "bls_g1_mul", + "args": [ + { + "name": "point", + "type": "BytesObject" + }, + { + "name": "scalar", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Multiplies a BLS12-381 G1 point by a scalar, both given in bytes format, and returns the resulting G1 point in bytes format." + }, + { + "export": "5", + "name": "bls_g2_add", + "args": [ + { + "name": "point1", + "type": "BytesObject" + }, + { + "name": "point2", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Adds two BLS12-381 G2 points given in bytes format and returns the resulting G2 point in bytes format." + }, + { + "export": "6", + "name": "bls_g2_mul", + "args": [ + { + "name": "point", + "type": "BytesObject" + }, + { + "name": "scalar", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Multiplies a BLS12-381 G2 point by a scalar, both given in bytes format, and returns the resulting G2 point in bytes format." } ] }, diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index 651285cd8..ac4cd5ba8 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -2832,6 +2832,126 @@ impl VmCallerEnv for Host { Ok(res.into()) } + fn bls_g1_add( + &self, + _vmcaller: &mut VmCaller, + p0: BytesObject, + p1: BytesObject, + ) -> Result { + let p0_bytes = self.visit_obj(p0, |bytes: &ScBytes| { + bytes.as_slice().try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid length for G1 point", + &[], + ) + }) + })?; + let p1_bytes = self.visit_obj(p1, |bytes: &ScBytes| { + bytes.as_slice().try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid length for G1 point", + &[], + ) + }) + })?; + let result = self.bls_g1_add_raw_internal(&p0_bytes, &p1_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_g1_mul( + &self, + _vmcaller: &mut VmCaller, + scalar: BytesObject, + p1: BytesObject, + ) -> Result { + let scalar_bytes = self.visit_obj(scalar, |bytes: &ScBytes| { + bytes.as_slice().try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "scalar value out of range for G1 multiplication", + &[], + ) + }) + })?; + let p1_bytes = self.visit_obj(p1, |bytes: &ScBytes| { + bytes.as_slice().try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid length for G1 point", + &[], + ) + }) + })?; + let result = self.bls_g1_mul_raw_internal(scalar_bytes, &p1_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_g2_add( + &self, + _vmcaller: &mut VmCaller, + p0: BytesObject, + p1: BytesObject, + ) -> Result { + let p0_bytes = self.visit_obj(p0, |bytes: &ScBytes| { + bytes.as_slice().try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid length for G2 point", + &[], + ) + }) + })?; + let p1_bytes = self.visit_obj(p1, |bytes: &ScBytes| { + bytes.as_slice().try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid length for G2 point", + &[], + ) + }) + })?; + let result = self.bls_g2_add_raw_internal(&p0_bytes, &p1_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_g2_mul( + &self, + _vmcaller: &mut VmCaller, + scalar: BytesObject, + p1: BytesObject, + ) -> Result { + let scalar_bytes = self.visit_obj(scalar, |bytes: &ScBytes| { + bytes.as_slice().try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "scalar value out of range for G2 multiplication", + &[], + ) + }) + })?; + let p1_bytes = self.visit_obj(p1, |bytes: &ScBytes| { + bytes.as_slice().try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid length for G2 point", + &[], + ) + }) + })?; + let result = self.bls_g2_mul_raw_internal(scalar_bytes, &p1_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + // endregion: "crypto" module functions // region: "test" module functions diff --git a/soroban-env-host/src/host/bls.rs b/soroban-env-host/src/host/bls.rs new file mode 100644 index 000000000..908d80842 --- /dev/null +++ b/soroban-env-host/src/host/bls.rs @@ -0,0 +1,582 @@ +#![allow(dead_code)] +use crate::host::prng::SEED_BYTES; +use crate::{ + budget::AsBudget, + err, + xdr::{ContractCostType, Hash, ScBytes, ScErrorCode, ScErrorType}, + BytesObject, Error, Host, HostError, U32Val, Val, +}; +use blst::{ + blst_fr, blst_fr_from_scalar, blst_lendian_from_scalar, blst_p1, blst_p1_add, blst_p1_affine, + blst_p1_affine_in_g1, blst_p1_from_affine, blst_p1_mult, blst_p1_on_curve, blst_p1_serialize, + blst_p1_uncompress, blst_p2, blst_p2_add, blst_p2_affine, blst_p2_affine_in_g2, + blst_p2_deserialize, blst_p2_from_affine, blst_p2_mult, blst_p2_on_curve, blst_p2_serialize, + blst_scalar, blst_scalar_fr_check, blst_scalar_from_fr, + blst_scalar_from_lendian, BLST_ERROR, +}; +use hex_literal::hex; +use hmac::{Hmac, Mac}; +use rand::RngCore; +use rand_chacha::ChaCha20Rng; +use sha2::Sha256; +use sha3::Keccak256; + +use super::metered_clone::MeteredContainer; + +pub const BLST_G1_UNCOMPRESSED_SIZE: usize = 96; +pub const BLST_G2_UNCOMPRESSED_SIZE: usize = 192; +pub const BLS_SCALAR_SIZE: usize = 32; +pub const BLST_RESULT_SIZE: usize = 255; + +impl Host { + // Ed25519 functions + pub(crate) fn ed25519_signature_from_bytesobj_input( + &self, + name: &'static str, + sig: BytesObject, + ) -> Result { + self.fixed_length_bytes_from_bytesobj_input::(name, sig) + } + + pub(crate) fn ed25519_pub_key_from_bytes( + &self, + bytes: &[u8], + ) -> Result { + self.charge_budget(ContractCostType::ComputeEd25519PubKey, None)?; + let vk_bytes = bytes.try_into().map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid length of ed25519 public key", + &[Val::from_u32(bytes.len() as u32).into()], + ) + })?; + ed25519_dalek::VerifyingKey::from_bytes(vk_bytes).map_err(|_| { + err!( + self, + (ScErrorType::Crypto, ScErrorCode::InvalidInput), + "invalid ed25519 public key", + bytes + ) + }) + } + + pub(crate) fn ed25519_pub_key_from_bytesobj_input( + &self, + k: BytesObject, + ) -> Result { + self.visit_obj(k, |bytes: &ScBytes| { + self.ed25519_pub_key_from_bytes(bytes.as_slice()) + }) + } + + pub(crate) fn verify_sig_ed25519_internal( + &self, + payload: &[u8], + verifying_key: &ed25519_dalek::VerifyingKey, + sig: &ed25519_dalek::Signature, + ) -> Result<(), HostError> { + let _span = tracy_span!("ed25519 verify"); + self.charge_budget( + ContractCostType::VerifyEd25519Sig, + Some(payload.len() as u64), + )?; + verifying_key.verify_strict(payload, sig).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "failed ED25519 verification", + &[], + ) + }) + } + + // ECDSA secp256k1 functions + + pub(crate) fn secp256k1_signature_from_bytes( + &self, + bytes: &[u8], + ) -> Result { + use k256::elliptic_curve::scalar::IsHigh; + self.charge_budget(ContractCostType::ComputeEcdsaSecp256k1Sig, None)?; + let sig: k256::ecdsa::Signature = + k256::ecdsa::Signature::try_from(bytes).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA-secp256k1 signature", + &[], + ) + })?; + if sig.s().is_high().into() { + Err(self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "ECDSA-secp256k1 signature 's' part is not normalized to low form", + &[], + )) + } else { + Ok(sig) + } + } + + pub(crate) fn secp256k1_signature_from_bytesobj_input( + &self, + k: BytesObject, + ) -> Result { + self.visit_obj(k, |bytes: &ScBytes| { + self.secp256k1_signature_from_bytes(bytes.as_slice()) + }) + } + + // NB: not metered as it's a trivial constant cost, just converting a byte to a byte, + // and always done exactly once as part of the secp256k1 recovery path. + pub(crate) fn secp256k1_recovery_id_from_u32val( + &self, + recovery_id: U32Val, + ) -> Result { + let rid32: u32 = u32::from(recovery_id); + if rid32 > k256::ecdsa::RecoveryId::MAX as u32 { + return Err(self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA-secp256k1 recovery ID", + &[recovery_id.to_val()], + )); + } + k256::ecdsa::RecoveryId::try_from(rid32 as u8).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA-secp256k1 recovery ID", + &[recovery_id.to_val()], + ) + }) + } + + pub(crate) fn recover_key_ecdsa_secp256k1_internal( + &self, + hash: &Hash, + sig: &k256::ecdsa::Signature, + rid: k256::ecdsa::RecoveryId, + ) -> Result { + let _span = tracy_span!("secp256k1 recover"); + self.charge_budget(ContractCostType::RecoverEcdsaSecp256k1Key, None)?; + let recovered_key = + k256::ecdsa::VerifyingKey::recover_from_prehash(hash.as_slice(), &sig, rid).map_err( + |_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "ECDSA-secp256k1 signature recovery failed", + &[], + ) + }, + )?; + let rk = ScBytes::from(crate::xdr::BytesM::try_from( + recovered_key + .to_encoded_point(/*compress:*/ false) + .as_bytes(), + )?); + self.add_host_object(rk) + } + + // SHA256 functions + + pub(crate) fn sha256_hash_from_bytesobj_input( + &self, + x: BytesObject, + ) -> Result, HostError> { + self.visit_obj(x, |bytes: &ScBytes| { + let hash = sha256_hash_from_bytes(bytes.as_slice(), self)?; + if hash.len() != 32 { + return Err(err!( + self, + (ScErrorType::Object, ScErrorCode::UnexpectedSize), + "expected 32-byte BytesObject for sha256 hash, got different size", + hash.len() + )); + } + Ok(hash) + }) + } + + // Keccak256/SHA3 functions + pub(crate) fn keccak256_hash_from_bytes_raw( + &self, + bytes: &[u8], + ) -> Result<[u8; 32], HostError> { + let _span = tracy_span!("keccak256"); + self.charge_budget( + ContractCostType::ComputeKeccak256Hash, + Some(bytes.len() as u64), + )?; + Ok(::digest(bytes).into()) + } + + pub(crate) fn keccak256_hash_from_bytes(&self, bytes: &[u8]) -> Result, HostError> { + Vec::::charge_bulk_init_cpy(32, self.as_budget())?; + self.keccak256_hash_from_bytes_raw(bytes) + .map(|x| x.to_vec()) + } + + pub(crate) fn keccak256_hash_from_bytesobj_input( + &self, + x: BytesObject, + ) -> Result, HostError> { + self.visit_obj(x, |bytes: &ScBytes| { + let hash = self.keccak256_hash_from_bytes(bytes.as_slice())?; + if hash.len() != 32 { + return Err(err!( + self, + (ScErrorType::Object, ScErrorCode::UnexpectedSize), + "expected 32-byte BytesObject for keccak256 hash, got different size", + hash.len() + )); + } + Ok(hash) + }) + } + + pub(crate) fn bls_g1_add_raw_internal( + &self, + p0: &[u8; BLST_G1_UNCOMPRESSED_SIZE], + p1: &[u8; BLST_G1_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLST_G1_UNCOMPRESSED_SIZE], HostError> { + let p0 = decode_p1(p0).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode p0", + &[], + ))?; + let p1 = decode_p1(p1).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode p1", + &[], + ))?; + let mut res = blst_p1::default(); + let mut out = [0u8; BLST_G1_UNCOMPRESSED_SIZE]; + + unsafe { blst_p1_add(&mut res, &p0, &p1) }; + + unsafe { + blst_p1_serialize(out.as_mut_ptr(), &res); + } + + Ok(out) + } + + pub(crate) fn bls_g1_mul_raw_internal( + &self, + scalar: &[u8; BLS_SCALAR_SIZE], + p1: &[u8; BLST_G1_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLST_G1_UNCOMPRESSED_SIZE], HostError> { + let p1 = decode_p1(p1).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode p1", + &[], + ))?; + let scalar_ftr = scalar_fr_from_bytes(scalar).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode scalar", + &[], + ))?; + let scalar = bls_fr_to_bytes(scalar_ftr); + let mut res = blst_p1::default(); + let mut out = [0u8; BLST_G1_UNCOMPRESSED_SIZE]; + unsafe { blst_p1_mult(&mut res, &p1, scalar.as_ptr(), BLST_RESULT_SIZE) }; + + unsafe { + blst_p1_serialize(out.as_mut_ptr(), &res); + } + + Ok(out) + } + + pub(crate) fn bls_g1_mul_raw_exp( + &self, + scalars: &[u8], + p_n: &[u8], + ) -> Result<[u8; BLST_G1_UNCOMPRESSED_SIZE], HostError> { + if let Some(value) = self.validate_points_input(&p_n, BLST_G1_UNCOMPRESSED_SIZE) { + return Err(value); + } + + let mut res = blst_p1::default(); + let mut out = [0u8; BLST_G1_UNCOMPRESSED_SIZE]; + for (i, chunk) in p_n.chunks_exact(BLST_G1_UNCOMPRESSED_SIZE).enumerate() { + let p1 = decode_p1(chunk.try_into().unwrap()).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode chunk", + &[], + ))?; + let mut tmp = blst_p1::default(); + let scalar = scalars[i]; + unsafe { blst_p1_mult(&mut tmp, &p1, &scalar, BLST_RESULT_SIZE) }; + unsafe { blst_p1_add(&mut res, &res, &tmp) }; + } + + unsafe { + blst_p1_serialize(out.as_mut_ptr(), &res); + } + + Ok(out) + } + + pub(crate) fn bls_g2_add_raw_internal( + &self, + p0: &[u8; BLST_G2_UNCOMPRESSED_SIZE], + p1: &[u8; BLST_G2_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLST_G2_UNCOMPRESSED_SIZE], HostError> { + let p0 = decode_p2(p0).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode p0", + &[], + ))?; + let p1 = decode_p2(p1).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode p1", + &[], + ))?; + let mut res = blst_p2::default(); + let mut out = [0u8; BLST_G2_UNCOMPRESSED_SIZE]; + + unsafe { blst_p2_add(&mut res, &p0, &p1) }; + + unsafe { + blst_p2_serialize(out.as_mut_ptr(), &res); + } + + Ok(out) + } + + pub(crate) fn bls_g2_mul_raw_internal( + &self, + scalar: &[u8; BLS_SCALAR_SIZE], + p1: &[u8; BLST_G2_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLST_G2_UNCOMPRESSED_SIZE], HostError> { + let p1 = decode_p2(p1).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode p1", + &[], + ))?; + let scalar_ftr = scalar_fr_from_bytes(scalar).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode scalar", + &[], + ))?; + let scalar = bls_fr_to_bytes(scalar_ftr); + let mut res = blst_p2::default(); + let mut out = [0u8; BLST_G2_UNCOMPRESSED_SIZE]; + unsafe { blst_p2_mult(&mut res, &p1, scalar.as_ptr(), BLST_RESULT_SIZE) }; + + unsafe { + blst_p2_serialize(out.as_mut_ptr(), &res); + } + Ok(out) + } + + pub(crate) fn bls_g2_mul_raw_exp_internal( + &self, + scalars: &[u8], + p_n: &[u8], + ) -> Result<[u8; BLST_G2_UNCOMPRESSED_SIZE], HostError> { + if let Some(value) = self.validate_points_input(&p_n, BLST_G2_UNCOMPRESSED_SIZE) { + return Err(value); + } + + let mut res = blst_p2::default(); + let mut out = [0u8; BLST_G2_UNCOMPRESSED_SIZE]; + for (i, chunk) in p_n.chunks_exact(BLST_G2_UNCOMPRESSED_SIZE).enumerate() { + let p2 = decode_p2(chunk.try_into().unwrap()).ok_or_else(|| self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode chunk", + &[], + ))?; + let mut tmp = blst_p2::default(); + let scalar = scalars[i]; + unsafe { blst_p2_mult(&mut tmp, &p2, &scalar, BLST_RESULT_SIZE) }; + unsafe { blst_p2_add(&mut res, &res, &tmp) }; + } + + unsafe { + blst_p2_serialize(out.as_mut_ptr(), &res); + } + + Ok(out) + } + fn validate_points_input(&self, p_n: &&[u8], size: usize) -> Option { + if p_n.len() % size != 0 { + return Some(self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + format!("number of points bytes should divisible by {}", size).as_str(), + &[Val::from_u32(p_n.len() as u32).into()], + )); + } + None + } +} + +pub(crate) fn sha256_hash_from_bytes_raw( + bytes: &[u8], + budget: impl AsBudget, +) -> Result<[u8; 32], HostError> { + let _span = tracy_span!("sha256"); + budget.as_budget().charge( + ContractCostType::ComputeSha256Hash, + Some(bytes.len() as u64), + )?; + Ok(::digest(bytes).into()) +} + +pub(crate) fn sha256_hash_from_bytes( + bytes: &[u8], + budget: impl AsBudget, +) -> Result, HostError> { + Vec::::charge_bulk_init_cpy(32, budget.clone())?; + sha256_hash_from_bytes_raw(bytes, budget).map(|x| x.to_vec()) +} + +pub(crate) fn chacha20_fill_bytes( + rng: &mut ChaCha20Rng, + dest: &mut [u8], + budget: impl AsBudget, +) -> Result<(), HostError> { + tracy_span!("chacha20"); + budget + .as_budget() + .charge(ContractCostType::ChaCha20DrawBytes, Some(dest.len() as u64))?; + rng.fill_bytes(dest); + Ok(()) +} + +// It is possible that a user-provided PRNG seed (either in a test or, more +// worryingly, in a production environment) is biased: it might be all zero, or +// all copies of a single byte, or otherwise statistically unlike a uniformly +// random bitstream with roughly 50-50 zero and one bits. +// +// Unfortunately the security properties of the stream cipher ChaCha used in the +// PRNG (being "indistinguishable from uniform random") are based on the +// assumption of an _unbiased_ seed. +// +// So we run any seed through HMAC-SHA256 here, with a constant uniform random +// salt, as an unbiasing step (this is the "randomness-extractor" phase of HKDF, +// which is the only part relevant to our needs, we don't need multiple keys). + +pub(crate) fn unbias_prng_seed( + seed: &[u8; SEED_BYTES as usize], + budget: impl AsBudget, +) -> Result<[u8; SEED_BYTES as usize], HostError> { + tracy_span!("unbias_prng_seed"); + + // Salt is fixed and must not be changed; it is effectively "part of the + // protocol" and must be the same for all implementations. + // + // Note: salt is a "public random value", intended to be statistically + // similar to a 32-byte draw on /dev/random but done in a transparent and + // reproducible way. In this case we use the Stellar Public Network ID, + // `sha256("Public Global Stellar Network ; September 2015")`. + // + // This number as a bitstring has 137 zeroes and 119 ones, which is within + // the range we get when taking 32-byte samples from /dev/random (feel free + // to check this yourself). + + const SALT: [u8; 32] = hex!("7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979"); + + // Running HMAC will run SHA256 2 times on 64 bytes each time (32-byte salt + // concatenated with 32-byte input). + budget + .as_budget() + .bulk_charge(ContractCostType::ComputeSha256Hash, 2, Some(64))?; + + let mut hmac = Hmac::::new_from_slice(&SALT) + .map_err(|_| Error::from_type_and_code(ScErrorType::Context, ScErrorCode::InternalError))?; + hmac.update(seed); + Ok(hmac.finalize().into_bytes().into()) +} + +pub fn bls_fr_to_bytes(scalar_fr: blst_fr) -> Option<[u8; BLS_SCALAR_SIZE]> { + let mut scalar = blst_scalar::default(); + let mut out = [0u8; BLS_SCALAR_SIZE]; + + unsafe { + blst_scalar_from_fr(&mut scalar, &scalar_fr); + blst_lendian_from_scalar(out.as_mut_ptr(), &scalar); + } + + Some(out) +} + +pub fn scalar_fr_from_bytes(bytes: &[u8; BLS_SCALAR_SIZE]) -> Option { + let mut scalar = blst_scalar::default(); + unsafe { + blst_scalar_from_lendian(&mut scalar, bytes.as_ptr()); + if blst_scalar_fr_check(&scalar) { + let mut fr = blst_fr::default(); + blst_fr_from_scalar(&mut fr, &scalar); + Some(fr) + } else { + None + } + } +} + +pub fn decode_p1_affine(bytes: &[u8; BLST_G1_UNCOMPRESSED_SIZE]) -> Option { + let mut raw = blst_p1_affine::default(); + unsafe { + if blst_p1_uncompress(&mut raw, bytes.as_ptr()) == BLST_ERROR::BLST_SUCCESS && blst_p1_affine_in_g1(&raw) { + Some(raw) + } else { + None + } + } +} + +pub fn decode_p1(bytes: &[u8; BLST_G1_UNCOMPRESSED_SIZE]) -> Option { + decode_p1_affine(bytes).and_then(|p1_affine| { + let mut raw = blst_p1::default(); + unsafe { + blst_p1_from_affine(&mut raw, &p1_affine); + if blst_p1_on_curve(&raw) { + Some(raw) + } else { + None + } + } + }) +} + +pub fn decode_p2_affine(bytes: &[u8; BLST_G2_UNCOMPRESSED_SIZE]) -> Option { + let mut raw = blst_p2_affine::default(); + unsafe { + if blst_p2_deserialize(&mut raw, bytes.as_ptr()) == BLST_ERROR::BLST_SUCCESS && blst_p2_affine_in_g2(&raw) { + Some(raw) + } else { + None + } + } +} + +pub fn decode_p2(bytes: &[u8; BLST_G2_UNCOMPRESSED_SIZE]) -> Option { + decode_p2_affine(bytes).and_then(|p2_affine| { + let mut raw = blst_p2::default(); + unsafe { + blst_p2_from_affine(&mut raw, &p2_affine); + if blst_p2_on_curve(&raw) { + Some(raw) + } else { + None + } + } + }) +} diff --git a/soroban-env-host/src/test/bls.rs b/soroban-env-host/src/test/bls.rs new file mode 100644 index 000000000..e69de29bb From 2edfa38b588dbe8056a09b5974b2364292a4b7cd Mon Sep 17 00:00:00 2001 From: Plamen Hristov Date: Wed, 10 Apr 2024 19:09:46 +0300 Subject: [PATCH 3/5] added all bls12-381 primitives --- soroban-env-common/env.json | 98 +++- soroban-env-host/src/host.rs | 143 +++++- soroban-env-host/src/host/bls.rs | 747 +++++++++++++------------------ soroban-env-host/src/test.rs | 1 + soroban-env-host/src/test/bls.rs | 352 +++++++++++++++ 5 files changed, 896 insertions(+), 445 deletions(-) diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json index 53af00b21..d99721789 100644 --- a/soroban-env-common/env.json +++ b/soroban-env-common/env.json @@ -2057,6 +2057,46 @@ }, { "export": "5", + "name": "bls_g1_multiexp", + "args": [ + { + "name": "scalars", + "type": "BytesObject" + }, + { + "name": "pn", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Performs multiexponentiation on a BLS12-381 G1 point by a vector of scalars, all given in bytes format, and returns the resulting G1 point in bytes format." + }, + { + "export": "6", + "name": "bls_map_to_g1", + "args": [ + { + "name": "msg", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Maps a message to a BLS12-381 G1 point given in bytes format and returns the resulting G1 point in bytes format." + }, + { + "export": "7", + "name": "bls_hash_to_g1", + "args": [ + { + "name": "msg", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Hashes a message to a BLS12-381 G1 point given in bytes format and returns the resulting G1 point in bytes format." + }, + { + "export": "8", "name": "bls_g2_add", "args": [ { @@ -2072,7 +2112,7 @@ "docs": "Adds two BLS12-381 G2 points given in bytes format and returns the resulting G2 point in bytes format." }, { - "export": "6", + "export": "9", "name": "bls_g2_mul", "args": [ { @@ -2086,6 +2126,62 @@ ], "return": "BytesObject", "docs": "Multiplies a BLS12-381 G2 point by a scalar, both given in bytes format, and returns the resulting G2 point in bytes format." + }, + { + "export": "a", + "name": "bls_map_to_g2", + "args": [ + { + "name": "msg", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Maps a message to a BLS12-381 G2 point given in bytes format and returns the resulting G2 point in bytes format." + }, + { + "export": "b", + "name": "bls_g2_multiexp", + "args": [ + { + "name": "scalars", + "type": "BytesObject" + }, + { + "name": "pn", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Performs multiexponentiation on a BLS12-381 G2 point by a vector of scalars, all given in bytes format, and returns the resulting G2 point in bytes format." + }, + { + "export": "c", + "name": "bls_hash_to_g2", + "args": [ + { + "name": "msg", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Hashes a message to a BLS12-381 G2 point given in bytes format and returns the resulting G2 point in bytes format." + }, + { + "export": "d", + "name": "bls_pairing", + "args": [ + { + "name": "p1", + "type": "BytesObject" + }, + { + "name": "p2", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Performs pairing operation on a BLS12-381 G1 point and a G2 point, both given in bytes format, and returns the resulting pairing in bytes format." } ] }, diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index ac4cd5ba8..fc052d682 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -22,6 +22,7 @@ use crate::{ VmCaller, VmCallerEnv, Void, }; +pub(crate) mod bls; mod comparison; mod conversion; pub(crate) mod crypto; @@ -54,6 +55,7 @@ use self::{ prng::Prng, }; +use crate::host::bls::{BLS_G1_UNCOMPRESSED_SIZE, BLS_G2_UNCOMPRESSED_SIZE, BLS_SCALAR_SIZE}; #[cfg(any(test, feature = "testutils"))] pub use frame::ContractFunctionSet; pub(crate) use frame::Frame; @@ -2869,7 +2871,8 @@ impl VmCallerEnv for Host { p1: BytesObject, ) -> Result { let scalar_bytes = self.visit_obj(scalar, |bytes: &ScBytes| { - bytes.as_slice().try_into().map_err(|_| { + let scalar_array: Result<[u8; BLS_SCALAR_SIZE], _> = bytes.as_slice().try_into(); + scalar_array.map_err(|_| { self.err( ScErrorType::Crypto, ScErrorCode::InvalidInput, @@ -2888,7 +2891,64 @@ impl VmCallerEnv for Host { ) }) })?; - let result = self.bls_g1_mul_raw_internal(scalar_bytes, &p1_bytes)?; + let result = self.bls_g1_mul_raw_internal(&scalar_bytes, &p1_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_g1_multiexp( + &self, + _vmcaller: &mut VmCaller, + scalars: BytesObject, + pn: BytesObject, + ) -> Result { + let scalars_bytes: Vec = self + .visit_obj(scalars, |bytes: &ScBytes| Ok(bytes.as_slice().to_vec())) + .map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "scalar value out of range for G1 multi exponentiation", + &[], + ) + })?; + + let pn_bytes: Vec = self + .visit_obj(pn, |bytes: &ScBytes| Ok(bytes.as_slice().to_vec())) + .map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "point out of range for G1 multi exponentiation", + &[], + ) + })?; + let result = self.bls_g1_multiexp_raw_internal(&scalars_bytes, &pn_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_map_to_g1( + &self, + _vmcaller: &mut VmCaller, + msg: BytesObject, + ) -> Result { + let msg_bytes: [u8; BLS_G1_UNCOMPRESSED_SIZE] = self + .visit_obj(msg, |bytes: &ScBytes| { + Ok(bytes.as_slice().try_into().unwrap()) + })?; + let result = self.bls_map_to_g1_internal(&msg_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_hash_to_g1( + &self, + _vmcaller: &mut VmCaller, + msg: BytesObject, + ) -> Result { + let msg_bytes: [u8; BLS_G1_UNCOMPRESSED_SIZE] = self + .visit_obj(msg, |bytes: &ScBytes| { + Ok(bytes.as_slice().try_into().unwrap()) + })?; + let result = self.bls_hash_to_g1_internal(&msg_bytes)?; self.add_host_object(self.scbytes_from_vec(result.to_vec())?) } @@ -2929,7 +2989,8 @@ impl VmCallerEnv for Host { p1: BytesObject, ) -> Result { let scalar_bytes = self.visit_obj(scalar, |bytes: &ScBytes| { - bytes.as_slice().try_into().map_err(|_| { + let scalar_array: Result<[u8; BLS_SCALAR_SIZE], _> = bytes.as_slice().try_into(); + scalar_array.map_err(|_| { self.err( ScErrorType::Crypto, ScErrorCode::InvalidInput, @@ -2948,7 +3009,81 @@ impl VmCallerEnv for Host { ) }) })?; - let result = self.bls_g2_mul_raw_internal(scalar_bytes, &p1_bytes)?; + let result = self.bls_g2_mul_raw_internal(&scalar_bytes, &p1_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_g2_multiexp( + &self, + _vmcaller: &mut VmCaller, + scalars: BytesObject, + pn: BytesObject, + ) -> Result { + let scalars_bytes: Vec = self + .visit_obj(scalars, |bytes: &ScBytes| Ok(bytes.as_slice().to_vec())) + .map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "scalar value out of range for G2 multi exponentiation", + &[], + ) + })?; + + let pn_bytes: Vec = self + .visit_obj(pn, |bytes: &ScBytes| Ok(bytes.as_slice().to_vec())) + .map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "point out of range for G2 multi exponentiation", + &[], + ) + })?; + let result = self.bls_g2_multiexp_raw_internal(&scalars_bytes, &pn_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_map_to_g2( + &self, + _vmcaller: &mut VmCaller, + msg: BytesObject, + ) -> Result { + let msg_bytes: [u8; BLS_G2_UNCOMPRESSED_SIZE] = self + .visit_obj(msg, |bytes: &ScBytes| { + Ok(bytes.as_slice().try_into().unwrap()) + })?; + let result = self.bls_map_to_g2_internal(&msg_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_hash_to_g2( + &self, + _vmcaller: &mut VmCaller, + msg: BytesObject, + ) -> Result { + let msg_bytes: [u8; BLS_G2_UNCOMPRESSED_SIZE] = self + .visit_obj(msg, |bytes: &ScBytes| { + Ok(bytes.as_slice().try_into().unwrap()) + })?; + let result = self.bls_hash_to_g2_internal(&msg_bytes)?; + self.add_host_object(self.scbytes_from_vec(result.to_vec())?) + } + + fn bls_pairing( + &self, + _vmcaller: &mut VmCaller, + p1: BytesObject, + p2: BytesObject, + ) -> Result { + let p1: [u8; BLS_G1_UNCOMPRESSED_SIZE] = self.visit_obj(p1, |bytes: &ScBytes| { + Ok(bytes.as_slice().try_into().unwrap()) + })?; + + let p2: [u8; BLS_G2_UNCOMPRESSED_SIZE] = self.visit_obj(p2, |bytes: &ScBytes| { + Ok(bytes.as_slice().try_into().unwrap()) + })?; + let result = self.bls_pairing_internal(&p1, &p2)?; self.add_host_object(self.scbytes_from_vec(result.to_vec())?) } diff --git a/soroban-env-host/src/host/bls.rs b/soroban-env-host/src/host/bls.rs index 908d80842..639b664b1 100644 --- a/soroban-env-host/src/host/bls.rs +++ b/soroban-env-host/src/host/bls.rs @@ -1,262 +1,44 @@ #![allow(dead_code)] -use crate::host::prng::SEED_BYTES; -use crate::{ - budget::AsBudget, - err, - xdr::{ContractCostType, Hash, ScBytes, ScErrorCode, ScErrorType}, - BytesObject, Error, Host, HostError, U32Val, Val, -}; + use blst::{ - blst_fr, blst_fr_from_scalar, blst_lendian_from_scalar, blst_p1, blst_p1_add, blst_p1_affine, - blst_p1_affine_in_g1, blst_p1_from_affine, blst_p1_mult, blst_p1_on_curve, blst_p1_serialize, - blst_p1_uncompress, blst_p2, blst_p2_add, blst_p2_affine, blst_p2_affine_in_g2, - blst_p2_deserialize, blst_p2_from_affine, blst_p2_mult, blst_p2_on_curve, blst_p2_serialize, - blst_scalar, blst_scalar_fr_check, blst_scalar_from_fr, - blst_scalar_from_lendian, BLST_ERROR, + blst_final_exp, blst_fp, blst_fp12, blst_fp2, blst_fp6, blst_fp_from_lendian, blst_fr, + blst_fr_from_scalar, blst_hash_to_g1, blst_hash_to_g2, blst_lendian_from_scalar, + blst_map_to_g1, blst_map_to_g2, blst_miller_loop, blst_p1, blst_p1_add, blst_p1_affine, + blst_p1_affine_in_g1, blst_p1_deserialize, blst_p1_from_affine, blst_p1_in_g1, blst_p1_mult, + blst_p1_serialize, blst_p2, blst_p2_add, blst_p2_affine, blst_p2_affine_in_g2, + blst_p2_deserialize, blst_p2_from_affine, blst_p2_in_g2, blst_p2_mult, blst_p2_serialize, + blst_scalar, blst_scalar_fr_check, blst_scalar_from_fr, blst_scalar_from_lendian, BLST_ERROR, }; -use hex_literal::hex; -use hmac::{Hmac, Mac}; -use rand::RngCore; -use rand_chacha::ChaCha20Rng; -use sha2::Sha256; -use sha3::Keccak256; -use super::metered_clone::MeteredContainer; +use crate::{ + xdr::{ScErrorCode, ScErrorType}, + Host, HostError, Val, +}; -pub const BLST_G1_UNCOMPRESSED_SIZE: usize = 96; -pub const BLST_G2_UNCOMPRESSED_SIZE: usize = 192; +pub const BLS_G1_UNCOMPRESSED_SIZE: usize = 96; +pub const BLS_G2_UNCOMPRESSED_SIZE: usize = 192; +pub const BLS_FP_SIZE: usize = 48; pub const BLS_SCALAR_SIZE: usize = 32; -pub const BLST_RESULT_SIZE: usize = 255; +pub const BLS_RESULT_SIZE: usize = 255; +const BLS_FP12_ZERO: blst_fp12 = blst_fp12 { + fp6: [blst_fp6 { + fp2: [blst_fp2 { + fp: [blst_fp { l: [0; 6] }; 2], + }; 3], + }; 2], +}; impl Host { - // Ed25519 functions - pub(crate) fn ed25519_signature_from_bytesobj_input( - &self, - name: &'static str, - sig: BytesObject, - ) -> Result { - self.fixed_length_bytes_from_bytesobj_input::(name, sig) - } - - pub(crate) fn ed25519_pub_key_from_bytes( - &self, - bytes: &[u8], - ) -> Result { - self.charge_budget(ContractCostType::ComputeEd25519PubKey, None)?; - let vk_bytes = bytes.try_into().map_err(|_| { - self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "invalid length of ed25519 public key", - &[Val::from_u32(bytes.len() as u32).into()], - ) - })?; - ed25519_dalek::VerifyingKey::from_bytes(vk_bytes).map_err(|_| { - err!( - self, - (ScErrorType::Crypto, ScErrorCode::InvalidInput), - "invalid ed25519 public key", - bytes - ) - }) - } - - pub(crate) fn ed25519_pub_key_from_bytesobj_input( - &self, - k: BytesObject, - ) -> Result { - self.visit_obj(k, |bytes: &ScBytes| { - self.ed25519_pub_key_from_bytes(bytes.as_slice()) - }) - } - - pub(crate) fn verify_sig_ed25519_internal( - &self, - payload: &[u8], - verifying_key: &ed25519_dalek::VerifyingKey, - sig: &ed25519_dalek::Signature, - ) -> Result<(), HostError> { - let _span = tracy_span!("ed25519 verify"); - self.charge_budget( - ContractCostType::VerifyEd25519Sig, - Some(payload.len() as u64), - )?; - verifying_key.verify_strict(payload, sig).map_err(|_| { - self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "failed ED25519 verification", - &[], - ) - }) - } - - // ECDSA secp256k1 functions - - pub(crate) fn secp256k1_signature_from_bytes( - &self, - bytes: &[u8], - ) -> Result { - use k256::elliptic_curve::scalar::IsHigh; - self.charge_budget(ContractCostType::ComputeEcdsaSecp256k1Sig, None)?; - let sig: k256::ecdsa::Signature = - k256::ecdsa::Signature::try_from(bytes).map_err(|_| { - self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "invalid ECDSA-secp256k1 signature", - &[], - ) - })?; - if sig.s().is_high().into() { - Err(self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "ECDSA-secp256k1 signature 's' part is not normalized to low form", - &[], - )) - } else { - Ok(sig) - } - } - - pub(crate) fn secp256k1_signature_from_bytesobj_input( - &self, - k: BytesObject, - ) -> Result { - self.visit_obj(k, |bytes: &ScBytes| { - self.secp256k1_signature_from_bytes(bytes.as_slice()) - }) - } - - // NB: not metered as it's a trivial constant cost, just converting a byte to a byte, - // and always done exactly once as part of the secp256k1 recovery path. - pub(crate) fn secp256k1_recovery_id_from_u32val( - &self, - recovery_id: U32Val, - ) -> Result { - let rid32: u32 = u32::from(recovery_id); - if rid32 > k256::ecdsa::RecoveryId::MAX as u32 { - return Err(self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "invalid ECDSA-secp256k1 recovery ID", - &[recovery_id.to_val()], - )); - } - k256::ecdsa::RecoveryId::try_from(rid32 as u8).map_err(|_| { - self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "invalid ECDSA-secp256k1 recovery ID", - &[recovery_id.to_val()], - ) - }) - } - - pub(crate) fn recover_key_ecdsa_secp256k1_internal( - &self, - hash: &Hash, - sig: &k256::ecdsa::Signature, - rid: k256::ecdsa::RecoveryId, - ) -> Result { - let _span = tracy_span!("secp256k1 recover"); - self.charge_budget(ContractCostType::RecoverEcdsaSecp256k1Key, None)?; - let recovered_key = - k256::ecdsa::VerifyingKey::recover_from_prehash(hash.as_slice(), &sig, rid).map_err( - |_| { - self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "ECDSA-secp256k1 signature recovery failed", - &[], - ) - }, - )?; - let rk = ScBytes::from(crate::xdr::BytesM::try_from( - recovered_key - .to_encoded_point(/*compress:*/ false) - .as_bytes(), - )?); - self.add_host_object(rk) - } - - // SHA256 functions - - pub(crate) fn sha256_hash_from_bytesobj_input( - &self, - x: BytesObject, - ) -> Result, HostError> { - self.visit_obj(x, |bytes: &ScBytes| { - let hash = sha256_hash_from_bytes(bytes.as_slice(), self)?; - if hash.len() != 32 { - return Err(err!( - self, - (ScErrorType::Object, ScErrorCode::UnexpectedSize), - "expected 32-byte BytesObject for sha256 hash, got different size", - hash.len() - )); - } - Ok(hash) - }) - } - - // Keccak256/SHA3 functions - pub(crate) fn keccak256_hash_from_bytes_raw( - &self, - bytes: &[u8], - ) -> Result<[u8; 32], HostError> { - let _span = tracy_span!("keccak256"); - self.charge_budget( - ContractCostType::ComputeKeccak256Hash, - Some(bytes.len() as u64), - )?; - Ok(::digest(bytes).into()) - } - - pub(crate) fn keccak256_hash_from_bytes(&self, bytes: &[u8]) -> Result, HostError> { - Vec::::charge_bulk_init_cpy(32, self.as_budget())?; - self.keccak256_hash_from_bytes_raw(bytes) - .map(|x| x.to_vec()) - } - - pub(crate) fn keccak256_hash_from_bytesobj_input( - &self, - x: BytesObject, - ) -> Result, HostError> { - self.visit_obj(x, |bytes: &ScBytes| { - let hash = self.keccak256_hash_from_bytes(bytes.as_slice())?; - if hash.len() != 32 { - return Err(err!( - self, - (ScErrorType::Object, ScErrorCode::UnexpectedSize), - "expected 32-byte BytesObject for keccak256 hash, got different size", - hash.len() - )); - } - Ok(hash) - }) - } - pub(crate) fn bls_g1_add_raw_internal( &self, - p0: &[u8; BLST_G1_UNCOMPRESSED_SIZE], - p1: &[u8; BLST_G1_UNCOMPRESSED_SIZE], - ) -> Result<[u8; BLST_G1_UNCOMPRESSED_SIZE], HostError> { - let p0 = decode_p1(p0).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode p0", - &[], - ))?; - let p1 = decode_p1(p1).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode p1", - &[], - ))?; + p0: &[u8; BLS_G1_UNCOMPRESSED_SIZE], + p1: &[u8; BLS_G1_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLS_G1_UNCOMPRESSED_SIZE], HostError> { + let p0 = self.parse_point_in_g1(p0)?; + let p1 = self.parse_point_in_g1(p1)?; + let mut res = blst_p1::default(); - let mut out = [0u8; BLST_G1_UNCOMPRESSED_SIZE]; + let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; unsafe { blst_p1_add(&mut res, &p0, &p1) }; @@ -270,53 +52,39 @@ impl Host { pub(crate) fn bls_g1_mul_raw_internal( &self, scalar: &[u8; BLS_SCALAR_SIZE], - p1: &[u8; BLST_G1_UNCOMPRESSED_SIZE], - ) -> Result<[u8; BLST_G1_UNCOMPRESSED_SIZE], HostError> { - let p1 = decode_p1(p1).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode p1", - &[], - ))?; - let scalar_ftr = scalar_fr_from_bytes(scalar).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode scalar", - &[], - ))?; - let scalar = bls_fr_to_bytes(scalar_ftr); + p1: &[u8; BLS_G1_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLS_G1_UNCOMPRESSED_SIZE], HostError> { + let p1 = self.parse_point_in_g1(p1)?; + let scalar = self.parse_scalar(scalar)?; let mut res = blst_p1::default(); - let mut out = [0u8; BLST_G1_UNCOMPRESSED_SIZE]; - unsafe { blst_p1_mult(&mut res, &p1, scalar.as_ptr(), BLST_RESULT_SIZE) }; - + let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + unsafe { blst_p1_mult(&mut res, &p1, scalar.as_ptr(), BLS_RESULT_SIZE) }; unsafe { blst_p1_serialize(out.as_mut_ptr(), &res); } Ok(out) } - - pub(crate) fn bls_g1_mul_raw_exp( + pub(crate) fn bls_g1_multiexp_raw_internal( &self, scalars: &[u8], p_n: &[u8], - ) -> Result<[u8; BLST_G1_UNCOMPRESSED_SIZE], HostError> { - if let Some(value) = self.validate_points_input(&p_n, BLST_G1_UNCOMPRESSED_SIZE) { + ) -> Result<[u8; BLS_G1_UNCOMPRESSED_SIZE], HostError> { + if let Some(value) = self.validate_points_input(&p_n, BLS_G1_UNCOMPRESSED_SIZE) { + return Err(value); + } + if let Some(value) = self.validate_points_input(&scalars, BLS_SCALAR_SIZE) { return Err(value); } let mut res = blst_p1::default(); - let mut out = [0u8; BLST_G1_UNCOMPRESSED_SIZE]; - for (i, chunk) in p_n.chunks_exact(BLST_G1_UNCOMPRESSED_SIZE).enumerate() { - let p1 = decode_p1(chunk.try_into().unwrap()).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode chunk", - &[], - ))?; + let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + let scalars = scalars.chunks_exact(BLS_SCALAR_SIZE); + for (chunk, scalar) in p_n.chunks_exact(BLS_G1_UNCOMPRESSED_SIZE).zip(scalars) { + let p1 = self.parse_point_in_g1(chunk.try_into().unwrap())?; let mut tmp = blst_p1::default(); - let scalar = scalars[i]; - unsafe { blst_p1_mult(&mut tmp, &p1, &scalar, BLST_RESULT_SIZE) }; + let scalar = self.parse_scalar(scalar.try_into().unwrap())?; + unsafe { blst_p1_mult(&mut tmp, &p1, scalar.as_ptr(), BLS_RESULT_SIZE) }; unsafe { blst_p1_add(&mut res, &res, &tmp) }; } @@ -329,23 +97,13 @@ impl Host { pub(crate) fn bls_g2_add_raw_internal( &self, - p0: &[u8; BLST_G2_UNCOMPRESSED_SIZE], - p1: &[u8; BLST_G2_UNCOMPRESSED_SIZE], - ) -> Result<[u8; BLST_G2_UNCOMPRESSED_SIZE], HostError> { - let p0 = decode_p2(p0).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode p0", - &[], - ))?; - let p1 = decode_p2(p1).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode p1", - &[], - ))?; + p0: &[u8; BLS_G2_UNCOMPRESSED_SIZE], + p1: &[u8; BLS_G2_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLS_G2_UNCOMPRESSED_SIZE], HostError> { + let p0 = self.parse_point_in_g2(p0)?; + let p1 = self.parse_point_in_g2(p1)?; let mut res = blst_p2::default(); - let mut out = [0u8; BLST_G2_UNCOMPRESSED_SIZE]; + let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; unsafe { blst_p2_add(&mut res, &p0, &p1) }; @@ -359,24 +117,13 @@ impl Host { pub(crate) fn bls_g2_mul_raw_internal( &self, scalar: &[u8; BLS_SCALAR_SIZE], - p1: &[u8; BLST_G2_UNCOMPRESSED_SIZE], - ) -> Result<[u8; BLST_G2_UNCOMPRESSED_SIZE], HostError> { - let p1 = decode_p2(p1).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode p1", - &[], - ))?; - let scalar_ftr = scalar_fr_from_bytes(scalar).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode scalar", - &[], - ))?; - let scalar = bls_fr_to_bytes(scalar_ftr); + p1: &[u8; BLS_G2_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLS_G2_UNCOMPRESSED_SIZE], HostError> { + let p1 = self.parse_point_in_g2(p1)?; + let scalar = self.parse_scalar(&scalar)?; let mut res = blst_p2::default(); - let mut out = [0u8; BLST_G2_UNCOMPRESSED_SIZE]; - unsafe { blst_p2_mult(&mut res, &p1, scalar.as_ptr(), BLST_RESULT_SIZE) }; + let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; + unsafe { blst_p2_mult(&mut res, &p1, scalar.as_ptr(), BLS_RESULT_SIZE) }; unsafe { blst_p2_serialize(out.as_mut_ptr(), &res); @@ -384,36 +131,157 @@ impl Host { Ok(out) } - pub(crate) fn bls_g2_mul_raw_exp_internal( + pub(crate) fn bls_g2_multiexp_raw_internal( &self, scalars: &[u8], p_n: &[u8], - ) -> Result<[u8; BLST_G2_UNCOMPRESSED_SIZE], HostError> { - if let Some(value) = self.validate_points_input(&p_n, BLST_G2_UNCOMPRESSED_SIZE) { + ) -> Result<[u8; BLS_G2_UNCOMPRESSED_SIZE], HostError> { + if let Some(value) = self.validate_points_input(&p_n, BLS_G2_UNCOMPRESSED_SIZE) { + return Err(value); + } + if let Some(value) = self.validate_points_input(&scalars, BLS_SCALAR_SIZE) { return Err(value); } let mut res = blst_p2::default(); - let mut out = [0u8; BLST_G2_UNCOMPRESSED_SIZE]; - for (i, chunk) in p_n.chunks_exact(BLST_G2_UNCOMPRESSED_SIZE).enumerate() { - let p2 = decode_p2(chunk.try_into().unwrap()).ok_or_else(|| self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "Failed to decode chunk", - &[], - ))?; + let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; + let scalars = scalars.chunks_exact(BLS_SCALAR_SIZE); + for (chunk, scalar) in p_n.chunks_exact(BLS_G1_UNCOMPRESSED_SIZE).zip(scalars) { + let p2 = self.parse_point_in_g2(chunk.try_into().unwrap())?; let mut tmp = blst_p2::default(); - let scalar = scalars[i]; - unsafe { blst_p2_mult(&mut tmp, &p2, &scalar, BLST_RESULT_SIZE) }; + let scalar = self.parse_scalar(scalar.try_into().unwrap())?; + unsafe { blst_p2_mult(&mut tmp, &p2, scalar.as_ptr(), BLS_RESULT_SIZE) }; unsafe { blst_p2_add(&mut res, &res, &tmp) }; } - unsafe { blst_p2_serialize(out.as_mut_ptr(), &res); } Ok(out) } + + pub(crate) fn bls_map_to_g1_internal( + &self, + fp: &[u8; BLS_FP_SIZE * 2], + ) -> Result<[u8; BLS_G1_UNCOMPRESSED_SIZE], HostError> { + let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + let (u_fp, v_fp) = fp.split_at(BLS_FP_SIZE); + let mut g1_point = blst_p1::default(); + let mut u_res = blst_fp::default(); + let mut v_res = blst_fp::default(); + unsafe { + blst_fp_from_lendian(&mut u_res, u_fp.as_ptr()); + blst_fp_from_lendian(&mut v_res, v_fp.as_ptr()); + blst_map_to_g1(&mut g1_point, &u_res, &v_res); + blst_p1_serialize(out.as_mut_ptr(), &g1_point); + } + Ok(out) + } + + pub(crate) fn bls_map_to_g2_internal( + &self, + fp2: &[u8; BLS_FP_SIZE * 4], + ) -> Result<[u8; BLS_G2_UNCOMPRESSED_SIZE], HostError> { + let mut g2_point = blst_p2::default(); + let mut u_res = blst_fp2::default(); + let mut v_res = blst_fp2::default(); + + let (u_fp2, v_fp2) = fp2.split_at(BLS_FP_SIZE * 2); + let (u0, u1) = u_fp2.split_at(BLS_FP_SIZE); + let (v0, v1) = v_fp2.split_at(BLS_FP_SIZE); + + unsafe { blst_fp_from_lendian(&mut u_res.fp[0], u0.as_ptr()) }; + unsafe { blst_fp_from_lendian(&mut u_res.fp[1], u1.as_ptr()) }; + unsafe { blst_fp_from_lendian(&mut v_res.fp[0], v0.as_ptr()) }; + unsafe { blst_fp_from_lendian(&mut v_res.fp[1], v1.as_ptr()) }; + + let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; + unsafe { + blst_map_to_g2(&mut g2_point, &u_res, &v_res); + blst_p2_serialize(out.as_mut_ptr(), &g2_point); + } + + Ok(out) + } + + pub(crate) fn bls_pairing_internal( + &self, + g1_point: &[u8; BLS_G1_UNCOMPRESSED_SIZE], + g2_point: &[u8; BLS_G2_UNCOMPRESSED_SIZE], + ) -> Result<[u8; BLS_FP_SIZE * 12], HostError> { + let mut tmp = blst_fp12::default(); + + let mut out = blst_fp12::default(); + let p1 = decode_p1_affine(g1_point).ok_or_else(|| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode G1 point", + &[], + ) + })?; + let p2 = decode_p2_affine(g2_point).ok_or_else(|| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode G2 point", + &[], + ) + })?; + + unsafe { + blst_miller_loop(&mut tmp, &p2, &p1); + blst_final_exp(&mut out, &tmp); + }; + return Ok(out.to_bendian()); + } + + pub(crate) fn bls_hash_to_g1_internal( + &self, + msg: &[u8], + ) -> Result<[u8; BLS_G1_UNCOMPRESSED_SIZE], HostError> { + let dst: &[u8; 50] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_"; + let aug = [0u8; 0]; + let mut res = blst_p1::default(); + unsafe { + blst_hash_to_g1( + &mut res, + msg.as_ptr(), + msg.len(), + dst.as_ptr(), + dst.len(), + aug.as_ptr(), + aug.len(), + ); + } + let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + unsafe { blst_p1_serialize(out.as_mut_ptr(), &res) }; + Ok(out) + } + + pub(crate) fn bls_hash_to_g2_internal( + &self, + msg: &[u8], + ) -> Result<[u8; BLS_G2_UNCOMPRESSED_SIZE], HostError> { + let dst: &[u8; 50] = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_"; + let aug = [0u8; 0]; + let mut res = blst_p2::default(); + unsafe { + blst_hash_to_g2( + &mut res, + msg.as_ptr(), + msg.len(), + dst.as_ptr(), + dst.len(), + aug.as_ptr(), + aug.len(), + ); + } + let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; + unsafe { blst_p2_serialize(out.as_mut_ptr(), &res) }; + Ok(out) + } + fn validate_points_input(&self, p_n: &&[u8], size: usize) -> Option { if p_n.len() % size != 0 { return Some(self.err( @@ -425,158 +293,157 @@ impl Host { } None } + + fn parse_point_in_g1(&self, p1: &[u8; BLS_G1_UNCOMPRESSED_SIZE]) -> Result { + decode_p1(p1).ok_or_else(|| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode point in g1", + &[], + ) + }) + } + + fn parse_point_in_g2(&self, p2: &[u8; BLS_G2_UNCOMPRESSED_SIZE]) -> Result { + decode_p2(p2).ok_or_else(|| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode point in g2", + &[], + ) + }) + } + + fn parse_scalar( + &self, + scalar: &[u8; BLS_SCALAR_SIZE], + ) -> Result<[u8; BLS_SCALAR_SIZE], HostError> { + let scalar_ftr = scalar_fr_from_bytes(scalar).ok_or_else(|| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to decode scalar", + &[], + ) + })?; + + bls_fr_to_bytes(scalar_ftr).ok_or_else(|| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "Failed to convert scalar", + &[], + ) + }) + } } -pub(crate) fn sha256_hash_from_bytes_raw( - bytes: &[u8], - budget: impl AsBudget, -) -> Result<[u8; 32], HostError> { - let _span = tracy_span!("sha256"); - budget.as_budget().charge( - ContractCostType::ComputeSha256Hash, - Some(bytes.len() as u64), - )?; - Ok(::digest(bytes).into()) +pub fn g1_one() -> [u8; BLS_G1_UNCOMPRESSED_SIZE] { + unsafe { serialize_default_p1(&blst::BLS12_381_G1) } } -pub(crate) fn sha256_hash_from_bytes( - bytes: &[u8], - budget: impl AsBudget, -) -> Result, HostError> { - Vec::::charge_bulk_init_cpy(32, budget.clone())?; - sha256_hash_from_bytes_raw(bytes, budget).map(|x| x.to_vec()) +pub fn g1_zero() -> [u8; BLS_G1_UNCOMPRESSED_SIZE] { + let mut zero = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + zero[0] = 0x40; + zero } -pub(crate) fn chacha20_fill_bytes( - rng: &mut ChaCha20Rng, - dest: &mut [u8], - budget: impl AsBudget, -) -> Result<(), HostError> { - tracy_span!("chacha20"); - budget - .as_budget() - .charge(ContractCostType::ChaCha20DrawBytes, Some(dest.len() as u64))?; - rng.fill_bytes(dest); - Ok(()) +pub fn g2_one() -> [u8; BLS_G2_UNCOMPRESSED_SIZE] { + unsafe { serialize_default_p2(&blst::BLS12_381_G2) } } -// It is possible that a user-provided PRNG seed (either in a test or, more -// worryingly, in a production environment) is biased: it might be all zero, or -// all copies of a single byte, or otherwise statistically unlike a uniformly -// random bitstream with roughly 50-50 zero and one bits. -// -// Unfortunately the security properties of the stream cipher ChaCha used in the -// PRNG (being "indistinguishable from uniform random") are based on the -// assumption of an _unbiased_ seed. -// -// So we run any seed through HMAC-SHA256 here, with a constant uniform random -// salt, as an unbiasing step (this is the "randomness-extractor" phase of HKDF, -// which is the only part relevant to our needs, we don't need multiple keys). - -pub(crate) fn unbias_prng_seed( - seed: &[u8; SEED_BYTES as usize], - budget: impl AsBudget, -) -> Result<[u8; SEED_BYTES as usize], HostError> { - tracy_span!("unbias_prng_seed"); - - // Salt is fixed and must not be changed; it is effectively "part of the - // protocol" and must be the same for all implementations. - // - // Note: salt is a "public random value", intended to be statistically - // similar to a 32-byte draw on /dev/random but done in a transparent and - // reproducible way. In this case we use the Stellar Public Network ID, - // `sha256("Public Global Stellar Network ; September 2015")`. - // - // This number as a bitstring has 137 zeroes and 119 ones, which is within - // the range we get when taking 32-byte samples from /dev/random (feel free - // to check this yourself). - - const SALT: [u8; 32] = hex!("7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979"); - - // Running HMAC will run SHA256 2 times on 64 bytes each time (32-byte salt - // concatenated with 32-byte input). - budget - .as_budget() - .bulk_charge(ContractCostType::ComputeSha256Hash, 2, Some(64))?; - - let mut hmac = Hmac::::new_from_slice(&SALT) - .map_err(|_| Error::from_type_and_code(ScErrorType::Context, ScErrorCode::InternalError))?; - hmac.update(seed); - Ok(hmac.finalize().into_bytes().into()) +pub fn g2_zero() -> [u8; BLS_G2_UNCOMPRESSED_SIZE] { + let mut zero = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; + zero[0] = 0x40; + zero } pub fn bls_fr_to_bytes(scalar_fr: blst_fr) -> Option<[u8; BLS_SCALAR_SIZE]> { - let mut scalar = blst_scalar::default(); let mut out = [0u8; BLS_SCALAR_SIZE]; - unsafe { + let mut scalar = blst_scalar::default(); blst_scalar_from_fr(&mut scalar, &scalar_fr); blst_lendian_from_scalar(out.as_mut_ptr(), &scalar); } - Some(out) } pub fn scalar_fr_from_bytes(bytes: &[u8; BLS_SCALAR_SIZE]) -> Option { - let mut scalar = blst_scalar::default(); unsafe { + let mut scalar = blst_scalar::default(); blst_scalar_from_lendian(&mut scalar, bytes.as_ptr()); - if blst_scalar_fr_check(&scalar) { + blst_scalar_fr_check(&scalar).then(|| { let mut fr = blst_fr::default(); blst_fr_from_scalar(&mut fr, &scalar); - Some(fr) - } else { - None - } + fr + }) } } -pub fn decode_p1_affine(bytes: &[u8; BLST_G1_UNCOMPRESSED_SIZE]) -> Option { - let mut raw = blst_p1_affine::default(); +pub fn decode_p1_affine(bytes: &[u8; BLS_G1_UNCOMPRESSED_SIZE]) -> Option { unsafe { - if blst_p1_uncompress(&mut raw, bytes.as_ptr()) == BLST_ERROR::BLST_SUCCESS && blst_p1_affine_in_g1(&raw) { - Some(raw) - } else { - None - } + let mut raw = blst_p1_affine::default(); + (blst_p1_deserialize(&mut raw, bytes.as_ptr()) == BLST_ERROR::BLST_SUCCESS + && blst_p1_affine_in_g1(&raw)) + .then_some(raw) } } -pub fn decode_p1(bytes: &[u8; BLST_G1_UNCOMPRESSED_SIZE]) -> Option { - decode_p1_affine(bytes).and_then(|p1_affine| { +pub fn decode_p1(bytes: &[u8; BLS_G1_UNCOMPRESSED_SIZE]) -> Option { + decode_p1_affine(bytes).and_then(|p1_affine| unsafe { let mut raw = blst_p1::default(); - unsafe { - blst_p1_from_affine(&mut raw, &p1_affine); - if blst_p1_on_curve(&raw) { - Some(raw) - } else { - None - } - } + blst_p1_from_affine(&mut raw, &p1_affine); + blst_p1_in_g1(&raw).then_some(raw) }) } -pub fn decode_p2_affine(bytes: &[u8; BLST_G2_UNCOMPRESSED_SIZE]) -> Option { - let mut raw = blst_p2_affine::default(); +pub fn decode_p2_affine(bytes: &[u8; BLS_G2_UNCOMPRESSED_SIZE]) -> Option { unsafe { - if blst_p2_deserialize(&mut raw, bytes.as_ptr()) == BLST_ERROR::BLST_SUCCESS && blst_p2_affine_in_g2(&raw) { - Some(raw) - } else { - None - } + let mut raw = blst_p2_affine::default(); + (blst_p2_deserialize(&mut raw, bytes.as_ptr()) == BLST_ERROR::BLST_SUCCESS + && blst_p2_affine_in_g2(&raw)) + .then_some(raw) } } -pub fn decode_p2(bytes: &[u8; BLST_G2_UNCOMPRESSED_SIZE]) -> Option { - decode_p2_affine(bytes).and_then(|p2_affine| { +pub fn decode_p2(bytes: &[u8; BLS_G2_UNCOMPRESSED_SIZE]) -> Option { + decode_p2_affine(bytes).and_then(|p2_affine| unsafe { let mut raw = blst_p2::default(); - unsafe { - blst_p2_from_affine(&mut raw, &p2_affine); - if blst_p2_on_curve(&raw) { - Some(raw) - } else { - None - } - } + blst_p2_from_affine(&mut raw, &p2_affine); + blst_p2_in_g2(&raw).then_some(raw) }) } + +unsafe fn serialize_default_p1(affine: &blst_p1_affine) -> [u8; BLS_G1_UNCOMPRESSED_SIZE] { + let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + let mut raw = blst_p1::default(); + blst_p1_from_affine(&mut raw, affine); + blst_p1_serialize(out.as_mut_ptr(), &raw); + out +} + +unsafe fn serialize_default_p2(affine: &blst_p2_affine) -> [u8; BLS_G2_UNCOMPRESSED_SIZE] { + let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; + let mut raw = blst_p2::default(); + blst_p2_from_affine(&mut raw, affine); + blst_p2_serialize(out.as_mut_ptr(), &raw); + out +} + +unsafe fn serialize_to_affine_p1(raw: &blst_p1) -> [u8; BLS_G1_UNCOMPRESSED_SIZE] { + let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + let affine = blst_p1_affine::default(); + blst_p1_affine_in_g1(&affine); + blst_p1_serialize(out.as_mut_ptr(), raw); + out +} + +unsafe fn serialize_to_affine_p2(raw: &blst_p2) -> [u8; BLS_G2_UNCOMPRESSED_SIZE] { + let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; + let affine = blst_p2_affine::default(); + blst_p2_affine_in_g2(&affine); + blst_p2_serialize(out.as_mut_ptr(), raw); + out +} diff --git a/soroban-env-host/src/test.rs b/soroban-env-host/src/test.rs index 6d8b6433f..86ef7751b 100644 --- a/soroban-env-host/src/test.rs +++ b/soroban-env-host/src/test.rs @@ -3,6 +3,7 @@ pub(crate) mod observe; mod address; mod auth; mod basic; +mod bls; mod budget_metering; mod bytes; mod complex; diff --git a/soroban-env-host/src/test/bls.rs b/soroban-env-host/src/test/bls.rs index e69de29bb..4e35cf806 100644 --- a/soroban-env-host/src/test/bls.rs +++ b/soroban-env-host/src/test/bls.rs @@ -0,0 +1,352 @@ +use blst::{ + blst_fp, blst_fp2, blst_fr, blst_keygen, blst_p1, blst_p1_affine, blst_p2, blst_p2_affine, + blst_scalar, +}; +use ed25519_dalek::ed25519::signature::rand_core::OsRng; +use rand::RngCore; + +use crate::host::bls::{ + bls_fr_to_bytes, decode_p1, decode_p1_affine, decode_p2, decode_p2_affine, g1_one, g1_zero, + g2_one, g2_zero, scalar_fr_from_bytes, BLS_FP_SIZE, BLS_G1_UNCOMPRESSED_SIZE, + BLS_G2_UNCOMPRESSED_SIZE, BLS_SCALAR_SIZE, +}; +use crate::Host; + +#[test] +fn test_bls_fr_to_bytes() { + let scalar_fr = blst_fr::default(); + let bytes = bls_fr_to_bytes(scalar_fr).unwrap(); + assert_eq!(bytes.len(), BLS_SCALAR_SIZE); +} + +#[test] +fn test_scalar_fr_from_bytes() { + let bytes = [0u8; BLS_SCALAR_SIZE]; + let scalar_fr = scalar_fr_from_bytes(&bytes).unwrap(); + assert_eq!(scalar_fr, blst_fr::default()); +} + +#[test] +fn test_decode_p1_affine() { + // additive identity + let mut bytes = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + bytes[0] = 0x40; + let p1_affine = decode_p1_affine(&bytes).unwrap(); + assert_eq!(p1_affine, blst_p1_affine::default()); +} + +#[test] +fn test_p1_zero() { + // additive identity + let p1_affine = g1_zero(); + assert_eq!(hex::encode(p1_affine), "400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_p1_one() { + // multiplicative identity + let p1_affine = g1_one(); + assert_eq!(hex::encode(p1_affine), "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1"); +} + +#[test] +fn test_p2_zero() { + // additive identity + let p2_affine = g2_zero(); + assert_eq!(hex::encode(p2_affine), "400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_p2_one() { + // multiplicative identity + let p2_affine = g2_one(); + assert_eq!(hex::encode(p2_affine), "13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801"); +} + +#[test] +fn test_decode_p1() { + let s = "026fcea34d1a4c5125142dfa3b616086309cab49e60e548d95de658af4d9329c269dc132bd5d884617e8767600daeee90c6f5d25f3d63540f3b799d291e5df4a90244346ed780d5c9d3afa8f3c9a196e089fa4edc4a9806592e8561d626579e3"; + let bytes = hex::decode(s).unwrap(); + let p1 = decode_p1(bytes.as_slice().try_into().unwrap()).unwrap(); + assert_eq!( + p1, + blst_p1 { + x: blst_fp { + l: [ + 9522032212070104096, + 14704008314964026780, + 6240597254928821153, + 5889904107358641280, + 17135096366920513157, + 1083974352369730169 + ] + }, + y: blst_fp { + l: [ + 9803444800102823628, + 5059586347318398602, + 591178676386880618, + 4712874644763535345, + 6778859501939582959, + 1645574050539793690 + ] + }, + z: blst_fp { + l: [ + 8505329371266088957, + 17002214543764226050, + 6865905132761471162, + 8632934651105793861, + 6631298214892334189, + 1582556514881692819 + ] + }, + } + ); +} + +#[test] +fn test_decode_p2_affine() { + // additive identity + let mut bytes = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; + bytes[0] = 0x40; + let p2_affine = decode_p2_affine(&bytes).unwrap(); + assert_eq!(p2_affine, blst_p2_affine::default()); +} + +#[test] +fn test_decode_p2() { + let bytes = hex::decode("14e9b22683a66543ec447b7aa76e4404424709728507581d0b3f60a8062c3f7c7d3365197c59f7c961fa9731084f5be60d0a936e93d556bdef2032cdcae2fa9902dcbe105e01d7ab7126d83486d882c4efd2fc1ac55044157333be19acf0cb7a10bc41c8081c9babd8d5b41b645badd4a679b3d4e1b3ea2c0e1f53b39c00b3889a40306c9b9ee2da5831e90148334d91016474d07e0f4e36d2d51b5ca11b633b9a940b9c126aebf4a2537c18fdc6967fb677824bfa902157e53cb499a021e57b").unwrap(); + let p2 = decode_p2(bytes.as_slice().try_into().unwrap()).unwrap(); + assert_eq!( + p2, + blst_p2 { + x: blst_fp2 { + fp: [ + blst_fp { + l: [ + 5910463804193348924, + 12106263024642462352, + 4583498720154949598, + 11556036403352332129, + 10086399433537606111, + 814493755602011239 + ] + }, + blst_fp { + l: [ + 11671710108490681288, + 14831228016838450683, + 7848753107382297332, + 3730035743707207515, + 2990542987574555629, + 1652844757846900206 + ] + } + ] + }, + y: blst_fp2 { + fp: [ + blst_fp { + l: [ + 6636923444040220808, + 6431740067118963969, + 1060006293027338967, + 13238938954656170417, + 7085560437947721606, + 53942804531120882 + ] + }, + blst_fp { + l: [ + 10240908403147073127, + 4495815664776461195, + 9095410607741535746, + 11386040834773514749, + 7488306074634164353, + 893202691648791915 + ] + } + ] + }, + z: blst_fp2 { + fp: [ + blst_fp { + l: [ + 8505329371266088957, + 17002214543764226050, + 6865905132761471162, + 8632934651105793861, + 6631298214892334189, + 1582556514881692819 + ] + }, + blst_fp { + l: [0, 0, 0, 0, 0, 0] + } + ] + }, + } + ); +} + +#[test] +fn test_bls_g1_add() { + let g1_zero = g1_zero(); + let g1_one = g1_one(); + let host = Host::default(); + let result = host.bls_g1_add_raw_internal(&g1_zero, &g1_zero); + assert_eq!(result.unwrap(), g1_zero); + let result = host.bls_g1_add_raw_internal(&g1_zero, &g1_one); + assert_eq!(result.unwrap(), g1_one); +} + +#[test] +fn test_bls_g2_add() { + let g2_zero = g2_zero(); + let g2_one = g2_one(); + let host = Host::default(); + let result = host.bls_g2_add_raw_internal(&g2_zero, &g2_zero); + assert_eq!(result.unwrap(), g2_zero); + let result = host.bls_g2_add_raw_internal(&g2_zero, &g2_one); + assert_eq!(result.unwrap(), g2_one); +} + +#[test] +fn test_bls_g1_mul() { + let scalar_zero = [0; BLS_SCALAR_SIZE]; + let mut scalar_one = [0; BLS_SCALAR_SIZE]; + scalar_one[0] = 1; + let host = Host::default(); + let result = host.bls_g1_mul_raw_internal(&scalar_zero, &g1_zero()); + assert_eq!(result.unwrap(), g1_zero()); + let result = host.bls_g1_mul_raw_internal(&scalar_one, &g1_one()); + assert_eq!(result.unwrap(), g1_one()); +} + +#[test] +fn test_bls_g2_mul() { + let scalar_zero = [0; BLS_SCALAR_SIZE]; + let mut scalar_one = [0; BLS_SCALAR_SIZE]; + scalar_one[0] = 1; + let host = Host::default(); + let result = host.bls_g2_mul_raw_internal(&scalar_zero, &g2_zero()); + assert_eq!(result.unwrap(), g2_zero()); + let result = host.bls_g2_mul_raw_internal(&scalar_one, &g2_one()); + assert_eq!(result.unwrap(), g2_one()); +} + +#[test] +fn test_bls_map_to_g1_internal() { + let host = Host::default(); + let fp = [0u8; BLS_FP_SIZE * 2]; + let result = host.bls_map_to_g1_internal(&fp); + assert!( + result.is_ok(), + "bls_map_to_g1_internal failed with valid input" + ); + let expected: [u8;BLS_G1_UNCOMPRESSED_SIZE] = hex::decode("19b6652bc7e44b6ca66a7803d1dff1b2d0fd02a32fa1b09f43716e21fec0b508e688e87b2d7a03618c066409ad53665c10549370803d643dee27b367d4381b08e1655cc8887914917419eed52ad0472115c9fac1a14974ddea16ada22eb37ba7").unwrap().try_into().unwrap(); + assert_eq!(result.unwrap(), expected); +} + +#[test] +fn test_bls_map_to_g2_internal() { + let host = Host::default(); + let fp = [0u8; BLS_FP_SIZE * 4]; + let result = host.bls_map_to_g2_internal(&fp); + assert!( + result.is_ok(), + "bls_map_to_g2_internal failed with valid input" + ); + let expected: [u8;BLS_G2_UNCOMPRESSED_SIZE] = hex::decode("18426da25dadd359adfda64fbaddac4414da2a841cb467935289877db450fac424361efb2e7fb141b7b98e6b2f888aef19da1b4d47efeeb154f8968b43da2125376e0999ba722141419b03fd857490562fa42a5d0973956d1932dd20c1e0a28403257c3be77016e69b75905a97871008a6dfd2e324a6748c48d3304380156987bd0905991824936fcfe34ab25c3b6caa0c2f8d431770d9be9b087c36fc5b66bb83ce6372669f48294193ef646105e0f21d17b134e7d1ad9c18f54b81f6a3707b").unwrap().try_into().unwrap(); + assert_eq!(result.unwrap(), expected); +} + +#[test] +fn test_bls_hash_to_g1_internal() { + let host = Host::default(); + let msg = ""; + let result = host.bls_hash_to_g1_internal(msg.as_bytes()); + // test vector from https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/664b13592116cecc9e52fb192dcde0ade36f904e/poc/vectors/BLS12381G1_XMD%3ASHA-256_SSWU_RO_.json#L18-L37 + let expected: [u8;BLS_G1_UNCOMPRESSED_SIZE] = hex::decode("052926add2207b76ca4fa57a8734416c8dc95e24501772c814278700eed6d1e4e8cf62d9c09db0fac349612b759e79a108ba738453bfed09cb546dbb0783dbb3a5f1f566ed67bb6be0e8c67e2e81a4cc68ee29813bb7994998f3eae0c9c6a265").unwrap().try_into().unwrap(); + assert_eq!(result, Ok(expected)); + + let msg = "abc"; + let result = host.bls_hash_to_g1_internal(msg.as_bytes()); + // test vector from https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/664b13592116cecc9e52fb192dcde0ade36f904e/poc/vectors/BLS12381G1_XMD%3ASHA-256_SSWU_RO_.json#L38-L56 + let expected: [u8;BLS_G1_UNCOMPRESSED_SIZE] = hex::decode("03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f69030b9c15f3fe6e5cf4211f346271d7b01c8f3b28be689c8429c85b67af215533311f0b8dfaaa154fa6b88176c229f2885d").unwrap().try_into().unwrap(); + assert_eq!(result, Ok(expected)); +} + +#[test] +fn test_bls_hash_to_g2_internal() { + let host = Host::default(); + let msg = ""; + let result = host.bls_hash_to_g2_internal(msg.as_bytes()); + // test vector from https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/664b13592116cecc9e52fb192dcde0ade36f904e/poc/vectors/BLS12381G2_XMD%3ASHA-256_SSWU_RO_.json#L18-L37 + let expected: [u8;BLS_G2_UNCOMPRESSED_SIZE] = hex::decode("05cb8437535e20ecffaef7752baddf98034139c38452458baeefab379ba13dff5bf5dd71b72418717047f5b0f37da03d0141ebfbdca40eb85b87142e130ab689c673cf60f1a3e98d69335266f30d9b8d4ac44c1038e9dcdd5393faf5c41fb78a12424ac32561493f3fe3c260708a12b7c620e7be00099a974e259ddc7d1f6395c3c811cdd19f1e8dbf3e9ecfdcbab8d60503921d7f6a12805e72940b963c0cf3471c7b2a524950ca195d11062ee75ec076daf2d4bc358c4b190c0c98064fdd92").unwrap().try_into().unwrap(); + assert_eq!(result, Ok(expected)); + + let msg = "abc"; + let result = host.bls_hash_to_g2_internal(msg.as_bytes()); + // test vector from https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/664b13592116cecc9e52fb192dcde0ade36f904e/poc/vectors/BLS12381G2_XMD%3ASHA-256_SSWU_RO_.json#L38-L56 + let expected: [u8;BLS_G2_UNCOMPRESSED_SIZE] = hex::decode("139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd802c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e600aa65dae3c8d732d10ecd2c50f8a1baf3001578f71c694e03866e9f3d49ac1e1ce70dd94a733534f106d4cec0eddd161787327b68159716a37440985269cf584bcb1e621d3a7202be6ea05c4cfe244aeb197642555a0645fb87bf7466b2ba48").unwrap().try_into().unwrap(); + assert_eq!(result, Ok(expected)); +} + +#[test] +fn test_bls_pk_g1() { + let host = Host::default(); + let msg = random_msg(); + let sk = random_scalar(); + + let pk = host.bls_g1_mul_raw_internal(&sk, &g1_one()).unwrap(); + let hashed_msg = host.bls_hash_to_g2_internal(&msg).unwrap(); + let signature = host.bls_g2_mul_raw_internal(&sk, &hashed_msg).unwrap(); + + let pk_msg_pairing = host.bls_pairing_internal(&pk, &hashed_msg).unwrap(); + let g1_gen_sig_pairing = host.bls_pairing_internal(&g1_one(), &signature).unwrap(); + assert_eq!(pk_msg_pairing, g1_gen_sig_pairing); +} + +#[test] +fn bls_pk_g2() { + let host = Host::default(); + let msg = random_msg(); + let sk = random_scalar(); + + let pk = host.bls_g2_mul_raw_internal(&sk, &g2_one()).unwrap(); + let hashed_msg = host.bls_hash_to_g1_internal(&msg).unwrap(); + let signature = host.bls_g1_mul_raw_internal(&sk, &hashed_msg).unwrap(); + + let pk_msg_pairing = host.bls_pairing_internal(&hashed_msg, &pk).unwrap(); + let g2_gen_sig_pairing = host.bls_pairing_internal(&signature, &g2_one()).unwrap(); + + assert_eq!(pk_msg_pairing, g2_gen_sig_pairing); +} + +fn random_msg() -> [u8; 32] { + let mut rng = OsRng; + let mut msg = [0u8; 32]; + rng.fill_bytes(&mut msg); + msg +} +fn random_scalar() -> [u8; BLS_SCALAR_SIZE] { + let mut rng = OsRng; + let mut buffer = [0u8; 64]; + rng.fill_bytes(&mut buffer); + + let mut scalar = blst_scalar::default(); + + unsafe { + blst_keygen( + &mut scalar, + buffer.as_ptr(), + buffer.len(), + [0; 0].as_ptr(), + 0, + ); + } + + scalar.b +} From 36570cc76545baa8b719b45007a11c9dc869c48c Mon Sep 17 00:00:00 2001 From: Plamen Hristov Date: Wed, 10 Apr 2024 19:14:08 +0300 Subject: [PATCH 4/5] Fixed conflicts from remote --- soroban-env-common/env.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json index d99721789..bc9600685 100644 --- a/soroban-env-common/env.json +++ b/soroban-env-common/env.json @@ -2024,7 +2024,7 @@ "min_supported_protocol": 21 }, { - "export": "3", + "export": "4", "name": "bls_g1_add", "args": [ { @@ -2040,7 +2040,7 @@ "docs": "Adds two BLS12-381 G1 points given in bytes format and returns the resulting G1 point in bytes format." }, { - "export": "4", + "export": "5", "name": "bls_g1_mul", "args": [ { @@ -2056,7 +2056,7 @@ "docs": "Multiplies a BLS12-381 G1 point by a scalar, both given in bytes format, and returns the resulting G1 point in bytes format." }, { - "export": "5", + "export": "6", "name": "bls_g1_multiexp", "args": [ { @@ -2072,7 +2072,7 @@ "docs": "Performs multiexponentiation on a BLS12-381 G1 point by a vector of scalars, all given in bytes format, and returns the resulting G1 point in bytes format." }, { - "export": "6", + "export": "7", "name": "bls_map_to_g1", "args": [ { @@ -2084,7 +2084,7 @@ "docs": "Maps a message to a BLS12-381 G1 point given in bytes format and returns the resulting G1 point in bytes format." }, { - "export": "7", + "export": "8", "name": "bls_hash_to_g1", "args": [ { @@ -2096,7 +2096,7 @@ "docs": "Hashes a message to a BLS12-381 G1 point given in bytes format and returns the resulting G1 point in bytes format." }, { - "export": "8", + "export": "9", "name": "bls_g2_add", "args": [ { @@ -2112,7 +2112,7 @@ "docs": "Adds two BLS12-381 G2 points given in bytes format and returns the resulting G2 point in bytes format." }, { - "export": "9", + "export": "a", "name": "bls_g2_mul", "args": [ { @@ -2128,7 +2128,7 @@ "docs": "Multiplies a BLS12-381 G2 point by a scalar, both given in bytes format, and returns the resulting G2 point in bytes format." }, { - "export": "a", + "export": "b", "name": "bls_map_to_g2", "args": [ { @@ -2140,7 +2140,7 @@ "docs": "Maps a message to a BLS12-381 G2 point given in bytes format and returns the resulting G2 point in bytes format." }, { - "export": "b", + "export": "c", "name": "bls_g2_multiexp", "args": [ { @@ -2156,7 +2156,7 @@ "docs": "Performs multiexponentiation on a BLS12-381 G2 point by a vector of scalars, all given in bytes format, and returns the resulting G2 point in bytes format." }, { - "export": "c", + "export": "d", "name": "bls_hash_to_g2", "args": [ { @@ -2168,7 +2168,7 @@ "docs": "Hashes a message to a BLS12-381 G2 point given in bytes format and returns the resulting G2 point in bytes format." }, { - "export": "d", + "export": "e", "name": "bls_pairing", "args": [ { From 6a85e3577e4e180e9d6a352067a593dcb24a4cff Mon Sep 17 00:00:00 2001 From: Plamen Hristov Date: Thu, 11 Apr 2024 10:37:11 +0300 Subject: [PATCH 5/5] Pulled out constants for destination --- soroban-env-host/src/host/bls.rs | 39 +++++++++----------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/soroban-env-host/src/host/bls.rs b/soroban-env-host/src/host/bls.rs index 639b664b1..aee595091 100644 --- a/soroban-env-host/src/host/bls.rs +++ b/soroban-env-host/src/host/bls.rs @@ -20,6 +20,9 @@ pub const BLS_G2_UNCOMPRESSED_SIZE: usize = 192; pub const BLS_FP_SIZE: usize = 48; pub const BLS_SCALAR_SIZE: usize = 32; pub const BLS_RESULT_SIZE: usize = 255; +pub const BLS_G1_DST: &[u8; 50] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_"; +pub const BLS_G2_DST: &[u8; 50] = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_"; + const BLS_FP12_ZERO: blst_fp12 = blst_fp12 { fp6: [blst_fp6 { fp2: [blst_fp2 { @@ -240,18 +243,16 @@ impl Host { &self, msg: &[u8], ) -> Result<[u8; BLS_G1_UNCOMPRESSED_SIZE], HostError> { - let dst: &[u8; 50] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_"; - let aug = [0u8; 0]; let mut res = blst_p1::default(); unsafe { blst_hash_to_g1( &mut res, msg.as_ptr(), msg.len(), - dst.as_ptr(), - dst.len(), - aug.as_ptr(), - aug.len(), + BLS_G1_DST.as_ptr(), + BLS_G1_DST.len(), + [].as_ptr(), + 0, ); } let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; @@ -263,18 +264,16 @@ impl Host { &self, msg: &[u8], ) -> Result<[u8; BLS_G2_UNCOMPRESSED_SIZE], HostError> { - let dst: &[u8; 50] = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_"; - let aug = [0u8; 0]; let mut res = blst_p2::default(); unsafe { blst_hash_to_g2( &mut res, msg.as_ptr(), msg.len(), - dst.as_ptr(), - dst.len(), - aug.as_ptr(), - aug.len(), + BLS_G2_DST.as_ptr(), + BLS_G2_DST.len(), + [].as_ptr(), + 0, ); } let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; @@ -431,19 +430,3 @@ unsafe fn serialize_default_p2(affine: &blst_p2_affine) -> [u8; BLS_G2_UNCOMPRES blst_p2_serialize(out.as_mut_ptr(), &raw); out } - -unsafe fn serialize_to_affine_p1(raw: &blst_p1) -> [u8; BLS_G1_UNCOMPRESSED_SIZE] { - let mut out = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; - let affine = blst_p1_affine::default(); - blst_p1_affine_in_g1(&affine); - blst_p1_serialize(out.as_mut_ptr(), raw); - out -} - -unsafe fn serialize_to_affine_p2(raw: &blst_p2) -> [u8; BLS_G2_UNCOMPRESSED_SIZE] { - let mut out = [0u8; BLS_G2_UNCOMPRESSED_SIZE]; - let affine = blst_p2_affine::default(); - blst_p2_affine_in_g2(&affine); - blst_p2_serialize(out.as_mut_ptr(), raw); - out -}