diff --git a/.circleci/config.yml b/.circleci/config.yml
index 9ed8c89b58..a21a30f6a5 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -101,6 +101,7 @@ workflows:
                 - main
                 - /^[0-9]+\.[0-9]+$/
                 # Add your branch here if benchmarking matters to your work
+                - bls12_381
                 - secp256r1-support
       - coverage
   deploy:
@@ -248,13 +249,17 @@ jobs:
           keys:
             - cargocache-v2-package_crypto-rust:1.73-{{ checksum "Cargo.lock" }}
       - run:
-          name: Build
+          name: Build (no features)
           working_directory: ~/project/packages/crypto
-          command: cargo build --locked
+          command: cargo build --locked --no-default-features
+      - run:
+          name: Build (all features)
+          working_directory: ~/project/packages/crypto
+          command: cargo build --locked --features std
       - run:
           name: Run tests
           working_directory: ~/project/packages/crypto
-          command: cargo test --locked
+          command: cargo test --locked --features std
       - save_cache:
           paths:
             - /usr/local/cargo/registry
@@ -1039,7 +1044,7 @@ jobs:
       - run:
           name: Run crypto benchmarks
           working_directory: ~/project/packages/crypto
-          command: cargo bench -- --color never --save-baseline crypto
+          command: cargo bench --features std -- --color never --save-baseline crypto
       - save_cache:
           paths:
             - /usr/local/cargo/registry
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f0982dfec..01c5b3fcb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,10 @@ and this project adheres to
   ([#2124])
 - cosmwasm-vm: Read the state version from Wasm modules and return them as part
   of `AnalyzeReport` ([#2129])
+- cosmwasm-vm: Add `bls12_381_aggregate_g1`, `bls12_381_aggregate_g2`,
+  `bls12_381_pairing_equality`, `bls12_381_hash_to_g1`, and
+  `bls12_381_hash_to_g2` to enable BLS12-381 curve operations, such as verifying
+  pairing equalities ([#2106])
 
 [#1983]: https://github.com/CosmWasm/cosmwasm/pull/1983
 [#2057]: https://github.com/CosmWasm/cosmwasm/pull/2057
@@ -46,6 +50,7 @@ and this project adheres to
 [#2092]: https://github.com/CosmWasm/cosmwasm/pull/2092
 [#2098]: https://github.com/CosmWasm/cosmwasm/pull/2098
 [#2099]: https://github.com/CosmWasm/cosmwasm/pull/2099
+[#2106]: https://github.com/CosmWasm/cosmwasm/pull/2106
 [#2107]: https://github.com/CosmWasm/cosmwasm/pull/2107
 [#2120]: https://github.com/CosmWasm/cosmwasm/pull/2120
 [#2124]: https://github.com/CosmWasm/cosmwasm/pull/2124
diff --git a/Cargo.lock b/Cargo.lock
index 532e697215..364bb2a743 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -126,6 +126,127 @@ version = "1.0.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -174,12 +295,28 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
 
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
 [[package]]
 name = "base64"
 version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
 
+[[package]]
+name = "base64-serde"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7"
+dependencies = [
+ "base64 0.21.7",
+ "serde",
+]
+
 [[package]]
 name = "bech32"
 version = "0.11.0"
@@ -194,9 +331,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.2"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
 
 [[package]]
 name = "bitvec"
@@ -418,7 +555,7 @@ dependencies = [
 name = "cosmwasm-core"
 version = "2.0.1"
 dependencies = [
- "base64",
+ "base64 0.22.0",
  "bnum",
  "cosmwasm-crypto",
  "cosmwasm-std",
@@ -438,17 +575,27 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "base64 0.22.0",
+ "base64-serde",
+ "cfg-if",
  "criterion",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "english-numbers",
+ "glob",
  "hex",
  "hex-literal",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
  "serde",
  "serde_json",
  "sha2",
@@ -494,7 +641,7 @@ dependencies = [
 name = "cosmwasm-std"
 version = "2.0.1"
 dependencies = [
- "base64",
+ "base64 0.22.0",
  "bech32",
  "chrono",
  "cosmwasm-core",
@@ -1229,6 +1376,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -1509,6 +1665,17 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
 [[package]]
 name = "num-integer"
 version = "0.1.45"
@@ -1521,9 +1688,9 @@ dependencies = [
 
 [[package]]
 name = "num-traits"
-version = "0.2.15"
+version = "0.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
 dependencies = [
  "autocfg",
 ]
@@ -1584,6 +1751,12 @@ dependencies = [
  "windows-sys 0.45.0",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.9"
@@ -2187,9 +2360,9 @@ dependencies = [
 
 [[package]]
 name = "subtle"
-version = "2.4.1"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
 
 [[package]]
 name = "syn"
@@ -2671,7 +2844,7 @@ version = "0.121.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab"
 dependencies = [
- "bitflags 2.4.2",
+ "bitflags 2.5.0",
  "indexmap 2.2.5",
  "semver",
 ]
diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock
index bf40b531fd..6cc04a7fc0 100644
--- a/contracts/burner/Cargo.lock
+++ b/contracts/burner/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -226,13 +347,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -840,6 +969,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -897,6 +1035,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1028,6 +1175,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1068,6 +1244,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1080,6 +1262,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1151,6 +1339,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock
index c949e716bc..03d070ef4a 100644
--- a/contracts/crypto-verify/Cargo.lock
+++ b/contracts/crypto-verify/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -221,13 +342,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -855,6 +984,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -918,6 +1056,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1055,6 +1202,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1095,6 +1271,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1117,6 +1299,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1188,6 +1376,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/crypto-verify/schema/crypto-verify.json b/contracts/crypto-verify/schema/crypto-verify.json
index 56eba68e5d..728ed09898 100644
--- a/contracts/crypto-verify/schema/crypto-verify.json
+++ b/contracts/crypto-verify/schema/crypto-verify.json
@@ -386,6 +386,114 @@
           }
         },
         "additionalProperties": false
+      },
+      {
+        "description": "BLS12-381 pairing equality verification (where the key is an element of G1)",
+        "type": "object",
+        "required": [
+          "verify_bls12_pairing_equality_g1"
+        ],
+        "properties": {
+          "verify_bls12_pairing_equality_g1": {
+            "type": "object",
+            "required": [
+              "dst",
+              "msg",
+              "pubkey",
+              "signature"
+            ],
+            "properties": {
+              "dst": {
+                "description": "The `dst` component used to hash the message to the curve",
+                "allOf": [
+                  {
+                    "$ref": "#/definitions/Binary"
+                  }
+                ]
+              },
+              "msg": {
+                "description": "The message that should be verified",
+                "allOf": [
+                  {
+                    "$ref": "#/definitions/Binary"
+                  }
+                ]
+              },
+              "pubkey": {
+                "description": "The public key point in its compressed format (element of G1)",
+                "allOf": [
+                  {
+                    "$ref": "#/definitions/Binary"
+                  }
+                ]
+              },
+              "signature": {
+                "description": "The signature point in its compressed format (element of G2)",
+                "allOf": [
+                  {
+                    "$ref": "#/definitions/Binary"
+                  }
+                ]
+              }
+            },
+            "additionalProperties": false
+          }
+        },
+        "additionalProperties": false
+      },
+      {
+        "description": "BLS12-381 pairing equality verification (where the key is an element of G2)",
+        "type": "object",
+        "required": [
+          "verify_bls12_pairing_equality_g2"
+        ],
+        "properties": {
+          "verify_bls12_pairing_equality_g2": {
+            "type": "object",
+            "required": [
+              "dst",
+              "msg",
+              "pubkey",
+              "signature"
+            ],
+            "properties": {
+              "dst": {
+                "description": "The `dst` component used to hash the message to the curve",
+                "allOf": [
+                  {
+                    "$ref": "#/definitions/Binary"
+                  }
+                ]
+              },
+              "msg": {
+                "description": "The message that should be verified",
+                "allOf": [
+                  {
+                    "$ref": "#/definitions/Binary"
+                  }
+                ]
+              },
+              "pubkey": {
+                "description": "The public key point in its compressed format (element of G2)",
+                "allOf": [
+                  {
+                    "$ref": "#/definitions/Binary"
+                  }
+                ]
+              },
+              "signature": {
+                "description": "The signature point in its compressed format (element of G1)",
+                "allOf": [
+                  {
+                    "$ref": "#/definitions/Binary"
+                  }
+                ]
+              }
+            },
+            "additionalProperties": false
+          }
+        },
+        "additionalProperties": false
       }
     ],
     "definitions": {
@@ -419,6 +527,34 @@
       },
       "additionalProperties": false
     },
+    "verify_bls12_pairing_equality_g1": {
+      "$schema": "http://json-schema.org/draft-07/schema#",
+      "title": "VerifyResponse",
+      "type": "object",
+      "required": [
+        "verifies"
+      ],
+      "properties": {
+        "verifies": {
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
+    "verify_bls12_pairing_equality_g2": {
+      "$schema": "http://json-schema.org/draft-07/schema#",
+      "title": "VerifyResponse",
+      "type": "object",
+      "required": [
+        "verifies"
+      ],
+      "properties": {
+        "verifies": {
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
     "verify_cosmos_signature": {
       "$schema": "http://json-schema.org/draft-07/schema#",
       "title": "VerifyResponse",
diff --git a/contracts/crypto-verify/schema/raw/query.json b/contracts/crypto-verify/schema/raw/query.json
index 76aa8b0ef7..f71e68a751 100644
--- a/contracts/crypto-verify/schema/raw/query.json
+++ b/contracts/crypto-verify/schema/raw/query.json
@@ -375,6 +375,114 @@
         }
       },
       "additionalProperties": false
+    },
+    {
+      "description": "BLS12-381 pairing equality verification (where the key is an element of G1)",
+      "type": "object",
+      "required": [
+        "verify_bls12_pairing_equality_g1"
+      ],
+      "properties": {
+        "verify_bls12_pairing_equality_g1": {
+          "type": "object",
+          "required": [
+            "dst",
+            "msg",
+            "pubkey",
+            "signature"
+          ],
+          "properties": {
+            "dst": {
+              "description": "The `dst` component used to hash the message to the curve",
+              "allOf": [
+                {
+                  "$ref": "#/definitions/Binary"
+                }
+              ]
+            },
+            "msg": {
+              "description": "The message that should be verified",
+              "allOf": [
+                {
+                  "$ref": "#/definitions/Binary"
+                }
+              ]
+            },
+            "pubkey": {
+              "description": "The public key point in its compressed format (element of G1)",
+              "allOf": [
+                {
+                  "$ref": "#/definitions/Binary"
+                }
+              ]
+            },
+            "signature": {
+              "description": "The signature point in its compressed format (element of G2)",
+              "allOf": [
+                {
+                  "$ref": "#/definitions/Binary"
+                }
+              ]
+            }
+          },
+          "additionalProperties": false
+        }
+      },
+      "additionalProperties": false
+    },
+    {
+      "description": "BLS12-381 pairing equality verification (where the key is an element of G2)",
+      "type": "object",
+      "required": [
+        "verify_bls12_pairing_equality_g2"
+      ],
+      "properties": {
+        "verify_bls12_pairing_equality_g2": {
+          "type": "object",
+          "required": [
+            "dst",
+            "msg",
+            "pubkey",
+            "signature"
+          ],
+          "properties": {
+            "dst": {
+              "description": "The `dst` component used to hash the message to the curve",
+              "allOf": [
+                {
+                  "$ref": "#/definitions/Binary"
+                }
+              ]
+            },
+            "msg": {
+              "description": "The message that should be verified",
+              "allOf": [
+                {
+                  "$ref": "#/definitions/Binary"
+                }
+              ]
+            },
+            "pubkey": {
+              "description": "The public key point in its compressed format (element of G2)",
+              "allOf": [
+                {
+                  "$ref": "#/definitions/Binary"
+                }
+              ]
+            },
+            "signature": {
+              "description": "The signature point in its compressed format (element of G1)",
+              "allOf": [
+                {
+                  "$ref": "#/definitions/Binary"
+                }
+              ]
+            }
+          },
+          "additionalProperties": false
+        }
+      },
+      "additionalProperties": false
     }
   ],
   "definitions": {
diff --git a/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality.json b/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality.json
new file mode 100644
index 0000000000..a2cdc3461c
--- /dev/null
+++ b/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality.json
@@ -0,0 +1,14 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "VerifyResponse",
+  "type": "object",
+  "required": [
+    "verifies"
+  ],
+  "properties": {
+    "verifies": {
+      "type": "boolean"
+    }
+  },
+  "additionalProperties": false
+}
diff --git a/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality_g1.json b/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality_g1.json
new file mode 100644
index 0000000000..a2cdc3461c
--- /dev/null
+++ b/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality_g1.json
@@ -0,0 +1,14 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "VerifyResponse",
+  "type": "object",
+  "required": [
+    "verifies"
+  ],
+  "properties": {
+    "verifies": {
+      "type": "boolean"
+    }
+  },
+  "additionalProperties": false
+}
diff --git a/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality_g2.json b/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality_g2.json
new file mode 100644
index 0000000000..a2cdc3461c
--- /dev/null
+++ b/contracts/crypto-verify/schema/raw/response_to_verify_bls12_pairing_equality_g2.json
@@ -0,0 +1,14 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "VerifyResponse",
+  "type": "object",
+  "required": [
+    "verifies"
+  ],
+  "properties": {
+    "verifies": {
+      "type": "boolean"
+    }
+  },
+  "additionalProperties": false
+}
diff --git a/contracts/crypto-verify/src/bls12_381.rs b/contracts/crypto-verify/src/bls12_381.rs
new file mode 100644
index 0000000000..93d7120ffe
--- /dev/null
+++ b/contracts/crypto-verify/src/bls12_381.rs
@@ -0,0 +1,34 @@
+use cosmwasm_std::{Api, HashFunction, StdResult, BLS12_381_G1_GENERATOR, BLS12_381_G2_GENERATOR};
+
+/// Signature verification with public key in G1 (e.g. drand classic mainnet, ETH2 block headers).
+///
+/// See https://hackmd.io/@benjaminion/bls12-381#Verification.
+pub fn verify_g1(
+    api: &dyn Api,
+    signature: &[u8],
+    pubkey: &[u8],
+    msg: &[u8],
+    dst: &[u8],
+) -> StdResult<bool> {
+    // The H(m) from the docs
+    let msg_hash = api.bls12_381_hash_to_g2(HashFunction::Sha256, msg, dst)?;
+    api.bls12_381_pairing_equality(&BLS12_381_G1_GENERATOR, signature, pubkey, &msg_hash)
+        .map_err(Into::into)
+}
+
+/// Signature verification with public key in G2 (e.g. drand Quicknet)
+///
+/// See https://hackmd.io/@benjaminion/bls12-381#Verification in combination with
+/// https://hackmd.io/@benjaminion/bls12-381#Swapping-G1-and-G2.
+pub fn verify_g2(
+    api: &dyn Api,
+    signature: &[u8],
+    pubkey: &[u8],
+    msg: &[u8],
+    dst: &[u8],
+) -> StdResult<bool> {
+    // The H(m) from the docs
+    let msg_hash = api.bls12_381_hash_to_g1(HashFunction::Sha256, msg, dst)?;
+    api.bls12_381_pairing_equality(signature, &BLS12_381_G2_GENERATOR, &msg_hash, pubkey)
+        .map_err(Into::into)
+}
diff --git a/contracts/crypto-verify/src/contract.rs b/contracts/crypto-verify/src/contract.rs
index 90e141452d..e31654e127 100644
--- a/contracts/crypto-verify/src/contract.rs
+++ b/contracts/crypto-verify/src/contract.rs
@@ -112,6 +112,22 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<QueryResponse> {
             &r,
             &s,
         )?),
+        QueryMsg::VerifyBls12PairingEqualityG1 {
+            signature,
+            pubkey,
+            msg,
+            dst,
+        } => to_json_binary(&query_verify_bls12_pairing_g1(
+            deps, &signature, &pubkey, &msg, &dst,
+        )?),
+        QueryMsg::VerifyBls12PairingEqualityG2 {
+            signature,
+            pubkey,
+            msg,
+            dst,
+        } => to_json_binary(&query_verify_bls12_pairing_g2(
+            deps, &signature, &pubkey, &msg, &dst,
+        )?),
     }
 }
 
@@ -282,6 +298,28 @@ pub fn query_list_verifications(deps: Deps) -> StdResult<ListVerificationsRespon
     })
 }
 
+pub fn query_verify_bls12_pairing_g1(
+    deps: Deps,
+    signature: &[u8],
+    pubkey: &[u8],
+    msg: &[u8],
+    dst: &[u8],
+) -> StdResult<VerifyResponse> {
+    let verifies = crate::bls12_381::verify_g1(deps.api, signature, pubkey, msg, dst)?;
+    Ok(VerifyResponse { verifies })
+}
+
+pub fn query_verify_bls12_pairing_g2(
+    deps: Deps,
+    signature: &[u8],
+    pubkey: &[u8],
+    msg: &[u8],
+    dst: &[u8],
+) -> StdResult<VerifyResponse> {
+    let verifies = crate::bls12_381::verify_g2(deps.api, signature, pubkey, msg, dst)?;
+    Ok(VerifyResponse { verifies })
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/contracts/crypto-verify/src/lib.rs b/contracts/crypto-verify/src/lib.rs
index 07f5a57850..c06473adb2 100644
--- a/contracts/crypto-verify/src/lib.rs
+++ b/contracts/crypto-verify/src/lib.rs
@@ -1,3 +1,4 @@
+mod bls12_381;
 pub mod contract;
 mod ethereum;
 pub mod msg;
diff --git a/contracts/crypto-verify/src/msg.rs b/contracts/crypto-verify/src/msg.rs
index 0f0b2556fb..1ddb2b94ab 100644
--- a/contracts/crypto-verify/src/msg.rs
+++ b/contracts/crypto-verify/src/msg.rs
@@ -108,6 +108,30 @@ pub enum QueryMsg {
         /// The representation of this component is a big-endian encoded 256bit integer
         s: Binary,
     },
+    /// BLS12-381 pairing equality verification (where the key is an element of G1)
+    #[returns(VerifyResponse)]
+    VerifyBls12PairingEqualityG1 {
+        /// The signature point in its compressed format (element of G2)
+        signature: Binary,
+        /// The public key point in its compressed format (element of G1)
+        pubkey: Binary,
+        /// The message that should be verified
+        msg: Binary,
+        /// The `dst` component used to hash the message to the curve
+        dst: Binary,
+    },
+    /// BLS12-381 pairing equality verification (where the key is an element of G2)
+    #[returns(VerifyResponse)]
+    VerifyBls12PairingEqualityG2 {
+        /// The signature point in its compressed format (element of G1)
+        signature: Binary,
+        /// The public key point in its compressed format (element of G2)
+        pubkey: Binary,
+        /// The message that should be verified
+        msg: Binary,
+        /// The `dst` component used to hash the message to the curve
+        dst: Binary,
+    },
 }
 
 #[cw_serde]
diff --git a/contracts/crypto-verify/tests/integration.rs b/contracts/crypto-verify/tests/integration.rs
index 8aac729095..0c9c380b72 100644
--- a/contracts/crypto-verify/tests/integration.rs
+++ b/contracts/crypto-verify/tests/integration.rs
@@ -25,6 +25,7 @@ use cosmwasm_vm::testing::{
 };
 use cosmwasm_vm::{from_slice, Instance};
 use hex_literal::hex;
+use sha2::{Digest, Sha256};
 
 use crypto_verify::msg::{InstantiateMsg, ListVerificationsResponse, QueryMsg, VerifyResponse};
 
@@ -73,6 +74,25 @@ const WEBAUTHN_SIGNATURE_R: &[u8] =
 const WEBAUTHN_SIGNATURE_S: &[u8] =
     &hex!("7a4fef4d0b11187f95f69eefbb428df8ac799bbd9305066b1e9c9fe9a5bcf8c4");
 
+// See https://github.com/drand/kyber-bls12381/issues/22 and
+// https://github.com/drand/drand/pull/1249
+const DOMAIN_HASH_TO_G1: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
+const DOMAIN_HASH_TO_G2: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
+
+/// Public key League of Entropy Mainnet (curl -sS https://drand.cloudflare.com/info)
+const PK_LEO_MAINNET: [u8; 48] = hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31");
+
+// Tests from quicknet (https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/info)
+const PK_QUICKNET: [u8; 96] = hex!("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a");
+
+fn build_drand_message(round: u64, previous_signature: &[u8]) -> Vec<u8> {
+    Sha256::new()
+        .chain_update(previous_signature)
+        .chain_update(round.to_be_bytes())
+        .finalize()
+        .to_vec()
+}
+
 const DESERIALIZATION_LIMIT: usize = 20_000;
 
 fn setup() -> Instance<MockApi, MockStorage, MockQuerier> {
@@ -89,6 +109,76 @@ fn instantiate_works() {
     setup();
 }
 
+#[test]
+fn bls12_381_verifies_g1() {
+    let mut deps = setup();
+
+    let previous_signature = hex::decode("a609e19a03c2fcc559e8dae14900aaefe517cb55c840f6e69bc8e4f66c8d18e8a609685d9917efbfb0c37f058c2de88f13d297c7e19e0ab24813079efe57a182554ff054c7638153f9b26a60e7111f71a0ff63d9571704905d3ca6df0b031747").unwrap();
+    let signature = hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap();
+    let round: u64 = 72785;
+
+    let msg = build_drand_message(round, &previous_signature);
+
+    let verify_msg = QueryMsg::VerifyBls12PairingEqualityG1 {
+        signature: signature.into(),
+        pubkey: PK_LEO_MAINNET.into(),
+        msg: msg.into(),
+        dst: DOMAIN_HASH_TO_G2.into(),
+    };
+
+    let raw = query(&mut deps, mock_env(), verify_msg).unwrap();
+    let res: VerifyResponse = from_slice(&raw, DESERIALIZATION_LIMIT).unwrap();
+
+    assert_eq!(res, VerifyResponse { verifies: true });
+}
+
+#[test]
+fn bls12_381_verifies_g2() {
+    let mut deps = setup();
+
+    let signature = hex::decode("b75c69d0b72a5d906e854e808ba7e2accb1542ac355ae486d591aa9d43765482e26cd02df835d3546d23c4b13e0dfc92").unwrap();
+    let round: u64 = 123;
+
+    let msg = build_drand_message(round, b"");
+
+    let verify_msg = QueryMsg::VerifyBls12PairingEqualityG2 {
+        signature: signature.into(),
+        pubkey: PK_QUICKNET.into(),
+        msg: msg.into(),
+        dst: DOMAIN_HASH_TO_G1.into(),
+    };
+
+    let raw = query(&mut deps, mock_env(), verify_msg).unwrap();
+    let res: VerifyResponse = from_slice(&raw, DESERIALIZATION_LIMIT).unwrap();
+
+    assert_eq!(res, VerifyResponse { verifies: true });
+}
+
+#[test]
+fn bls12_381_errors() {
+    let mut deps = setup();
+
+    let mut previous_signature = hex::decode("a609e19a03c2fcc559e8dae14900aaefe517cb55c840f6e69bc8e4f66c8d18e8a609685d9917efbfb0c37f058c2de88f13d297c7e19e0ab24813079efe57a182554ff054c7638153f9b26a60e7111f71a0ff63d9571704905d3ca6df0b031747").unwrap();
+    let signature = hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap();
+    let round: u64 = 72785;
+
+    previous_signature[0] ^= 0x3;
+
+    let msg = build_drand_message(round, &previous_signature);
+
+    let verify_msg = QueryMsg::VerifyBls12PairingEqualityG1 {
+        signature: signature.into(),
+        pubkey: PK_LEO_MAINNET.into(),
+        msg: msg.into(),
+        dst: DOMAIN_HASH_TO_G2.into(),
+    };
+
+    let raw = query(&mut deps, mock_env(), verify_msg).unwrap();
+    let res: VerifyResponse = from_slice(&raw, DESERIALIZATION_LIMIT).unwrap();
+
+    assert_eq!(res, VerifyResponse { verifies: false });
+}
+
 #[test]
 fn cosmos_signature_verify_works() {
     let mut deps = setup();
diff --git a/contracts/cyberpunk/Cargo.lock b/contracts/cyberpunk/Cargo.lock
index 1508ee0e12..9a0da287bc 100644
--- a/contracts/cyberpunk/Cargo.lock
+++ b/contracts/cyberpunk/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayref"
 version = "0.3.6"
@@ -244,13 +365,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -900,6 +1029,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -983,6 +1121,15 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1120,6 +1267,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1160,6 +1336,12 @@ dependencies = [
  "windows-targets 0.48.0",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1172,6 +1354,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1243,6 +1431,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/empty/Cargo.lock b/contracts/empty/Cargo.lock
index 7f256a5b85..359bc8fb7c 100644
--- a/contracts/empty/Cargo.lock
+++ b/contracts/empty/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -839,6 +968,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -896,6 +1034,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1027,6 +1174,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1067,6 +1243,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1079,6 +1261,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1150,6 +1338,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock
index 4ade5373ba..6ad92b1e4f 100644
--- a/contracts/floaty/Cargo.lock
+++ b/contracts/floaty/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -841,6 +970,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -898,6 +1036,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1029,6 +1176,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1069,6 +1245,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1158,6 +1340,16 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_chacha"
 version = "0.3.1"
diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock
index 936911c156..b39d508281 100644
--- a/contracts/hackatom/Cargo.lock
+++ b/contracts/hackatom/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -842,6 +971,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -899,6 +1037,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1030,6 +1177,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1070,6 +1246,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1082,6 +1264,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1153,6 +1341,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock
index 1a26458f65..5c24b52a23 100644
--- a/contracts/ibc-reflect-send/Cargo.lock
+++ b/contracts/ibc-reflect-send/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -829,6 +958,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -897,6 +1035,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1028,6 +1175,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1068,6 +1244,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1080,6 +1262,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1151,6 +1339,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock
index 1149e062ad..a272c6a4fd 100644
--- a/contracts/ibc-reflect/Cargo.lock
+++ b/contracts/ibc-reflect/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -829,6 +958,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -897,6 +1035,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1028,6 +1175,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1068,6 +1244,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1080,6 +1262,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1151,6 +1339,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock
index 2e30b080d8..57fabaa518 100644
--- a/contracts/queue/Cargo.lock
+++ b/contracts/queue/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -829,6 +958,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -886,6 +1024,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1017,6 +1164,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1057,6 +1233,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1069,6 +1251,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1151,6 +1339,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock
index 6ae7c92024..0764b4cd14 100644
--- a/contracts/reflect/Cargo.lock
+++ b/contracts/reflect/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -829,6 +958,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -886,6 +1024,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1017,6 +1164,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1057,6 +1233,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1069,6 +1251,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1140,6 +1328,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock
index b3c391445c..15c5d6a243 100644
--- a/contracts/staking/Cargo.lock
+++ b/contracts/staking/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -829,6 +958,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -886,6 +1024,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "1.0.9"
@@ -1017,6 +1164,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1057,6 +1233,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1069,6 +1251,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1140,6 +1328,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/contracts/virus/Cargo.lock b/contracts/virus/Cargo.lock
index 770cbced79..9a60df1cd5 100644
--- a/contracts/virus/Cargo.lock
+++ b/contracts/virus/Cargo.lock
@@ -46,6 +46,127 @@ version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 
+[[package]]
+name = "ark-bls12-381"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba"
+dependencies = [
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+ "itertools",
+ "num-traits",
+ "rayon",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "digest",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rayon",
+ "rustc_version",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf"
+dependencies = [
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "derivative",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "digest",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+ "rayon",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.7.2"
@@ -215,13 +336,21 @@ dependencies = [
 name = "cosmwasm-crypto"
 version = "2.0.1"
 dependencies = [
+ "ark-bls12-381",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "cfg-if",
  "derive_more",
  "digest",
  "ecdsa",
  "ed25519-zebra",
  "k256",
+ "num-traits",
  "p256",
  "rand_core",
+ "rayon",
+ "sha2",
  "thiserror",
 ]
 
@@ -829,6 +958,15 @@ dependencies = [
  "ahash 0.7.8",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.11",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.14.3"
@@ -886,6 +1024,15 @@ dependencies = [
  "hashbrown 0.14.3",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.7"
@@ -1017,6 +1164,35 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
 
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "object"
 version = "0.25.3"
@@ -1057,6 +1233,12 @@ dependencies = [
  "windows-targets",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -1069,6 +1251,12 @@ version = "3.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "primeorder"
 version = "0.13.6"
@@ -1140,6 +1328,26 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
diff --git a/packages/core/src/errors/mod.rs b/packages/core/src/errors/mod.rs
index 60533e027d..6c10d5f80a 100644
--- a/packages/core/src/errors/mod.rs
+++ b/packages/core/src/errors/mod.rs
@@ -13,4 +13,4 @@ pub use core_error::{
 };
 pub use recover_pubkey_error::RecoverPubkeyError;
 pub use system_error::SystemError;
-pub use verification_error::VerificationError;
+pub use verification_error::{AggregationError, PairingEqualityError, VerificationError};
diff --git a/packages/core/src/errors/recover_pubkey_error.rs b/packages/core/src/errors/recover_pubkey_error.rs
index d9b432120d..6bf97e6d95 100644
--- a/packages/core/src/errors/recover_pubkey_error.rs
+++ b/packages/core/src/errors/recover_pubkey_error.rs
@@ -60,13 +60,17 @@ impl From<CryptoError> for RecoverPubkeyError {
     fn from(original: CryptoError) -> Self {
         match original {
             CryptoError::InvalidHashFormat { .. } => RecoverPubkeyError::InvalidHashFormat,
-            CryptoError::InvalidPubkeyFormat { .. } => panic!("Conversion not supported"),
             CryptoError::InvalidSignatureFormat { .. } => {
                 RecoverPubkeyError::InvalidSignatureFormat
             }
             CryptoError::GenericErr { .. } => RecoverPubkeyError::unknown_err(original.code()),
             CryptoError::InvalidRecoveryParam { .. } => RecoverPubkeyError::InvalidRecoveryParam,
-            CryptoError::BatchErr { .. } => panic!("Conversion not supported"),
+            CryptoError::Aggregation { .. }
+            | CryptoError::PairingEquality { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::InvalidPubkeyFormat { .. }
+            | CryptoError::InvalidPoint { .. }
+            | CryptoError::UnknownHashFunction { .. } => panic!("Conversion not supported"),
         }
     }
 }
diff --git a/packages/core/src/errors/verification_error.rs b/packages/core/src/errors/verification_error.rs
index c13139a980..3d9120ddd1 100644
--- a/packages/core/src/errors/verification_error.rs
+++ b/packages/core/src/errors/verification_error.rs
@@ -6,9 +6,31 @@ use super::BT;
 #[cfg(not(target_arch = "wasm32"))]
 use cosmwasm_crypto::CryptoError;
 
+#[derive(Display, Debug, PartialEq)]
+#[cfg_attr(feature = "std", derive(thiserror::Error))]
+pub enum AggregationError {
+    #[display("List of points is empty")]
+    Empty,
+    #[display("List is not an expected multiple")]
+    NotMultiple,
+}
+
+#[derive(Display, Debug, PartialEq)]
+#[cfg_attr(feature = "std", derive(thiserror::Error))]
+pub enum PairingEqualityError {
+    #[display("List is not a multiple of 48")]
+    NotMultipleG1,
+    #[display("List is not a multiple of 96")]
+    NotMultipleG2,
+    #[display("Not the same amount of points passed")]
+    UnequalPointAmount,
+}
+
 #[derive(Display, Debug)]
 #[cfg_attr(feature = "std", derive(thiserror::Error))]
 pub enum VerificationError {
+    #[display("Aggregation error: {source}")]
+    Aggregation { source: AggregationError },
     #[display("Batch error")]
     BatchErr,
     #[display("Generic error")]
@@ -21,6 +43,12 @@ pub enum VerificationError {
     InvalidPubkeyFormat,
     #[display("Invalid recovery parameter. Supported values: 0 and 1.")]
     InvalidRecoveryParam,
+    #[display("Invalid point")]
+    InvalidPoint,
+    #[display("Unknown hash function")]
+    UnknownHashFunction,
+    #[display("Aggregation pairing equality error: {source}")]
+    PairingEquality { source: PairingEqualityError },
     #[display("Unknown error: {error_code}")]
     UnknownErr { error_code: u32, backtrace: BT },
 }
@@ -38,6 +66,12 @@ impl VerificationError {
 impl PartialEq<VerificationError> for VerificationError {
     fn eq(&self, rhs: &VerificationError) -> bool {
         match self {
+            VerificationError::Aggregation { source: lhs_source } => {
+                matches!(rhs, VerificationError::Aggregation { source: rhs_source } if rhs_source == lhs_source)
+            }
+            VerificationError::PairingEquality { source: lhs_source } => {
+                matches!(rhs, VerificationError::PairingEquality { source: rhs_source } if rhs_source == lhs_source)
+            }
             VerificationError::BatchErr => matches!(rhs, VerificationError::BatchErr),
             VerificationError::GenericErr => matches!(rhs, VerificationError::GenericErr),
             VerificationError::InvalidHashFormat => {
@@ -52,6 +86,10 @@ impl PartialEq<VerificationError> for VerificationError {
             VerificationError::InvalidRecoveryParam => {
                 matches!(rhs, VerificationError::InvalidRecoveryParam)
             }
+            VerificationError::InvalidPoint => matches!(rhs, VerificationError::InvalidPoint),
+            VerificationError::UnknownHashFunction => {
+                matches!(rhs, VerificationError::UnknownHashFunction)
+            }
             VerificationError::UnknownErr { error_code, .. } => {
                 if let VerificationError::UnknownErr {
                     error_code: rhs_error_code,
@@ -71,12 +109,44 @@ impl PartialEq<VerificationError> for VerificationError {
 impl From<CryptoError> for VerificationError {
     fn from(original: CryptoError) -> Self {
         match original {
+            CryptoError::Aggregation {
+                source: cosmwasm_crypto::AggregationError::Empty,
+                ..
+            } => VerificationError::Aggregation {
+                source: AggregationError::Empty,
+            },
+            CryptoError::Aggregation {
+                source: cosmwasm_crypto::AggregationError::NotMultiple { .. },
+                ..
+            } => VerificationError::Aggregation {
+                source: AggregationError::NotMultiple,
+            },
+            CryptoError::PairingEquality {
+                source: cosmwasm_crypto::PairingEqualityError::NotMultipleG1 { .. },
+                ..
+            } => VerificationError::PairingEquality {
+                source: PairingEqualityError::NotMultipleG1,
+            },
+            CryptoError::PairingEquality {
+                source: cosmwasm_crypto::PairingEqualityError::NotMultipleG2 { .. },
+                ..
+            } => VerificationError::PairingEquality {
+                source: PairingEqualityError::NotMultipleG2,
+            },
+            CryptoError::PairingEquality {
+                source: cosmwasm_crypto::PairingEqualityError::UnequalPointAmount { .. },
+                ..
+            } => VerificationError::PairingEquality {
+                source: PairingEqualityError::UnequalPointAmount,
+            },
             CryptoError::InvalidHashFormat { .. } => VerificationError::InvalidHashFormat,
             CryptoError::InvalidPubkeyFormat { .. } => VerificationError::InvalidPubkeyFormat,
             CryptoError::InvalidSignatureFormat { .. } => VerificationError::InvalidSignatureFormat,
             CryptoError::GenericErr { .. } => VerificationError::GenericErr,
             CryptoError::InvalidRecoveryParam { .. } => VerificationError::InvalidRecoveryParam,
+            CryptoError::InvalidPoint { .. } => VerificationError::InvalidPoint,
             CryptoError::BatchErr { .. } => VerificationError::BatchErr,
+            CryptoError::UnknownHashFunction { .. } => VerificationError::UnknownHashFunction,
         }
     }
 }
diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs
index d538a87799..0537a84028 100644
--- a/packages/core/src/lib.rs
+++ b/packages/core/src/lib.rs
@@ -28,10 +28,11 @@ pub use crate::addresses::{instantiate2_address, Addr, CanonicalAddr, Instantiat
 pub use crate::binary::Binary;
 pub use crate::encoding::{from_base64, from_hex, to_base64, to_hex};
 pub use crate::errors::{
-    CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
-    CoinFromStrError, CoinsError, ConversionOverflowError, CoreError, CoreResult,
-    DivideByZeroError, DivisionError, OverflowError, OverflowOperation, RecoverPubkeyError,
-    RoundDownOverflowError, RoundUpOverflowError, SystemError, VerificationError,
+    AggregationError, CheckedFromRatioError, CheckedMultiplyFractionError,
+    CheckedMultiplyRatioError, CoinFromStrError, CoinsError, ConversionOverflowError, CoreError,
+    CoreResult, DivideByZeroError, DivisionError, OverflowError, OverflowOperation,
+    PairingEqualityError, RecoverPubkeyError, RoundDownOverflowError, RoundUpOverflowError,
+    SystemError, VerificationError,
 };
 pub use crate::hex_binary::HexBinary;
 pub use crate::math::{
diff --git a/packages/crypto/Cargo.toml b/packages/crypto/Cargo.toml
index a7f480a89e..1edfbb072e 100644
--- a/packages/crypto/Cargo.toml
+++ b/packages/crypto/Cargo.toml
@@ -9,24 +9,42 @@ license = "Apache-2.0"
 
 [features]
 default = []
-std = ["dep:thiserror"]
+std = [
+    "dep:ark-bls12-381",
+    "dep:ark-ec",
+    "dep:ark-ff",
+    "dep:ark-serialize",
+    "dep:num-traits",
+    "dep:rayon",
+    "dep:sha2",
+    "dep:thiserror"
+]
 
 [lib]
 # See https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
 bench = false
 
 [dependencies]
+ark-bls12-381 = { version = "0.4.0", optional = true }
+ark-ec = { version = "0.4.2", features = ["parallel"], optional = true }
+ark-ff = { version = "0.4.2", features = ["asm", "parallel"], optional = true }
+ark-serialize = { version = "0.4.2", optional = true }
+cfg-if = "1.0.0"
 derive_more = { version = "1.0.0-beta.6", default-features = false, features = ["display", "from"] }
-k256 = { version = "0.13.3", default-features = false, features = ["ecdsa"] }
-ed25519-zebra = { version = "4.0.3", default-features = false }
 digest = "0.10"
-rand_core = "0.6"
-# Not used directly, but needed to bump transitive dependency, see: https://github.com/CosmWasm/cosmwasm/pull/1899 for details.
-ecdsa = "0.16.2"
+ecdsa = "0.16.2" # Not used directly, but needed to bump transitive dependency, see: https://github.com/CosmWasm/cosmwasm/pull/1899 for details.
+ed25519-zebra = { version = "4.0.3", default-features = false }
+k256 = { version = "0.13.3", default-features = false, features = ["ecdsa"] }
+num-traits = { version = "0.2.18", optional = true }
 p256 = { version = "0.13.2", default-features = false, features = ["ecdsa"] }
+rand_core = "0.6"
+rayon = { version = "1.9.0", optional = true }
+sha2 = { version = "0.10", optional = true }
 thiserror = { version = "1.0.26", optional = true }
 
 [dev-dependencies]
+base64 = "0.22.0"
+base64-serde = "0.7.0"
 criterion = "0.5.1"
 rand_core = { version = "0.6", features = ["getrandom"] }
 serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] }
@@ -36,7 +54,9 @@ sha3 = "0.10"
 hex = { version = "0.4", features = ["serde"] }
 hex-literal = "0.4.1"
 english-numbers = "0.3"
+glob = "0.3.1"
 
 [[bench]]
 name = "main"
 harness = false
+required-features = ["std"]
diff --git a/packages/crypto/benches/main.rs b/packages/crypto/benches/main.rs
index ca1ba3077e..a3af20d839 100644
--- a/packages/crypto/benches/main.rs
+++ b/packages/crypto/benches/main.rs
@@ -1,6 +1,10 @@
+use ark_bls12_381::{G1Affine, G2Affine, G2Projective};
+use ark_ec::AffineRepr;
+use ark_ff::UniformRand;
+use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
 use criterion::{criterion_group, criterion_main, Criterion, PlottingBackend};
 use rand_core::OsRng;
-use std::time::Duration;
+use std::{hint::black_box, io, time::Duration};
 
 use english_numbers::convert_no_fmt;
 use hex_literal::hex;
@@ -12,8 +16,10 @@ use k256::ecdsa::SigningKey; // type alias
 use sha2::Sha256;
 
 use cosmwasm_crypto::{
-    ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify,
-    secp256r1_recover_pubkey, secp256r1_verify,
+    bls12_381_aggregate_g1, bls12_381_aggregate_g2, bls12_381_hash_to_g1, bls12_381_hash_to_g2,
+    bls12_381_pairing_equality, ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey,
+    secp256k1_verify, secp256r1_recover_pubkey, secp256r1_verify, HashFunction,
+    BLS12_381_G1_GENERATOR, BLS12_381_G1_POINT_LEN, BLS12_381_G2_POINT_LEN,
 };
 use std::cmp::min;
 
@@ -35,6 +41,14 @@ const COSMOS_ED25519_PUBLIC_KEY_HEX: &str =
 // Test data from https://tools.ietf.org/html/rfc8032#section-7.1
 const COSMOS_ED25519_TESTS_JSON: &str = "./testdata/ed25519_tests.json";
 
+// BLS test vector
+// Path: "packages/crypto/testdata/bls-tests/verify/verify_valid_case_2ea479adf8c40300.json"
+const BLS_PUBKEY: [u8; 48] = hex!("a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a");
+const BLS_MESSAGE: [u8; 32] =
+    hex!("5656565656565656565656565656565656565656565656565656565656565656");
+const BLS_DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
+const BLS_SIGNATURE: [u8; 96] = hex!("882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb");
+
 #[derive(Deserialize, Debug)]
 struct Encoded {
     #[serde(rename = "privkey")]
@@ -79,6 +93,154 @@ fn read_decode_cosmos_sigs() -> (Vec<Vec<u8>>, Vec<Vec<u8>>, Vec<Vec<u8>>) {
     (messages, signatures, public_keys)
 }
 
+fn bench_bls<M>(group: &mut criterion::BenchmarkGroup<'_, M>)
+where
+    M: criterion::measurement::Measurement,
+{
+    let two_pow_max = 8;
+    let num_random_points = 2_usize.pow(two_pow_max);
+
+    {
+        let random_points_g1: Vec<G1Affine> = (0..num_random_points)
+            .map(|_| G1Affine::rand(&mut OsRng))
+            .collect();
+        let mut g1_serialized = io::Cursor::new(Vec::new());
+        random_points_g1
+            .serialize_compressed(&mut g1_serialized)
+            .unwrap();
+        let g1_serialized = &g1_serialized.into_inner()[8..];
+
+        let random_points_g2: Vec<G2Affine> = (0..num_random_points)
+            .map(|_| G2Affine::rand(&mut OsRng))
+            .collect();
+        let mut g2_serialized = io::Cursor::new(Vec::new());
+        random_points_g2
+            .serialize_compressed(&mut g2_serialized)
+            .unwrap();
+        let g2_serialized = &g2_serialized.into_inner()[8..];
+
+        for i in 1..=two_pow_max {
+            let num_points = 2_usize.pow(i);
+            let points_to_aggregate_g1 = &g1_serialized[..num_points * BLS12_381_G1_POINT_LEN];
+            group.bench_function(format!("bls12_381_aggregate_g1_{num_points}"), |b| {
+                b.iter(|| bls12_381_aggregate_g1(points_to_aggregate_g1).unwrap());
+            });
+        }
+
+        for i in 1..=two_pow_max {
+            let num_points = 2_usize.pow(i);
+            let points_to_aggregate_g2 = &g2_serialized[..num_points * BLS12_381_G2_POINT_LEN];
+            group.bench_function(format!("bls12_381_aggregate_g2_{num_points}"), |b| {
+                b.iter(|| bls12_381_aggregate_g2(points_to_aggregate_g2).unwrap());
+            });
+        }
+    }
+
+    {
+        const MESSAGE: &[u8] = b"message";
+        const DST: &[u8] = b"dst";
+        let secret_keys: Vec<ark_bls12_381::Fr> = (0..num_random_points)
+            .map(|_| ark_bls12_381::Fr::rand(&mut OsRng))
+            .collect();
+        let public_keys: Vec<G1Affine> = secret_keys
+            .iter()
+            .map(|secret_key| G1Affine::generator() * secret_key)
+            .map(Into::into)
+            .collect();
+        let messages: Vec<G2Affine> = (0..num_random_points)
+            .map(|_| bls12_381_hash_to_g2(HashFunction::Sha256, MESSAGE, DST))
+            .map(|bytes| G2Affine::deserialize_compressed(&bytes[..]).unwrap())
+            .collect();
+        let signatures: Vec<G2Projective> = secret_keys
+            .iter()
+            .zip(messages.iter())
+            .map(|(secret_key, message)| *message * secret_key)
+            .collect();
+
+        for i in 1..=two_pow_max {
+            let num_points = 2_usize.pow(i);
+            let messages = &messages[..num_points];
+            let keys = &public_keys[..num_points];
+            let aggregated_signature: G2Affine =
+                signatures[..num_points].iter().sum::<G2Projective>().into();
+
+            let serialized_pubkeys: Vec<u8> = keys
+                .iter()
+                .flat_map(|key| {
+                    let mut serialized = [0_u8; 48];
+                    key.serialize_compressed(&mut serialized[..]).unwrap();
+                    serialized
+                })
+                .collect();
+
+            let serialized_messages: Vec<u8> = messages
+                .iter()
+                .flat_map(|message| {
+                    let mut serialized = [0_u8; 96];
+                    message.serialize_compressed(&mut serialized[..]).unwrap();
+                    serialized
+                })
+                .collect();
+
+            let mut serialized_signature = [0_u8; 96];
+            aggregated_signature
+                .serialize_compressed(&mut serialized_signature[..])
+                .unwrap();
+
+            group.bench_function(format!("bls12_381_pairing_equality_{num_points}"), |b| {
+                b.iter(|| {
+                    let is_valid = black_box(bls12_381_pairing_equality(
+                        &serialized_pubkeys,
+                        &serialized_messages,
+                        &BLS12_381_G1_GENERATOR,
+                        &serialized_signature,
+                    ))
+                    .unwrap();
+
+                    assert!(is_valid);
+                });
+            });
+        }
+    }
+
+    group.bench_function("bls12_381_hash_to_g1", |b| {
+        b.iter(|| {
+            bls12_381_hash_to_g1(
+                black_box(HashFunction::Sha256),
+                black_box(&BLS_MESSAGE),
+                black_box(BLS_DST),
+            )
+        });
+    });
+
+    group.bench_function("bls12_381_hash_to_g2", |b| {
+        b.iter(|| {
+            bls12_381_hash_to_g2(
+                black_box(HashFunction::Sha256),
+                black_box(&BLS_MESSAGE),
+                black_box(BLS_DST),
+            )
+        });
+    });
+
+    group.bench_function("bls12_381_verify", |b| {
+        let generator = BLS12_381_G1_GENERATOR;
+        let message = bls12_381_hash_to_g2(HashFunction::Sha256, &BLS_MESSAGE, BLS_DST);
+
+        b.iter(|| {
+            let is_equal = bls12_381_pairing_equality(
+                black_box(&BLS_PUBKEY),
+                &message,
+                &generator,
+                black_box(&BLS_SIGNATURE),
+            )
+            .unwrap();
+
+            assert!(is_equal);
+        });
+    });
+}
+
 fn bench_crypto(c: &mut Criterion) {
     let mut group = c.benchmark_group("Crypto");
 
@@ -137,6 +299,8 @@ fn bench_crypto(c: &mut Criterion) {
         });
     });
 
+    bench_bls(&mut group);
+
     group.bench_function("ed25519_verify", |b| {
         let message = hex::decode(COSMOS_ED25519_MSG_HEX).unwrap();
         let signature = hex::decode(COSMOS_ED25519_SIGNATURE_HEX).unwrap();
diff --git a/packages/crypto/src/bls12_318/aggregate.rs b/packages/crypto/src/bls12_318/aggregate.rs
new file mode 100644
index 0000000000..dfaa97990f
--- /dev/null
+++ b/packages/crypto/src/bls12_318/aggregate.rs
@@ -0,0 +1,241 @@
+use crate::{errors::Aggregation, CryptoError};
+
+use super::points::{g1_from_fixed, g2_from_fixed, G1, G2};
+
+const G1_POINT_SIZE: usize = 48;
+const G2_POINT_SIZE: usize = 96;
+
+/// Takes a list of points in G1 (48 bytes each) and aggregates them.
+///
+/// This is like Aggregate from <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05>
+/// but works for signatures as well as public keys.
+pub fn bls12_381_aggregate_g1(points: &[u8]) -> Result<[u8; 48], CryptoError> {
+    if points.is_empty() {
+        return Err(Aggregation::Empty.into());
+    } else if points.len() % G1_POINT_SIZE != 0 {
+        return Err(Aggregation::NotMultiple {
+            expected_multiple: G1_POINT_SIZE,
+            remainder: points.len() % G1_POINT_SIZE,
+        }
+        .into());
+    }
+
+    let points_count = points.len() / G1_POINT_SIZE;
+
+    use rayon::prelude::*;
+
+    let points: Vec<[u8; 48]> = points
+        .chunks_exact(G1_POINT_SIZE)
+        .map(|data| {
+            let mut buf = [0u8; 48];
+            buf[..].copy_from_slice(data);
+            buf
+        })
+        .collect();
+
+    let mut decoded_points = Vec::with_capacity(points_count);
+    points
+        .par_iter()
+        .map(g1_from_fixed)
+        .collect_into_vec(&mut decoded_points);
+
+    let out: Result<Vec<G1>, CryptoError> = decoded_points.into_iter().collect();
+    let out = out?;
+
+    let out = g1_sum(&out);
+
+    Ok(out.to_compressed())
+}
+
+/// Takes a list of points in G2 (96 bytes each) and aggregates them.
+///
+/// This is like Aggregate from <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05>
+/// but works for signatures as well as public keys.
+pub fn bls12_381_aggregate_g2(points: &[u8]) -> Result<[u8; 96], CryptoError> {
+    if points.is_empty() {
+        return Err(Aggregation::Empty.into());
+    } else if points.len() % G2_POINT_SIZE != 0 {
+        return Err(Aggregation::NotMultiple {
+            expected_multiple: G2_POINT_SIZE,
+            remainder: points.len() % G2_POINT_SIZE,
+        }
+        .into());
+    }
+
+    let points_count = points.len() / G2_POINT_SIZE;
+
+    use rayon::prelude::*;
+
+    let points: Vec<[u8; 96]> = points
+        .chunks_exact(G2_POINT_SIZE)
+        .map(|data| {
+            let mut buf = [0u8; 96];
+            buf[..].copy_from_slice(data);
+            buf
+        })
+        .collect();
+
+    let mut decoded_points = Vec::with_capacity(points_count);
+    points
+        .par_iter()
+        .map(g2_from_fixed)
+        .collect_into_vec(&mut decoded_points);
+
+    let out: Result<Vec<G2>, CryptoError> = decoded_points.into_iter().collect();
+    let out = out?;
+
+    let out = g2_sum(&out);
+
+    Ok(out.to_compressed())
+}
+
+/// Creates a sum of points in G1.
+///
+/// This is fast since math is done on projective points. Parallelization does not help here
+/// for ~500 elements.
+#[inline]
+fn g1_sum(elements: &[G1]) -> G1 {
+    elements.iter().sum()
+}
+
+/// Creates a sum of points in G2.
+///
+/// This is fast since math is done on projective points. Parallelization does not help here
+/// for ~500 elements.
+#[inline]
+fn g2_sum(elements: &[G2]) -> G2 {
+    elements.iter().sum()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::points::{g1_from_variable, g1s_from_variable};
+    use super::*;
+    use base64::engine::general_purpose::STANDARD;
+    use base64_serde::base64_serde_type;
+    use hex_literal::hex;
+
+    base64_serde_type!(Base64Standard, STANDARD);
+
+    #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+    struct EthPubkey(#[serde(with = "Base64Standard")] Vec<u8>);
+
+    #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+    struct EthHeaders {
+        public_keys: Vec<EthPubkey>,
+        #[serde(with = "Base64Standard")]
+        message: Vec<u8>,
+        #[serde(with = "Base64Standard")]
+        signature: Vec<u8>,
+        #[serde(with = "Base64Standard")]
+        aggregate_pubkey: Vec<u8>,
+    }
+
+    const ETH_HEADER_FILE: &str =
+        include_str!("../../testdata/eth-headers/1699693797.394876721s.json");
+
+    fn read_eth_header_file() -> EthHeaders {
+        serde_json::from_str(ETH_HEADER_FILE).unwrap()
+    }
+
+    /// Arbitrary point in G1
+    fn p1() -> G1 {
+        // Public key of classic League of Entropy Mainnet (curl -sS https://drand.cloudflare.com/info)
+        g1_from_fixed(&hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31")).unwrap()
+    }
+
+    /// Arbitrary point in G2
+    fn p2() -> G2 {
+        g2_from_fixed(&hex!("b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55")).unwrap()
+    }
+
+    #[test]
+    fn bls12_318_aggregate_g1_empty_err() {
+        let res = bls12_381_aggregate_g1(b"");
+        assert!(res.is_err());
+    }
+
+    #[test]
+    fn bls12_318_aggregate_g2_empty_err() {
+        let res = bls12_381_aggregate_g2(b"");
+        assert!(res.is_err());
+    }
+
+    #[test]
+    fn g1_sum_works() {
+        // no elements
+        let sum = g1_sum(&[]);
+        assert_eq!(sum, G1::identity());
+
+        // one element
+        let sum = g1_sum(&[G1::identity()]);
+        assert_eq!(sum, G1::identity());
+        let sum = g1_sum(&[p1()]);
+        assert_eq!(sum, p1());
+
+        {
+            let file = read_eth_header_file();
+
+            let pubkeys: Vec<&[u8]> = file.public_keys.iter().map(|m| m.0.as_slice()).collect();
+            let points: Vec<G1> = g1s_from_variable(&pubkeys)
+                .into_iter()
+                .map(|res| res.unwrap())
+                .collect();
+            let expected_sum = g1_from_variable(&file.aggregate_pubkey).unwrap();
+            let sum = g1_sum(&points);
+            assert_eq!(sum, expected_sum);
+        }
+    }
+
+    #[test]
+    fn g2_sum_works() {
+        // no elements
+        let sum = g2_sum(&[]);
+        assert_eq!(sum, G2::identity());
+
+        // single
+        let sum = g2_sum(&[p2()]);
+        assert_eq!(sum, p2());
+
+        // multiple 1
+        let a = g2_from_fixed(&hex!("b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55")).unwrap();
+        let b = g2_from_fixed(&hex!("b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9")).unwrap();
+        let c = g2_from_fixed(&hex!("948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115")).unwrap();
+        let expected = g2_from_fixed(&hex!("9683b3e6701f9a4b706709577963110043af78a5b41991b998475a3d3fd62abf35ce03b33908418efc95a058494a8ae504354b9f626231f6b3f3c849dfdeaf5017c4780e2aee1850ceaf4b4d9ce70971a3d2cfcd97b7e5ecf6759f8da5f76d31")).unwrap();
+        let sum = g2_sum(&[a.clone(), b.clone(), c.clone()]);
+        assert_eq!(sum, expected);
+        let sum = g2_sum(&[b.clone(), a.clone(), c.clone()]);
+        assert_eq!(sum, expected);
+        let sum = g2_sum(&[c.clone(), b.clone(), a.clone()]);
+        assert_eq!(sum, expected);
+
+        // multiple 2
+        let a = g2_from_fixed(&hex!("882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb")).unwrap();
+        let b = g2_from_fixed(&hex!("af1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe")).unwrap();
+        let c = g2_from_fixed(&hex!("a4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6")).unwrap();
+        let expected = g2_from_fixed(&hex!("ad38fc73846583b08d110d16ab1d026c6ea77ac2071e8ae832f56ac0cbcdeb9f5678ba5ce42bd8dce334cc47b5abcba40a58f7f1f80ab304193eb98836cc14d8183ec14cc77de0f80c4ffd49e168927a968b5cdaa4cf46b9805be84ad7efa77b")).unwrap();
+        let sum = g2_sum(&[a.clone(), b.clone(), c.clone()]);
+        assert_eq!(sum, expected);
+        let sum = g2_sum(&[b.clone(), a.clone(), c.clone()]);
+        assert_eq!(sum, expected);
+        let sum = g2_sum(&[c.clone(), b.clone(), a.clone()]);
+        assert_eq!(sum, expected);
+
+        // multiple 3
+        let a = g2_from_fixed(&hex!("91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121")).unwrap();
+        let b = g2_from_fixed(&hex!("9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df")).unwrap();
+        let c = g2_from_fixed(&hex!("ae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9")).unwrap();
+        let expected = g2_from_fixed(&hex!("9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfc4ff1d930")).unwrap();
+        let sum = g2_sum(&[a.clone(), b.clone(), c.clone()]);
+        assert_eq!(sum, expected);
+        let sum = g2_sum(&[b.clone(), a.clone(), c.clone()]);
+        assert_eq!(sum, expected);
+        let sum = g2_sum(&[c.clone(), b.clone(), a.clone()]);
+        assert_eq!(sum, expected);
+
+        // infinity
+        let inf = g2_from_fixed(&hex!("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")).unwrap();
+        let sum = g2_sum(&[inf.clone()]);
+        assert_eq!(sum, inf);
+    }
+}
diff --git a/packages/crypto/src/bls12_318/constants.rs b/packages/crypto/src/bls12_318/constants.rs
new file mode 100644
index 0000000000..8281719cf5
--- /dev/null
+++ b/packages/crypto/src/bls12_318/constants.rs
@@ -0,0 +1,87 @@
+pub const BLS12_381_G1_POINT_LEN: usize = 48;
+pub const BLS12_381_G2_POINT_LEN: usize = 96;
+
+/// A generator in G1 (in compressed serialization).
+///
+/// This can be used directly for signature verification
+/// (see e.g. https://twitter.com/simon_warta/status/1786342207106019765)
+pub const BLS12_381_G1_GENERATOR: [u8; BLS12_381_G1_POINT_LEN] = [
+    151, 241, 211, 167, 49, 151, 215, 148, 38, 149, 99, 140, 79, 169, 172, 15, 195, 104, 140, 79,
+    151, 116, 185, 5, 161, 78, 58, 63, 23, 27, 172, 88, 108, 85, 232, 63, 249, 122, 26, 239, 251,
+    58, 240, 10, 219, 34, 198, 187,
+];
+
+/// A generator in G2 (in compressed serialization).
+///
+/// This can be used directly for signature verification
+/// (see e.g. https://twitter.com/simon_warta/status/1786342207106019765)
+pub const BLS12_381_G2_GENERATOR: [u8; BLS12_381_G2_POINT_LEN] = [
+    147, 224, 43, 96, 82, 113, 159, 96, 125, 172, 211, 160, 136, 39, 79, 101, 89, 107, 208, 208,
+    153, 32, 182, 26, 181, 218, 97, 187, 220, 127, 80, 73, 51, 76, 241, 18, 19, 148, 93, 87, 229,
+    172, 125, 5, 93, 4, 43, 126, 2, 74, 162, 178, 240, 143, 10, 145, 38, 8, 5, 39, 45, 197, 16, 81,
+    198, 228, 122, 212, 250, 64, 59, 2, 180, 81, 11, 100, 122, 227, 209, 119, 11, 172, 3, 38, 168,
+    5, 187, 239, 212, 128, 86, 200, 193, 33, 189, 184,
+];
+
+#[cfg(all(test, feature = "std"))]
+mod test {
+    use ark_bls12_381::{G1Affine, G2Affine};
+    use ark_ec::AffineRepr;
+    use ark_serialize::CanonicalSerialize;
+    use hex_literal::hex;
+
+    use super::{
+        BLS12_381_G1_GENERATOR, BLS12_381_G1_POINT_LEN, BLS12_381_G2_GENERATOR,
+        BLS12_381_G2_POINT_LEN,
+    };
+
+    fn bls12_381_g1_generator() -> [u8; BLS12_381_G1_POINT_LEN] {
+        let mut point = [0_u8; BLS12_381_G1_POINT_LEN];
+        G1Affine::generator()
+            .serialize_compressed(&mut point[..])
+            .unwrap();
+
+        point
+    }
+
+    fn bls12_381_g2_generator() -> [u8; BLS12_381_G2_POINT_LEN] {
+        let mut point = [0_u8; BLS12_381_G2_POINT_LEN];
+        G2Affine::generator()
+            .serialize_compressed(&mut point[..])
+            .unwrap();
+
+        point
+    }
+
+    // Note about the bitwise OR operation on the X coordinates:
+    //
+    // The first bit of the x-coordinate sets the "compression" flag. The most significant three bits of a G1/G2 coordinate are used for storing some information.
+    // If we didn't do that to the output, the constants wouldn't check out due to the constants being constructed by a standard adhereing BLS library,
+    // where it set the compression flag since it's the standard way of serializing the points.
+    //
+    // Ref: https://github.com/zcash/librustzcash/blob/6e0364cd42a2b3d2b958a54771ef51a8db79dd29/pairing/src/bls12_381/README.md#serialization
+
+    #[test]
+    fn g1_generator_correct() {
+        // Source: <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-02#section-4.3.2>
+        //
+        // See the `x` coordinate
+        let mut generator = hex!("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb");
+        generator[0] |= 0b1000_0000;
+        assert_eq!(generator, bls12_381_g1_generator());
+        assert_eq!(bls12_381_g1_generator(), BLS12_381_G1_GENERATOR);
+    }
+
+    #[test]
+    fn g2_generator_correct() {
+        // Source: <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-pairing-friendly-curves-02#section-4.3.2>
+        //
+        // $$
+        // G2_{raw} = x'_1 || x'_0
+        // $$
+        let mut generator = hex!("13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8");
+        generator[0] |= 0b1000_0000;
+        assert_eq!(generator, bls12_381_g2_generator());
+        assert_eq!(bls12_381_g2_generator(), BLS12_381_G2_GENERATOR);
+    }
+}
diff --git a/packages/crypto/src/bls12_318/hash.rs b/packages/crypto/src/bls12_318/hash.rs
new file mode 100644
index 0000000000..2d26e4aa81
--- /dev/null
+++ b/packages/crypto/src/bls12_318/hash.rs
@@ -0,0 +1,122 @@
+//!
+//! Note about the usage of `.unwrap()` here:
+//!
+//! Since the underlying curve implementation, when implemented sanely, should never request 255 curve elements at the same time,
+//! the expansion will always finish without exiting with an error (since that is the only "ABORT" condition).
+//!
+//! Therefore we can conclude, if the implementation is done as defined in the IETF publication, won't ever error out.
+//!
+//! IETF doc in question: <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-5.4.1>
+//!
+//! In addition to that I (@aumetra) skimmed through the tree of traits making up our hash-to-curve configuration,
+//! and I have not found a condition where an error is returned.
+//!
+//! ark crate versions that I looked at:
+//!
+//! - ark-bls12-381 v0.4.0
+//! - ark-ec v0.4.2
+//! - ark-ff v0.4.2
+//!
+
+use ark_bls12_381::{g1, g2};
+use ark_ec::{
+    hashing::{
+        curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurveBasedHasher, HashToCurve as _,
+    },
+    short_weierstrass::Projective,
+};
+use ark_ff::field_hashers::DefaultFieldHasher;
+use ark_serialize::CanonicalSerialize;
+use sha2::Sha256;
+
+use crate::{CryptoError, BLS12_381_G1_POINT_LEN, BLS12_381_G2_POINT_LEN};
+
+type HashToCurve<CurveConfig, Hash> =
+    MapToCurveBasedHasher<Projective<CurveConfig>, DefaultFieldHasher<Hash>, WBMap<CurveConfig>>;
+
+#[derive(Clone, Copy, Debug)]
+#[non_exhaustive]
+pub enum HashFunction {
+    Sha256 = 0,
+}
+
+#[doc(hidden)]
+impl HashFunction {
+    pub fn from_u32(idx: u32) -> Result<Self, CryptoError> {
+        let hash = match idx {
+            0 => Self::Sha256,
+            _ => return Err(CryptoError::unknown_hash_function()),
+        };
+
+        Ok(hash)
+    }
+}
+
+pub fn bls12_381_hash_to_g1(
+    hash: HashFunction,
+    msg: &[u8],
+    dst: &[u8],
+) -> [u8; BLS12_381_G1_POINT_LEN] {
+    let point = match hash {
+        HashFunction::Sha256 => HashToCurve::<g1::Config, Sha256>::new(dst)
+            .unwrap()
+            .hash(msg)
+            .unwrap(),
+    };
+
+    let mut serialized = [0; BLS12_381_G1_POINT_LEN];
+    point.serialize_compressed(&mut serialized[..]).unwrap();
+    serialized
+}
+
+pub fn bls12_381_hash_to_g2(
+    hash: HashFunction,
+    msg: &[u8],
+    dst: &[u8],
+) -> [u8; BLS12_381_G2_POINT_LEN] {
+    let point = match hash {
+        HashFunction::Sha256 => HashToCurve::<g2::Config, Sha256>::new(dst)
+            .unwrap()
+            .hash(msg)
+            .unwrap(),
+    };
+
+    let mut serialized = [0; BLS12_381_G2_POINT_LEN];
+    point.serialize_compressed(&mut serialized[..]).unwrap();
+    serialized
+}
+
+#[cfg(test)]
+mod test {
+    use hex_literal::hex;
+
+    use crate::{bls12_381_hash_to_g1, bls12_381_hash_to_g2, HashFunction};
+
+    #[test]
+    fn hash_to_g1_works() {
+        // See: <https://datatracker.ietf.org/doc/rfc9380/>; Section J.9.1
+
+        let msg = b"abc";
+        let dst = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
+
+        let hashed_point = bls12_381_hash_to_g1(HashFunction::Sha256, msg, dst);
+        let mut serialized_expected_compressed = hex!("03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f6903");
+        // Set the compression tag
+        serialized_expected_compressed[0] |= 0b1000_0000;
+
+        assert_eq!(hashed_point, serialized_expected_compressed);
+    }
+
+    #[test]
+    fn hash_to_g2_works() {
+        let msg = b"abc";
+        let dst = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_";
+
+        let hashed_point = bls12_381_hash_to_g2(HashFunction::Sha256, msg, dst);
+        let mut serialized_expected_compressed = hex!("139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd802c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e6");
+        // Set the compression tag
+        serialized_expected_compressed[0] |= 0b1000_0000;
+
+        assert_eq!(hashed_point, serialized_expected_compressed);
+    }
+}
diff --git a/packages/crypto/src/bls12_318/mod.rs b/packages/crypto/src/bls12_318/mod.rs
new file mode 100644
index 0000000000..719b0b59c1
--- /dev/null
+++ b/packages/crypto/src/bls12_318/mod.rs
@@ -0,0 +1,19 @@
+mod constants;
+
+pub use self::constants::{
+    BLS12_381_G1_GENERATOR, BLS12_381_G1_POINT_LEN, BLS12_381_G2_GENERATOR, BLS12_381_G2_POINT_LEN,
+};
+
+cfg_if::cfg_if! {
+    if #[cfg(feature = "std")] {
+        mod aggregate;
+        mod hash;
+        mod pairing;
+        mod points;
+
+        pub use self::aggregate::{bls12_381_aggregate_g1, bls12_381_aggregate_g2};
+        pub use self::hash::{bls12_381_hash_to_g1, bls12_381_hash_to_g2, HashFunction};
+        pub use self::pairing::bls12_381_pairing_equality;
+        pub use self::points::{bls12_381_g1_is_identity, bls12_381_g2_is_identity};
+    }
+}
diff --git a/packages/crypto/src/bls12_318/pairing.rs b/packages/crypto/src/bls12_318/pairing.rs
new file mode 100644
index 0000000000..6e630722cc
--- /dev/null
+++ b/packages/crypto/src/bls12_318/pairing.rs
@@ -0,0 +1,233 @@
+use core::ops::Neg;
+
+use crate::{errors::PairingEquality, CryptoError, BLS12_381_G1_POINT_LEN, BLS12_381_G2_POINT_LEN};
+
+use super::points::{g1_from_variable, g2_from_variable};
+use ark_bls12_381::Bls12_381;
+use ark_ec::{
+    bls12::{G1Prepared, G2Prepared},
+    pairing::Pairing,
+};
+use num_traits::Zero;
+use rayon::{
+    iter::{IndexedParallelIterator, ParallelIterator},
+    slice::ParallelSlice,
+};
+
+pub fn bls12_381_pairing_equality(
+    ps: &[u8],
+    qs: &[u8],
+    r: &[u8],
+    s: &[u8],
+) -> Result<bool, CryptoError> {
+    if ps.len() % BLS12_381_G1_POINT_LEN != 0 {
+        return Err(PairingEquality::NotMultipleG1 {
+            remainder: ps.len() % BLS12_381_G1_POINT_LEN,
+        }
+        .into());
+    } else if qs.len() % BLS12_381_G2_POINT_LEN != 0 {
+        return Err(PairingEquality::NotMultipleG2 {
+            remainder: qs.len() % BLS12_381_G2_POINT_LEN,
+        }
+        .into());
+    } else if (ps.len() / BLS12_381_G1_POINT_LEN) != (qs.len() / BLS12_381_G2_POINT_LEN) {
+        return Err(PairingEquality::UnequalPointAmount {
+            left: ps.len() / BLS12_381_G1_POINT_LEN,
+            right: qs.len() / BLS12_381_G2_POINT_LEN,
+        }
+        .into());
+    }
+
+    let p_iter = ps
+        .par_chunks_exact(BLS12_381_G1_POINT_LEN)
+        .map(g1_from_variable)
+        .chain([g1_from_variable(r).map(Neg::neg)])
+        .map(|g1_res| g1_res.map(|g1| G1Prepared::from(g1.0)));
+
+    let q_iter = qs
+        .par_chunks_exact(BLS12_381_G2_POINT_LEN)
+        .map(g2_from_variable)
+        .chain([g2_from_variable(s)])
+        .map(|g2_res| g2_res.map(|g2| G2Prepared::from(g2.0)));
+
+    let pq_pairs: Vec<_> = p_iter
+        .zip_eq(q_iter)
+        .map(|(p_res, q_res)| Ok((p_res?, q_res?)))
+        .collect::<Result<_, CryptoError>>()?;
+
+    let (ps, qs): (Vec<_>, Vec<_>) = pq_pairs.into_iter().unzip();
+
+    Ok(Bls12_381::multi_pairing(ps, qs).is_zero())
+}
+
+#[cfg(test)]
+mod test {
+    use hex_literal::hex;
+    use sha2::{Digest, Sha256};
+
+    use crate::{
+        bls12_318::points::{g1_from_fixed, g2_from_fixed, g2_from_variable, G1},
+        bls12_381_hash_to_g2, bls12_381_pairing_equality, CryptoError, HashFunction,
+        PairingEqualityError,
+    };
+
+    // Let's directly go for something really cool and advanced:
+    // dRand compatibility of this API
+
+    // See https://github.com/drand/kyber-bls12381/issues/22 and
+    // https://github.com/drand/drand/pull/1249
+    const DOMAIN_HASH_TO_G2: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
+
+    /// Public key League of Entropy Mainnet (curl -sS https://drand.cloudflare.com/info)
+    const PK_LEO_MAINNET: [u8; 48] = hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31");
+
+    /// The identity of G1 (the point at infinity).
+    ///
+    /// See https://docs.rs/bls12_381/latest/bls12_381/notes/serialization/index.html for encoding info.
+    const G1_IDENTITY: [u8; 48] = [
+        0b11000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    ];
+
+    /// The identity of G2 (the point at infinity).
+    ///
+    /// See https://docs.rs/bls12_381/latest/bls12_381/notes/serialization/index.html for encoding info.
+    const G2_IDENTITY: [u8; 96] = [
+        0b11000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0,
+    ];
+
+    fn build_message(round: u64, previous_signature: &[u8]) -> digest::Output<Sha256> {
+        Sha256::new()
+            .chain_update(previous_signature)
+            .chain_update(round.to_be_bytes())
+            .finalize()
+    }
+
+    #[test]
+    fn pairing_equality_works() {
+        let previous_signature = hex::decode("a609e19a03c2fcc559e8dae14900aaefe517cb55c840f6e69bc8e4f66c8d18e8a609685d9917efbfb0c37f058c2de88f13d297c7e19e0ab24813079efe57a182554ff054c7638153f9b26a60e7111f71a0ff63d9571704905d3ca6df0b031747").unwrap();
+        let signature = hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap();
+        let round: u64 = 72785;
+
+        let key = g1_from_fixed(&PK_LEO_MAINNET).unwrap();
+        let sigma = g2_from_variable(&signature).unwrap();
+        let g1 = G1::generator();
+        let msg = build_message(round, &previous_signature);
+        let g2_msg = bls12_381_hash_to_g2(HashFunction::Sha256, msg.as_slice(), DOMAIN_HASH_TO_G2);
+
+        assert!(bls12_381_pairing_equality(
+            &g1.to_compressed(),
+            &sigma.to_compressed(),
+            &PK_LEO_MAINNET,
+            &g2_msg
+        )
+        .unwrap());
+
+        // Do this in a separate scope to not shadow with wrong values
+        {
+            // Wrong round -> Therefore wrong hashed G2 point
+            #[allow(clippy::unusual_byte_groupings)]
+            let msg = build_message(0xDEAD_2_BAD, &previous_signature);
+            let g2_msg =
+                bls12_381_hash_to_g2(HashFunction::Sha256, msg.as_slice(), DOMAIN_HASH_TO_G2);
+
+            assert!(!bls12_381_pairing_equality(
+                &g1.to_compressed(),
+                &sigma.to_compressed(),
+                &PK_LEO_MAINNET,
+                &g2_msg
+            )
+            .unwrap());
+        }
+
+        // curl -sS https://drand.cloudflare.com/public/1
+        let previous_signature =
+            hex::decode("176f93498eac9ca337150b46d21dd58673ea4e3581185f869672e59fa4cb390a")
+                .unwrap();
+        let signature = hex::decode("8d61d9100567de44682506aea1a7a6fa6e5491cd27a0a0ed349ef6910ac5ac20ff7bc3e09d7c046566c9f7f3c6f3b10104990e7cb424998203d8f7de586fb7fa5f60045417a432684f85093b06ca91c769f0e7ca19268375e659c2a2352b4655").unwrap();
+        let round: u64 = 1;
+
+        // Aggregate things down
+        let aggregated_key = &key + &key;
+        let aggregated_sigma = &sigma + &g2_from_variable(&signature).unwrap();
+        let aggregated_g1 = &g1 + &g1;
+        let aggregated_msg = &g2_from_fixed(&g2_msg).unwrap()
+            + &g2_from_fixed(&bls12_381_hash_to_g2(
+                HashFunction::Sha256,
+                build_message(round, &previous_signature).as_slice(),
+                DOMAIN_HASH_TO_G2,
+            ))
+            .unwrap();
+
+        assert!(bls12_381_pairing_equality(
+            &aggregated_g1.to_compressed(),
+            &aggregated_sigma.to_compressed(),
+            &aggregated_key.to_compressed(),
+            &aggregated_msg.to_compressed()
+        )
+        .unwrap());
+    }
+
+    /// This tests 1 == e(a, b) as there is no term on the left-hand side.
+    /// This is true for `a` or `b` being the point at infinity. See
+    /// https://eips.ethereum.org/EIPS/eip-2537#test-cases
+    #[test]
+    fn pairing_equality_works_for_empty_lhs() {
+        // a and b not point at infinity (Non-degeneracy)
+        let a = PK_LEO_MAINNET;
+        let b = bls12_381_hash_to_g2(HashFunction::Sha256, b"blub", DOMAIN_HASH_TO_G2);
+        let equal = bls12_381_pairing_equality(&[], &[], &a, &b).unwrap();
+        assert!(!equal);
+
+        // a point at infinity
+        let a = G1_IDENTITY;
+        let b = bls12_381_hash_to_g2(HashFunction::Sha256, b"blub", DOMAIN_HASH_TO_G2);
+        let equal = bls12_381_pairing_equality(&[], &[], &a, &b).unwrap();
+        assert!(equal);
+
+        // b point at infinity
+        let a = PK_LEO_MAINNET;
+        let b = G2_IDENTITY;
+        let equal = bls12_381_pairing_equality(&[], &[], &a, &b).unwrap();
+        assert!(equal);
+
+        // a and b point at infinity
+        let a = G1_IDENTITY;
+        let b = G2_IDENTITY;
+        let equal = bls12_381_pairing_equality(&[], &[], &a, &b).unwrap();
+        assert!(equal);
+    }
+
+    #[test]
+    fn pairing_equality_error_cases_work() {
+        let result = bls12_381_pairing_equality(&[12], &[0; 96], &[12], &[12]);
+        assert!(matches!(
+            result,
+            Err(CryptoError::PairingEquality {
+                source: PairingEqualityError::NotMultipleG1 { remainder: 1 },
+                ..
+            })
+        ));
+
+        let result = bls12_381_pairing_equality(&[0; 48], &[12], &[12], &[12]);
+        assert!(matches!(
+            result,
+            Err(CryptoError::PairingEquality {
+                source: PairingEqualityError::NotMultipleG2 { remainder: 1 },
+                ..
+            })
+        ));
+
+        let result = bls12_381_pairing_equality(&[0; 96], &[0; 96], &[12], &[12]);
+        assert!(matches!(
+            result,
+            Err(CryptoError::PairingEquality {
+                source: PairingEqualityError::UnequalPointAmount { left: 2, right: 1 },
+                ..
+            })
+        ));
+    }
+}
diff --git a/packages/crypto/src/bls12_318/points.rs b/packages/crypto/src/bls12_318/points.rs
new file mode 100644
index 0000000000..a6e4b7c3e5
--- /dev/null
+++ b/packages/crypto/src/bls12_318/points.rs
@@ -0,0 +1,314 @@
+#![allow(unused)]
+
+use alloc::vec::Vec;
+use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
+use core::ops::Add;
+use core::{fmt, ops::Neg};
+
+use ark_bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective};
+use ark_ec::AffineRepr;
+use num_traits::Zero;
+
+use crate::errors::InvalidPoint;
+use crate::{CryptoError, BLS12_381_G1_POINT_LEN, BLS12_381_G2_POINT_LEN};
+
+/// Point on G1
+#[derive(Debug, PartialEq, Clone)]
+pub struct G1(pub(crate) G1Affine);
+
+impl G1 {
+    /// Creates the generaor in G1
+    #[inline]
+    pub fn generator() -> Self {
+        Self(G1Affine::generator())
+    }
+
+    /// Creates the identity element in G1 (point at infinity)
+    #[inline]
+    pub fn identity() -> Self {
+        Self(G1Affine::identity())
+    }
+
+    /// Check if the point is the identity element
+    #[inline]
+    pub fn is_identity(&self) -> bool {
+        self.0.is_zero()
+    }
+
+    #[inline]
+    pub fn to_compressed(&self) -> [u8; BLS12_381_G1_POINT_LEN] {
+        let mut serialized = [0; BLS12_381_G1_POINT_LEN];
+        self.0.serialize_compressed(&mut serialized[..]).unwrap();
+        serialized
+    }
+}
+
+impl Add<&G1> for &G1 {
+    type Output = G1;
+
+    fn add(self, rhs: &G1) -> G1 {
+        let sum = self.0 + G1Projective::from(rhs.0);
+        G1(sum.into())
+    }
+}
+
+impl Neg for G1 {
+    type Output = G1;
+
+    fn neg(self) -> Self::Output {
+        G1(-self.0)
+    }
+}
+
+impl<'a> core::iter::Sum<&'a G1> for G1 {
+    fn sum<I: Iterator<Item = &'a G1>>(iter: I) -> Self {
+        let zero = G1Projective::zero();
+        let sum = iter.fold(zero, |acc, next| acc + G1Projective::from(next.0));
+        G1(sum.into())
+    }
+}
+
+/// Point on G2
+#[derive(Debug, PartialEq, Clone)]
+pub struct G2(pub(crate) G2Affine);
+
+impl G2 {
+    /// Creates the generaor in G2
+    #[inline]
+    pub fn generator() -> Self {
+        Self(G2Affine::generator())
+    }
+
+    /// Creates the identity element in G2 (point at infinity)
+    #[inline]
+    pub fn identity() -> Self {
+        Self(G2Affine::identity())
+    }
+
+    /// Check if the point is the identity element
+    #[inline]
+    pub fn is_identity(&self) -> bool {
+        self.0.is_zero()
+    }
+
+    #[inline]
+    pub fn to_compressed(&self) -> [u8; BLS12_381_G2_POINT_LEN] {
+        let mut serialized = [0; BLS12_381_G2_POINT_LEN];
+        self.0.serialize_compressed(&mut serialized[..]).unwrap();
+        serialized
+    }
+}
+
+impl Add<&G2> for &G2 {
+    type Output = G2;
+
+    fn add(self, rhs: &G2) -> Self::Output {
+        [self, rhs].into_iter().sum()
+    }
+}
+
+impl<'a> core::iter::Sum<&'a G2> for G2 {
+    fn sum<I: Iterator<Item = &'a G2>>(iter: I) -> Self {
+        let zero = G2Projective::zero();
+        let sum = iter.fold(zero, |acc, next| acc + G2Projective::from(next.0));
+        G2(sum.into())
+    }
+}
+
+pub fn g1_from_variable(data: &[u8]) -> Result<G1, CryptoError> {
+    if data.len() != BLS12_381_G1_POINT_LEN {
+        return Err(InvalidPoint::InvalidLength {
+            expected: BLS12_381_G1_POINT_LEN,
+            actual: data.len(),
+        }
+        .into());
+    }
+
+    let mut buf = [0u8; BLS12_381_G1_POINT_LEN];
+    buf[..].copy_from_slice(data);
+    g1_from_fixed(&buf)
+}
+
+pub fn g1s_from_variable(data_list: &[&[u8]]) -> Vec<Result<G1, CryptoError>> {
+    use rayon::prelude::*;
+    let mut out = Vec::with_capacity(data_list.len());
+    data_list
+        .par_iter()
+        .map(|&data| g1_from_variable(data))
+        .collect_into_vec(&mut out);
+    out
+}
+
+pub fn g2_from_variable(data: &[u8]) -> Result<G2, CryptoError> {
+    if data.len() != BLS12_381_G2_POINT_LEN {
+        return Err(InvalidPoint::InvalidLength {
+            expected: BLS12_381_G2_POINT_LEN,
+            actual: data.len(),
+        }
+        .into());
+    }
+
+    let mut buf = [0u8; BLS12_381_G2_POINT_LEN];
+    buf[..].copy_from_slice(data);
+    g2_from_fixed(&buf)
+}
+
+pub fn g1_from_fixed(data: &[u8; BLS12_381_G1_POINT_LEN]) -> Result<G1, CryptoError> {
+    G1Affine::deserialize_compressed(&data[..])
+        .ok()
+        .map(G1)
+        .ok_or_else(|| InvalidPoint::DecodingError {}.into())
+}
+
+/// Like [`g1_from_fixed`] without guaranteeing that the encoding represents a valid element.
+/// Only use this when you know for sure the encoding is correct.
+pub fn g1_from_fixed_unchecked(data: [u8; BLS12_381_G1_POINT_LEN]) -> Result<G1, CryptoError> {
+    G1Affine::deserialize_compressed_unchecked(&data[..])
+        .ok()
+        .map(G1)
+        .ok_or_else(|| InvalidPoint::DecodingError {}.into())
+}
+
+pub fn g2_from_fixed(data: &[u8; BLS12_381_G2_POINT_LEN]) -> Result<G2, CryptoError> {
+    G2Affine::deserialize_compressed(&data[..])
+        .ok()
+        .map(G2)
+        .ok_or_else(|| InvalidPoint::DecodingError {}.into())
+}
+
+/// Like [`g2_from_fixed`] without guaranteeing that the encoding represents a valid element.
+/// Only use this when you know for sure the encoding is correct.
+pub fn g2_from_fixed_unchecked(data: [u8; BLS12_381_G2_POINT_LEN]) -> Result<G2, CryptoError> {
+    G2Affine::deserialize_compressed_unchecked(&data[..])
+        .ok()
+        .map(G2)
+        .ok_or_else(|| InvalidPoint::DecodingError {}.into())
+}
+
+pub fn bls12_381_g1_is_identity(g1: &[u8; BLS12_381_G1_POINT_LEN]) -> Result<bool, CryptoError> {
+    g1_from_fixed(g1).map(|point| point.is_identity())
+}
+
+pub fn bls12_381_g2_is_identity(g2: &[u8; BLS12_381_G2_POINT_LEN]) -> Result<bool, CryptoError> {
+    g2_from_fixed(g2).map(|point| point.is_identity())
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{BLS12_381_G1_GENERATOR, BLS12_381_G2_GENERATOR};
+
+    use super::*;
+    use hex_literal::hex;
+
+    #[test]
+    fn g1_generator_works() {
+        let generator = G1::generator();
+        assert_eq!(generator.to_compressed(), BLS12_381_G1_GENERATOR);
+    }
+
+    #[test]
+    fn g2_generator_works() {
+        let generator = G2::generator();
+        assert_eq!(generator.to_compressed(), BLS12_381_G2_GENERATOR);
+    }
+
+    #[test]
+    fn g1_from_variable_works() {
+        let result = g1_from_variable(&hex::decode("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31").unwrap());
+        assert!(result.is_ok());
+
+        let result = g1_from_variable(&hex::decode("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af").unwrap());
+        match result.unwrap_err() {
+            CryptoError::InvalidPoint {
+                source: InvalidPoint::InvalidLength { expected, actual },
+                ..
+            } => {
+                assert_eq!(expected, 48);
+                assert_eq!(actual, 47);
+            }
+            err => panic!("Unexpected error: {:?}", err),
+        }
+    }
+
+    #[test]
+    fn g2_from_variable_works() {
+        let result = g2_from_variable(&hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap());
+        assert!(result.is_ok());
+
+        let result = g2_from_variable(&hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e").unwrap());
+        match result.unwrap_err() {
+            CryptoError::InvalidPoint {
+                source: InvalidPoint::InvalidLength { expected, actual },
+                ..
+            } => {
+                assert_eq!(expected, 96);
+                assert_eq!(actual, 95);
+            }
+            err => panic!("Unexpected error: {:?}", err),
+        }
+    }
+
+    #[test]
+    fn g1_from_fixed_works() {
+        let result = g1_from_fixed(&hex_literal::hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31"));
+        assert!(result.is_ok());
+
+        let result = g1_from_fixed(&hex_literal::hex!("118f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31"));
+        match result.unwrap_err() {
+            CryptoError::InvalidPoint {
+                source: InvalidPoint::DecodingError {},
+                ..
+            } => {}
+            err => panic!("Unexpected error: {:?}", err),
+        }
+
+        let result = g1_from_fixed(&hex_literal::hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af22"));
+        match result.unwrap_err() {
+            CryptoError::InvalidPoint {
+                source: InvalidPoint::DecodingError {},
+                ..
+            } => {}
+            err => panic!("Unexpected error: {:?}", err),
+        }
+    }
+
+    #[test]
+    fn g1_from_fixed_unchecked_works() {
+        let data = hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31");
+        let a = g1_from_fixed_unchecked(data).unwrap();
+        let b = g1_from_fixed(&data).unwrap();
+        assert_eq!(a, b);
+    }
+
+    #[test]
+    fn g2_from_fixed_works() {
+        let result = g2_from_fixed(&hex_literal::hex!("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42"));
+        assert!(result.is_ok());
+
+        let result = g2_from_fixed(&hex_literal::hex!("11f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42"));
+        match result.unwrap_err() {
+            CryptoError::InvalidPoint {
+                source: InvalidPoint::DecodingError {},
+                ..
+            } => {}
+            err => panic!("Unexpected error: {:?}", err),
+        }
+
+        let result = g2_from_fixed(&hex_literal::hex!("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e44"));
+        match result.unwrap_err() {
+            CryptoError::InvalidPoint {
+                source: InvalidPoint::DecodingError {},
+                ..
+            } => {}
+            err => panic!("Unexpected error: {:?}", err),
+        }
+    }
+
+    #[test]
+    fn g2_from_fixed_unchecked_works() {
+        let data = hex!("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42");
+        let a = g2_from_fixed_unchecked(data).unwrap();
+        let b = g2_from_fixed(&data).unwrap();
+        assert_eq!(a, b);
+    }
+}
diff --git a/packages/crypto/src/errors.rs b/packages/crypto/src/errors.rs
index 989427fba7..bd3a6ccc41 100644
--- a/packages/crypto/src/errors.rs
+++ b/packages/crypto/src/errors.rs
@@ -6,9 +6,43 @@ use crate::BT;
 
 pub type CryptoResult<T> = core::result::Result<T, CryptoError>;
 
+#[derive(Debug, Display)]
+#[cfg_attr(feature = "std", derive(thiserror::Error))]
+pub enum Aggregation {
+    #[display("List of points is empty")]
+    Empty,
+    #[display("List is not a multiple of {expected_multiple}. Remainder: {remainder}")]
+    NotMultiple {
+        expected_multiple: usize,
+        remainder: usize,
+    },
+}
+
+#[derive(Debug, Display)]
+#[cfg_attr(feature = "std", derive(thiserror::Error))]
+pub enum PairingEquality {
+    #[display("List is not a multiple of 48. Remainder: {remainder}")]
+    NotMultipleG1 { remainder: usize },
+    #[display("List is not a multiple of 96. Remainder: {remainder}")]
+    NotMultipleG2 { remainder: usize },
+    #[display("Not the same amount of points passed. Left: {left}, Right: {right}")]
+    UnequalPointAmount { left: usize, right: usize },
+}
+
+#[derive(Debug, Display)]
+#[cfg_attr(feature = "std", derive(thiserror::Error))]
+pub enum InvalidPoint {
+    #[display("Invalid input length for point (must be in compressed format): Expected {expected}, actual: {actual}")]
+    InvalidLength { expected: usize, actual: usize },
+    #[display("Invalid point")]
+    DecodingError {},
+}
+
 #[derive(Display, Debug)]
 #[cfg_attr(feature = "std", derive(thiserror::Error))]
 pub enum CryptoError {
+    #[display("Point aggregation error: {source}")]
+    Aggregation { source: Aggregation, backtrace: BT },
     #[display("Batch verify error: {msg}")]
     BatchErr { msg: String, backtrace: BT },
     #[display("Crypto error: {msg}")]
@@ -21,6 +55,15 @@ pub enum CryptoError {
     InvalidSignatureFormat { backtrace: BT },
     #[display("Invalid recovery parameter. Supported values: 0 and 1.")]
     InvalidRecoveryParam { backtrace: BT },
+    #[display("Invalid point: {source}")]
+    InvalidPoint { source: InvalidPoint, backtrace: BT },
+    #[display("Pairing equality error: {source}")]
+    PairingEquality {
+        source: PairingEquality,
+        backtrace: BT,
+    },
+    #[display("Unknown hash function")]
+    UnknownHashFunction { backtrace: BT },
 }
 
 impl CryptoError {
@@ -62,6 +105,12 @@ impl CryptoError {
         }
     }
 
+    pub fn unknown_hash_function() -> Self {
+        CryptoError::UnknownHashFunction {
+            backtrace: BT::capture(),
+        }
+    }
+
     /// Numeric error code that can easily be passed over the
     /// contract VM boundary.
     pub fn code(&self) -> u32 {
@@ -71,7 +120,59 @@ impl CryptoError {
             CryptoError::InvalidPubkeyFormat { .. } => 5,
             CryptoError::InvalidRecoveryParam { .. } => 6,
             CryptoError::BatchErr { .. } => 7,
+            CryptoError::InvalidPoint { .. } => 8,
+            CryptoError::UnknownHashFunction { .. } => 9,
             CryptoError::GenericErr { .. } => 10,
+            CryptoError::PairingEquality {
+                source: PairingEquality::NotMultipleG1 { .. },
+                ..
+            } => 11,
+            CryptoError::PairingEquality {
+                source: PairingEquality::NotMultipleG2 { .. },
+                ..
+            } => 12,
+            CryptoError::PairingEquality {
+                source: PairingEquality::UnequalPointAmount { .. },
+                ..
+            } => 13,
+            CryptoError::Aggregation {
+                source: Aggregation::Empty,
+                ..
+            } => 14,
+            CryptoError::Aggregation {
+                source: Aggregation::NotMultiple { .. },
+                ..
+            } => 15,
+        }
+    }
+}
+
+impl From<Aggregation> for CryptoError {
+    #[track_caller]
+    fn from(value: Aggregation) -> Self {
+        Self::Aggregation {
+            source: value,
+            backtrace: BT::capture(),
+        }
+    }
+}
+
+impl From<PairingEquality> for CryptoError {
+    #[track_caller]
+    fn from(value: PairingEquality) -> Self {
+        Self::PairingEquality {
+            source: value,
+            backtrace: BT::capture(),
+        }
+    }
+}
+
+impl From<InvalidPoint> for CryptoError {
+    #[track_caller]
+    fn from(value: InvalidPoint) -> Self {
+        Self::InvalidPoint {
+            source: value,
+            backtrace: BT::capture(),
         }
     }
 }
diff --git a/packages/crypto/src/lib.rs b/packages/crypto/src/lib.rs
index 04667b7320..36d40016e1 100644
--- a/packages/crypto/src/lib.rs
+++ b/packages/crypto/src/lib.rs
@@ -11,6 +11,7 @@ extern crate alloc;
 extern crate std; // allow for file I/O during tests
 
 mod backtrace;
+mod bls12_318;
 mod ecdsa;
 mod ed25519;
 mod errors;
@@ -18,6 +19,18 @@ mod identity_digest;
 mod secp256k1;
 mod secp256r1;
 
+#[cfg(feature = "std")]
+#[doc(hidden)]
+pub use crate::bls12_318::{
+    bls12_381_aggregate_g1, bls12_381_aggregate_g2, bls12_381_g1_is_identity,
+    bls12_381_g2_is_identity, bls12_381_hash_to_g1, bls12_381_hash_to_g2,
+    bls12_381_pairing_equality, HashFunction,
+};
+
+#[doc(hidden)]
+pub use crate::bls12_318::{
+    BLS12_381_G1_GENERATOR, BLS12_381_G1_POINT_LEN, BLS12_381_G2_GENERATOR, BLS12_381_G2_POINT_LEN,
+};
 #[doc(hidden)]
 pub use crate::ecdsa::{ECDSA_PUBKEY_MAX_LEN, ECDSA_SIGNATURE_LEN, MESSAGE_HASH_MAX_LEN};
 #[doc(hidden)]
@@ -25,7 +38,10 @@ pub use crate::ed25519::EDDSA_PUBKEY_LEN;
 #[doc(hidden)]
 pub use crate::ed25519::{ed25519_batch_verify, ed25519_verify};
 #[doc(hidden)]
-pub use crate::errors::{CryptoError, CryptoResult};
+pub use crate::errors::{
+    Aggregation as AggregationError, CryptoError, CryptoResult,
+    PairingEquality as PairingEqualityError,
+};
 #[doc(hidden)]
 pub use crate::secp256k1::{secp256k1_recover_pubkey, secp256k1_verify};
 #[doc(hidden)]
diff --git a/packages/crypto/src/secp256r1.rs b/packages/crypto/src/secp256r1.rs
index 574f625500..8778569f64 100644
--- a/packages/crypto/src/secp256r1.rs
+++ b/packages/crypto/src/secp256r1.rs
@@ -1,5 +1,4 @@
 use alloc::{string::ToString, vec::Vec};
-use core::convert::TryInto;
 use digest::{Digest, Update}; // trait
 use ecdsa::RecoveryId;
 use p256::{
diff --git a/packages/crypto/testdata/bls-tests/aggregate/aggregate_0x0000000000000000000000000000000000000000000000000000000000000000.json b/packages/crypto/testdata/bls-tests/aggregate/aggregate_0x0000000000000000000000000000000000000000000000000000000000000000.json
new file mode 100644
index 0000000000..0da8111bb5
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate/aggregate_0x0000000000000000000000000000000000000000000000000000000000000000.json
@@ -0,0 +1,8 @@
+{
+  "input": [
+    "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55",
+    "0xb23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9",
+    "0x948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115"
+  ],
+  "output": "0x9683b3e6701f9a4b706709577963110043af78a5b41991b998475a3d3fd62abf35ce03b33908418efc95a058494a8ae504354b9f626231f6b3f3c849dfdeaf5017c4780e2aee1850ceaf4b4d9ce70971a3d2cfcd97b7e5ecf6759f8da5f76d31"
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate/aggregate_0x5656565656565656565656565656565656565656565656565656565656565656.json b/packages/crypto/testdata/bls-tests/aggregate/aggregate_0x5656565656565656565656565656565656565656565656565656565656565656.json
new file mode 100644
index 0000000000..c4e082946b
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate/aggregate_0x5656565656565656565656565656565656565656565656565656565656565656.json
@@ -0,0 +1,8 @@
+{
+  "input": [
+    "0x882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb",
+    "0xaf1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe",
+    "0xa4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6"
+  ],
+  "output": "0xad38fc73846583b08d110d16ab1d026c6ea77ac2071e8ae832f56ac0cbcdeb9f5678ba5ce42bd8dce334cc47b5abcba40a58f7f1f80ab304193eb98836cc14d8183ec14cc77de0f80c4ffd49e168927a968b5cdaa4cf46b9805be84ad7efa77b"
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate/aggregate_0xabababababababababababababababababababababababababababababababab.json b/packages/crypto/testdata/bls-tests/aggregate/aggregate_0xabababababababababababababababababababababababababababababababab.json
new file mode 100644
index 0000000000..115215719d
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate/aggregate_0xabababababababababababababababababababababababababababababababab.json
@@ -0,0 +1,8 @@
+{
+  "input": [
+    "0x91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121",
+    "0x9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df",
+    "0xae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9"
+  ],
+  "output": "0x9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfc4ff1d930"
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate/aggregate_infinity_signature.json b/packages/crypto/testdata/bls-tests/aggregate/aggregate_infinity_signature.json
new file mode 100644
index 0000000000..b547ebcfcf
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate/aggregate_infinity_signature.json
@@ -0,0 +1,6 @@
+{
+  "input": [
+    "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  ],
+  "output": "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate/aggregate_na_signatures.json b/packages/crypto/testdata/bls-tests/aggregate/aggregate_na_signatures.json
new file mode 100644
index 0000000000..8f12bef2bb
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate/aggregate_na_signatures.json
@@ -0,0 +1 @@
+{ "input": [], "output": null }
diff --git a/packages/crypto/testdata/bls-tests/aggregate/aggregate_single_signature.json b/packages/crypto/testdata/bls-tests/aggregate/aggregate_single_signature.json
new file mode 100644
index 0000000000..5aa2f05334
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate/aggregate_single_signature.json
@@ -0,0 +1,6 @@
+{
+  "input": [
+    "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55"
+  ],
+  "output": "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55"
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_infinity_pubkey.json b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_infinity_pubkey.json
new file mode 100644
index 0000000000..032a76da90
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_infinity_pubkey.json
@@ -0,0 +1,18 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+      "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+    ],
+    "messages": [
+      "0x0000000000000000000000000000000000000000000000000000000000000000",
+      "0x5656565656565656565656565656565656565656565656565656565656565656",
+      "0xabababababababababababababababababababababababababababababababab",
+      "0x1212121212121212121212121212121212121212121212121212121212121212"
+    ],
+    "signature": "0x9104e74b9dfd3ad502f25d6a5ef57db0ed7d9a0e00f3500586d8ce44231212542fcfaf87840539b398bf07626705cf1105d246ca1062c6c2e1a53029a0f790ed5e3cb1f52f8234dc5144c45fc847c0cd37a92d68e7c5ba7c648a8a339f171244"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_na_pubkeys_and_infinity_signature.json b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_na_pubkeys_and_infinity_signature.json
new file mode 100644
index 0000000000..85315bb99f
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_na_pubkeys_and_infinity_signature.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkeys": [],
+    "messages": [],
+    "signature": "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_na_pubkeys_and_na_signature.json b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_na_pubkeys_and_na_signature.json
new file mode 100644
index 0000000000..eba62099a5
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_na_pubkeys_and_na_signature.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkeys": [],
+    "messages": [],
+    "signature": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_tampered_signature.json b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_tampered_signature.json
new file mode 100644
index 0000000000..6c8cdd587c
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_tampered_signature.json
@@ -0,0 +1,16 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f"
+    ],
+    "messages": [
+      "0x0000000000000000000000000000000000000000000000000000000000000000",
+      "0x5656565656565656565656565656565656565656565656565656565656565656",
+      "0xabababababababababababababababababababababababababababababababab"
+    ],
+    "signature": "0x9104e74bffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_valid.json b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_valid.json
new file mode 100644
index 0000000000..5439474964
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/aggregate_verify/aggregate_verify_valid.json
@@ -0,0 +1,16 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f"
+    ],
+    "messages": [
+      "0x0000000000000000000000000000000000000000000000000000000000000000",
+      "0x5656565656565656565656565656565656565656565656565656565656565656",
+      "0xabababababababababababababababababababababababababababababababab"
+    ],
+    "signature": "0x9104e74b9dfd3ad502f25d6a5ef57db0ed7d9a0e00f3500586d8ce44231212542fcfaf87840539b398bf07626705cf1105d246ca1062c6c2e1a53029a0f790ed5e3cb1f52f8234dc5144c45fc847c0cd37a92d68e7c5ba7c648a8a339f171244"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_invalid_forged_signature_set.json b/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_invalid_forged_signature_set.json
new file mode 100644
index 0000000000..b4fc984f7e
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_invalid_forged_signature_set.json
@@ -0,0 +1,17 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81"
+    ],
+    "messages": [
+      "0x0000000000000000000000000000000000000000000000000000000000000000",
+      "0x5656565656565656565656565656565656565656565656565656565656565656"
+    ],
+    "signatures": [
+      "0xa70f1f1b4bd97d182ebb55d08be3f90b1dc232bb50b44e259381a642ef0bad3629ad3542f3e8ff6a84e451fc0b595e090fc4f0e860cfc5584715ef1b6cd717b9994378f7a51b815bbf5a0d95bc3402583ad2e95a229731e539906249a5e4355c",
+      "0xb758eb7e15c101f53be2214d2a6b65e8fe7053146dbe3c73c9fe9b5efecdf63ca06a4d5d938dbf18fe6600529c0011a7013f45ae012b02904d5c7c33316e935a0e084abead4f43f84383c52cd3b3f14024437e251a2a7c0d5147954022873a58"
+    ]
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_invalid_infinity_signature_set.json b/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_invalid_infinity_signature_set.json
new file mode 100644
index 0000000000..81afb7ddfb
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_invalid_infinity_signature_set.json
@@ -0,0 +1,17 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+    ],
+    "messages": [
+      "0x0000000000000000000000000000000000000000000000000000000000000000",
+      "0x5656565656565656565656565656565656565656565656565656565656565656"
+    ],
+    "signatures": [
+      "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55",
+      "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+    ]
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_valid_multiple_signature_set.json b/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_valid_multiple_signature_set.json
new file mode 100644
index 0000000000..6fd7766d8e
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_valid_multiple_signature_set.json
@@ -0,0 +1,17 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81"
+    ],
+    "messages": [
+      "0x0000000000000000000000000000000000000000000000000000000000000000",
+      "0x5656565656565656565656565656565656565656565656565656565656565656"
+    ],
+    "signatures": [
+      "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55",
+      "0xaf1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe"
+    ]
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_valid_simple_signature_set.json b/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_valid_simple_signature_set.json
new file mode 100644
index 0000000000..71856ee767
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/batch_verify/batch_verify_valid_simple_signature_set.json
@@ -0,0 +1,20 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f"
+    ],
+    "messages": [
+      "0x0000000000000000000000000000000000000000000000000000000000000000",
+      "0x5656565656565656565656565656565656565656565656565656565656565656",
+      "0xabababababababababababababababababababababababababababababababab"
+    ],
+    "signatures": [
+      "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55",
+      "0xaf1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe",
+      "0xae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9"
+    ]
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_infinity_with_false_b_flag.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_infinity_with_false_b_flag.json
new file mode 100644
index 0000000000..506c207740
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_infinity_with_false_b_flag.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_infinity_with_true_b_flag.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_infinity_with_true_b_flag.json
new file mode 100644
index 0000000000..0e2290859b
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_infinity_with_true_b_flag.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "c01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_not_in_G1.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_not_in_G1.json
new file mode 100644
index 0000000000..76f51aae8f
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_not_in_G1.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_not_in_curve.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_not_in_curve.json
new file mode 100644
index 0000000000..956124eb66
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_not_in_curve.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde0"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_too_few_bytes.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_too_few_bytes.json
new file mode 100644
index 0000000000..12a65563f1
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_too_few_bytes.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_too_many_bytes.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_too_many_bytes.json
new file mode 100644
index 0000000000..190a890549
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_too_many_bytes.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaa900"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_b_flag_and_a_flag_true.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_b_flag_and_a_flag_true.json
new file mode 100644
index 0000000000..6553aba4b2
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_b_flag_and_a_flag_true.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_b_flag_and_x_nonzero.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_b_flag_and_x_nonzero.json
new file mode 100644
index 0000000000..baa64d596c
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_b_flag_and_x_nonzero.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "c123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_wrong_c_flag.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_wrong_c_flag.json
new file mode 100644
index 0000000000..0e15859ff7
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_with_wrong_c_flag.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_x_equal_to_modulus.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_x_equal_to_modulus.json
new file mode 100644
index 0000000000..f3e1640dbd
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_x_equal_to_modulus.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_x_greater_than_modulus.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_x_greater_than_modulus.json
new file mode 100644
index 0000000000..67981234eb
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_fails_x_greater_than_modulus.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_succeeds_correct_point.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_succeeds_correct_point.json
new file mode 100644
index 0000000000..45cda6b339
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_succeeds_correct_point.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_succeeds_infinity_with_true_b_flag.json b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_succeeds_infinity_with_true_b_flag.json
new file mode 100644
index 0000000000..ddb1fdb2f8
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G1/deserialization_succeeds_infinity_with_true_b_flag.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "pubkey": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_infinity_with_false_b_flag.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_infinity_with_false_b_flag.json
new file mode 100644
index 0000000000..e50f48ad5c
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_infinity_with_false_b_flag.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_infinity_with_true_b_flag.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_infinity_with_true_b_flag.json
new file mode 100644
index 0000000000..bb32b7cdd9
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_infinity_with_true_b_flag.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "c01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_not_in_G2.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_not_in_G2.json
new file mode 100644
index 0000000000..0e5fb4094e
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_not_in_G2.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_not_in_curve.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_not_in_curve.json
new file mode 100644
index 0000000000..e645a87a43
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_not_in_curve.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde0"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_too_few_bytes.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_too_few_bytes.json
new file mode 100644
index 0000000000..46a78bec87
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_too_few_bytes.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcd"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_too_many_bytes.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_too_many_bytes.json
new file mode 100644
index 0000000000..8763465a6a
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_too_many_bytes.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_b_flag_and_a_flag_true.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_b_flag_and_a_flag_true.json
new file mode 100644
index 0000000000..b96535439b
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_b_flag_and_a_flag_true.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_b_flag_and_x_nonzero.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_b_flag_and_x_nonzero.json
new file mode 100644
index 0000000000..de700882b3
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_b_flag_and_x_nonzero.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "c123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_wrong_c_flag.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_wrong_c_flag.json
new file mode 100644
index 0000000000..da4859d39f
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_with_wrong_c_flag.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xim_equal_to_modulus.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xim_equal_to_modulus.json
new file mode 100644
index 0000000000..be05d530f6
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xim_equal_to_modulus.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xim_greater_than_modulus.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xim_greater_than_modulus.json
new file mode 100644
index 0000000000..875c9631ec
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xim_greater_than_modulus.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xre_equal_to_modulus.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xre_equal_to_modulus.json
new file mode 100644
index 0000000000..9fb0046aac
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xre_equal_to_modulus.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xre_greater_than_modulus.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xre_greater_than_modulus.json
new file mode 100644
index 0000000000..0ee3ad4743
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_fails_xre_greater_than_modulus.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_succeeds_correct_point.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_succeeds_correct_point.json
new file mode 100644
index 0000000000..94df5ec77d
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_succeeds_correct_point.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "b2cc74bc9f089ed9764bbceac5edba416bef5e73701288977b9cac1ccb6964269d4ebf78b4e8aa7792ba09d3e49c8e6a1351bdf582971f796bbaf6320e81251c9d28f674d720cca07ed14596b96697cf18238e0e03ebd7fc1353d885a39407e0"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_succeeds_infinity_with_true_b_flag.json b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_succeeds_infinity_with_true_b_flag.json
new file mode 100644
index 0000000000..4448a0b216
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/deserialization_G2/deserialization_succeeds_infinity_with_true_b_flag.json
@@ -0,0 +1,6 @@
+{
+  "input": {
+    "signature": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_4f079f946446fabf.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_4f079f946446fabf.json
new file mode 100644
index 0000000000..828d1d6288
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_4f079f946446fabf.json
@@ -0,0 +1,12 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f"
+    ],
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0x912c3615f69575407db9392eb21fee18fff797eeb2fbe1816366ca2a08ae574d8824dbfafb4c9eaa1cf61b63c6f9b69911f269b664c42947dd1b53ef1081926c1e82bb2a465f927124b08391a5249036146d6f3f1e17ff5f162f779746d830d1"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_5a38e6b4017fe4dd.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_5a38e6b4017fe4dd.json
new file mode 100644
index 0000000000..ae601d41cf
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_5a38e6b4017fe4dd.json
@@ -0,0 +1,13 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f"
+    ],
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfc4ff1d930"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_a698ea45b109f303.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_a698ea45b109f303.json
new file mode 100644
index 0000000000..263fde852b
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_extra_pubkey_a698ea45b109f303.json
@@ -0,0 +1,11 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f"
+    ],
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_infinity_pubkey.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_infinity_pubkey.json
new file mode 100644
index 0000000000..e07310d7f2
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_infinity_pubkey.json
@@ -0,0 +1,13 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+      "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+    ],
+    "message": "0x1212121212121212121212121212121212121212121212121212121212121212",
+    "signature": "0xafcb4d980f079265caa61aee3e26bf48bebc5dc3e7f2d7346834d76cbc812f636c937b6b44a9323d8bc4b1cdf71d6811035ddc2634017faab2845308f568f2b9a0356140727356eae9eded8b87fd8cb8024b440c57aee06076128bb32921f584"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_na_pubkeys_and_infinity_signature.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_na_pubkeys_and_infinity_signature.json
new file mode 100644
index 0000000000..f44a27a974
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_na_pubkeys_and_infinity_signature.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkeys": [],
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_na_pubkeys_and_na_signature.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_na_pubkeys_and_na_signature.json
new file mode 100644
index 0000000000..4a2b63fdf2
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_na_pubkeys_and_na_signature.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkeys": [],
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_3d7576f3c0e3570a.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_3d7576f3c0e3570a.json
new file mode 100644
index 0000000000..67d20772bd
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_3d7576f3c0e3570a.json
@@ -0,0 +1,12 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f"
+    ],
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfcffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_5e745ad0c6199a6c.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_5e745ad0c6199a6c.json
new file mode 100644
index 0000000000..e9d43c306f
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_5e745ad0c6199a6c.json
@@ -0,0 +1,10 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a"
+    ],
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380bffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_652ce62f09290811.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_652ce62f09290811.json
new file mode 100644
index 0000000000..6a8b5396ae
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_tampered_signature_652ce62f09290811.json
@@ -0,0 +1,11 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81"
+    ],
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0x912c3615f69575407db9392eb21fee18fff797eeb2fbe1816366ca2a08ae574d8824dbfafb4c9eaa1cf61b63c6f9b69911f269b664c42947dd1b53ef1081926c1e82bb2a465f927124b08391a5249036146d6f3f1e17ff5f162f7797ffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_3d7576f3c0e3570a.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_3d7576f3c0e3570a.json
new file mode 100644
index 0000000000..d2a946f857
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_3d7576f3c0e3570a.json
@@ -0,0 +1,12 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+      "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f"
+    ],
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x9712c3edd73a209c742b8250759db12549b3eaf43b5ca61376d9f30e2747dbcf842d8b2ac0901d2a093713e20284a7670fcf6954e9ab93de991bb9b313e664785a075fc285806fa5224c82bde146561b446ccfc706a64b8579513cfc4ff1d930"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_5e745ad0c6199a6c.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_5e745ad0c6199a6c.json
new file mode 100644
index 0000000000..d40529925d
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_5e745ad0c6199a6c.json
@@ -0,0 +1,10 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a"
+    ],
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_652ce62f09290811.json b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_652ce62f09290811.json
new file mode 100644
index 0000000000..0cb8f24123
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/fast_aggregate_verify/fast_aggregate_verify_valid_652ce62f09290811.json
@@ -0,0 +1,11 @@
+{
+  "input": {
+    "pubkeys": [
+      "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+      "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81"
+    ],
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0x912c3615f69575407db9392eb21fee18fff797eeb2fbe1816366ca2a08ae574d8824dbfafb4c9eaa1cf61b63c6f9b69911f269b664c42947dd1b53ef1081926c1e82bb2a465f927124b08391a5249036146d6f3f1e17ff5f162f779746d830d1"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__2782afaa8406d038.json b/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__2782afaa8406d038.json
new file mode 100644
index 0000000000..468ae9a728
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__2782afaa8406d038.json
@@ -0,0 +1,9 @@
+{
+  "input": {
+    "msg": "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+  },
+  "output": {
+    "x": "0x01a6ba2f9a11fa5598b2d8ace0fbe0a0eacb65deceb476fbbcb64fd24557c2f4b18ecfc5663e54ae16a84f5ab7f62534,0x11fca2ff525572795a801eed17eb12785887c7b63fb77a42be46ce4a34131d71f7a73e95fee3f812aea3de78b4d01569",
+    "y": "0x0b6798718c8aed24bc19cb27f866f1c9effcdbf92397ad6448b5c9db90d2b9da6cbabf48adc1adf59a1a28344e79d57e,0x03a47f8e6d1763ba0cad63d6114c0accbef65707825a511b251a660a9b3994249ae4e63fac38b23da0c398689ee2ab52"
+  }
+}
diff --git a/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__7590bd067999bbfb.json b/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__7590bd067999bbfb.json
new file mode 100644
index 0000000000..6c33fd8c53
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__7590bd067999bbfb.json
@@ -0,0 +1,7 @@
+{
+  "input": { "msg": "abc" },
+  "output": {
+    "x": "0x02c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e6,0x139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd8",
+    "y": "0x1787327b68159716a37440985269cf584bcb1e621d3a7202be6ea05c4cfe244aeb197642555a0645fb87bf7466b2ba48,0x00aa65dae3c8d732d10ecd2c50f8a1baf3001578f71c694e03866e9f3d49ac1e1ce70dd94a733534f106d4cec0eddd16"
+  }
+}
diff --git a/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__a54942c8e365f378.json b/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__a54942c8e365f378.json
new file mode 100644
index 0000000000..55f4176742
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__a54942c8e365f378.json
@@ -0,0 +1,7 @@
+{
+  "input": { "msg": "" },
+  "output": {
+    "x": "0x0141ebfbdca40eb85b87142e130ab689c673cf60f1a3e98d69335266f30d9b8d4ac44c1038e9dcdd5393faf5c41fb78a,0x05cb8437535e20ecffaef7752baddf98034139c38452458baeefab379ba13dff5bf5dd71b72418717047f5b0f37da03d",
+    "y": "0x0503921d7f6a12805e72940b963c0cf3471c7b2a524950ca195d11062ee75ec076daf2d4bc358c4b190c0c98064fdd92,0x12424ac32561493f3fe3c260708a12b7c620e7be00099a974e259ddc7d1f6395c3c811cdd19f1e8dbf3e9ecfdcbab8d6"
+  }
+}
diff --git a/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__c938b486cf69e8f7.json b/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__c938b486cf69e8f7.json
new file mode 100644
index 0000000000..65c926bfcc
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/hash_to_G2/hash_to_G2__c938b486cf69e8f7.json
@@ -0,0 +1,7 @@
+{
+  "input": { "msg": "abcdef0123456789" },
+  "output": {
+    "x": "0x121982811d2491fde9ba7ed31ef9ca474f0e1501297f68c298e9f4c0028add35aea8bb83d53c08cfc007c1e005723cd0,0x190d119345b94fbd15497bcba94ecf7db2cbfd1e1fe7da034d26cbba169fb3968288b3fafb265f9ebd380512a71c3f2c",
+    "y": "0x05571a0f8d3c08d094576981f4a3b8eda0a8e771fcdcc8ecceaf1356a6acf17574518acb506e435b639353c2e14827c8,0x0bb5e7572275c567462d91807de765611490205a941a5a6af3b1691bfe596c31225d3aabdf15faff860cb4ef17c7c3be"
+  }
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_infinity_pubkey_and_infinity_signature.json b/packages/crypto/testdata/bls-tests/verify/verify_infinity_pubkey_and_infinity_signature.json
new file mode 100644
index 0000000000..51ba76e875
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_infinity_pubkey_and_infinity_signature.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "message": "0x1212121212121212121212121212121212121212121212121212121212121212",
+    "signature": "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_195246ee3bd3b6ec.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_195246ee3bd3b6ec.json
new file mode 100644
index 0000000000..96f61efaf6
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_195246ee3bd3b6ec.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0xae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9ffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_2ea479adf8c40300.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_2ea479adf8c40300.json
new file mode 100644
index 0000000000..8d024cd8b9
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_2ea479adf8c40300.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0x882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972ffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_2f09d443ab8a3ac2.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_2f09d443ab8a3ac2.json
new file mode 100644
index 0000000000..328d2d2320
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_2f09d443ab8a3ac2.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_3208262581c8fc09.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_3208262581c8fc09.json
new file mode 100644
index 0000000000..65d3e865c8
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_3208262581c8fc09.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0xaf1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363ffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_6b3b17f6962a490c.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_6b3b17f6962a490c.json
new file mode 100644
index 0000000000..1d278a1a18
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_6b3b17f6962a490c.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0xa4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_6eeb7c52dfd9baf0.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_6eeb7c52dfd9baf0.json
new file mode 100644
index 0000000000..5d90c78597
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_6eeb7c52dfd9baf0.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5ffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_8761a0b7e920c323.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_8761a0b7e920c323.json
new file mode 100644
index 0000000000..1472779132
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_8761a0b7e920c323.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b71ffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_d34885d766d5f705.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_d34885d766d5f705.json
new file mode 100644
index 0000000000..c7d8d37525
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_d34885d766d5f705.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0x948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075effffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_e8a50c445c855360.json b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_e8a50c445c855360.json
new file mode 100644
index 0000000000..af9428ed95
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_tampered_signature_case_e8a50c445c855360.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380bffffffff"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_195246ee3bd3b6ec.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_195246ee3bd3b6ec.json
new file mode 100644
index 0000000000..bc8030a178
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_195246ee3bd3b6ec.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0xae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_2ea479adf8c40300.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_2ea479adf8c40300.json
new file mode 100644
index 0000000000..2f2f7a65d7
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_2ea479adf8c40300.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0x882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_2f09d443ab8a3ac2.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_2f09d443ab8a3ac2.json
new file mode 100644
index 0000000000..46b8e4f9b1
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_2f09d443ab8a3ac2.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_3208262581c8fc09.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_3208262581c8fc09.json
new file mode 100644
index 0000000000..15e948e0cf
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_3208262581c8fc09.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0xaf1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_6b3b17f6962a490c.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_6b3b17f6962a490c.json
new file mode 100644
index 0000000000..ce1816c87d
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_6b3b17f6962a490c.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0xa4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_6eeb7c52dfd9baf0.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_6eeb7c52dfd9baf0.json
new file mode 100644
index 0000000000..c81da01e85
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_6eeb7c52dfd9baf0.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_8761a0b7e920c323.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_8761a0b7e920c323.json
new file mode 100644
index 0000000000..b922c21542
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_8761a0b7e920c323.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_d34885d766d5f705.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_d34885d766d5f705.json
new file mode 100644
index 0000000000..876f7351ad
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_d34885d766d5f705.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0x948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_valid_case_e8a50c445c855360.json b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_e8a50c445c855360.json
new file mode 100644
index 0000000000..cc4cd014b2
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_valid_case_e8a50c445c855360.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_195246ee3bd3b6ec.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_195246ee3bd3b6ec.json
new file mode 100644
index 0000000000..5a5ed2b2d7
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_195246ee3bd3b6ec.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x9674e2228034527f4c083206032b020310face156d4a4685e2fcaec2f6f3665aa635d90347b6ce124eb879266b1e801d185de36a0a289b85e9039662634f2eea1e02e670bc7ab849d006a70b2f93b84597558a05b879c8d445f387a5d5b653df"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_2ea479adf8c40300.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_2ea479adf8c40300.json
new file mode 100644
index 0000000000..92285b7465
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_2ea479adf8c40300.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0xa4efa926610b8bd1c8330c918b7a5e9bf374e53435ef8b7ec186abf62e1b1f65aeaaeb365677ac1d1172a1f5b44b4e6d022c252c58486c0a759fbdc7de15a756acc4d343064035667a594b4c2a6f0b0b421975977f297dba63ee2f63ffe47bb6"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_2f09d443ab8a3ac2.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_2f09d443ab8a3ac2.json
new file mode 100644
index 0000000000..1c51e235b3
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_2f09d443ab8a3ac2.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_3208262581c8fc09.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_3208262581c8fc09.json
new file mode 100644
index 0000000000..4c356ca42a
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_3208262581c8fc09.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0x882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_6b3b17f6962a490c.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_6b3b17f6962a490c.json
new file mode 100644
index 0000000000..f75ed36e69
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_6b3b17f6962a490c.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
+    "signature": "0xaf1390c3c47acdb37131a51216da683c509fce0e954328a59f93aebda7e4ff974ba208d9a4a2a2389f892a9d418d618418dd7f7a6bc7aa0da999a9d3a5b815bc085e14fd001f6a1948768a3f4afefc8b8240dda329f984cb345c6363272ba4fe"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_6eeb7c52dfd9baf0.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_6eeb7c52dfd9baf0.json
new file mode 100644
index 0000000000..4737cdbb5b
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_6eeb7c52dfd9baf0.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0x91347bccf740d859038fcdcaf233eeceb2a436bcaaee9b2aa3bfb70efe29dfb2677562ccbea1c8e061fb9971b0753c240622fab78489ce96768259fc01360346da5b9f579e5da0d941e4c6ba18a0e64906082375394f337fa1af2b7127b0d121"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_8761a0b7e920c323.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_8761a0b7e920c323.json
new file mode 100644
index 0000000000..a84115ab26
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_8761a0b7e920c323.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0xabababababababababababababababababababababababababababababababab",
+    "signature": "0xae82747ddeefe4fd64cf9cedb9b04ae3e8a43420cd255e3c7cd06a8d88b7c7f8638543719981c5d16fa3527c468c25f0026704a6951bde891360c7e8d12ddee0559004ccdbe6046b55bae1b257ee97f7cdb955773d7cf29adf3ccbb9975e4eb9"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_d34885d766d5f705.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_d34885d766d5f705.json
new file mode 100644
index 0000000000..7f0cb1af49
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_d34885d766d5f705.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xb53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0xb23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_e8a50c445c855360.json b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_e8a50c445c855360.json
new file mode 100644
index 0000000000..24c8e2f584
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verify_wrong_pubkey_case_e8a50c445c855360.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
+    "message": "0x0000000000000000000000000000000000000000000000000000000000000000",
+    "signature": "0x948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115"
+  },
+  "output": false
+}
diff --git a/packages/crypto/testdata/bls-tests/verify/verifycase_one_privkey_47117849458281be.json b/packages/crypto/testdata/bls-tests/verify/verifycase_one_privkey_47117849458281be.json
new file mode 100644
index 0000000000..826afd43c2
--- /dev/null
+++ b/packages/crypto/testdata/bls-tests/verify/verifycase_one_privkey_47117849458281be.json
@@ -0,0 +1,8 @@
+{
+  "input": {
+    "pubkey": "0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb",
+    "message": "0x1212121212121212121212121212121212121212121212121212121212121212",
+    "signature": "0xa42ae16f1c2a5fa69c04cb5998d2add790764ce8dd45bf25b29b4700829232052b52352dcff1cf255b3a7810ad7269601810f03b2bc8b68cf289cf295b206770605a190b6842583e47c3d1c0f73c54907bfb2a602157d46a4353a20283018763"
+  },
+  "output": true
+}
diff --git a/packages/crypto/testdata/eth-headers/1699693797.394876721s.json b/packages/crypto/testdata/eth-headers/1699693797.394876721s.json
new file mode 100644
index 0000000000..e203c3b07d
--- /dev/null
+++ b/packages/crypto/testdata/eth-headers/1699693797.394876721s.json
@@ -0,0 +1,503 @@
+{
+  "public_keys": [
+    "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"
+  ],
+  "message": "ZOUaZJZMirvzYYwSTf3ZGcHH0QjxpkkkDHYqWWwZzZQ=",
+  "signature": "hvW2G4MSoCl6NfJAykQgoZNchj12ptXkrOYBeKlInOr0LkYMix3wkswqcZtZrOYkCH49XENcdVTzTFI22mzr6obbF/4nnoXIXPcferxUhLpBD25V354oUbNk3zFZnpoH",
+  "aggregate_pubkey": "gJo644kMo1WhhRi+FtFBB/4Gmw9hGjPq9ts2JKz06MmXrYqVBM2xphZ7oIUvQ4UU"
+}
diff --git a/packages/crypto/tests/bls12_381.rs b/packages/crypto/tests/bls12_381.rs
new file mode 100644
index 0000000000..93ccf12060
--- /dev/null
+++ b/packages/crypto/tests/bls12_381.rs
@@ -0,0 +1,440 @@
+#![cfg(feature = "std")]
+
+use std::{error::Error, fs};
+
+use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
+use base64::engine::general_purpose::STANDARD;
+use base64_serde::base64_serde_type;
+use cosmwasm_crypto::{
+    bls12_381_aggregate_g1, bls12_381_aggregate_g2, bls12_381_g1_is_identity,
+    bls12_381_g2_is_identity, bls12_381_hash_to_g2, bls12_381_pairing_equality, HashFunction,
+    BLS12_381_G1_GENERATOR, BLS12_381_G2_POINT_LEN,
+};
+
+const PROOF_OF_POSSESSION_DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
+
+base64_serde_type!(Base64Standard, STANDARD);
+
+#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+struct EthPubkey(#[serde(with = "Base64Standard")] Vec<u8>);
+
+#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+struct EthHeaders {
+    public_keys: Vec<EthPubkey>,
+    #[serde(with = "Base64Standard")]
+    message: Vec<u8>,
+    #[serde(with = "Base64Standard")]
+    signature: Vec<u8>,
+    #[serde(with = "Base64Standard")]
+    aggregate_pubkey: Vec<u8>,
+}
+
+#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+struct AggregateTestFile {
+    input: Vec<String>,
+    output: Option<String>,
+}
+
+struct AggregateTest {
+    input: Vec<Vec<u8>>,
+    output: Option<Vec<u8>>,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct HashTestInput {
+    msg: String,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct HashTestOutput {
+    x: String,
+    y: String,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct HashTestFile {
+    input: HashTestInput,
+    output: HashTestOutput,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct VerifyTestInput {
+    pubkey: String,
+    message: String,
+    signature: String,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct VerifyTestFile {
+    input: VerifyTestInput,
+    output: bool,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct AggregateVerifyInput {
+    pubkeys: Vec<String>,
+    messages: Vec<String>,
+    signature: String,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct AggregateVerifyFile {
+    input: AggregateVerifyInput,
+    output: bool,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct BatchVerifyInput {
+    pubkeys: Vec<String>,
+    messages: Vec<String>,
+    signatures: Vec<String>,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct BatchVerifyFile {
+    input: BatchVerifyInput,
+    output: bool,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct FastAggregateVerifyInput {
+    pubkeys: Vec<String>,
+    message: String,
+    signature: String,
+}
+
+#[derive(serde::Deserialize, serde::Serialize)]
+struct FastAggregateVerifyFile {
+    input: FastAggregateVerifyInput,
+    output: bool,
+}
+
+const ETH_HEADER_FILE: &str = include_str!("../testdata/eth-headers/1699693797.394876721s.json");
+const AGGREGATE_1: &str = include_str!("../testdata/bls-tests/aggregate/aggregate_0x0000000000000000000000000000000000000000000000000000000000000000.json");
+const AGGREGATE_2: &str = include_str!("../testdata/bls-tests/aggregate/aggregate_0x5656565656565656565656565656565656565656565656565656565656565656.json");
+const AGGREGATE_3: &str = include_str!("../testdata/bls-tests/aggregate/aggregate_0xabababababababababababababababababababababababababababababababab.json");
+const AGGREGATE_4: &str =
+    include_str!("../testdata/bls-tests/aggregate/aggregate_infinity_signature.json");
+const AGGREGATE_5: &str =
+    include_str!("../testdata/bls-tests/aggregate/aggregate_na_signatures.json");
+const AGGREGATE_6: &str =
+    include_str!("../testdata/bls-tests/aggregate/aggregate_single_signature.json");
+
+fn read_eth_header_file() -> EthHeaders {
+    serde_json::from_str(ETH_HEADER_FILE).unwrap()
+}
+
+fn read_aggregate_test(json: &str) -> AggregateTest {
+    let file: AggregateTestFile = serde_json::from_str(json).unwrap();
+    AggregateTest {
+        input: file
+            .input
+            .into_iter()
+            .map(|entry| hex::decode(&entry[2..]).unwrap())
+            .collect(),
+        output: file.output.map(|entry| hex::decode(&entry[2..]).unwrap()),
+    }
+}
+
+// Test for https://eth2book.info/capella/part2/building_blocks/signatures/#aggregating-public-keys
+#[test]
+fn bls12_381_aggregate_g1_works() {
+    let file = read_eth_header_file();
+
+    let pubkeys: Vec<&[u8]> = file.public_keys.iter().map(|m| m.0.as_slice()).collect();
+    let pubkeys_combined: Vec<u8> = pubkeys.concat();
+
+    let sum = bls12_381_aggregate_g1(&pubkeys_combined).unwrap();
+    assert_eq!(sum.as_slice(), file.aggregate_pubkey);
+}
+
+// Test for https://eth2book.info/capella/part2/building_blocks/signatures/#aggregating-signatures
+#[test]
+fn bls12_381_aggregate_g2_works() {
+    for json in [
+        AGGREGATE_1,
+        AGGREGATE_2,
+        AGGREGATE_3,
+        AGGREGATE_4,
+        AGGREGATE_5,
+        AGGREGATE_6,
+    ] {
+        let test = read_aggregate_test(json);
+        let signatures: Vec<&[u8]> = test.input.iter().map(|m| m.as_slice()).collect();
+        let signatures_combined: Vec<u8> = signatures.concat();
+
+        // Skip empty signatures since we explicitly error on empty inputs
+        if signatures_combined.is_empty() {
+            continue;
+        }
+
+        let sum = bls12_381_aggregate_g2(&signatures_combined).unwrap();
+        match test.output {
+            Some(expected) => assert_eq!(sum.as_slice(), expected),
+            None => assert_eq!(
+                sum.as_slice(),
+                // point at infinity – is this what we want here?
+                [
+                    // C_bit set (compression)
+                    // I_bit set (point at infinity)
+                    // S_bit unset (sign)
+                    0b11000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+                ]
+            ),
+        }
+    }
+}
+
+#[test]
+fn bls12_381_hash_to_g2_works() {
+    let paths = glob::glob("testdata/bls-tests/hash_to_G2/*.json")
+        .unwrap()
+        .flatten();
+
+    for path in paths {
+        let test_data = fs::read(&path).unwrap();
+        let test_data: HashTestFile = serde_json::from_slice(&test_data).unwrap();
+        let g2_point = bls12_381_hash_to_g2(
+            HashFunction::Sha256,
+            test_data.input.msg.as_bytes(),
+            b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_",
+        );
+
+        let prepared_x = test_data.output.x.replace("0x", "");
+        let (x1, x2) = prepared_x.split_once(',').unwrap();
+        let decoded_x = hex::decode(format!("{x2}{x1}")).unwrap();
+
+        let prepared_y = test_data.output.y.replace("0x", "");
+        let (y1, y2) = prepared_y.split_once(',').unwrap();
+        let decoded_y = hex::decode(format!("{y2}{y1}")).unwrap();
+        let uncompressed = [decoded_x.as_slice(), &decoded_y].concat();
+
+        let affine = ark_bls12_381::G2Affine::deserialize_uncompressed(&uncompressed[..]).unwrap();
+        let mut compressed_affine = [0; BLS12_381_G2_POINT_LEN];
+        affine
+            .serialize_compressed(&mut compressed_affine[..])
+            .unwrap();
+
+        assert_eq!(
+            g2_point,
+            compressed_affine,
+            "Failed with test vector {}",
+            path.display()
+        );
+    }
+}
+
+#[test]
+fn bls12_381_verify_works() {
+    let paths = glob::glob("testdata/bls-tests/verify/*.json")
+        .unwrap()
+        .flatten();
+
+    for path in paths {
+        let test_data = fs::read(&path).unwrap();
+        let test_data: VerifyTestFile = serde_json::from_slice(&test_data).unwrap();
+
+        let pubkey = hex::decode(&test_data.input.pubkey[2..]).unwrap();
+        let message = hex::decode(&test_data.input.message[2..]).unwrap();
+        let signature = hex::decode(&test_data.input.signature[2..]).unwrap();
+
+        let message_point =
+            bls12_381_hash_to_g2(HashFunction::Sha256, &message, PROOF_OF_POSSESSION_DST);
+
+        let pubkey = pubkey.try_into().unwrap();
+        let signature = signature.try_into().unwrap();
+
+        let verify_result = (|| {
+            if bls12_381_g1_is_identity(&pubkey)? {
+                println!("pubkey is identity");
+                return Ok(false);
+            }
+
+            if bls12_381_g2_is_identity(&signature)? {
+                println!("signature is identity");
+                return Ok(false);
+            }
+
+            let bool_result = bls12_381_pairing_equality(
+                &pubkey,
+                &message_point,
+                &BLS12_381_G1_GENERATOR,
+                &signature,
+            )?;
+
+            if !bool_result {
+                println!("pairing is not equal");
+            }
+
+            Ok::<_, Box<dyn Error>>(bool_result)
+        })();
+
+        let verify_result = verify_result
+            .map_err(|err| eprintln!("error: {err}"))
+            .unwrap_or(false);
+
+        assert_eq!(
+            verify_result,
+            test_data.output,
+            "Failed with test vector {}",
+            path.display()
+        );
+
+        println!("Finished case {}", path.display());
+        println!("========================");
+    }
+}
+
+#[test]
+fn bls12_381_aggregate_verify_works() {
+    let paths = glob::glob("testdata/bls-tests/aggregate_verify/*.json")
+        .unwrap()
+        .flatten();
+
+    for path in paths {
+        let test_data = fs::read(&path).unwrap();
+        let test_data: AggregateVerifyFile = serde_json::from_slice(&test_data).unwrap();
+
+        let signature = hex::decode(&test_data.input.signature[2..]).unwrap();
+
+        let messages: Vec<u8> = test_data
+            .input
+            .messages
+            .iter()
+            .flat_map(|message| {
+                let msg = hex::decode(&message[2..]).unwrap();
+                bls12_381_hash_to_g2(HashFunction::Sha256, &msg, PROOF_OF_POSSESSION_DST)
+            })
+            .collect();
+
+        let verify_result = (|| {
+            let signature = signature.as_slice().try_into()?;
+            if bls12_381_g2_is_identity(&signature)? {
+                println!("signature is identity");
+                return Ok(false);
+            }
+
+            let mut pubkeys: Vec<u8> = Vec::with_capacity(test_data.input.pubkeys.len() * 48);
+            for pubkey in test_data.input.pubkeys {
+                let pubkey = hex::decode(&pubkey[2..]).unwrap();
+
+                if bls12_381_g1_is_identity(&pubkey.as_slice().try_into()?)? {
+                    println!("pubkey is identity");
+                    return Ok(false);
+                }
+
+                pubkeys.extend(pubkey);
+            }
+
+            if pubkeys.is_empty() || messages.is_empty() {
+                println!("no keys or no signatures");
+                return Ok(false);
+            }
+
+            let bool_result = bls12_381_pairing_equality(
+                &pubkeys,
+                &messages,
+                &BLS12_381_G1_GENERATOR,
+                &signature,
+            )?;
+
+            if !bool_result {
+                println!("pairing is not equal");
+            }
+
+            Ok::<_, Box<dyn Error>>(bool_result)
+        })();
+
+        let verify_result = verify_result
+            .map_err(|err| eprintln!("error: {err:?}"))
+            .unwrap_or(false);
+
+        assert_eq!(
+            verify_result,
+            test_data.output,
+            "Failed with test vector {}",
+            path.display()
+        );
+
+        println!("Finished case {}", path.display());
+        println!("========================");
+    }
+}
+
+#[test]
+fn bls12_381_fast_aggregate_verify_works() {
+    let paths = glob::glob("testdata/bls-tests/fast_aggregate_verify/*.json")
+        .unwrap()
+        .flatten();
+
+    for path in paths {
+        let test_data = fs::read(&path).unwrap();
+        let test_data: FastAggregateVerifyFile = serde_json::from_slice(&test_data).unwrap();
+
+        let message = hex::decode(&test_data.input.message[2..]).unwrap();
+        let signature = hex::decode(&test_data.input.signature[2..]).unwrap();
+
+        let message_point =
+            bls12_381_hash_to_g2(HashFunction::Sha256, &message, PROOF_OF_POSSESSION_DST);
+        let signature = signature.try_into().unwrap();
+
+        let verify_result = (|| {
+            let mut pubkeys: Vec<u8> = Vec::with_capacity(test_data.input.pubkeys.len() * 48);
+            for pubkey in test_data.input.pubkeys {
+                let pubkey = hex::decode(&pubkey[2..]).unwrap();
+
+                if bls12_381_g1_is_identity(&pubkey.as_slice().try_into()?)? {
+                    println!("pubkey is identity");
+                    return Ok(false);
+                }
+
+                pubkeys.extend(pubkey);
+            }
+
+            // Reject cases with empty public keys since the aggregation will:
+            //
+            // 1. error out with our implementation specifically
+            // 2. if it wouldn't error out, it would return the identity element of G1, making the
+            //    signature validation return invalid anyway
+            if pubkeys.is_empty() {
+                return Ok(false);
+            }
+            let pubkey = bls12_381_aggregate_g1(&pubkeys).unwrap();
+
+            if bls12_381_g2_is_identity(&signature)? {
+                println!("signature is identity");
+                return Ok(false);
+            }
+
+            let bool_result = bls12_381_pairing_equality(
+                &pubkey,
+                &message_point,
+                &BLS12_381_G1_GENERATOR,
+                &signature,
+            )?;
+
+            if !bool_result {
+                println!("pairing is not equal");
+            }
+
+            Ok::<_, Box<dyn Error>>(bool_result)
+        })();
+
+        let verify_result = verify_result
+            .map_err(|err| eprintln!("error: {err}"))
+            .unwrap_or(false);
+
+        assert_eq!(
+            verify_result,
+            test_data.output,
+            "Failed with test vector {}",
+            path.display()
+        );
+
+        println!("Finished case {}", path.display());
+        println!("========================");
+    }
+}
diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml
index 32d3573214..2e5704a371 100644
--- a/packages/std/Cargo.toml
+++ b/packages/std/Cargo.toml
@@ -59,6 +59,7 @@ cosmwasm_2_1 = ["cosmwasm_2_0"]
 base64 = "0.22.0"
 cosmwasm-derive = { version = "2.0.1", path = "../derive" }
 cosmwasm-core = { path = "../core", version = "2.0.1", features = ["std"] }
+cosmwasm-crypto = { version = "2.0.1", path = "../crypto" }
 derive_more = { version = "1.0.0-beta.6", default-features = false, features = ["debug"] }
 hex = "0.4"
 schemars = { workspace = true }
@@ -69,7 +70,7 @@ thiserror = "1.0.26"
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
 bech32 = "0.11.0"
-cosmwasm-crypto = { version = "2.0.1", path = "../crypto" }
+cosmwasm-crypto = { version = "2.0.1", path = "../crypto", features = ["std"] }
 rand_core = { version = "0.6.4", features = ["getrandom"] }
 
 [dev-dependencies]
diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs
index e1a7b500ca..6647585945 100644
--- a/packages/std/src/imports.rs
+++ b/packages/std/src/imports.rs
@@ -14,6 +14,8 @@ use crate::{
     iterator::{Order, Record},
     memory::get_optional_region_address,
 };
+#[cfg(feature = "cosmwasm_2_1")]
+use crate::{AggregationError, HashFunction, PairingEqualityError};
 use crate::{RecoverPubkeyError, StdError, StdResult, SystemError, VerificationError};
 
 /// An upper bound for typical canonical address lengths (e.g. 20 in Cosmos SDK/Ethereum or 32 in Nano/Substrate)
@@ -46,6 +48,21 @@ extern "C" {
     fn addr_canonicalize(source_ptr: u32, destination_ptr: u32) -> u32;
     fn addr_humanize(source_ptr: u32, destination_ptr: u32) -> u32;
 
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_aggregate_g1(g1s_ptr: u32, out_ptr: u32) -> u32;
+
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_aggregate_g2(g2s_ptr: u32, out_ptr: u32) -> u32;
+
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_pairing_equality(ps_ptr: u32, qs_ptr: u32, r_ptr: u32, s_ptr: u32) -> u32;
+
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_hash_to_g1(hash_function: u32, msg_ptr: u32, dst_ptr: u32, out_ptr: u32) -> u32;
+
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_hash_to_g2(hash_function: u32, msg_ptr: u32, dst_ptr: u32, out_ptr: u32) -> u32;
+
     /// Verifies message hashes against a signature with a public key, using the
     /// secp256k1 ECDSA parametrization.
     /// Returns 0 on verification success, 1 on verification failure, and values
@@ -375,6 +392,145 @@ impl Api for ExternalApi {
         Ok(Addr::unchecked(address))
     }
 
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_aggregate_g1(&self, g1s: &[u8]) -> Result<[u8; 48], VerificationError> {
+        let point = [0_u8; 48];
+
+        let send = Region::from_slice(g1s);
+        let send_ptr = send.as_ptr() as u32;
+
+        let out = Region::from_slice(&point);
+        let out_ptr = out.as_ptr() as u32;
+        let result = unsafe { bls12_381_aggregate_g1(send_ptr, out_ptr) };
+        match result {
+            0 => Ok(point),
+            8 => Err(VerificationError::InvalidPoint),
+            16 => Err(VerificationError::Aggregation {
+                source: AggregationError::Empty,
+            }),
+            17 => Err(VerificationError::Aggregation {
+                source: AggregationError::NotMultiple,
+            }),
+            error_code => Err(VerificationError::unknown_err(error_code)),
+        }
+    }
+
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_aggregate_g2(&self, g2s: &[u8]) -> Result<[u8; 96], VerificationError> {
+        let point = [0_u8; 96];
+
+        let send = Region::from_slice(g2s);
+        let send_ptr = send.as_ptr() as u32;
+
+        let out = Region::from_slice(&point);
+        let out_ptr = out.as_ptr() as u32;
+        let result = unsafe { bls12_381_aggregate_g2(send_ptr, out_ptr) };
+        match result {
+            0 => Ok(point),
+            8 => Err(VerificationError::InvalidPoint),
+            14 => Err(VerificationError::Aggregation {
+                source: AggregationError::Empty,
+            }),
+            15 => Err(VerificationError::Aggregation {
+                source: AggregationError::NotMultiple,
+            }),
+            error_code => Err(VerificationError::unknown_err(error_code)),
+        }
+    }
+
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_pairing_equality(
+        &self,
+        ps: &[u8],
+        qs: &[u8],
+        r: &[u8],
+        s: &[u8],
+    ) -> Result<bool, VerificationError> {
+        let send_ps = Region::from_slice(ps);
+        let send_qs = Region::from_slice(qs);
+        let send_r = Region::from_slice(r);
+        let send_s = Region::from_slice(s);
+
+        let send_ps_ptr = send_ps.as_ptr() as u32;
+        let send_qs_ptr = send_qs.as_ptr() as u32;
+        let send_r_ptr = send_r.as_ptr() as u32;
+        let send_s_ptr = send_s.as_ptr() as u32;
+
+        let result =
+            unsafe { bls12_381_pairing_equality(send_ps_ptr, send_qs_ptr, send_r_ptr, send_s_ptr) };
+        match result {
+            0 => Ok(true),
+            1 => Ok(false),
+            8 => Err(VerificationError::InvalidPoint),
+            11 => Err(VerificationError::PairingEquality {
+                source: PairingEqualityError::NotMultipleG1,
+            }),
+            12 => Err(VerificationError::PairingEquality {
+                source: PairingEqualityError::NotMultipleG2,
+            }),
+            13 => Err(VerificationError::PairingEquality {
+                source: PairingEqualityError::UnequalPointAmount,
+            }),
+            error_code => Err(VerificationError::unknown_err(error_code)),
+        }
+    }
+
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_hash_to_g1(
+        &self,
+        hash_function: HashFunction,
+        msg: &[u8],
+        dst: &[u8],
+    ) -> Result<[u8; 48], VerificationError> {
+        let point = [0_u8; 48];
+
+        let send_msg = Region::from_slice(msg);
+        let send_msg_ptr = send_msg.as_ptr() as u32;
+
+        let send_dst = Region::from_slice(dst);
+        let send_dst_ptr = send_dst.as_ptr() as u32;
+
+        let out = Region::from_slice(&point);
+        let out_ptr = out.as_ptr() as u32;
+        let result = unsafe {
+            bls12_381_hash_to_g1(hash_function as u32, send_msg_ptr, send_dst_ptr, out_ptr)
+        };
+
+        match result {
+            0 => Ok(point),
+            9 => Err(VerificationError::UnknownHashFunction),
+            error_code => Err(VerificationError::unknown_err(error_code)),
+        }
+    }
+
+    #[cfg(feature = "cosmwasm_2_1")]
+    fn bls12_381_hash_to_g2(
+        &self,
+        hash_function: HashFunction,
+        msg: &[u8],
+        dst: &[u8],
+    ) -> Result<[u8; 96], VerificationError> {
+        let point = [0_u8; 96];
+
+        let send_msg = Region::from_slice(msg);
+        let send_msg_ptr = send_msg.as_ptr() as u32;
+
+        let send_dst = Region::from_slice(dst);
+        let send_dst_ptr = send_dst.as_ptr() as u32;
+
+        let out = Region::from_slice(&point);
+        let out_ptr = out.as_ptr() as u32;
+        let result = unsafe {
+            bls12_381_hash_to_g2(hash_function as u32, send_msg_ptr, send_dst_ptr, out_ptr)
+        };
+
+        match result {
+            0 => Ok(point),
+            9 => Err(VerificationError::UnknownHashFunction),
+            error_code => Err(VerificationError::unknown_err(error_code)),
+        }
+    }
+
     fn secp256k1_verify(
         &self,
         message_hash: &[u8],
diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs
index 6860b9bc09..c668b28783 100644
--- a/packages/std/src/lib.rs
+++ b/packages/std/src/lib.rs
@@ -77,7 +77,7 @@ pub use crate::serde::{
 };
 pub use crate::stdack::StdAck;
 pub use crate::storage::MemoryStorage;
-pub use crate::traits::{Api, Querier, QuerierResult, QuerierWrapper, Storage};
+pub use crate::traits::{Api, HashFunction, Querier, QuerierResult, QuerierWrapper, Storage};
 pub use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo, TransactionInfo};
 
 // Exposed in wasm build only
@@ -109,12 +109,12 @@ pub mod testing;
 pub use cosmwasm_core::CoreError as StdError;
 pub use cosmwasm_core::CoreResult as StdResult;
 pub use cosmwasm_core::{
-    from_base64, from_hex, instantiate2_address, to_base64, to_hex, Addr, Binary, CanonicalAddr,
-    CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
+    from_base64, from_hex, instantiate2_address, to_base64, to_hex, Addr, AggregationError, Binary,
+    CanonicalAddr, CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError,
     CoinFromStrError, CoinsError, ConversionOverflowError, Decimal, Decimal256,
     Decimal256RangeExceeded, DecimalRangeExceeded, DivideByZeroError, DivisionError, Fraction,
     HexBinary, Instantiate2AddressError, Int128, Int256, Int512, Int64, Isqrt, OverflowError,
-    OverflowOperation, RecoverPubkeyError, SignedDecimal, SignedDecimal256,
+    OverflowOperation, PairingEqualityError, RecoverPubkeyError, SignedDecimal, SignedDecimal256,
     SignedDecimal256RangeExceeded, SignedDecimalRangeExceeded, SystemError, Timestamp, Uint128,
     Uint256, Uint512, Uint64, VerificationError,
 };
@@ -122,4 +122,6 @@ pub use cosmwasm_core::{
 #[cfg(not(target_arch = "wasm32"))]
 pub use cosmwasm_core::assert_approx_eq;
 
+pub use cosmwasm_crypto::{BLS12_381_G1_GENERATOR, BLS12_381_G2_GENERATOR};
+
 pub use cosmwasm_derive::entry_point;
diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs
index bf83a3a6bb..423efcd543 100644
--- a/packages/std/src/testing/mock.rs
+++ b/packages/std/src/testing/mock.rs
@@ -1,4 +1,5 @@
 use crate::prelude::*;
+use crate::HashFunction;
 use alloc::collections::BTreeMap;
 #[cfg(feature = "cosmwasm_1_3")]
 use alloc::collections::BTreeSet;
@@ -151,6 +152,50 @@ impl Api for MockApi {
             .map_err(|_| StdError::generic_err("Bech32 encoding error"))
     }
 
+    fn bls12_381_aggregate_g1(&self, g1s: &[u8]) -> Result<[u8; 48], VerificationError> {
+        cosmwasm_crypto::bls12_381_aggregate_g1(g1s).map_err(Into::into)
+    }
+
+    fn bls12_381_aggregate_g2(&self, g2s: &[u8]) -> Result<[u8; 96], VerificationError> {
+        cosmwasm_crypto::bls12_381_aggregate_g2(g2s).map_err(Into::into)
+    }
+
+    fn bls12_381_pairing_equality(
+        &self,
+        ps: &[u8],
+        qs: &[u8],
+        r: &[u8],
+        s: &[u8],
+    ) -> Result<bool, VerificationError> {
+        cosmwasm_crypto::bls12_381_pairing_equality(ps, qs, r, s).map_err(Into::into)
+    }
+
+    fn bls12_381_hash_to_g1(
+        &self,
+        hash_function: HashFunction,
+        msg: &[u8],
+        dst: &[u8],
+    ) -> Result<[u8; 48], VerificationError> {
+        Ok(cosmwasm_crypto::bls12_381_hash_to_g1(
+            hash_function.into(),
+            msg,
+            dst,
+        ))
+    }
+
+    fn bls12_381_hash_to_g2(
+        &self,
+        hash_function: HashFunction,
+        msg: &[u8],
+        dst: &[u8],
+    ) -> Result<[u8; 96], VerificationError> {
+        Ok(cosmwasm_crypto::bls12_381_hash_to_g2(
+            hash_function.into(),
+            msg,
+            dst,
+        ))
+    }
+
     fn secp256k1_verify(
         &self,
         message_hash: &[u8],
@@ -1122,6 +1167,8 @@ mod tests {
     use crate::{coin, coins, instantiate2_address, ContractInfoResponse, HexBinary, Response};
     #[cfg(feature = "staking")]
     use crate::{Decimal, Delegation};
+    use base64::{engine::general_purpose, Engine};
+    use cosmwasm_crypto::BLS12_381_G1_GENERATOR;
     use hex_literal::hex;
     use serde::Deserialize;
 
@@ -1140,6 +1187,16 @@ mod tests {
     const ED25519_PUBKEY_HEX: &str =
         "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c";
 
+    // See https://github.com/drand/kyber-bls12381/issues/22 and
+    // https://github.com/drand/drand/pull/1249
+    const DOMAIN_HASH_TO_G2: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
+
+    /// Public key League of Entropy Mainnet (curl -sS https://drand.cloudflare.com/info)
+    const PK_LEO_MAINNET: [u8; 48] = hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31");
+
+    const ETH_BLOCK_HEADER: &[u8] =
+        include_bytes!("../../../crypto/testdata/eth-headers/1699693797.394876721s.json");
+
     #[test]
     fn mock_info_works() {
         let info = mock_info("my name", &coins(100, "atom"));
@@ -1257,6 +1314,141 @@ mod tests {
         );
     }
 
+    #[test]
+    fn bls12_381_aggregate_g1_works() {
+        #[derive(serde::Deserialize)]
+        struct EthHeader {
+            public_keys: Vec<String>,
+            aggregate_pubkey: String,
+        }
+
+        let api = MockApi::default();
+        let header: EthHeader = serde_json::from_slice(ETH_BLOCK_HEADER).unwrap();
+        let expected = general_purpose::STANDARD
+            .decode(header.aggregate_pubkey)
+            .unwrap();
+
+        let pubkeys: Vec<u8> = header
+            .public_keys
+            .into_iter()
+            .flat_map(|key| general_purpose::STANDARD.decode(key).unwrap())
+            .collect();
+        let sum = api.bls12_381_aggregate_g1(&pubkeys).unwrap();
+
+        assert_eq!(expected, sum);
+    }
+
+    #[test]
+    fn bls12_381_aggregate_g2_works() {
+        let api = MockApi::default();
+
+        let points: Vec<u8> = [
+            hex!("b6ed936746e01f8ecf281f020953fbf1f01debd5657c4a383940b020b26507f6076334f91e2366c96e9ab279fb5158090352ea1c5b0c9274504f4f0e7053af24802e51e4568d164fe986834f41e55c8e850ce1f98458c0cfc9ab380b55285a55"),
+            hex!("b23c46be3a001c63ca711f87a005c200cc550b9429d5f4eb38d74322144f1b63926da3388979e5321012fb1a0526bcd100b5ef5fe72628ce4cd5e904aeaa3279527843fae5ca9ca675f4f51ed8f83bbf7155da9ecc9663100a885d5dc6df96d9"),
+            hex!("948a7cb99f76d616c2c564ce9bf4a519f1bea6b0a624a02276443c245854219fabb8d4ce061d255af5330b078d5380681751aa7053da2c98bae898edc218c75f07e24d8802a17cd1f6833b71e58f5eb5b94208b4d0bb3848cecb075ea21be115"),
+        ]
+        .into_iter()
+        .flatten()
+        .collect();
+
+        let expected = hex!("9683b3e6701f9a4b706709577963110043af78a5b41991b998475a3d3fd62abf35ce03b33908418efc95a058494a8ae504354b9f626231f6b3f3c849dfdeaf5017c4780e2aee1850ceaf4b4d9ce70971a3d2cfcd97b7e5ecf6759f8da5f76d31");
+        let sum = api.bls12_381_aggregate_g2(&points).unwrap();
+
+        assert_eq!(sum, expected);
+    }
+
+    #[test]
+    fn bls12_381_pairing_equality_works() {
+        let api = MockApi::default();
+
+        let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
+        let ps = hex!("a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79ab301803f8b5ac4a1133581fc676dfedc60d891dd5fa99028805e5ea5b08d3491af75d0707adab3b70c6a6a580217bf81b53d21a4cfd562c469cc81514d4ce5a6b577d8403d32a394dc265dd190b47fa9f829fdd7963afdf972e5e77854051f6f");
+        let qs: Vec<u8> = [
+            hex!("0000000000000000000000000000000000000000000000000000000000000000"),
+            hex!("5656565656565656565656565656565656565656565656565656565656565656"),
+            hex!("abababababababababababababababababababababababababababababababab"),
+        ]
+        .into_iter()
+        .flat_map(|msg| {
+            api.bls12_381_hash_to_g2(HashFunction::Sha256, &msg, dst)
+                .unwrap()
+        })
+        .collect();
+        let s = hex!("9104e74b9dfd3ad502f25d6a5ef57db0ed7d9a0e00f3500586d8ce44231212542fcfaf87840539b398bf07626705cf1105d246ca1062c6c2e1a53029a0f790ed5e3cb1f52f8234dc5144c45fc847c0cd37a92d68e7c5ba7c648a8a339f171244");
+
+        let is_valid = api
+            .bls12_381_pairing_equality(&ps, &qs, &BLS12_381_G1_GENERATOR, &s)
+            .unwrap();
+        assert!(is_valid);
+    }
+
+    #[test]
+    fn bls12_381_hash_to_g1_works() {
+        // See: <https://datatracker.ietf.org/doc/rfc9380/>; Section J.9.1
+
+        let api = MockApi::default();
+        let msg = b"abc";
+        let dst = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
+
+        let hashed_point = api
+            .bls12_381_hash_to_g1(HashFunction::Sha256, msg, dst)
+            .unwrap();
+        let mut serialized_expected_compressed = hex!("03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f6903");
+        // Set the compression tag
+        serialized_expected_compressed[0] |= 0b1000_0000;
+
+        assert_eq!(hashed_point, serialized_expected_compressed);
+    }
+
+    #[test]
+    fn bls12_381_hash_to_g2_works() {
+        let api = MockApi::default();
+        let msg = b"abc";
+        let dst = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_";
+
+        let hashed_point = api
+            .bls12_381_hash_to_g2(HashFunction::Sha256, msg, dst)
+            .unwrap();
+        let mut serialized_expected_compressed = hex!("139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd802c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e6");
+        // Set the compression tag
+        serialized_expected_compressed[0] |= 0b1000_0000;
+
+        assert_eq!(hashed_point, serialized_expected_compressed);
+    }
+
+    #[test]
+    fn bls12_318_pairing_equality_works() {
+        fn build_bls_message(round: u64, previous_signature: &[u8]) -> Vec<u8> {
+            Sha256::new()
+                .chain_update(previous_signature)
+                .chain_update(round.to_be_bytes())
+                .finalize()
+                .to_vec()
+        }
+
+        let api = MockApi::default();
+
+        let previous_signature = hex::decode("a609e19a03c2fcc559e8dae14900aaefe517cb55c840f6e69bc8e4f66c8d18e8a609685d9917efbfb0c37f058c2de88f13d297c7e19e0ab24813079efe57a182554ff054c7638153f9b26a60e7111f71a0ff63d9571704905d3ca6df0b031747").unwrap();
+        let signature = hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap();
+        let round: u64 = 72785;
+
+        let msg = build_bls_message(round, &previous_signature);
+        let msg_point = api
+            .bls12_381_hash_to_g2(HashFunction::Sha256, &msg, DOMAIN_HASH_TO_G2)
+            .unwrap();
+
+        let is_valid = api
+            .bls12_381_pairing_equality(
+                &BLS12_381_G1_GENERATOR,
+                &signature,
+                &PK_LEO_MAINNET,
+                &msg_point,
+            )
+            .unwrap();
+
+        assert!(is_valid);
+    }
+
     // Basic "works" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
     #[test]
     fn secp256k1_verify_works() {
diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs
index 8a4eda0832..038f17f994 100644
--- a/packages/std/src/traits.rs
+++ b/packages/std/src/traits.rs
@@ -31,6 +31,21 @@ use crate::{from_json, to_json_binary, to_json_vec, Binary};
 use crate::{DenomMetadata, PageRequest};
 use crate::{RecoverPubkeyError, StdError, StdResult, VerificationError};
 
+#[derive(Clone, Copy, Debug)]
+#[non_exhaustive]
+pub enum HashFunction {
+    Sha256 = 0,
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl From<HashFunction> for cosmwasm_crypto::HashFunction {
+    fn from(value: HashFunction) -> Self {
+        match value {
+            HashFunction::Sha256 => cosmwasm_crypto::HashFunction::Sha256,
+        }
+    }
+}
+
 /// Storage provides read and write access to a persistent storage.
 /// If you only want to provide read access, provide `&Storage`
 pub trait Storage {
@@ -164,6 +179,93 @@ pub trait Api {
         recovery_param: u8,
     ) -> Result<Vec<u8>, RecoverPubkeyError>;
 
+    #[allow(unused_variables)]
+    fn bls12_381_aggregate_g1(&self, g1s: &[u8]) -> Result<[u8; 48], VerificationError> {
+        // Support for BLS12-381 is added in 2.1, i.e. we can't add a compile time requirement for new function.
+        // Any implementation of the Api trait which does not implement this function but tries to call it will
+        // panic at runtime. We don't assume such cases exist.
+        // See also https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-default-item
+        unimplemented!()
+    }
+
+    #[allow(unused_variables)]
+    fn bls12_381_aggregate_g2(&self, g2s: &[u8]) -> Result<[u8; 96], VerificationError> {
+        // Support for BLS12-381 is added in 2.1, i.e. we can't add a compile time requirement for new function.
+        // Any implementation of the Api trait which does not implement this function but tries to call it will
+        // panic at runtime. We don't assume such cases exist.
+        // See also https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-default-item
+        unimplemented!()
+    }
+
+    /// Checks the following pairing equality:
+    ///
+    /// e(p_1, q_1) × e(p_2, q_2) × … × e(p_n, q_n) = e(s, q)
+    ///
+    /// The argument `ps` contain the points p_1, ..., p_n ∈ G1 as a concatenation of 48 byte elements.
+    /// The argument `qs` contain the points q_1, ..., q_n ∈ G2 as a concatenation of 96 byte elements.
+    ///
+    /// ## Examples
+    ///
+    /// A simple signature check with one pairing on the left hand side (e(p, q) = e(s, q)):
+    ///
+    /// ```
+    /// # use cosmwasm_std::{Api, HashFunction, StdResult};
+    /// pub fn verify(
+    ///     api: &dyn Api,
+    ///     g1_generator: &[u8],
+    ///     signature: &[u8],
+    ///     pubkey: &[u8],
+    ///     msg: &[u8],
+    ///     dst: &[u8],
+    /// ) -> StdResult<bool> {
+    ///     let msg_hashed = api.bls12_381_hash_to_g2(HashFunction::Sha256, msg, dst)?;
+    ///     api.bls12_381_pairing_equality(g1_generator, signature, pubkey, &msg_hashed)
+    ///         .map_err(Into::into)
+    /// }
+    /// ```
+    #[allow(unused_variables)]
+    fn bls12_381_pairing_equality(
+        &self,
+        ps: &[u8],
+        qs: &[u8],
+        r: &[u8],
+        s: &[u8],
+    ) -> Result<bool, VerificationError> {
+        // Support for BLS12-381 is added in 2.1, i.e. we can't add a compile time requirement for new function.
+        // Any implementation of the Api trait which does not implement this function but tries to call it will
+        // panic at runtime. We don't assume such cases exist.
+        // See also https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-default-item
+        unimplemented!()
+    }
+
+    #[allow(unused_variables)]
+    fn bls12_381_hash_to_g1(
+        &self,
+        hash_function: HashFunction,
+        msg: &[u8],
+        dst: &[u8],
+    ) -> Result<[u8; 48], VerificationError> {
+        // Support for BLS12-381 is added in 2.1, i.e. we can't add a compile time requirement for new function.
+        // Any implementation of the Api trait which does not implement this function but tries to call it will
+        // panic at runtime. We don't assume such cases exist.
+        // See also https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-default-item
+        unimplemented!()
+    }
+
+    #[allow(unused_variables)]
+    fn bls12_381_hash_to_g2(
+        &self,
+        hash_function: HashFunction,
+        msg: &[u8],
+        dst: &[u8],
+    ) -> Result<[u8; 96], VerificationError> {
+        // Support for BLS12-381 is added in 2.1, i.e. we can't add a compile time requirement for new function.
+        // Any implementation of the Api trait which does not implement this function but tries to call it will
+        // panic at runtime. We don't assume such cases exist.
+        // See also https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-default-item
+        unimplemented!()
+    }
+
     #[allow(unused_variables)]
     fn secp256r1_verify(
         &self,
diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs
index 7a0ecb4ebc..e354a10731 100644
--- a/packages/vm/src/compatibility.rs
+++ b/packages/vm/src/compatibility.rs
@@ -20,6 +20,11 @@ const SUPPORTED_IMPORTS: &[&str] = &[
     "env.addr_validate",
     "env.addr_canonicalize",
     "env.addr_humanize",
+    "env.bls12_381_aggregate_g1",
+    "env.bls12_381_aggregate_g2",
+    "env.bls12_381_pairing_equality",
+    "env.bls12_381_hash_to_g1",
+    "env.bls12_381_hash_to_g2",
     "env.secp256k1_verify",
     "env.secp256k1_recover_pubkey",
     "env.secp256r1_verify",
diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs
index dbf10e436b..e30a0877e2 100644
--- a/packages/vm/src/environment.rs
+++ b/packages/vm/src/environment.rs
@@ -45,6 +45,19 @@ pub struct GasConfig {
     pub ed25519_batch_verify_cost: u64,
     /// ed25519 batch signature verification cost (single public key)
     pub ed25519_batch_verify_one_pubkey_cost: u64,
+    /// bls12-381 aggregate cost per point (g1)
+    pub bls12_381_aggregate_g1_per_point: u64,
+    /// bls12-381 aggregate cost per point (g2)
+    pub bls12_381_aggregate_g2_per_point: u64,
+    /// bls12-381 hash to g1 cost
+    pub bls12_381_hash_to_g1_cost: u64,
+    /// bls12-381 hash to g2 cost
+    pub bls12_381_hash_to_g2_cost: u64,
+    /// bls12-381 pairing equality check cost
+    pub bls12_381_pairing_equality_cost: u64,
+    /// bls12-381 aggregated pairing equality check cost per point
+    /// (added on top of the base pairing equality check cost)
+    pub bls12_381_aggregated_pairing_equality_cost_per_pair: u64,
 }
 
 impl Default for GasConfig {
@@ -66,6 +79,14 @@ impl Default for GasConfig {
             // From https://docs.rs/ed25519-zebra/2.2.0/ed25519_zebra/batch/index.html
             ed25519_batch_verify_cost: 63 * GAS_PER_US / 2,
             ed25519_batch_verify_one_pubkey_cost: 63 * GAS_PER_US / 4,
+            // just assume the production machines have more than 4 cores, so we can half that
+            bls12_381_aggregate_g1_per_point: 16 * GAS_PER_US / 2,
+            bls12_381_aggregate_g2_per_point: 33 * GAS_PER_US / 2,
+            bls12_381_hash_to_g1_cost: 324 * GAS_PER_US,
+            bls12_381_hash_to_g2_cost: 528 * GAS_PER_US,
+            // god i wish i was lying
+            bls12_381_pairing_equality_cost: 1038 * GAS_PER_US,
+            bls12_381_aggregated_pairing_equality_cost_per_pair: 108 * GAS_PER_US,
         }
     }
 }
diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs
index b0324f16f5..ef86c276db 100644
--- a/packages/vm/src/imports.rs
+++ b/packages/vm/src/imports.rs
@@ -4,8 +4,10 @@ use std::cmp::max;
 use std::marker::PhantomData;
 
 use cosmwasm_crypto::{
-    ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify,
-    secp256r1_recover_pubkey, secp256r1_verify, CryptoError,
+    bls12_381_aggregate_g1, bls12_381_aggregate_g2, bls12_381_hash_to_g1, bls12_381_hash_to_g2,
+    bls12_381_pairing_equality, ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey,
+    secp256k1_verify, secp256r1_recover_pubkey, secp256r1_verify, CryptoError, HashFunction,
+    BLS12_381_G1_POINT_LEN, BLS12_381_G2_POINT_LEN,
 };
 use cosmwasm_crypto::{
     ECDSA_PUBKEY_MAX_LEN, ECDSA_SIGNATURE_LEN, EDDSA_PUBKEY_LEN, MESSAGE_HASH_MAX_LEN,
@@ -239,6 +241,230 @@ const SECP256K1_VERIFY_CODE_VALID: u32 = 0;
 /// Return code (error code) for an invalid signature
 const SECP256K1_VERIFY_CODE_INVALID: u32 = 1;
 
+/// Return code (error code) for a valid pairing
+const BLS12_381_VALID_PAIRING: u32 = 0;
+
+/// Return code (error code) for an invalid pairing
+const BLS12_381_INVALID_PAIRING: u32 = 1;
+
+/// Return code (error code) if the aggregating the points on curve was successful
+const BLS12_381_AGGREGATE_SUCCESS: u32 = 0;
+
+/// Return code (error code) for success when hashing to the curve
+const BLS12_381_HASH_TO_CURVE_SUCCESS: u32 = 0;
+
+/// Maximum size of continous points passed to aggregate functions
+const BLS12_381_MAX_AGGREGATE_SIZE: usize = 2 * MI;
+
+/// Maximum size of the message passed to the hash-to-curve functions
+const BLS12_381_MAX_MESSAGE_SIZE: usize = 5 * MI;
+
+/// Maximum size of the destination passed to the hash-to-curve functions
+const BLS12_381_MAX_DST_SIZE: usize = 5 * KI;
+
+pub fn do_bls12_381_aggregate_g1<
+    A: BackendApi + 'static,
+    S: Storage + 'static,
+    Q: Querier + 'static,
+>(
+    mut env: FunctionEnvMut<Environment<A, S, Q>>,
+    g1s_ptr: u32,
+    out_ptr: u32,
+) -> VmResult<u32> {
+    let (data, mut store) = env.data_and_store_mut();
+    let memory = data.memory(&store);
+
+    let g1s = read_region(&memory, g1s_ptr, BLS12_381_MAX_AGGREGATE_SIZE)?;
+
+    let estimated_point_count = (g1s.len() / BLS12_381_G1_POINT_LEN) as u64;
+    let gas_info = GasInfo::with_cost(
+        data.gas_config.bls12_381_aggregate_g1_per_point * estimated_point_count,
+    );
+    process_gas_info(data, &mut store, gas_info)?;
+
+    let code = match bls12_381_aggregate_g1(&g1s) {
+        Ok(point) => {
+            let memory = data.memory(&store);
+            write_region(&memory, out_ptr, &point)?;
+            BLS12_381_AGGREGATE_SUCCESS
+        }
+        Err(err) => match err {
+            CryptoError::InvalidPoint { .. } | CryptoError::Aggregation { .. } => err.code(),
+            CryptoError::PairingEquality { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::GenericErr { .. }
+            | CryptoError::InvalidHashFormat { .. }
+            | CryptoError::InvalidPubkeyFormat { .. }
+            | CryptoError::InvalidRecoveryParam { .. }
+            | CryptoError::InvalidSignatureFormat { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
+                panic!("Error must not happen for this call")
+            }
+        },
+    };
+
+    Ok(code)
+}
+
+pub fn do_bls12_381_aggregate_g2<
+    A: BackendApi + 'static,
+    S: Storage + 'static,
+    Q: Querier + 'static,
+>(
+    mut env: FunctionEnvMut<Environment<A, S, Q>>,
+    g2s_ptr: u32,
+    out_ptr: u32,
+) -> VmResult<u32> {
+    let (data, mut store) = env.data_and_store_mut();
+    let memory = data.memory(&store);
+
+    let g2s = read_region(&memory, g2s_ptr, BLS12_381_MAX_AGGREGATE_SIZE)?;
+
+    let estimated_point_count = (g2s.len() / BLS12_381_G2_POINT_LEN) as u64;
+    let gas_info = GasInfo::with_cost(
+        data.gas_config.bls12_381_aggregate_g2_per_point * estimated_point_count,
+    );
+    process_gas_info(data, &mut store, gas_info)?;
+
+    let code = match bls12_381_aggregate_g2(&g2s) {
+        Ok(point) => {
+            let memory = data.memory(&store);
+            write_region(&memory, out_ptr, &point)?;
+            BLS12_381_AGGREGATE_SUCCESS
+        }
+        Err(err) => match err {
+            CryptoError::InvalidPoint { .. } | CryptoError::Aggregation { .. } => err.code(),
+            CryptoError::PairingEquality { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::GenericErr { .. }
+            | CryptoError::InvalidHashFormat { .. }
+            | CryptoError::InvalidPubkeyFormat { .. }
+            | CryptoError::InvalidRecoveryParam { .. }
+            | CryptoError::InvalidSignatureFormat { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
+                panic!("Error must not happen for this call")
+            }
+        },
+    };
+
+    Ok(code)
+}
+
+pub fn do_bls12_381_pairing_equality<
+    A: BackendApi + 'static,
+    S: Storage + 'static,
+    Q: Querier + 'static,
+>(
+    mut env: FunctionEnvMut<Environment<A, S, Q>>,
+    ps_ptr: u32,
+    qs_ptr: u32,
+    r_ptr: u32,
+    s_ptr: u32,
+) -> VmResult<u32> {
+    let (data, mut store) = env.data_and_store_mut();
+    let memory = data.memory(&store);
+
+    let ps = read_region(&memory, ps_ptr, BLS12_381_MAX_AGGREGATE_SIZE)?;
+    let qs = read_region(&memory, qs_ptr, BLS12_381_MAX_AGGREGATE_SIZE)?;
+    let r = read_region(&memory, r_ptr, BLS12_381_G1_POINT_LEN)?;
+    let s = read_region(&memory, s_ptr, BLS12_381_G2_POINT_LEN)?;
+
+    let estimated_point_count = (ps.len() / BLS12_381_G1_POINT_LEN) as u64;
+    let additional_cost = data
+        .gas_config
+        .bls12_381_aggregated_pairing_equality_cost_per_pair
+        // Add one since we do not include any pairs in the base benchmark, and we always need to add one for the `r` and `s` pair.
+        * (estimated_point_count  + 1);
+
+    let gas_info =
+        GasInfo::with_cost(data.gas_config.bls12_381_pairing_equality_cost + additional_cost);
+    process_gas_info(data, &mut store, gas_info)?;
+
+    let code = match bls12_381_pairing_equality(&ps, &qs, &r, &s) {
+        Ok(true) => BLS12_381_VALID_PAIRING,
+        Ok(false) => BLS12_381_INVALID_PAIRING,
+        Err(err) => match err {
+            CryptoError::PairingEquality { .. } | CryptoError::InvalidPoint { .. } => err.code(),
+            CryptoError::Aggregation { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::GenericErr { .. }
+            | CryptoError::InvalidHashFormat { .. }
+            | CryptoError::InvalidPubkeyFormat { .. }
+            | CryptoError::InvalidRecoveryParam { .. }
+            | CryptoError::InvalidSignatureFormat { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
+                panic!("Error must not happen for this call")
+            }
+        },
+    };
+
+    Ok(code)
+}
+
+pub fn do_bls12_381_hash_to_g1<
+    A: BackendApi + 'static,
+    S: Storage + 'static,
+    Q: Querier + 'static,
+>(
+    mut env: FunctionEnvMut<Environment<A, S, Q>>,
+    hash_function: u32,
+    msg_ptr: u32,
+    dst_ptr: u32,
+    out_ptr: u32,
+) -> VmResult<u32> {
+    let (data, mut store) = env.data_and_store_mut();
+    let memory = data.memory(&store);
+
+    let msg = read_region(&memory, msg_ptr, BLS12_381_MAX_MESSAGE_SIZE)?;
+    let dst = read_region(&memory, dst_ptr, BLS12_381_MAX_DST_SIZE)?;
+
+    let gas_info = GasInfo::with_cost(data.gas_config.bls12_381_hash_to_g1_cost);
+    process_gas_info(data, &mut store, gas_info)?;
+
+    let hash_function = match HashFunction::from_u32(hash_function) {
+        Ok(func) => func,
+        Err(error) => return Ok(error.code()),
+    };
+    let point = bls12_381_hash_to_g1(hash_function, &msg, &dst);
+
+    let memory = data.memory(&store);
+    write_region(&memory, out_ptr, &point)?;
+
+    Ok(BLS12_381_HASH_TO_CURVE_SUCCESS)
+}
+
+pub fn do_bls12_381_hash_to_g2<
+    A: BackendApi + 'static,
+    S: Storage + 'static,
+    Q: Querier + 'static,
+>(
+    mut env: FunctionEnvMut<Environment<A, S, Q>>,
+    hash_function: u32,
+    msg_ptr: u32,
+    dst_ptr: u32,
+    out_ptr: u32,
+) -> VmResult<u32> {
+    let (data, mut store) = env.data_and_store_mut();
+    let memory = data.memory(&store);
+
+    let msg = read_region(&memory, msg_ptr, BLS12_381_MAX_MESSAGE_SIZE)?;
+    let dst = read_region(&memory, dst_ptr, BLS12_381_MAX_DST_SIZE)?;
+
+    let gas_info = GasInfo::with_cost(data.gas_config.bls12_381_hash_to_g2_cost);
+    process_gas_info(data, &mut store, gas_info)?;
+
+    let hash_function = match HashFunction::from_u32(hash_function) {
+        Ok(func) => func,
+        Err(error) => return Ok(error.code()),
+    };
+    let point = bls12_381_hash_to_g2(hash_function, &msg, &dst);
+
+    let memory = data.memory(&store);
+    write_region(&memory, out_ptr, &point)?;
+
+    Ok(BLS12_381_HASH_TO_CURVE_SUCCESS)
+}
+
 pub fn do_secp256k1_verify<A: BackendApi + 'static, S: Storage + 'static, Q: Querier + 'static>(
     mut env: FunctionEnvMut<Environment<A, S, Q>>,
     hash_ptr: u32,
@@ -267,7 +493,12 @@ pub fn do_secp256k1_verify<A: BackendApi + 'static, S: Storage + 'static, Q: Que
             | CryptoError::InvalidPubkeyFormat { .. }
             | CryptoError::InvalidSignatureFormat { .. }
             | CryptoError::GenericErr { .. } => err.code(),
-            CryptoError::BatchErr { .. } | CryptoError::InvalidRecoveryParam { .. } => {
+            CryptoError::Aggregation { .. }
+            | CryptoError::PairingEquality { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::InvalidPoint { .. }
+            | CryptoError::InvalidRecoveryParam { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
                 panic!("Error must not happen for this call")
             }
         },
@@ -307,7 +538,12 @@ pub fn do_secp256k1_recover_pubkey<
             | CryptoError::InvalidSignatureFormat { .. }
             | CryptoError::InvalidRecoveryParam { .. }
             | CryptoError::GenericErr { .. } => Ok(to_high_half(err.code())),
-            CryptoError::BatchErr { .. } | CryptoError::InvalidPubkeyFormat { .. } => {
+            CryptoError::Aggregation { .. }
+            | CryptoError::PairingEquality { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::InvalidPoint { .. }
+            | CryptoError::InvalidPubkeyFormat { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
                 panic!("Error must not happen for this call")
             }
         },
@@ -348,7 +584,12 @@ pub fn do_secp256r1_verify<A: BackendApi + 'static, S: Storage + 'static, Q: Que
             | CryptoError::InvalidPubkeyFormat { .. }
             | CryptoError::InvalidSignatureFormat { .. }
             | CryptoError::GenericErr { .. } => err.code(),
-            CryptoError::BatchErr { .. } | CryptoError::InvalidRecoveryParam { .. } => {
+            CryptoError::Aggregation { .. }
+            | CryptoError::PairingEquality { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::InvalidPoint { .. }
+            | CryptoError::InvalidRecoveryParam { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
                 panic!("Error must not happen for this call")
             }
         },
@@ -388,7 +629,12 @@ pub fn do_secp256r1_recover_pubkey<
             | CryptoError::InvalidSignatureFormat { .. }
             | CryptoError::InvalidRecoveryParam { .. }
             | CryptoError::GenericErr { .. } => Ok(to_high_half(err.code())),
-            CryptoError::BatchErr { .. } | CryptoError::InvalidPubkeyFormat { .. } => {
+            CryptoError::Aggregation { .. }
+            | CryptoError::PairingEquality { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::InvalidPoint { .. }
+            | CryptoError::InvalidPubkeyFormat { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
                 panic!("Error must not happen for this call")
             }
         },
@@ -436,9 +682,13 @@ pub fn do_ed25519_verify<A: BackendApi + 'static, S: Storage + 'static, Q: Queri
             CryptoError::InvalidPubkeyFormat { .. }
             | CryptoError::InvalidSignatureFormat { .. }
             | CryptoError::GenericErr { .. } => err.code(),
-            CryptoError::BatchErr { .. }
+            CryptoError::Aggregation { .. }
+            | CryptoError::PairingEquality { .. }
+            | CryptoError::BatchErr { .. }
+            | CryptoError::InvalidPoint { .. }
             | CryptoError::InvalidHashFormat { .. }
-            | CryptoError::InvalidRecoveryParam { .. } => {
+            | CryptoError::InvalidRecoveryParam { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
                 panic!("Error must not happen for this call")
             }
         },
@@ -499,7 +749,12 @@ pub fn do_ed25519_batch_verify<
             | CryptoError::InvalidPubkeyFormat { .. }
             | CryptoError::InvalidSignatureFormat { .. }
             | CryptoError::GenericErr { .. } => err.code(),
-            CryptoError::InvalidHashFormat { .. } | CryptoError::InvalidRecoveryParam { .. } => {
+            CryptoError::Aggregation { .. }
+            | CryptoError::PairingEquality { .. }
+            | CryptoError::InvalidHashFormat { .. }
+            | CryptoError::InvalidPoint { .. }
+            | CryptoError::InvalidRecoveryParam { .. }
+            | CryptoError::UnknownHashFunction { .. } => {
                 panic!("Error must not happen for this call")
             }
         },
diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs
index eb6c3a9283..08082d7ee8 100644
--- a/packages/vm/src/instance.rs
+++ b/packages/vm/src/instance.rs
@@ -14,10 +14,11 @@ use crate::conversion::{ref_to_u32, to_u32};
 use crate::environment::Environment;
 use crate::errors::{CommunicationError, VmError, VmResult};
 use crate::imports::{
-    do_abort, do_addr_canonicalize, do_addr_humanize, do_addr_validate, do_db_read, do_db_remove,
-    do_db_write, do_debug, do_ed25519_batch_verify, do_ed25519_verify, do_query_chain,
-    do_secp256k1_recover_pubkey, do_secp256k1_verify, do_secp256r1_recover_pubkey,
-    do_secp256r1_verify,
+    do_abort, do_addr_canonicalize, do_addr_humanize, do_addr_validate, do_bls12_381_aggregate_g1,
+    do_bls12_381_aggregate_g2, do_bls12_381_hash_to_g1, do_bls12_381_hash_to_g2,
+    do_bls12_381_pairing_equality, do_db_read, do_db_remove, do_db_write, do_debug,
+    do_ed25519_batch_verify, do_ed25519_verify, do_query_chain, do_secp256k1_recover_pubkey,
+    do_secp256k1_verify, do_secp256r1_recover_pubkey, do_secp256r1_verify,
 };
 #[cfg(feature = "iterator")]
 use crate::imports::{do_db_next, do_db_next_key, do_db_next_value, do_db_scan};
@@ -142,6 +143,49 @@ where
             Function::new_typed_with_env(&mut store, &fe, do_addr_humanize),
         );
 
+        // Reads a list of points on of the subgroup G1 on the BLS12-381 curve and aggregates them down to a single element.
+        // The "out_ptr" parameter has to be a pointer to a region with the sufficient size to fit an element of G1 (48 bytes).
+        // Returns a u32 as a result. 0 signifies success, anything else may be converted into a `CryptoError`.
+        env_imports.insert(
+            "bls12_381_aggregate_g1",
+            Function::new_typed_with_env(&mut store, &fe, do_bls12_381_aggregate_g1),
+        );
+
+        // Reads a list of points on of the subgroup G2 on the BLS12-381 curve and aggregates them down to a single element.
+        // The "out_ptr" parameter has to be a pointer to a region with the sufficient size to fit an element of G2 (96 bytes).
+        // Returns a u32 as a result. 0 signifies success, anything else may be converted into a `CryptoError`.
+        env_imports.insert(
+            "bls12_381_aggregate_g2",
+            Function::new_typed_with_env(&mut store, &fe, do_bls12_381_aggregate_g2),
+        );
+
+        // Four parameters, "ps", "qs", "r", "s", which all represent elements on the BLS12-381 curve (where "ps" and "r" are elements of the G1 subgroup, and "qs" and "s" elements of G2).
+        // The "ps" and "qs" are interpreted as a continous list of points in the subgroups G1 and G2 respectively.
+        // Returns a single u32 which signifies the validity of the pairing equality.
+        // Returns 0 if the pairing equality exists, 1 if it doesnt, and any other code may be interpreted as a `CryptoError`.
+        env_imports.insert(
+            "bls12_381_pairing_equality",
+            Function::new_typed_with_env(&mut store, &fe, do_bls12_381_pairing_equality),
+        );
+
+        // Three parameters, "hash_function" and "msg" and "dst", are passed down which are both arbitrary octet strings.
+        // The "hash_function" parameter is interpreted as a case of the "HashFunction" enum.
+        // The "out_ptr" parameter has to be a pointer to a region with the sufficient size to fit an element of G1 (48 bytes).
+        // Returns a u32 as a result. 0 signifies success, anything else may be converted into a `CryptoError`.
+        env_imports.insert(
+            "bls12_381_hash_to_g1",
+            Function::new_typed_with_env(&mut store, &fe, do_bls12_381_hash_to_g1),
+        );
+
+        // Three parameters, "hash_function" and "msg" and "dst", are passed down which are both arbitrary octet strings.
+        // The "hash_function" parameter is interpreted as a case of the "HashFunction" enum.
+        // The "out_ptr" parameter has to be a pointer to a region with the sufficient size to fit an element of G2 (96 bytes).
+        // Returns a u32 as a result. 0 signifies success, anything else may be converted into a `CryptoError`.
+        env_imports.insert(
+            "bls12_381_hash_to_g2",
+            Function::new_typed_with_env(&mut store, &fe, do_bls12_381_hash_to_g2),
+        );
+
         // Verifies message hashes against a signature with a public key, using the secp256k1 ECDSA parametrization.
         // Returns 0 on verification success, 1 on verification failure, and values greater than 1 in case of error.
         // Ownership of input pointers is not transferred to the host.
diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs
index 9373401d88..fc6565ffaf 100644
--- a/packages/vm/src/testing/instance.rs
+++ b/packages/vm/src/testing/instance.rs
@@ -17,7 +17,7 @@ use super::storage::MockStorage;
 /// This gas limit is used in integration tests and should be high enough to allow a reasonable
 /// number of contract executions and queries on one instance. For this reason it is significatly
 /// higher than the limit for a single execution that we have in the production setup.
-const DEFAULT_GAS_LIMIT: u64 = 500_000_000; // ~0.5ms
+const DEFAULT_GAS_LIMIT: u64 = 2_000_000_000; // ~2.0ms
 const DEFAULT_MEMORY_LIMIT: Option<Size> = Some(Size::mebi(16));
 
 pub fn mock_instance(