From 26a077e2327ab0f85ca1d707cc9abf9edcca909f Mon Sep 17 00:00:00 2001 From: Georgy Shepelev Date: Thu, 28 Mar 2024 12:33:30 +0100 Subject: [PATCH] feat(node): Implement builtin actor providing bls12-381 (#3771) Co-authored-by: LouiseMedova --- Cargo.lock | 177 ++++++ Cargo.toml | 11 + examples/bls381/Cargo.toml | 21 + examples/bls381/build.rs | 21 + examples/bls381/src/lib.rs | 52 ++ examples/bls381/src/wasm.rs | 182 ++++++ gbuiltins/bls381/Cargo.toml | 16 + gbuiltins/bls381/src/lib.rs | 178 ++++++ gclient/Cargo.toml | 7 + gclient/tests/builtin_bls381.rs | 146 +++++ node/service/Cargo.toml | 1 + node/service/src/client.rs | 2 + node/testing/Cargo.toml | 2 + node/testing/src/client.rs | 1 + pallets/gear-builtin/Cargo.toml | 23 + pallets/gear-builtin/src/benchmarking.rs | 212 ++++++- pallets/gear-builtin/src/bls12_381.rs | 372 ++++++++++++ pallets/gear-builtin/src/lib.rs | 1 + pallets/gear-builtin/src/mock.rs | 9 +- pallets/gear-builtin/src/tests/bls381.rs | 700 +++++++++++++++++++++++ pallets/gear-builtin/src/tests/mod.rs | 1 + pallets/gear-builtin/src/weights.rs | 141 +++++ runtime/vara/src/lib.rs | 2 +- utils/crates-io/src/handler.rs | 8 + utils/wasm-proc/src/main.rs | 9 +- 25 files changed, 2290 insertions(+), 5 deletions(-) create mode 100644 examples/bls381/Cargo.toml create mode 100644 examples/bls381/build.rs create mode 100644 examples/bls381/src/lib.rs create mode 100644 examples/bls381/src/wasm.rs create mode 100644 gbuiltins/bls381/Cargo.toml create mode 100644 gbuiltins/bls381/src/lib.rs create mode 100644 gclient/tests/builtin_bls381.rs create mode 100644 pallets/gear-builtin/src/bls12_381.rs create mode 100644 pallets/gear-builtin/src/tests/bls381.rs diff --git a/Cargo.lock b/Cargo.lock index 4d8ee2ceffb..4cf4ec8b0ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,29 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "ark-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-models-ext", + "ark-std", +] + [[package]] name = "ark-bls12-381" version = "0.4.0" @@ -274,6 +297,45 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-bls12-381-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-models-ext", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-bw6-761" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-bw6-761-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" +dependencies = [ + "ark-bw6-761", + "ark-ec", + "ark-ff", + "ark-models-ext", + "ark-std", +] + [[package]] name = "ark-ec" version = "0.4.2" @@ -288,9 +350,35 @@ dependencies = [ "hashbrown 0.13.2", "itertools", "num-traits", + "rayon", "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ed-on-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ff", + "ark-models-ext", + "ark-std", +] + [[package]] name = "ark-ed-on-bls12-381-bandersnatch" version = "0.4.0" @@ -303,6 +391,19 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff", + "ark-models-ext", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.4.2" @@ -346,6 +447,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-models-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", +] + [[package]] name = "ark-poly" version = "0.4.2" @@ -432,6 +546,7 @@ checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", "rand 0.8.5", + "rayon", ] [[package]] @@ -2196,6 +2311,17 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "demo-bls381" +version = "0.1.0" +dependencies = [ + "gbuiltin-bls381", + "gear-wasm-builder", + "gstd", + "hex-literal", + "parity-scale-codec", +] + [[package]] name = "demo-calc-hash" version = "0.1.0" @@ -4011,6 +4137,19 @@ dependencies = [ "gear-dlmalloc", ] +[[package]] +name = "gbuiltin-bls381" +version = "1.2.0" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-scale 0.0.12", + "ark-serialize", + "derive_more", + "parity-scale-codec", +] + [[package]] name = "gcli" version = "1.2.0" @@ -4059,8 +4198,15 @@ name = "gclient" version = "1.2.0" dependencies = [ "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-scale 0.0.12", + "ark-serialize", + "ark-std", "async-trait", "demo-async-tester", + "demo-bls381", "demo-calc-hash", "demo-calc-hash-in-one-block", "demo-constructor", @@ -4437,6 +4583,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", + "sp-crypto-ec-utils", "sp-inherents", "sp-io", "sp-keyring", @@ -4643,6 +4790,7 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-grandpa", "sp-core", + "sp-crypto-ec-utils", "sp-keystore", "sp-offchain", "sp-runtime", @@ -7877,6 +8025,12 @@ dependencies = [ name = "pallet-gear-builtin" version = "1.2.0" dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-scale 0.0.12", + "ark-serialize", + "ark-std", "demo-waiting-proxy", "derive_more", "env_logger", @@ -7885,6 +8039,7 @@ dependencies = [ "frame-support", "frame-support-test", "frame-system", + "gbuiltin-bls381", "gear-common", "gear-core", "gear-core-errors", @@ -7905,6 +8060,7 @@ dependencies = [ "primitive-types", "scale-info", "sp-core", + "sp-crypto-ec-utils", "sp-externalities", "sp-io", "sp-runtime", @@ -12044,6 +12200,27 @@ dependencies = [ "syn 2.0.53", ] +[[package]] +name = "sp-crypto-ec-utils" +version = "0.4.1" +source = "git+https://github.com/gear-tech/polkadot-sdk.git?branch=gear-v1.1.0#2dd122c65e27d0d09d84e1533e14565cf179aec1" +dependencies = [ + "ark-bls12-377", + "ark-bls12-377-ext", + "ark-bls12-381", + "ark-bls12-381-ext", + "ark-bw6-761", + "ark-bw6-761-ext", + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ed-on-bls12-377-ext", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch-ext", + "ark-scale 0.0.12", + "sp-runtime-interface", + "sp-std 8.0.0 (git+https://github.com/gear-tech/polkadot-sdk.git?branch=gear-v1.1.0)", +] + [[package]] name = "sp-database" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index f7ffd42a579..07a1439f84c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "examples/async-signal-entry", "examples/async-tester", "examples/autoreply", + "examples/bls381", "examples/calc-hash", "examples/custom", "examples/delayed-reservation-sender", @@ -72,6 +73,7 @@ members = [ "examples/waiting-proxy", "examples/wat", "galloc", + "gbuiltins/bls381", "gcli", "gclient", "gcore", @@ -166,6 +168,12 @@ which = "4.4.2" winapi = "0.3.9" paste = "1.0" tempfile = "3.10.1" +ark-std = { version = "0.4.0", default-features = false } +ark-bls12-381 = { version = "0.4.0", default-features = false } +ark-serialize = { version = "0.4", default-features = false } +ark-ec = { version = "0.4.2", default-features = false } +ark-ff = { version = "0.4.2", default-features = false } +ark-scale = { version = "0.0.12", default-features = false } # Published deps # @@ -182,6 +190,7 @@ authorship = { package = "gear-authorship", path = "node/authorship" } common = { package = "gear-common", path = "common", default-features = false } core-processor = { package = "gear-core-processor", path = "core-processor", default-features = false } galloc = { path = "galloc" } +gbuiltin-bls381 = { path = "gbuiltins/bls381", default-features = false } gcore = { path = "gcore" } gcli = { path = "gcli" } gclient = { path = "gclient" } @@ -304,6 +313,7 @@ sc-consensus-babe = { version = "0.10.0-dev", git = "https://github.com/gear-tec sc-consensus-babe-rpc = { version = "0.10.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.1.0" } sc-consensus-epochs = { version = "0.10.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.1.0" } sc-consensus-slots = { version = "0.10.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.1.0" } +sp-crypto-ec-utils = { version = "0.4.1", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.1.0", default-features = false } sp-debug-derive = { version = "8.0.0", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.1.0", default-features = false } sc-chain-spec = { version = "4.0.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.1.0" } sc-cli = { version = "0.10.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.1.0" } @@ -379,6 +389,7 @@ demo-async-init = { path = "examples/async-init" } demo-async-recursion = { path = "examples/async-recursion" } demo-async-signal-entry = { path = "examples/async-signal-entry" } demo-async-tester = { path = "examples/async-tester" } +demo-bls381 = { path = "examples/bls381" } demo-calc-hash = { path = "examples/calc-hash" } demo-calc-hash-in-one-block = { path = "examples/calc-hash/in-one-block" } demo-calc-hash-over-blocks = { path = "examples/calc-hash/over-blocks" } diff --git a/examples/bls381/Cargo.toml b/examples/bls381/Cargo.toml new file mode 100644 index 00000000000..6a6bd9b57ef --- /dev/null +++ b/examples/bls381/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "demo-bls381" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +codec = { workspace = true, features = ["derive"] } +gstd = { workspace = true, features = ["debug"] } +gbuiltin-bls381.workspace = true +hex-literal.workspace = true + +[build-dependencies] +gear-wasm-builder.workspace = true + +[features] +default = ["std"] +std = ["codec/std"] diff --git a/examples/bls381/build.rs b/examples/bls381/build.rs new file mode 100644 index 00000000000..6a370b53a71 --- /dev/null +++ b/examples/bls381/build.rs @@ -0,0 +1,21 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +fn main() { + gear_wasm_builder::build(); +} diff --git a/examples/bls381/src/lib.rs b/examples/bls381/src/lib.rs new file mode 100644 index 00000000000..b982a3ceefe --- /dev/null +++ b/examples/bls381/src/lib.rs @@ -0,0 +1,52 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use codec::{Decode, Encode}; + +#[cfg(feature = "std")] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +} + +#[cfg(feature = "std")] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +#[derive(Encode, Decode)] +#[codec(crate = codec)] +pub enum HandleMessage { + MillerLoop { + message: Vec, + signatures: Vec>, + }, + Exp, +} + +#[derive(Encode, Decode)] +#[codec(crate = codec)] +pub struct InitMessage { + pub g2_gen: Vec, + pub pub_keys: Vec>, +} + +#[cfg(not(feature = "std"))] +mod wasm; diff --git a/examples/bls381/src/wasm.rs b/examples/bls381/src/wasm.rs new file mode 100644 index 00000000000..ca8e74b0f9b --- /dev/null +++ b/examples/bls381/src/wasm.rs @@ -0,0 +1,182 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use ark_bls12_381::{Bls12_381, G1Affine, G2Affine}; +use ark_ec::pairing::Pairing; +use gbuiltin_bls381::*; +use gstd::{ + codec::{Decode, Encode}, + msg, + prelude::*, + ActorId, +}; +use hex_literal::hex; + +type ArkScale = ark_scale::ArkScale; + +const BUILTIN_BLS381: ActorId = ActorId::new(hex!( + "6b6e292c382945e80bf51af2ba7fe9f458dcff81ae6075c46f9095e1bbecdc37" +)); + +#[allow(dead_code)] +#[derive(Default)] +pub struct Contract { + g2_gen: G2Affine, + pub_keys: Vec, + aggregate_pub_key: G2Affine, + miller_out: ( + // encoded ArkScale::> + Option>, + Option>, + ), +} + +static mut CONTRACT: Option = None; + +#[no_mangle] +extern "C" fn init() { + let init_msg: InitMessage = msg::load().expect("Unable to decode `InitMessage`"); + + let g2_gen = ::G2Affine> as Decode>::decode( + &mut init_msg.g2_gen.as_slice(), + ) + .unwrap(); + + let mut pub_keys = Vec::new(); + let mut aggregate_pub_key: G2Affine = Default::default(); + + for pub_key_bytes in init_msg.pub_keys.iter() { + let pub_key = ::G2Affine> as Decode>::decode( + &mut pub_key_bytes.as_slice(), + ) + .unwrap(); + aggregate_pub_key = (aggregate_pub_key + pub_key.0).into(); + pub_keys.push(pub_key.0); + } + + let contract = Contract { + g2_gen: g2_gen.0, + pub_keys, + aggregate_pub_key, + miller_out: (None, None), + }; + + unsafe { CONTRACT = Some(contract) } +} + +#[gstd::async_main] +async fn main() { + let msg: HandleMessage = msg::load().expect("Unable to decode `HandleMessage`"); + let contract = unsafe { CONTRACT.as_mut().expect("The contract is not initialized") }; + + match msg { + HandleMessage::MillerLoop { + message, + signatures, + } => { + let aggregate_pub_key: ArkScale> = + vec![contract.aggregate_pub_key].into(); + + let request = Request::MultiMillerLoop { + a: message, + b: aggregate_pub_key.encode(), + } + .encode(); + let reply = msg::send_bytes_for_reply(BUILTIN_BLS381, &request, 0, 0) + .expect("Failed to send message") + .await + .expect("Received error reply"); + + let response = Response::decode(&mut reply.as_slice()).unwrap(); + let miller_out1 = match response { + Response::MultiMillerLoop(v) => v, + _ => unreachable!(), + }; + + let mut aggregate_signature: G1Affine = Default::default(); + for signature in signatures.iter() { + let signature = ::G1Affine> as Decode>::decode( + &mut signature.as_slice(), + ) + .unwrap(); + aggregate_signature = (aggregate_signature + signature.0).into(); + } + let aggregate_signature: ArkScale> = vec![aggregate_signature].into(); + let g2_gen: ArkScale> = vec![contract.g2_gen].into(); + let request = Request::MultiMillerLoop { + a: aggregate_signature.encode(), + b: g2_gen.encode(), + } + .encode(); + let reply = msg::send_bytes_for_reply(BUILTIN_BLS381, &request, 0, 0) + .expect("Failed to send message") + .await + .expect("Received error reply"); + let response = Response::decode(&mut reply.as_slice()).unwrap(); + let miller_out2 = match response { + Response::MultiMillerLoop(v) => v, + _ => unreachable!(), + }; + + contract.miller_out = (Some(miller_out1), Some(miller_out2)); + } + + HandleMessage::Exp => { + if let (Some(miller_out1), Some(miller_out2)) = &contract.miller_out { + let request = Request::FinalExponentiation { + f: miller_out1.clone(), + } + .encode(); + let reply = msg::send_bytes_for_reply(BUILTIN_BLS381, &request, 0, 0) + .expect("Failed to send message") + .await + .expect("Received error reply"); + let response = Response::decode(&mut reply.as_slice()).unwrap(); + let exp1 = match response { + Response::FinalExponentiation(v) => { + ArkScale::<::TargetField>::decode(&mut v.as_slice()) + .unwrap() + } + _ => unreachable!(), + }; + + let request = Request::FinalExponentiation { + f: miller_out2.clone(), + } + .encode(); + let reply = msg::send_bytes_for_reply(BUILTIN_BLS381, &request, 0, 0) + .expect("Failed to send message") + .await + .expect("Received error reply"); + let response = Response::decode(&mut reply.as_slice()).unwrap(); + let exp2 = match response { + Response::FinalExponentiation(v) => { + ArkScale::<::TargetField>::decode(&mut v.as_slice()) + .unwrap() + } + _ => unreachable!(), + }; + + assert_eq!(exp1.0, exp2.0); + + contract.miller_out = (None, None); + } + } + } +} diff --git a/gbuiltins/bls381/Cargo.toml b/gbuiltins/bls381/Cargo.toml new file mode 100644 index 00000000000..7a9f5236a55 --- /dev/null +++ b/gbuiltins/bls381/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "gbuiltin-bls381" +description = "Entities for working with Gear builtin actor providing bls12_381 cryptography" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +codec = { workspace = true, features = ["derive"] } +derive_more.workspace = true +ark-bls12-381 = { workspace = true, features = ["curve"] } +ark-ec.workspace = true +ark-ff.workspace = true +ark-scale = { workspace = true, features = ["hazmat"] } +ark-serialize = { workspace = true, features = ["derive"] } diff --git a/gbuiltins/bls381/src/lib.rs b/gbuiltins/bls381/src/lib.rs new file mode 100644 index 00000000000..418db75b11b --- /dev/null +++ b/gbuiltins/bls381/src/lib.rs @@ -0,0 +1,178 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![no_std] + +extern crate alloc; + +use alloc::vec::Vec; +use codec::{Decode, Encode}; + +pub use ark_bls12_381; +pub use ark_ec; +pub use ark_ff; +pub use ark_scale; +pub use ark_serialize; + +/// Constant defines codec index of [`Request::MultiMillerLoop`]. +pub const REQUEST_MULTI_MILLER_LOOP: u8 = 0; +/// Constant defines codec index of [`Request::FinalExponentiation`]. +pub const REQUEST_FINAL_EXPONENTIATION: u8 = 1; +/// Constant defines codec index of [`Request::MultiScalarMultiplicationG1`]. +pub const REQUEST_MULTI_SCALAR_MULTIPLICATION_G1: u8 = 2; +/// Constant defines codec index of [`Request::MultiScalarMultiplicationG2`]. +pub const REQUEST_MULTI_SCALAR_MULTIPLICATION_G2: u8 = 3; +/// Constant defines codec index of [`Request::ProjectiveMultiplicationG1`]. +pub const REQUEST_PROJECTIVE_MULTIPLICATION_G1: u8 = 4; +/// Constant defines codec index of [`Request::ProjectiveMultiplicationG2`]. +pub const REQUEST_PROJECTIVE_MULTIPLICATION_G2: u8 = 5; + +/// Type that should be used to create a message to the bls12_381 builtin actor. +/// Use the following crates to construct a request: +/// - `ark-scale`: ; +/// - `ark-bls12-381`: . +#[derive(Encode, Clone, PartialEq, Eq, Debug)] +#[codec(crate = codec)] +pub enum Request { + /// Request to pairing multi Miller loop for *BLS12-381*. + /// + /// Encoded: + /// - `a`: [`ArkScale>`]. + /// - `b`: [`ArkScale>`]. + #[codec(index = 0)] + MultiMillerLoop { a: Vec, b: Vec }, + + /// Request to pairing final exponentiation for *BLS12-381*. + /// + /// Encoded: [`ArkScale<`]. + #[codec(index = 1)] + FinalExponentiation { f: Vec }, + + /// Request to multi scalar multiplication on *G1* for *BLS12-381* + /// + /// Encoded: + /// - `bases`: [`ArkScale>`]. + /// - `scalars`: [`ArkScale>`]. + #[codec(index = 2)] + MultiScalarMultiplicationG1 { bases: Vec, scalars: Vec }, + + /// Request to multi scalar multiplication on *G2* for *BLS12-381* + /// + /// Encoded: + /// - `bases`: [`ArkScale>`]. + /// - `scalars`: [`ArkScale>`]. + #[codec(index = 3)] + MultiScalarMultiplicationG2 { bases: Vec, scalars: Vec }, + + /// Request to projective multiplication on *G1* for *BLS12-381*. + /// + /// Encoded: + /// - `base`: [`ArkScaleProjective`]. + /// - `scalar`: [`ArkScale>`]. + #[codec(index = 4)] + ProjectiveMultiplicationG1 { base: Vec, scalar: Vec }, + + /// Request to projective multiplication on *G2* for *BLS12-381*. + /// + /// Encoded: + /// - `base`: [`ArkScaleProjective`]. + /// - `scalar`: [`ArkScale>`]. + #[codec(index = 5)] + ProjectiveMultiplicationG2 { base: Vec, scalar: Vec }, +} + +/// The enumeration contains result to a request. +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[codec(crate = codec)] +pub enum Response { + /// Result of the multi Miller loop, encoded: [`ArkScale`]. + MultiMillerLoop(Vec), + /// Result of the final exponentiation, encoded: [`ArkScale`]. + FinalExponentiation(Vec), + /// Result of the multi scalar multiplication, encoded: [`ArkScaleProjective`]. + MultiScalarMultiplicationG1(Vec), + /// Result of the multi scalar multiplication, encoded: [`ArkScaleProjective`]. + MultiScalarMultiplicationG2(Vec), + /// Result of the projective multiplication, encoded: [`ArkScaleProjective`]. + ProjectiveMultiplicationG1(Vec), + /// Result of the projective multiplication, encoded: [`ArkScaleProjective`]. + ProjectiveMultiplicationG2(Vec), +} + +#[cfg(test)] +mod tests { + extern crate alloc; + + use super::*; + use alloc::vec; + + // The standard Decode implementation cannot be used for precise gas charging. + // The following test checks that scale codec indexes of variants are set correctly. + #[test] + fn codec_enum_indexes() { + for (index, (variant, request)) in [ + ( + REQUEST_MULTI_MILLER_LOOP, + Request::MultiMillerLoop { + a: vec![], + b: vec![], + }, + ), + ( + REQUEST_FINAL_EXPONENTIATION, + Request::FinalExponentiation { f: vec![] }, + ), + ( + REQUEST_MULTI_SCALAR_MULTIPLICATION_G1, + Request::MultiScalarMultiplicationG1 { + bases: vec![], + scalars: vec![], + }, + ), + ( + REQUEST_MULTI_SCALAR_MULTIPLICATION_G2, + Request::MultiScalarMultiplicationG2 { + bases: vec![], + scalars: vec![], + }, + ), + ( + REQUEST_PROJECTIVE_MULTIPLICATION_G1, + Request::ProjectiveMultiplicationG1 { + base: vec![], + scalar: vec![], + }, + ), + ( + REQUEST_PROJECTIVE_MULTIPLICATION_G2, + Request::ProjectiveMultiplicationG2 { + base: vec![], + scalar: vec![], + }, + ), + ] + .into_iter() + .enumerate() + { + assert_eq!(index, variant.into()); + let encoded = request.encode(); + + assert!(matches!(encoded.first().copied(), Some(v) if v == variant)); + } + } +} diff --git a/gclient/Cargo.toml b/gclient/Cargo.toml index daf873f476c..93ae66def02 100644 --- a/gclient/Cargo.toml +++ b/gclient/Cargo.toml @@ -47,3 +47,10 @@ demo-reserve-gas = { workspace = true, features = ["std"] } gmeta = { workspace = true } gstd = { workspace = true, features = ["debug"] } demo-wat.workspace = true +demo-bls381 = { workspace = true, features = ["std"] } +ark-serialize = { workspace = true, features = ["derive"] } +ark-scale = { workspace = true, features = ["hazmat"] } +ark-bls12-381 = { workspace = true, features = ["curve"] } +ark-ec = { workspace = true } +ark-ff = { workspace = true } +ark-std = { workspace = true } diff --git a/gclient/tests/builtin_bls381.rs b/gclient/tests/builtin_bls381.rs new file mode 100644 index 00000000000..7b28112e730 --- /dev/null +++ b/gclient/tests/builtin_bls381.rs @@ -0,0 +1,146 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use ark_bls12_381::{G1Affine, G1Projective as G1, G2Affine, G2Projective as G2}; +use ark_ec::Group; +use ark_serialize::CanonicalSerialize; +use ark_std::{ops::Mul, UniformRand}; +use demo_bls381::*; +use gclient::{EventListener, EventProcessor, GearApi, Result}; +use gstd::prelude::*; + +type ArkScale = ark_scale::ArkScale; +type ScalarField = ::ScalarField; + +async fn common_upload_program( + client: &GearApi, + code: Vec, + payload: impl Encode, +) -> Result<([u8; 32], [u8; 32])> { + let encoded_payload = payload.encode(); + let gas_limit = client + .calculate_upload_gas(None, code.clone(), encoded_payload, 0, true) + .await? + .min_limit; + println!("init gas {gas_limit:?}"); + let (message_id, program_id, _) = client + .upload_program( + code, + gclient::now_micros().to_le_bytes(), + payload, + gas_limit, + 0, + ) + .await?; + + Ok((message_id.into(), program_id.into())) +} + +async fn upload_program( + client: &GearApi, + listener: &mut EventListener, + payload: impl Encode, +) -> Result<[u8; 32]> { + let (message_id, program_id) = + common_upload_program(client, WASM_BINARY.to_vec(), payload).await?; + + assert!(listener + .message_processed(message_id.into()) + .await? + .succeed()); + + Ok(program_id) +} + +#[tokio::test] +async fn builtin_bls381() -> Result<()> { + let client = GearApi::dev_from_path("../target/release/gear").await?; + let mut listener = client.subscribe().await?; + + let mut rng = ark_std::test_rng(); + + let generator: G2 = G2::generator(); + let message: G1Affine = G1::rand(&mut rng).into(); + let mut pub_keys = Vec::new(); + let mut signatures = Vec::new(); + for _ in 0..2 { + let priv_key: ScalarField = UniformRand::rand(&mut rng); + let pub_key: G2Affine = generator.mul(priv_key).into(); + let mut pub_key_bytes = Vec::new(); + pub_key.serialize_uncompressed(&mut pub_key_bytes).unwrap(); + pub_keys.push(pub_key_bytes); + + // sign + let signature: G1Affine = message.mul(priv_key).into(); + let mut sig_bytes = Vec::new(); + signature.serialize_uncompressed(&mut sig_bytes).unwrap(); + signatures.push(sig_bytes); + } + + let mut gen_bytes = Vec::new(); + generator.serialize_uncompressed(&mut gen_bytes).unwrap(); + + let program_id = upload_program( + &client, + &mut listener, + InitMessage { + g2_gen: gen_bytes, + pub_keys, + }, + ) + .await?; + + let message: ArkScale> = vec![message].into(); + let message_bytes = message.encode(); + + let payload = HandleMessage::MillerLoop { + message: message_bytes, + signatures, + }; + let gas_limit = client + .calculate_handle_gas(None, program_id.into(), payload.encode(), 0, true) + .await? + .min_limit; + println!("gas_limit {gas_limit:?}"); + + let (message_id, _) = client + .send_message(program_id.into(), payload, gas_limit, 0) + .await?; + + assert!(listener.message_processed(message_id).await?.succeed()); + + let gas_limit = client + .calculate_handle_gas( + None, + program_id.into(), + HandleMessage::Exp.encode(), + 0, + true, + ) + .await? + .min_limit; + println!("gas_limit {gas_limit:?}"); + + let (message_id, _) = client + .send_message(program_id.into(), HandleMessage::Exp, gas_limit, 0) + .await?; + + assert!(listener.message_processed(message_id).await?.succeed()); + + Ok(()) +} diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index 54676aacaa6..253641c0712 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -64,6 +64,7 @@ sc-sysinfo.workspace = true # Substrate Primitives sp-core = { workspace = true, features = ["std"] } +sp-crypto-ec-utils = { workspace = true, features = ["std", "bls12-381"] } sp-api = { workspace = true, features = ["std"] } sp-authority-discovery = { workspace = true, optional = true, features = ["std"] } sp-consensus.workspace = true diff --git a/node/service/src/client.rs b/node/service/src/client.rs index f23e43ad295..b398dca40c1 100644 --- a/node/service/src/client.rs +++ b/node/service/src/client.rs @@ -55,12 +55,14 @@ impl sc_executor::NativeExecutionDispatch for VaraExecutorDispatch { frame_benchmarking::benchmarking::HostFunctions, gear_ri::gear_ri::HostFunctions, gear_ri::sandbox::HostFunctions, + sp_crypto_ec_utils::bls12_381::host_calls::HostFunctions, ); /// Otherwise we only use the default Substrate host functions. #[cfg(not(feature = "runtime-benchmarks"))] type ExtendHostFunctions = ( gear_ri::gear_ri::HostFunctions, gear_ri::sandbox::HostFunctions, + sp_crypto_ec_utils::bls12_381::host_calls::HostFunctions, ); fn dispatch(method: &str, data: &[u8]) -> Option> { diff --git a/node/testing/Cargo.toml b/node/testing/Cargo.toml index ec58506d22f..a3445314ef4 100644 --- a/node/testing/Cargo.toml +++ b/node/testing/Cargo.toml @@ -38,6 +38,7 @@ substrate-test-client.workspace = true # Substrate Primitives sp-core.workspace = true +sp-crypto-ec-utils = { workspace = true, features = ["bls12-381"] } sp-api.workspace = true sp-consensus.workspace = true sp-keyring.workspace = true @@ -60,6 +61,7 @@ std = [ "pallet-gear-rpc-runtime-api/std", "gear-runtime-interface/std", "sp-core/std", + "sp-crypto-ec-utils/std", "sp-api/std", "sp-runtime/std", "sp-inherents/std", diff --git a/node/testing/src/client.rs b/node/testing/src/client.rs index c44283a419e..b37daea9135 100644 --- a/node/testing/src/client.rs +++ b/node/testing/src/client.rs @@ -31,6 +31,7 @@ impl sc_executor::NativeExecutionDispatch for LocalExecutorDispatch { frame_benchmarking::benchmarking::HostFunctions, gear_runtime_interface::gear_ri::HostFunctions, gear_runtime_interface::sandbox::HostFunctions, + sp_crypto_ec_utils::bls12_381::host_calls::HostFunctions, ); fn dispatch(method: &str, data: &[u8]) -> Option> { diff --git a/pallets/gear-builtin/Cargo.toml b/pallets/gear-builtin/Cargo.toml index 477f2d23330..9488324136a 100644 --- a/pallets/gear-builtin/Cargo.toml +++ b/pallets/gear-builtin/Cargo.toml @@ -19,13 +19,21 @@ primitive-types = { workspace = true, features = ["scale-info"] } log.workspace = true derive_more.workspace = true impl-trait-for-tuples.workspace = true +ark-serialize = { workspace = true, features = ["derive"] } +ark-scale = { workspace = true, features = ["hazmat"] } +ark-bls12-381 = { workspace = true, features = ["curve"], optional = true } +ark-ec = { workspace = true, optional = true } +ark-ff = { workspace = true, optional = true } +ark-std = { workspace = true, optional = true } core-processor.workspace = true +gbuiltin-bls381.workspace = true gear-core.workspace = true gear-core-errors.workspace = true frame-support.workspace = true frame-system.workspace = true frame-benchmarking = { workspace = true, optional = true } +sp-crypto-ec-utils = { workspace = true, features = ["bls12-381"] } sp-std.workspace = true sp-runtime = { workspace = true, features = ["serde"] } pallet-gear.workspace = true @@ -48,6 +56,10 @@ frame-executive = { workspace = true, features = ["std"] } frame-support-test = { workspace = true, features = ["std"] } env_logger.workspace = true hex-literal.workspace = true +ark-bls12-381.workspace = true +ark-ec.workspace = true +ark-ff.workspace = true +ark-std.workspace = true [features] default = ["std"] @@ -59,12 +71,23 @@ std = [ "pallet-gear/std", "parity-scale-codec/std", "scale-info/std", + "sp-crypto-ec-utils/std", "sp-runtime/std", "sp-std/std", + "ark-serialize/std", + "ark-scale/std", + "ark-bls12-381?/std", + "ark-ec?/std", + "ark-ff?/std", + "ark-std?/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/gear-builtin/src/benchmarking.rs b/pallets/gear-builtin/src/benchmarking.rs index d04f3471b7d..66005f7973b 100644 --- a/pallets/gear-builtin/src/benchmarking.rs +++ b/pallets/gear-builtin/src/benchmarking.rs @@ -21,8 +21,17 @@ #[allow(unused)] use crate::Pallet as BuiltinActorPallet; use crate::*; +use ark_bls12_381::{Bls12_381, G1Affine, G1Projective as G1, G2Affine, G2Projective as G2}; +use ark_ec::{pairing::Pairing, short_weierstrass::SWCurveConfig, Group, ScalarMul}; +use ark_ff::biginteger::BigInt; +use ark_scale::hazmat::ArkScaleProjective; +use ark_std::{ops::Mul, UniformRand}; use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; -use gear_core::message::{Payload, StoredDispatch}; +use gear_core::message::{Payload, StoredDispatch, MAX_PAYLOAD_SIZE}; +use parity_scale_codec::{Compact, Encode, Input}; + +type ArkScale = ark_scale::ArkScale; +type ScalarField = ::ScalarField; macro_rules! impl_builtin_actor { ($name: ident, $id: literal) => { @@ -81,6 +90,18 @@ pub type BenchmarkingBuiltinActor = ( DummyActor15, ); +const MAX_BIG_INT: u32 = 100; + +fn naive_var_base_msm(bases: &[G::MulBase], scalars: &[G::ScalarField]) -> G { + let mut acc = G::zero(); + + for (base, scalar) in bases.iter().zip(scalars.iter()) { + acc += *base * scalar; + } + + acc +} + benchmarks! { where_clause { where @@ -101,6 +122,195 @@ benchmarks! { } verify { // No changes in runtime are expected since the actual dispatch doesn't take place. } + + decode_bytes { + let a in 1 .. MAX_PAYLOAD_SIZE as u32; + + let bytes = vec![1u8; a as usize]; + let encoded = bytes.encode(); + let mut _decoded = vec![]; + }: { + let mut input = encoded.as_slice(); + let len = u32::from(Compact::::decode(&mut input).unwrap()) as usize; + + let mut items = vec![0u8; len]; + let bytes_slice = items.as_mut_slice(); + input.read(bytes_slice).unwrap(); + + _decoded = items; + } verify { + assert_eq!(bytes, _decoded); + } + + bls12_381_multi_miller_loop { + let c in 0 .. 100; + + let count = c as usize; + + let mut rng = ark_std::test_rng(); + + let messages = (0..count).map(|_| G1::rand(&mut rng).into()).collect::>(); + + let message: ArkScale::G1Affine>> = messages.into(); + let encoded_message = message.encode(); + + let pub_keys = { + let mut pub_keys = Vec::with_capacity(count); + let generator: G2 = G2::generator(); + for _ in 0..count { + let priv_key: ScalarField = UniformRand::rand(&mut rng); + let pub_key: G2Affine = generator.mul(priv_key).into(); + pub_keys.push(pub_key); + } + + pub_keys + }; + let pub_key: ArkScale::G2Affine>> = pub_keys.into(); + let encoded_pub_key = pub_key.encode(); + + let mut _result: Result, ()> = Err(()); + }: { + _result = sp_crypto_ec_utils::bls12_381::host_calls::bls12_381_multi_miller_loop( + encoded_message, + encoded_pub_key, + ); + } verify { + assert!(_result.is_ok()); + } + + bls12_381_final_exponentiation { + let mut rng = ark_std::test_rng(); + + // message + let message: G1Affine = G1::rand(&mut rng).into(); + let message: ArkScale::G1Affine>> = vec![message].into(); + let encoded_message = message.encode(); + + let priv_key: ScalarField = UniformRand::rand(&mut rng); + let generator: G2 = G2::generator(); + let pub_key: G2Affine = generator.mul(priv_key).into(); + let pub_key: ArkScale::G2Affine>> = vec![pub_key].into(); + let encoded_pub_key = pub_key.encode(); + + let miller_loop = sp_crypto_ec_utils::bls12_381::host_calls::bls12_381_multi_miller_loop( + encoded_message, + encoded_pub_key, + ).unwrap(); + + let mut _result: Result, ()> = Err(()); + }: { + _result = sp_crypto_ec_utils::bls12_381::host_calls::bls12_381_final_exponentiation(miller_loop); + } verify { + assert!(_result.is_ok()); + } + + bls12_381_msm_g1 { + let c in 1 .. 1_000; + + let count = c as usize; + + let mut rng = ark_std::test_rng(); + + let scalar = (0..count) + .map(|_| ::ScalarField::rand(&mut rng)) + .max() + .unwrap(); + let scalars = vec![scalar; count]; + let ark_scalars: ArkScale::ScalarField>> = scalars.clone().into(); + let encoded_scalars = ark_scalars.encode(); + + let bases = (0..count).map(|_| G1::rand(&mut rng)).collect::>(); + let bases = G1::batch_convert_to_mul_base(&bases); + let ark_bases: ArkScale> = bases.clone().into(); + let encoded_bases = ark_bases.encode(); + + let mut _result: Result, ()> = Err(()); + }: { + _result = sp_crypto_ec_utils::bls12_381::host_calls::bls12_381_msm_g1(encoded_bases, encoded_scalars); + } verify { + let naive = naive_var_base_msm::(bases.as_slice(), scalars.as_slice()); + let encoded = _result.unwrap(); + let fast = ArkScaleProjective::::decode(&mut &encoded[..]).unwrap(); + assert_eq!(naive, fast.0); + } + + bls12_381_msm_g2 { + let c in 1 .. 1_000; + + let count = c as usize; + + let mut rng = ark_std::test_rng(); + + let scalar = (0..count) + .map(|_| ::ScalarField::rand(&mut rng)) + .max() + .unwrap(); + let scalars = vec![scalar; count]; + let ark_scalars: ArkScale::ScalarField>> = scalars.clone().into(); + let encoded_scalars = ark_scalars.encode(); + + let bases = (0..count).map(|_| G2::rand(&mut rng)).collect::>(); + let bases = G2::batch_convert_to_mul_base(&bases); + let ark_bases: ArkScale> = bases.clone().into(); + let encoded_bases = ark_bases.encode(); + + let mut _result: Result, ()> = Err(()); + }: { + _result = sp_crypto_ec_utils::bls12_381::host_calls::bls12_381_msm_g2(encoded_bases, encoded_scalars); + } verify { + let naive = naive_var_base_msm::(bases.as_slice(), scalars.as_slice()); + let encoded = _result.unwrap(); + let fast = ArkScaleProjective::::decode(&mut &encoded[..]).unwrap(); + assert_eq!(naive, fast.0); + } + + bls12_381_mul_projective_g1 { + let c in 1 .. MAX_BIG_INT; + + let mut rng = ark_std::test_rng(); + + let bigint = BigInt::<{ MAX_BIG_INT as usize }>::rand(&mut rng); + let bigint = bigint.as_ref()[..c as usize].to_vec(); + let ark_bigint: ArkScale> = bigint.clone().into(); + let encoded_bigint = ark_bigint.encode(); + + let base = G1::rand(&mut rng); + let ark_base: ArkScaleProjective = base.into(); + let encoded_base = ark_base.encode(); + + let mut _result: Result, ()> = Err(()); + }: { + _result = sp_crypto_ec_utils::bls12_381::host_calls::bls12_381_mul_projective_g1(encoded_base, encoded_bigint); + } verify { + let encoded = _result.unwrap(); + let result = ArkScaleProjective::::decode(&mut &encoded[..]).unwrap(); + let standard = ::mul_projective(&base, &bigint); + assert_eq!(standard, result.0); + } + + bls12_381_mul_projective_g2 { + let c in 1 .. MAX_BIG_INT; + + let mut rng = ark_std::test_rng(); + + let bigint = BigInt::<{ MAX_BIG_INT as usize }>::rand(&mut rng); + let bigint = bigint.as_ref()[..c as usize].to_vec(); + let ark_bigint: ArkScale> = bigint.clone().into(); + let encoded_bigint = ark_bigint.encode(); + + let base = G2::rand(&mut rng); + let ark_base: ArkScaleProjective = base.into(); + let encoded_base = ark_base.encode(); + + let mut _result: Result, ()> = Err(()); + }: { + _result = sp_crypto_ec_utils::bls12_381::host_calls::bls12_381_mul_projective_g2(encoded_base, encoded_bigint); + } verify { + let encoded = _result.unwrap(); + let result = ArkScaleProjective::::decode(&mut &encoded[..]).unwrap(); + let standard = ::mul_projective(&base, &bigint); + assert_eq!(standard, result.0); + } } impl_benchmark_test_suite!( diff --git a/pallets/gear-builtin/src/bls12_381.rs b/pallets/gear-builtin/src/bls12_381.rs new file mode 100644 index 00000000000..a89ea561a5e --- /dev/null +++ b/pallets/gear-builtin/src/bls12_381.rs @@ -0,0 +1,372 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use ark_scale::HOST_CALL; +use ark_serialize::{CanonicalDeserialize, Compress, Validate}; +use core::marker::PhantomData; +use gbuiltin_bls381::*; +use parity_scale_codec::{Compact, Input}; +use sp_crypto_ec_utils::bls12_381; + +const IS_COMPRESSED: Compress = ark_scale::is_compressed(HOST_CALL); +const IS_VALIDATED: Validate = ark_scale::is_validated(HOST_CALL); + +pub struct Actor(PhantomData); + +impl BuiltinActor for Actor { + const ID: u64 = 1; + + type Error = BuiltinActorError; + + fn handle(dispatch: &StoredDispatch, gas_limit: u64) -> (Result, u64) { + let message = dispatch.message(); + let payload = message.payload_bytes(); + let (result, gas_spent) = match payload.first().copied() { + Some(REQUEST_MULTI_MILLER_LOOP) => multi_miller_loop::(&payload[1..], gas_limit), + Some(REQUEST_FINAL_EXPONENTIATION) => { + final_exponentiation::(&payload[1..], gas_limit) + } + Some(REQUEST_MULTI_SCALAR_MULTIPLICATION_G1) => msm_g1::(&payload[1..], gas_limit), + Some(REQUEST_MULTI_SCALAR_MULTIPLICATION_G2) => msm_g2::(&payload[1..], gas_limit), + Some(REQUEST_PROJECTIVE_MULTIPLICATION_G1) => { + projective_multiplication_g1::(&payload[1..], gas_limit) + } + Some(REQUEST_PROJECTIVE_MULTIPLICATION_G2) => { + projective_multiplication_g2::(&payload[1..], gas_limit) + } + _ => (Err(BuiltinActorError::DecodingError), 0), + }; + + ( + result.map(|response| { + response + .encode() + .try_into() + .unwrap_or_else(|_| unreachable!("Response message is too large")) + }), + gas_spent, + ) + } +} + +fn decode_vec( + gas_limit: u64, + mut gas_spent: u64, + input: &mut I, +) -> (u64, Option, BuiltinActorError>>) { + let Ok(len) = Compact::::decode(input).map(u32::from) else { + log::debug!( + target: LOG_TARGET, + "Failed to scale-decode vector length" + ); + return (gas_spent, Some(Err(BuiltinActorError::DecodingError))); + }; + + let to_spend = ::WeightInfo::decode_bytes(len).ref_time(); + if gas_limit < gas_spent + to_spend { + return (gas_spent, None); + } + + gas_spent += to_spend; + + let mut items = vec![0u8; len as usize]; + let bytes_slice = items.as_mut_slice(); + let result = input.read(bytes_slice).map(|_| items).map_err(|_| { + log::debug!( + target: LOG_TARGET, + "Failed to scale-decode vector data", + ); + + BuiltinActorError::DecodingError + }); + + (gas_spent, Some(result)) +} + +fn multi_miller_loop( + mut payload: &[u8], + gas_limit: u64, +) -> (Result, u64) { + // TODO: consider to refactor #3841 + let (gas_spent, result) = decode_vec::(gas_limit, 0, &mut payload); + let a = match result { + Some(Ok(array)) => array, + Some(Err(e)) => return (Err(e), gas_spent), + None => return (Err(BuiltinActorError::InsufficientGas), gas_spent), + }; + + let (mut gas_spent, result) = decode_vec::(gas_limit, gas_spent, &mut payload); + let b = match result { + Some(Ok(array)) => array, + Some(Err(e)) => return (Err(e), gas_spent), + None => return (Err(BuiltinActorError::InsufficientGas), gas_spent), + }; + + // decode the count of items + + let mut slice = a.as_slice(); + let mut reader = ark_scale::rw::InputAsRead(&mut slice); + let Ok(count) = u64::deserialize_with_mode(&mut reader, IS_COMPRESSED, IS_VALIDATED) else { + log::debug!( + target: LOG_TARGET, + "Failed to decode item count in a", + ); + + return (Err(BuiltinActorError::DecodingError), gas_spent); + }; + + let mut slice = b.as_slice(); + let mut reader = ark_scale::rw::InputAsRead(&mut slice); + match u64::deserialize_with_mode(&mut reader, IS_COMPRESSED, IS_VALIDATED) { + Ok(count_b) if count_b != count => { + return ( + Err(BuiltinActorError::Custom(LimitedStr::from_small_str( + "Multi Miller loop: non equal item count", + ))), + gas_spent, + ) + } + Err(_) => return (Err(BuiltinActorError::DecodingError), gas_spent), + Ok(_) => (), + } + + let to_spend = ::WeightInfo::bls12_381_multi_miller_loop(count as u32).ref_time(); + if gas_limit < gas_spent + to_spend { + return (Err(BuiltinActorError::InsufficientGas), gas_spent); + } + + gas_spent += to_spend; + + match bls12_381::host_calls::bls12_381_multi_miller_loop(a, b) { + Ok(result) => (Ok(Response::MultiMillerLoop(result)), gas_spent), + Err(_) => ( + Err(BuiltinActorError::Custom(LimitedStr::from_small_str( + "Multi Miller loop: computation error", + ))), + gas_spent, + ), + } +} + +fn final_exponentiation( + mut payload: &[u8], + gas_limit: u64, +) -> (Result, u64) { + let (mut gas_spent, result) = decode_vec::(gas_limit, 0, &mut payload); + let f = match result { + Some(Ok(array)) => array, + Some(Err(e)) => return (Err(e), gas_spent), + None => return (Err(BuiltinActorError::InsufficientGas), gas_spent), + }; + + let to_spend = ::WeightInfo::bls12_381_final_exponentiation().ref_time(); + if gas_limit < gas_spent + to_spend { + return (Err(BuiltinActorError::InsufficientGas), gas_spent); + } + + gas_spent += to_spend; + + match bls12_381::host_calls::bls12_381_final_exponentiation(f) { + Ok(result) => (Ok(Response::FinalExponentiation(result)), gas_spent), + Err(_) => ( + Err(BuiltinActorError::Custom(LimitedStr::from_small_str( + "Final exponentiation: computation error", + ))), + gas_spent, + ), + } +} + +fn msm( + mut payload: &[u8], + gas_limit: u64, + gas_to_spend: impl FnOnce(u32) -> u64, + call: impl FnOnce(Vec, Vec) -> Result, +) -> (Result, u64) { + let (gas_spent, result) = decode_vec::(gas_limit, 0, &mut payload); + let bases = match result { + Some(Ok(array)) => array, + Some(Err(e)) => return (Err(e), gas_spent), + None => return (Err(BuiltinActorError::InsufficientGas), gas_spent), + }; + + let (mut gas_spent, result) = decode_vec::(gas_limit, gas_spent, &mut payload); + let scalars = match result { + Some(Ok(array)) => array, + Some(Err(e)) => return (Err(e), gas_spent), + None => return (Err(BuiltinActorError::InsufficientGas), gas_spent), + }; + + // decode the count of items + + let mut slice = bases.as_slice(); + let mut reader = ark_scale::rw::InputAsRead(&mut slice); + let Ok(count) = u64::deserialize_with_mode(&mut reader, IS_COMPRESSED, IS_VALIDATED) else { + log::debug!( + target: LOG_TARGET, + "Failed to decode item count in bases", + ); + + return (Err(BuiltinActorError::DecodingError), gas_spent); + }; + + let mut slice = scalars.as_slice(); + let mut reader = ark_scale::rw::InputAsRead(&mut slice); + match u64::deserialize_with_mode(&mut reader, IS_COMPRESSED, IS_VALIDATED) { + Ok(count_b) if count_b != count => { + return ( + Err(BuiltinActorError::Custom(LimitedStr::from_small_str( + "Multi scalar multiplication: non equal item count", + ))), + gas_spent, + ) + } + Err(_) => { + log::debug!( + target: LOG_TARGET, + "Failed to decode item count in scalars", + ); + + return (Err(BuiltinActorError::DecodingError), gas_spent); + } + Ok(_) => (), + } + + let to_spend = gas_to_spend(count as u32); + if gas_limit < gas_spent + to_spend { + return (Err(BuiltinActorError::InsufficientGas), gas_spent); + } + + gas_spent += to_spend; + + match call(bases, scalars) { + Ok(result) => (Ok(result), gas_spent), + Err(_) => ( + Err(BuiltinActorError::Custom(LimitedStr::from_small_str( + "Multi scalar multiplication: computation error", + ))), + gas_spent, + ), + } +} + +fn msm_g1(payload: &[u8], gas_limit: u64) -> (Result, u64) { + msm::( + payload, + gas_limit, + |count| ::WeightInfo::bls12_381_msm_g1(count).ref_time(), + |bases, scalars| { + bls12_381::host_calls::bls12_381_msm_g1(bases, scalars) + .map(Response::MultiScalarMultiplicationG1) + }, + ) +} + +fn msm_g2(payload: &[u8], gas_limit: u64) -> (Result, u64) { + msm::( + payload, + gas_limit, + |count| ::WeightInfo::bls12_381_msm_g2(count).ref_time(), + |bases, scalars| { + bls12_381::host_calls::bls12_381_msm_g2(bases, scalars) + .map(Response::MultiScalarMultiplicationG2) + }, + ) +} + +fn projective_multiplication( + mut payload: &[u8], + gas_limit: u64, + gas_to_spend: impl FnOnce(u32) -> u64, + call: impl FnOnce(Vec, Vec) -> Result, +) -> (Result, u64) { + let (gas_spent, result) = decode_vec::(gas_limit, 0, &mut payload); + let base = match result { + Some(Ok(array)) => array, + Some(Err(e)) => return (Err(e), gas_spent), + None => return (Err(BuiltinActorError::InsufficientGas), gas_spent), + }; + + let (mut gas_spent, result) = decode_vec::(gas_limit, gas_spent, &mut payload); + let scalar = match result { + Some(Ok(array)) => array, + Some(Err(e)) => return (Err(e), gas_spent), + None => return (Err(BuiltinActorError::InsufficientGas), gas_spent), + }; + + // decode the count of items + + let mut slice = scalar.as_slice(); + let mut reader = ark_scale::rw::InputAsRead(&mut slice); + let Ok(count) = u64::deserialize_with_mode(&mut reader, IS_COMPRESSED, IS_VALIDATED) else { + log::debug!( + target: LOG_TARGET, + "Failed to decode item count in scalar", + ); + + return (Err(BuiltinActorError::DecodingError), gas_spent); + }; + + let to_spend = gas_to_spend(count as u32); + if gas_limit < gas_spent + to_spend { + return (Err(BuiltinActorError::InsufficientGas), gas_spent); + } + + gas_spent += to_spend; + + match call(base, scalar) { + Ok(result) => (Ok(result), gas_spent), + Err(_) => ( + Err(BuiltinActorError::Custom(LimitedStr::from_small_str( + "Projective multiplication: computation error", + ))), + gas_spent, + ), + } +} + +fn projective_multiplication_g1( + payload: &[u8], + gas_limit: u64, +) -> (Result, u64) { + projective_multiplication::( + payload, + gas_limit, + |count| ::WeightInfo::bls12_381_mul_projective_g1(count).ref_time(), + |base, scalar| { + bls12_381::host_calls::bls12_381_mul_projective_g1(base, scalar) + .map(Response::ProjectiveMultiplicationG1) + }, + ) +} + +fn projective_multiplication_g2( + payload: &[u8], + gas_limit: u64, +) -> (Result, u64) { + projective_multiplication::( + payload, + gas_limit, + |count| ::WeightInfo::bls12_381_mul_projective_g2(count).ref_time(), + |base, scalar| { + bls12_381::host_calls::bls12_381_mul_projective_g2(base, scalar) + .map(Response::ProjectiveMultiplicationG2) + }, + ) +} diff --git a/pallets/gear-builtin/src/lib.rs b/pallets/gear-builtin/src/lib.rs index 211242217eb..b575d9d9392 100644 --- a/pallets/gear-builtin/src/lib.rs +++ b/pallets/gear-builtin/src/lib.rs @@ -34,6 +34,7 @@ extern crate alloc; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; +pub mod bls12_381; pub mod weights; #[cfg(test)] diff --git a/pallets/gear-builtin/src/mock.rs b/pallets/gear-builtin/src/mock.rs index cf94c36d281..b2825123748 100644 --- a/pallets/gear-builtin/src/mock.rs +++ b/pallets/gear-builtin/src/mock.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{self as pallet_gear_builtin, BuiltinActor, BuiltinActorError}; +use crate::{self as pallet_gear_builtin, bls12_381, BuiltinActor, BuiltinActorError}; use common::{GasProvider, GasTree}; use core::cell::RefCell; use frame_support::{ @@ -211,7 +211,12 @@ impl BuiltinActor for HonestBuiltinActor { } impl pallet_gear_builtin::Config for Test { - type Builtins = (SuccessBuiltinActor, ErrorBuiltinActor, HonestBuiltinActor); + type Builtins = ( + SuccessBuiltinActor, + ErrorBuiltinActor, + HonestBuiltinActor, + bls12_381::Actor, + ); type WeightInfo = (); } diff --git a/pallets/gear-builtin/src/tests/bls381.rs b/pallets/gear-builtin/src/tests/bls381.rs new file mode 100644 index 00000000000..dac7bfa369b --- /dev/null +++ b/pallets/gear-builtin/src/tests/bls381.rs @@ -0,0 +1,700 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::mock::*; +use ark_bls12_381::{Bls12_381, G1Affine, G1Projective as G1, G2Affine, G2Projective as G2}; +use ark_ec::{ + pairing::Pairing, + short_weierstrass::{Projective as SWProjective, SWCurveConfig}, + Group, ScalarMul, VariableBaseMSM, +}; +use ark_ff::biginteger::BigInt; +use ark_scale::hazmat::ArkScaleProjective; +use ark_std::{ops::Mul, UniformRand}; +use common::Origin; +use frame_support::assert_ok; +use gbuiltin_bls381::*; +use gear_core::ids::ProgramId; +use gear_core_errors::{ErrorReplyReason, ReplyCode, SimpleExecutionError}; +use pallet_gear::GasInfo; +use parity_scale_codec::{Decode, Encode}; +use primitive_types::H256; + +type ArkScale = ark_scale::ArkScale; +type ScalarField = ::ScalarField; + +const ACTOR_ID: [u8; 32] = + hex_literal::hex!("6b6e292c382945e80bf51af2ba7fe9f458dcff81ae6075c46f9095e1bbecdc37"); + +pub(crate) fn init_logger() { + let _ = env_logger::Builder::from_default_env() + .format_module_path(false) + .format_level(true) + .try_init(); +} + +fn get_gas_info(builtin_id: ProgramId, payload: Vec) -> GasInfo { + start_transaction(); + let res = Gear::calculate_gas_info( + SIGNER.into_origin(), + pallet_gear::manager::HandleKind::Handle(builtin_id), + payload, + 0, + true, + None, + None, + ) + .expect("calculate_gas_info failed"); + rollback_transaction(); + + assert_ne!(res.min_limit, 0); + assert_ne!(res.burned, 0); + // < 90% * block_gas_limit + assert!(res.burned < BlockGasLimit::get() / 10 * 9); + + res +} + +#[test] +fn decoding_error() { + init_logger(); + + new_test_ext().execute_with(|| { + let builtin_actor_id: ProgramId = H256::from(ACTOR_ID).cast(); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + vec![255u8; 10], + 1_000_000_000, + 0, + false, + )); + + run_to_next_block(); + + // An error reply should have been sent. + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::UserspacePanic, + ))) + } + _ => false, + })); + }); +} + +#[test] +fn multi_miller_loop() { + init_logger(); + + new_test_ext().execute_with(|| { + let mut rng = ark_std::test_rng(); + + let message: G1Affine = G1::rand(&mut rng).into(); + let priv_key: ScalarField = UniformRand::rand(&mut rng); + let generator: G2 = G2::generator(); + let pub_key: G2Affine = generator.mul(priv_key).into(); + + let a: ArkScale::G1Affine>> = vec![message].into(); + let b: ArkScale::G2Affine>> = vec![].into(); + let payload = Request::MultiMillerLoop { a: a.encode(), b: b.encode(), }.encode(); + + // Case of the incorrect arguments + let builtin_id: ProgramId = H256::from(ACTOR_ID).cast(); + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_id, + payload.clone(), + 10_000_000_000, + 0, + false, + )); + + run_to_next_block(); + + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::UserspacePanic, + ))) + } + _ => false, + })); + + let result = ::multi_miller_loop(vec![message], vec![pub_key]); + + let a: ArkScale::G1Affine>> = vec![message].into(); + let b: ArkScale::G2Affine>> = vec![pub_key].into(); + let payload = Request::MultiMillerLoop { a: a.encode(), b: b.encode(), }.encode(); + + let builtin_id: ProgramId = H256::from(ACTOR_ID).cast(); + let gas_info = get_gas_info(builtin_id, payload.clone()); + + // Check the case of insufficient gas + System::reset_events(); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_id, + payload.clone(), + gas_info.min_limit / 2, + 0, + false, + )); + + run_to_next_block(); + + // An error reply should have been sent. + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::RanOutOfGas, + ))) + } + _ => false, + })); + + // Check the computations are correct + System::reset_events(); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_id, + payload, + gas_info.min_limit, + 0, + false, + )); + + run_to_next_block(); + + let response = match System::events().into_iter().find_map(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + assert_eq!(message.destination(), ProgramId::from(SIGNER)); + assert!(matches!(message.details(), Some(details) if matches!(details.to_reply_code(), ReplyCode::Success(..)))); + + Some(message.payload_bytes().to_vec()) + } + + _ => None, + }) { + Some(response) => response, + _ => unreachable!(), + }; + + let builtin_result = match Response::decode(&mut response.as_slice()) { + Ok(Response::MultiMillerLoop(builtin_result)) => builtin_result, + _ => unreachable!(), + }; + + let builtin_result = ArkScale::<::TargetField>::decode(&mut builtin_result.as_slice()).unwrap(); + assert_eq!(result.0, builtin_result.0); + }); +} + +#[test] +fn final_exponentiation() { + init_logger(); + + new_test_ext().execute_with(|| { + let mut rng = ark_std::test_rng(); + + // message + let message: G1Affine = G1::rand(&mut rng).into(); + let priv_key: ScalarField = UniformRand::rand(&mut rng); + let generator: G2 = G2::generator(); + let pub_key: G2Affine = generator.mul(priv_key).into(); + + let loop_result = ::multi_miller_loop(vec![message], vec![pub_key]); + let result = ::final_exponentiation(loop_result); + + let f: ArkScale<::TargetField> = loop_result.0.into(); + let payload = Request::FinalExponentiation { f: f.encode() }.encode(); + + let builtin_actor_id: ProgramId = H256::from(ACTOR_ID).cast(); + let gas_info = get_gas_info(builtin_actor_id, payload.clone()); + + // check case of insufficient gas + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload.clone(), + gas_info.min_limit / 2, + 0, + false, + )); + + run_to_next_block(); + + // An error reply should have been sent. + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::RanOutOfGas, + ))) + } + _ => false, + })); + + // check the computations are correct + System::reset_events(); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload, + gas_info.min_limit, + 0, + false, + )); + + run_to_next_block(); + + let response = match System::events().into_iter().find_map(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + assert_eq!(message.destination(), ProgramId::from(SIGNER)); + assert!(matches!(message.details(), Some(details) if matches!(details.to_reply_code(), ReplyCode::Success(..)))); + + Some(message.payload_bytes().to_vec()) + } + + _ => None, + }) { + Some(response) => response, + _ => unreachable!(), + }; + + let builtin_result = match Response::decode(&mut response.as_slice()) { + Ok(Response::FinalExponentiation(builtin_result)) => builtin_result, + _ => unreachable!(), + }; + + let builtin_result = ArkScale::<::TargetField>::decode(&mut builtin_result.as_slice()).unwrap(); + assert!(matches!(result, Some(r) if r.0 == builtin_result.0)); + }); +} + +#[test] +fn msm_g1() { + init_logger(); + + new_test_ext().execute_with(|| { + let mut rng = ark_std::test_rng(); + + let count = 5usize; + + let scalars = (0..count) + .map(|_| ::ScalarField::rand(&mut rng)) + .collect::>(); + + let bases = (0..count).map(|_| G1::rand(&mut rng)).collect::>(); + let bases = G1::batch_convert_to_mul_base(&bases); + + let ark_scalars: ArkScale::ScalarField>> = scalars[1..].to_vec().into(); + let ark_bases: ArkScale> = bases.clone().into(); + + let payload = Request::MultiScalarMultiplicationG1 { bases: ark_bases.encode(), scalars: ark_scalars.encode() }.encode(); + + // Case of the incorrect arguments + let builtin_id: ProgramId = H256::from(ACTOR_ID).cast(); + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_id, + payload.clone(), + 10_000_000_000, + 0, + false, + )); + + run_to_next_block(); + + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::UserspacePanic, + ))) + } + _ => false, + })); + + let result = as VariableBaseMSM>::msm(&bases, &scalars); + + let ark_scalars: ArkScale::ScalarField>> = scalars.into(); + let ark_bases: ArkScale> = bases.into(); + + let payload = Request::MultiScalarMultiplicationG1 { bases: ark_bases.encode(), scalars: ark_scalars.encode() }.encode(); + + let builtin_actor_id: ProgramId = H256::from(ACTOR_ID).cast(); + let gas_info = get_gas_info(builtin_actor_id, payload.clone()); + + // Check the case of insufficient gas + System::reset_events(); + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload.clone(), + gas_info.min_limit / 2, + 0, + false, + )); + + run_to_next_block(); + + // An error reply should have been sent. + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::RanOutOfGas, + ))) + } + _ => false, + })); + + // Check the computations are correct + System::reset_events(); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload, + gas_info.min_limit, + 0, + false, + )); + + run_to_next_block(); + + let response = match System::events().into_iter().find_map(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + assert_eq!(message.destination(), ProgramId::from(SIGNER)); + assert!(matches!(message.details(), Some(details) if matches!(details.to_reply_code(), ReplyCode::Success(..)))); + + Some(message.payload_bytes().to_vec()) + } + + _ => None, + }) { + Some(response) => response, + _ => unreachable!(), + }; + + let builtin_result = match Response::decode(&mut response.as_slice()) { + Ok(Response::MultiScalarMultiplicationG1(builtin_result)) => builtin_result, + _ => unreachable!(), + }; + + let builtin_result = ArkScaleProjective::::decode(&mut builtin_result.as_slice()).unwrap(); + assert!(matches!(result, Ok(r) if r == builtin_result.0)); + }); +} + +#[test] +fn msm_g2() { + init_logger(); + + new_test_ext().execute_with(|| { + let mut rng = ark_std::test_rng(); + + let count = 5usize; + + let scalars = (0..count) + .map(|_| ::ScalarField::rand(&mut rng)) + .collect::>(); + + let bases = (0..count).map(|_| G2::rand(&mut rng)).collect::>(); + let bases = G2::batch_convert_to_mul_base(&bases); + + let ark_scalars: ArkScale::ScalarField>> = scalars[1..].to_vec().into(); + let ark_bases: ArkScale> = bases.clone().into(); + + let payload = Request::MultiScalarMultiplicationG1 { bases: ark_bases.encode(), scalars: ark_scalars.encode() }.encode(); + + // Case of the incorrect arguments + let builtin_id: ProgramId = H256::from(ACTOR_ID).cast(); + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_id, + payload.clone(), + 10_000_000_000, + 0, + false, + )); + + run_to_next_block(); + + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::UserspacePanic, + ))) + } + _ => false, + })); + + let result = as VariableBaseMSM>::msm(&bases, &scalars); + + let ark_scalars: ArkScale::ScalarField>> = scalars.into(); + let ark_bases: ArkScale> = bases.into(); + + let payload = Request::MultiScalarMultiplicationG2 { bases: ark_bases.encode(), scalars: ark_scalars.encode() }.encode(); + + let builtin_actor_id: ProgramId = H256::from(ACTOR_ID).cast(); + let gas_info = get_gas_info(builtin_actor_id, payload.clone()); + + // Check the case of insufficient gas + System::reset_events(); + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload.clone(), + gas_info.min_limit / 2, + 0, + false, + )); + + run_to_next_block(); + + // An error reply should have been sent. + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::RanOutOfGas, + ))) + } + _ => false, + })); + + // Check the computations are correct + System::reset_events(); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload, + gas_info.min_limit, + 0, + false, + )); + + run_to_next_block(); + + let response = match System::events().into_iter().find_map(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + assert_eq!(message.destination(), ProgramId::from(SIGNER)); + assert!(matches!(message.details(), Some(details) if matches!(details.to_reply_code(), ReplyCode::Success(..)))); + + Some(message.payload_bytes().to_vec()) + } + + _ => None, + }) { + Some(response) => response, + _ => unreachable!(), + }; + + let builtin_result = match Response::decode(&mut response.as_slice()) { + Ok(Response::MultiScalarMultiplicationG2(builtin_result)) => builtin_result, + _ => unreachable!(), + }; + + let builtin_result = ArkScaleProjective::::decode(&mut builtin_result.as_slice()).unwrap(); + assert!(matches!(result, Ok(r) if r == builtin_result.0)); + }); +} + +#[test] +fn mul_projective_g1() { + init_logger(); + + new_test_ext().execute_with(|| { + let mut rng = ark_std::test_rng(); + + let bigint = BigInt::<3>::rand(&mut rng); + let bigint = bigint.0.to_vec(); + let base = G1::rand(&mut rng); + + let result = ::mul_projective(&base, &bigint); + + let ark_bigint: ArkScale> = bigint.into(); + let ark_base: ArkScaleProjective = base.into(); + let payload = Request::ProjectiveMultiplicationG1 { base: ark_base.encode(), scalar: ark_bigint.encode() }.encode(); + let builtin_actor_id: ProgramId = H256::from(ACTOR_ID).cast(); + let gas_info = get_gas_info(builtin_actor_id, payload.clone()); + + // Check the case of insufficient gas + System::reset_events(); + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload.clone(), + gas_info.min_limit / 2, + 0, + false, + )); + + run_to_next_block(); + + // An error reply should have been sent. + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::RanOutOfGas, + ))) + } + _ => false, + })); + + // Check the computations are correct + System::reset_events(); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload, + gas_info.min_limit, + 0, + false, + )); + + run_to_next_block(); + + let response = match System::events().into_iter().find_map(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + assert_eq!(message.destination(), ProgramId::from(SIGNER)); + assert!(matches!(message.details(), Some(details) if matches!(details.to_reply_code(), ReplyCode::Success(..)))); + + Some(message.payload_bytes().to_vec()) + } + + _ => None, + }) { + Some(response) => response, + _ => unreachable!(), + }; + + let builtin_result = match Response::decode(&mut response.as_slice()) { + Ok(Response::ProjectiveMultiplicationG1(builtin_result)) => builtin_result, + _ => unreachable!(), + }; + + let builtin_result = ArkScaleProjective::::decode(&mut builtin_result.as_slice()).unwrap(); + assert_eq!(result, builtin_result.0); + }); +} + +#[test] +fn mul_projective_g2() { + init_logger(); + + new_test_ext().execute_with(|| { + let mut rng = ark_std::test_rng(); + + let bigint = BigInt::<3>::rand(&mut rng); + let bigint = bigint.0.to_vec(); + let base = G2::rand(&mut rng); + + let result = ::mul_projective(&base, &bigint); + + let ark_bigint: ArkScale> = bigint.into(); + let ark_base: ArkScaleProjective = base.into(); + let payload = Request::ProjectiveMultiplicationG2 { base: ark_base.encode(), scalar: ark_bigint.encode() }.encode(); + let builtin_actor_id: ProgramId = H256::from(ACTOR_ID).cast(); + let gas_info = get_gas_info(builtin_actor_id, payload.clone()); + + // Check the case of insufficient gas + System::reset_events(); + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload.clone(), + gas_info.min_limit / 2, + 0, + false, + )); + + run_to_next_block(); + + // An error reply should have been sent. + assert!(System::events().into_iter().any(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + message.destination() == ProgramId::from(SIGNER) + && matches!(message.details(), Some(details) if details.to_reply_code() + == ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::RanOutOfGas, + ))) + } + _ => false, + })); + + // Check the computations are correct + System::reset_events(); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(SIGNER), + builtin_actor_id, + payload, + gas_info.min_limit, + 0, + false, + )); + + run_to_next_block(); + + let response = match System::events().into_iter().find_map(|e| match e.event { + RuntimeEvent::Gear(pallet_gear::Event::::UserMessageSent { message, .. }) => { + assert_eq!(message.destination(), ProgramId::from(SIGNER)); + assert!(matches!(message.details(), Some(details) if matches!(details.to_reply_code(), ReplyCode::Success(..)))); + + Some(message.payload_bytes().to_vec()) + } + + _ => None, + }) { + Some(response) => response, + _ => unreachable!(), + }; + + let builtin_result = match Response::decode(&mut response.as_slice()) { + Ok(Response::ProjectiveMultiplicationG2(builtin_result)) => builtin_result, + _ => unreachable!(), + }; + + let builtin_result = ArkScaleProjective::::decode(&mut builtin_result.as_slice()).unwrap(); + assert_eq!(result, builtin_result.0); + }); +} diff --git a/pallets/gear-builtin/src/tests/mod.rs b/pallets/gear-builtin/src/tests/mod.rs index 3166e9ec895..b90c278c4c5 100644 --- a/pallets/gear-builtin/src/tests/mod.rs +++ b/pallets/gear-builtin/src/tests/mod.rs @@ -20,3 +20,4 @@ pub mod bad_builtin_ids; pub mod basic; +pub mod bls381; diff --git a/pallets/gear-builtin/src/weights.rs b/pallets/gear-builtin/src/weights.rs index eee56c79343..feffe25a9a8 100644 --- a/pallets/gear-builtin/src/weights.rs +++ b/pallets/gear-builtin/src/weights.rs @@ -51,6 +51,13 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn calculate_id() -> Weight; fn create_dispatcher() -> Weight; + fn decode_bytes(a: u32, ) -> Weight; + fn bls12_381_multi_miller_loop(c: u32, ) -> Weight; + fn bls12_381_final_exponentiation() -> Weight; + fn bls12_381_msm_g1(c: u32, ) -> Weight; + fn bls12_381_msm_g2(c: u32, ) -> Weight; + fn bls12_381_mul_projective_g1(c: u32, ) -> Weight; + fn bls12_381_mul_projective_g2(c: u32, ) -> Weight; } /// Weights for `pallet_gear_builtin` using a Gear node and recommended hardware. @@ -72,6 +79,73 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(7_000_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// The range of component `a` is `[0, 33554332]`. + fn decode_bytes(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(160, 0).saturating_mul(a.into())) + } + /// The range of component `c` is `[0, 100]`. + fn bls12_381_multi_miller_loop(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(273_990_134, 0) + // Standard Error: 723_591 + .saturating_add(Weight::from_parts(149_551_274, 0).saturating_mul(c.into())) + } + fn bls12_381_final_exponentiation() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 539_000_000 picoseconds. + Weight::from_parts(554_000_000, 0) + } + /// The range of component `c` is `[0, 1000]`. + fn bls12_381_msm_g1(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 25_000_000 picoseconds. + Weight::from_parts(522_081_240, 0) + // Standard Error: 103_908 + .saturating_add(Weight::from_parts(4_129_174, 0).saturating_mul(c.into())) + } + /// The range of component `c` is `[0, 1000]`. + fn bls12_381_msm_g2(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 52_000_000 picoseconds. + Weight::from_parts(521_472_222, 0) + // Standard Error: 451_388 + .saturating_add(Weight::from_parts(12_974_500, 0).saturating_mul(c.into())) + } + /// The range of component `c` is `[1, 100]`. + fn bls12_381_mul_projective_g1(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 0) + // Standard Error: 92_373 + .saturating_add(Weight::from_parts(38_727_595, 0).saturating_mul(c.into())) + } + /// The range of component `c` is `[1, 100]`. + fn bls12_381_mul_projective_g2(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 110_000_000 picoseconds. + Weight::from_parts(111_000_000, 0) + // Standard Error: 286_785 + .saturating_add(Weight::from_parts(114_799_709, 0).saturating_mul(c.into())) + } } impl WeightInfo for () { @@ -91,4 +165,71 @@ impl WeightInfo for () { Weight::from_parts(8_000_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// The range of component `a` is `[0, 33554332]`. + fn decode_bytes(a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(160, 0).saturating_mul(a.into())) + } + /// The range of component `c` is `[0, 100]`. + fn bls12_381_multi_miller_loop(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(273_990_134, 0) + // Standard Error: 723_591 + .saturating_add(Weight::from_parts(149_551_274, 0).saturating_mul(c.into())) + } + fn bls12_381_final_exponentiation() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 539_000_000 picoseconds. + Weight::from_parts(554_000_000, 0) + } + /// The range of component `c` is `[0, 1000]`. + fn bls12_381_msm_g1(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 25_000_000 picoseconds. + Weight::from_parts(522_081_240, 0) + // Standard Error: 103_908 + .saturating_add(Weight::from_parts(4_129_174, 0).saturating_mul(c.into())) + } + /// The range of component `c` is `[0, 1000]`. + fn bls12_381_msm_g2(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 52_000_000 picoseconds. + Weight::from_parts(521_472_222, 0) + // Standard Error: 451_388 + .saturating_add(Weight::from_parts(12_974_500, 0).saturating_mul(c.into())) + } + /// The range of component `c` is `[1, 100]`. + fn bls12_381_mul_projective_g1(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 0) + // Standard Error: 92_373 + .saturating_add(Weight::from_parts(38_727_595, 0).saturating_mul(c.into())) + } + /// The range of component `c` is `[1, 100]`. + fn bls12_381_mul_projective_g2(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 110_000_000 picoseconds. + Weight::from_parts(111_000_000, 0) + // Standard Error: 286_785 + .saturating_add(Weight::from_parts(114_799_709, 0).saturating_mul(c.into())) + } } diff --git a/runtime/vara/src/lib.rs b/runtime/vara/src/lib.rs index 703aa4cdea6..12bedaff9f6 100644 --- a/runtime/vara/src/lib.rs +++ b/runtime/vara/src/lib.rs @@ -1058,7 +1058,7 @@ impl pallet_gear_messenger::Config for Runtime { /// Builtin actors arranged in a tuple. #[cfg(not(feature = "runtime-benchmarks"))] -pub type BuiltinActors = (); +pub type BuiltinActors = (pallet_gear_builtin::bls12_381::Actor,); #[cfg(feature = "runtime-benchmarks")] pub type BuiltinActors = pallet_gear_builtin::benchmarking::BenchmarkingBuiltinActor; diff --git a/utils/crates-io/src/handler.rs b/utils/crates-io/src/handler.rs index 3a434923cec..08843f3cabc 100644 --- a/utils/crates-io/src/handler.rs +++ b/utils/crates-io/src/handler.rs @@ -277,6 +277,14 @@ mod substrate { table.insert("version", GP_RUNTIME_INTERFACE_VERSION.into()); table.insert("package", "gp-runtime-interface".into()); } + // Depends on sp-wasm-interface. + // + // ref: + // - sp-runtime-interface-18.0.0 + // - sp-runtime-interface-proc-macro-12.0.0 + "sp-crypto-ec-utils" => { + table.insert("package", "gp-crypto-ec-utils".into()); + } _ => {} } diff --git a/utils/wasm-proc/src/main.rs b/utils/wasm-proc/src/main.rs index b30391dfa84..92cc67724f1 100644 --- a/utils/wasm-proc/src/main.rs +++ b/utils/wasm-proc/src/main.rs @@ -24,7 +24,7 @@ use gear_wasm_builder::{ use parity_wasm::elements::External; use std::{collections::HashSet, fs, path::PathBuf}; -const RT_ALLOWED_IMPORTS: [&str; 67] = [ +const RT_ALLOWED_IMPORTS: [&str; 73] = [ // From `Allocator` (substrate/primitives/io/src/lib.rs) "ext_allocator_free_version_1", "ext_allocator_malloc_version_1", @@ -103,6 +103,13 @@ const RT_ALLOWED_IMPORTS: [&str; 67] = [ "ext_storage_start_transaction_version_1", // From `Trie` (substrate/primitives/io/src/lib.rs) "ext_trie_blake2_256_ordered_root_version_2", + // From `sp-crypto-ec-utils` + "ext_host_calls_bls12_381_final_exponentiation_version_1", + "ext_host_calls_bls12_381_msm_g1_version_1", + "ext_host_calls_bls12_381_msm_g2_version_1", + "ext_host_calls_bls12_381_mul_projective_g1_version_1", + "ext_host_calls_bls12_381_mul_projective_g2_version_1", + "ext_host_calls_bls12_381_multi_miller_loop_version_1", ]; #[derive(Debug, clap::Parser)]