diff --git a/Cargo.lock b/Cargo.lock index 000d5882f..b4e01f2b9 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.1" dependencies = [ "arbitrary", "backtrace", + "blst", "bytes-lit", "curve25519-dalek", "ecdsa", @@ -1647,6 +1676,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" @@ -2076,3 +2114,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..bc9600685 100644 --- a/soroban-env-common/env.json +++ b/soroban-env-common/env.json @@ -2022,6 +2022,166 @@ "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": "4", + "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": "5", + "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": "6", + "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": "7", + "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": "8", + "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": "9", + "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": "a", + "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." + }, + { + "export": "b", + "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": "c", + "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": "d", + "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": "e", + "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/Cargo.toml b/soroban-env-host/Cargo.toml index 1e9734cf1..549e4ee8d 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 } diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index 10d905fc3..fd41f7efe 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; @@ -2850,6 +2852,259 @@ 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| { + let scalar_array: Result<[u8; BLS_SCALAR_SIZE], _> = bytes.as_slice().try_into(); + scalar_array.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_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())?) + } + + 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| { + let scalar_array: Result<[u8; BLS_SCALAR_SIZE], _> = bytes.as_slice().try_into(); + scalar_array.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())?) + } + + 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())?) + } + // 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..aee595091 --- /dev/null +++ b/soroban-env-host/src/host/bls.rs @@ -0,0 +1,432 @@ +#![allow(dead_code)] + +use blst::{ + 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 crate::{ + xdr::{ScErrorCode, ScErrorType}, + Host, HostError, Val, +}; + +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 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 { + fp: [blst_fp { l: [0; 6] }; 2], + }; 3], + }; 2], +}; + +impl Host { + pub(crate) fn bls_g1_add_raw_internal( + &self, + 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; BLS_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; 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; 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_multiexp_raw_internal( + &self, + scalars: &[u8], + p_n: &[u8], + ) -> 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; 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 = 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) }; + } + + unsafe { + blst_p1_serialize(out.as_mut_ptr(), &res); + } + + Ok(out) + } + + pub(crate) fn bls_g2_add_raw_internal( + &self, + 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; BLS_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; 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; 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); + } + Ok(out) + } + + pub(crate) fn bls_g2_multiexp_raw_internal( + &self, + scalars: &[u8], + p_n: &[u8], + ) -> 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; 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 = 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 mut res = blst_p1::default(); + unsafe { + blst_hash_to_g1( + &mut res, + msg.as_ptr(), + msg.len(), + BLS_G1_DST.as_ptr(), + BLS_G1_DST.len(), + [].as_ptr(), + 0, + ); + } + 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 mut res = blst_p2::default(); + unsafe { + blst_hash_to_g2( + &mut res, + msg.as_ptr(), + msg.len(), + BLS_G2_DST.as_ptr(), + BLS_G2_DST.len(), + [].as_ptr(), + 0, + ); + } + 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( + 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 + } + + 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 fn g1_one() -> [u8; BLS_G1_UNCOMPRESSED_SIZE] { + unsafe { serialize_default_p1(&blst::BLS12_381_G1) } +} + +pub fn g1_zero() -> [u8; BLS_G1_UNCOMPRESSED_SIZE] { + let mut zero = [0u8; BLS_G1_UNCOMPRESSED_SIZE]; + zero[0] = 0x40; + zero +} + +pub fn g2_one() -> [u8; BLS_G2_UNCOMPRESSED_SIZE] { + unsafe { serialize_default_p2(&blst::BLS12_381_G2) } +} + +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 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 { + unsafe { + let mut scalar = blst_scalar::default(); + blst_scalar_from_lendian(&mut scalar, bytes.as_ptr()); + blst_scalar_fr_check(&scalar).then(|| { + let mut fr = blst_fr::default(); + blst_fr_from_scalar(&mut fr, &scalar); + fr + }) + } +} + +pub fn decode_p1_affine(bytes: &[u8; BLS_G1_UNCOMPRESSED_SIZE]) -> Option { + unsafe { + 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; BLS_G1_UNCOMPRESSED_SIZE]) -> Option { + decode_p1_affine(bytes).and_then(|p1_affine| unsafe { + let mut raw = blst_p1::default(); + blst_p1_from_affine(&mut raw, &p1_affine); + blst_p1_in_g1(&raw).then_some(raw) + }) +} + +pub fn decode_p2_affine(bytes: &[u8; BLS_G2_UNCOMPRESSED_SIZE]) -> Option { + unsafe { + 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; BLS_G2_UNCOMPRESSED_SIZE]) -> Option { + decode_p2_affine(bytes).and_then(|p2_affine| unsafe { + let mut raw = blst_p2::default(); + 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 +} 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 new file mode 100644 index 000000000..4e35cf806 --- /dev/null +++ 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 +}