Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BLS12-381 Crypto Primitives #1071

Merged
merged 35 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
17eb804
added bls12-381 crypto primitives
mschoenebeck Apr 22, 2023
ece632e
correct bit/byte mistake in comment
mschoenebeck Apr 22, 2023
10776fc
corrected SHA256 hash of BLS_PRIMITIVES protocol feature
mschoenebeck Apr 24, 2023
9b42070
updated submodule
mschoenebeck May 1, 2023
ff765e0
synced with main
mschoenebeck May 2, 2023
66773fd
synced with Antelope main
mschoenebeck May 2, 2023
53e267d
updated submodule bls12_381 (clang fixes)
mschoenebeck May 8, 2023
201a0f7
updated bls lib
mschoenebeck May 8, 2023
06ab49e
synced with antelope/main
mschoenebeck May 8, 2023
22656ad
synced submodules
mschoenebeck May 9, 2023
89773d7
sync with branch 'main' of https://github.com/AntelopeIO/leap
mschoenebeck May 24, 2023
bcbd4a4
sync with antelope/main
mschoenebeck May 24, 2023
362c3ea
updated submodules
mschoenebeck May 24, 2023
c931460
removed bls types from abi_serializer
mschoenebeck May 24, 2023
1a35039
removed bls typedefs from types.hpp and deleted bls_utils.hpp/cpp
mschoenebeck May 24, 2023
e920c99
Merge branch 'main' of https://github.com/AntelopeIO/leap
mschoenebeck Jun 7, 2023
8f7d439
update according to Github comments
mschoenebeck Jun 7, 2023
c4b0e19
Merge branch 'main' of https://github.com/AntelopeIO/leap
mschoenebeck Jun 8, 2023
76c82ae
updated bls12_381::init()
mschoenebeck Jun 8, 2023
14d9bdb
Merge branch 'main' of https://github.com/AntelopeIO/leap
mschoenebeck Jun 10, 2023
06411fa
added yield() to pairing::calculate and g2::multiExp
mschoenebeck Jun 10, 2023
8a0e21f
renamed bls lib
mschoenebeck Jun 14, 2023
81020c3
regenerate deepmind log for new BLS_PRIMITIVES
spoonincode Jun 14, 2023
7159dc2
updated bls module
mschoenebeck Jun 15, 2023
5f827ac
Merge branch 'main' of https://github.com/AntelopeIO/leap
mschoenebeck Jun 16, 2023
1ca2373
updated bls lib
mschoenebeck Jun 16, 2023
0d3203b
added optional return values (instead of throwing exceptions) and ena…
mschoenebeck Jun 27, 2023
3b77508
Merge pull request #2 from AntelopeIO/bls-regen-dmlog
mschoenebeck Jun 27, 2023
233ae01
added unit tests for garbage io
mschoenebeck Jun 28, 2023
76e4fd9
added bls lib to EosioTester to make CDT integration tests work
mschoenebeck Jun 30, 2023
9007161
Merge branch 'main' of https://github.com/AntelopeIO/leap
mschoenebeck Jun 30, 2023
e050376
added test contract for bls primitives
mschoenebeck Jul 17, 2023
1e5822d
synced with main
mschoenebeck Jul 17, 2023
712b1a0
updated eos-vm submodule
mschoenebeck Jul 17, 2023
59cc737
fixed intrinsic mapping and deep-mind.log errors
mschoenebeck Jul 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@
[submodule "libraries/cli11/cli11"]
path = libraries/cli11/cli11
url = https://github.com/AntelopeIO/CLI11.git
[submodule "libraries/libfc/libraries/bls12_381"]
path = libraries/libfc/libraries/bls12_381
url = https://github.com/mschoenebeck/bls12_381.git
19 changes: 19 additions & 0 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <fc/log/logger_config.hpp>
#include <fc/scoped_exit.hpp>
#include <fc/variant_object.hpp>
#include <bls12_381.hpp>

#include <new>
#include <shared_mutex>
Expand Down Expand Up @@ -340,6 +341,8 @@ struct controller_impl {
set_activation_handler<builtin_protocol_feature_t::get_code_hash>();
set_activation_handler<builtin_protocol_feature_t::get_block_num>();
set_activation_handler<builtin_protocol_feature_t::crypto_primitives>();
set_activation_handler<builtin_protocol_feature_t::bls_primitives>();
bls12_381::init();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the correct place for the library's init function? The call to init only needs to happen once when nodeos starts. It is a live dispatcher that checks if adc and bmi2 cpu features are available and if so sets the faster asm routines.


self.irreversible_block.connect([this](const block_state_ptr& bsp) {
// producer_plugin has already asserted irreversible_block signal is
Expand Down Expand Up @@ -3846,6 +3849,22 @@ void controller_impl::on_activation<builtin_protocol_feature_t::crypto_primitive
} );
}

template<>
void controller_impl::on_activation<builtin_protocol_feature_t::bls_primitives>() {
db.modify( db.get<protocol_state_object>(), [&]( auto& ps ) {
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_g1_add" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_g2_add" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_g1_mul" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_g2_mul" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_g1_exp" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_g2_exp" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_pairing" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_g1_map" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_g2_map" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "bls_fp_mod" );
} );
}

/// End of protocol feature activation handlers

} } /// eosio::chain
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum class builtin_protocol_feature_t : uint32_t {
configurable_wasm_limits = 18, // configurable_wasm_limits2,
crypto_primitives = 19,
get_block_num = 20,
bls_primitives = 21,
reserved_private_fork_protocol_features = 500000,
};

Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ namespace eosio::chain {
using public_key_type = fc::crypto::public_key;
using private_key_type = fc::crypto::private_key;
using signature_type = fc::crypto::signature;

// configurable boost deque (for boost >= 1.71) performs much better than std::deque in our use cases
using block_1024_option_t = boost::container::deque_options< boost::container::block_size<1024u> >::type;
template<typename T>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,17 @@ inline constexpr auto get_intrinsic_table() {
"env.sha3",
"env.blake2_f",
"env.k1_recover",
"env.get_block_num"
"env.get_block_num",
"env.bls_g1_add",
"env.bls_g2_add",
"env.bls_g1_mul",
"env.bls_g2_mul",
"env.bls_g1_exp",
"env.bls_g2_exp",
"env.bls_g1_pairing",
"env.bls_g1_map",
"env.bls_g2_map",
"env.bls_fp_mod"
);
}
inline constexpr std::size_t find_intrinsic_index(std::string_view hf) {
Expand Down
111 changes: 111 additions & 0 deletions libraries/chain/include/eosio/chain/webassembly/interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,117 @@ namespace webassembly {
*/
int32_t k1_recover( span<const char> signature, span<const char> digest, span<char> pub) const;

/**
* Host function for G1 addition on the elliptic curve bls12-381
*
* @ingroup crypto
* @param op1 - a span containing the first operand G1 point.
* @param op2 - a span containing the second operand G1 point.
* @param[out] result - the result op1 + op2.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_g1_add(span<const char> op1, span<const char> op2, span<char> result) const;

/**
* Host function for G2 addition on the elliptic curve bls12-381
*
* @ingroup crypto
* @param op1 - a span containing the first operand G2 point.
* @param op2 - a span containing the second operand G2 point.
* @param[out] result - the result op1 + op2.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_g2_add(span<const char> op1, span<const char> op2, span<char> result) const;

/**
* Host function for G1 scalar multiplication on the elliptic curve bls12-381
*
* @ingroup crypto
* @param point - a span containing the G1 point operand.
* @param scalar - a span containing the scalar operand.
* @param[out] result - the result: scalar * point.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_g1_mul(span<const char> point, span<const char> scalar, span<char> result) const;

/**
* Host function for G2 scalar multiplication on the elliptic curve bls12-381
*
* @ingroup crypto
* @param point - a span containing the G2 point operand.
* @param scalar - a span containing the scalar operand.
* @param[out] result - the result op1 * op2.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_g2_mul(span<const char> point, span<const char> scalar, span<char> result) const;

/**
* Host function for G1 multi-exponentiation on the elliptic curve bls12-381
*
* @ingroup crypto
* @param points - a span containing a list of G1 points (P0, P1, P2... Pn).
* @param scalars - a span containing a list of scalars (s0, s1, s2... sn).
* @param n - the number of elements in the lists.
* @param[out] result - the result s0 * P0 + s1 * P1 + ... + sn * Pn.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_g1_exp(span<const char> points, span<const char> scalars, const uint32_t n, span<char> result) const;

/**
* Host function for G2 multi-exponentiation on the elliptic curve bls12-381
*
* @ingroup crypto
* @param points - a span containing a list of G2 points (P0, P1, P2... Pn).
* @param scalars - a span containing a list of scalars (s0, s1, s2... sn).
* @param n - the number of elements in the lists.
* @param[out] result - the result s0 * P0 + s1 * P1 + ... + sn * Pn.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_g2_exp(span<const char> points, span<const char> scalars, const uint32_t n, span<char> result) const;

/**
* Host function to calculate the pairing of (G1, G2) pairs on the elliptic curve bls12-381
*
* @ingroup crypto
* @param g1_points - a span containing a list of G1 points (P0, P1, P2... Pn).
* @param g2_points - a span containing a list of G2 points (P0, P1, P2... Pn).
* @param n - the number of elements in the lists.
* @param[out] result - the result e(g1_0, g2_0) * e(g1_1, g2_1) * ... * e(g1_n, g2_n)
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_pairing(span<const char> g1_points, span<const char> g2_points, const uint32_t n, span<char> result) const;

/**
* Host function for mapping fp to G1 on the elliptic curve bls12-381
*
* @ingroup crypto
* @param e - a span containing the field element fp to be mapped.
* @param[out] result - the resulting element in G1.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_g1_map(span<const char> e, span<char> result) const;

/**
* Host function for mapping fp2 to G2 on the elliptic curve bls12-381
*
* @ingroup crypto
* @param e - a span containing the field element fp2 to be mapped.
* @param[out] result - the resulting element in G2.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_g2_map(span<const char> e, span<char> result) const;

/**
* Host function for modular reduction of 64 bytes wide scalar to a field element (fp, 48 bytes) of the elliptic curve bls12-381
* Involves Montgomery conversion on the resulting field element.
*
* @ingroup crypto
* @param s - a span containing the 64 bytes wide scalar to be reduced.
* @param[out] result - the resulting field element fp in Montogomery form.
* @return -1 if there was an error 0 otherwise
*/
int32_t bls_fp_mod(span<const char> s, span<char> result) const;

// compiler builtins api
void __ashlti3(legacy_ptr<int128_t>, uint64_t, uint64_t, uint32_t) const;
void __ashrti3(legacy_ptr<int128_t>, uint64_t, uint64_t, uint32_t) const;
Expand Down
11 changes: 11 additions & 0 deletions libraries/chain/protocol_feature_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,17 @@ Adds new cryptographic host functions
Builtin protocol feature: GET_BLOCK_NUM

Enables new `get_block_num` intrinsic which returns the current block number.
*/
{}
} )
( builtin_protocol_feature_t::bls_primitives, builtin_protocol_feature_spec{
"BLS_PRIMITIVES",
fc::variant("01969c44de35999b924095ae7f50081a7f274409fdbccb9fc54fa7836c76089c").as<digest_type>(),
// SHA256 hash of the raw message below within the comment delimiters (do not modify message below).
/*
Builtin protocol feature: BLS_PRIMITIVES
Adds new cryptographic host functions
- Add, multiply, multi-exponentiation and pairing functions for the bls12-381 elliptic curve.
*/
{}
} )
Expand Down
138 changes: 138 additions & 0 deletions libraries/chain/webassembly/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <fc/crypto/sha3.hpp>
#include <fc/crypto/k1_recover.hpp>
#include <bn256/bn256.h>
#include <bls12_381.hpp>

namespace {
uint32_t ceil_log2(uint32_t n)
Expand Down Expand Up @@ -250,4 +251,141 @@ namespace eosio { namespace chain { namespace webassembly {
return return_code::success;
}

int32_t interface::bls_g1_add(span<const char> op1, span<const char> op2, span<char> result) const
{
if(op1.size() != 144 || op2.size() != 144 || result.size() != 144)
return return_code::failure;
bls12_381::g1 a = bls12_381::g1::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(op1.data()), 144}, false, true);
bls12_381::g1 b = bls12_381::g1::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(op2.data()), 144}, false, true);
bls12_381::g1 c = a.add(b);
c.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 144}, true);
return return_code::success;
}

int32_t interface::bls_g2_add(span<const char> op1, span<const char> op2, span<char> result) const
{
if(op1.size() != 288 || op2.size() != 288 || result.size() != 288)
return return_code::failure;
bls12_381::g2 a = bls12_381::g2::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(op1.data()), 288}, false, true);
bls12_381::g2 b = bls12_381::g2::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(op2.data()), 288}, false, true);
bls12_381::g2 c = a.add(b);
c.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 288}, true);
return return_code::success;
}

int32_t interface::bls_g1_mul(span<const char> point, span<const char> scalar, span<char> result) const
{
if(point.size() != 144 || scalar.size() != 32 || result.size() != 144)
return return_code::failure;
bls12_381::g1 a = bls12_381::g1::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(point.data()), 144}, false, true);
std::array<uint64_t, 4> b = bls12_381::scalar::fromBytesLE<4>({reinterpret_cast<const uint8_t*>(scalar.data()), 32});
bls12_381::g1 c = a.mulScalar(b);
c.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 144}, true);
return return_code::success;
}

int32_t interface::bls_g2_mul(span<const char> point, span<const char> scalar, span<char> result) const
{
if(point.size() != 288 || scalar.size() != 32 || result.size() != 288)
return return_code::failure;
bls12_381::g2 a = bls12_381::g2::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(point.data()), 288}, false, true);
std::array<uint64_t, 4> b = bls12_381::scalar::fromBytesLE<4>({reinterpret_cast<const uint8_t*>(scalar.data()), 32});
bls12_381::g2 c = a.mulScalar(b);
c.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 288}, true);
return return_code::success;
}

int32_t interface::bls_g1_exp(span<const char> points, span<const char> scalars, const uint32_t n, span<char> result) const
{
if(points.size() != n*144 || scalars.size() != n*32 || result.size() != 144)
return return_code::failure;
std::vector<bls12_381::g1> pv;
std::vector<std::array<uint64_t, 4>> sv;
pv.reserve(n);
sv.reserve(n);
for(uint32_t i = 0; i < n; i++)
{
bls12_381::g1 p = bls12_381::g1::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(points.data() + i*144), 144}, false, true);
std::array<uint64_t, 4> s = bls12_381::scalar::fromBytesLE<4>({reinterpret_cast<const uint8_t*>(scalars.data() + i*32), 32});
pv.push_back(p);
sv.push_back(s);
if(i%10 == 0)
context.trx_context.checktime();
}
bls12_381::g1 r = bls12_381::g1::multiExp(pv, sv);
r.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 144}, true);
return return_code::success;
}

int32_t interface::bls_g2_exp(span<const char> points, span<const char> scalars, const uint32_t n, span<char> result) const
{
if(points.size() != n*288 || scalars.size() != n*32 || result.size() != 288)
return return_code::failure;
std::vector<bls12_381::g2> pv;
std::vector<std::array<uint64_t, 4>> sv;
pv.reserve(n);
sv.reserve(n);
for(uint32_t i = 0; i < n; i++)
{
bls12_381::g2 p = bls12_381::g2::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(points.data() + i*288), 288}, false, true);
std::array<uint64_t, 4> s = bls12_381::scalar::fromBytesLE<4>({reinterpret_cast<const uint8_t*>(scalars.data() + i*32), 32});
pv.push_back(p);
sv.push_back(s);
if(i%6 == 0)
context.trx_context.checktime();
}
bls12_381::g2 r = bls12_381::g2::multiExp(pv, sv);
r.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 288}, true);
return return_code::success;
}

int32_t interface::bls_pairing(span<const char> g1_points, span<const char> g2_points, const uint32_t n, span<char> result) const
{
if(g1_points.size() != n*144 || g2_points.size() != n*288 || result.size() != 576)
return return_code::failure;
std::vector<std::tuple<bls12_381::g1, bls12_381::g2>> v;
v.reserve(n);
for(uint32_t i = 0; i < n; i++)
{
bls12_381::g1 p_g1 = bls12_381::g1::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(g1_points.data() + i*144), 144}, false, true);
bls12_381::g2 p_g2 = bls12_381::g2::fromJacobianBytesLE({reinterpret_cast<const uint8_t*>(g2_points.data() + i*288), 288}, false, true);
bls12_381::pairing::add_pair(v, p_g1, p_g2);
if(i%4 == 0)
context.trx_context.checktime();
}
bls12_381::fp12 r = bls12_381::pairing::calculate(v);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of v.size() == 80000 how long does bls12_381::pairing::calculate() take?

Copy link
Contributor Author

@mschoenebeck mschoenebeck Jun 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my machine about 20 seconds. I was thinking about it after I read you previous comment: Does that mean, we need to be able to pass the yield() function to pairing::calculate() and execute it there in reasonable intervals as well? (Edit: probably not, since now with the checktime() inside the loop it wouldn't be possible to add that many pairs to v anymore)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since now with the checktime() inside the loop it wouldn't be possible to add that many pairs to v anymore

yeah, good point. But is there some number that takes, for example, 25ms to make it through the loop, but then takes 100ms to make it through bls12_381::pairing::calculate()? That too would be considered a security defect. I'm not sure of the timing for these operations so maybe it's a non issue.

Copy link
Contributor Author

@mschoenebeck mschoenebeck Jun 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. So I did some measuring again:

It takes v.size() == 58 pairs in order for the entire loop to take 25ms (on my machine). The call to pairing::calculate(v) then takes about an additional 14.7ms.

So the pairing is about half of the cost of the loop. I'm not exactly sure how those numbers would vary on a faster machine but I guess it would be a similar ratio between loop execution and paring calculation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's "only" ~10ms but I think we should be prudent and protect against that -- it definitely would fail previous guidelines we had back in the bug bounty days.

Since we control the library, the best choice is to pass in the ability for bls12_381::pairing::calculate() to poll a yield function. An example of that is,

int32_t interface::alt_bn128_pair(span<const char> g1_g2_pairs) const {
auto checktime = [this]() { context.trx_context.checktime(); };
auto res = bn256::pairing_check({(const uint8_t*)g1_g2_pairs.data(), g1_g2_pairs.size()} , checktime);

where the declaration of that is

/// pairing_check calculates the Optimal Ate pairing for a set of points.
///  @param marshaled_g1g2_pairs marshaled g1 g2 pair sequence
///  @return -1 for unmarshal error, 0 for unsuccessful pairing and 1 for successful pairing
int32_t pairing_check(std::span<const uint8_t> marshaled_g1g2_pairs, std::function<void()> yield);

if you wanted to make the yield optional for users, you could potentially do something like

int32_t pairing_check(std::span<const uint8_t> marshaled_g1g2_pairs, std::function<void()> yield = std::function<void()>());

or just require users to pass in a stub function. Either way is fine; importantly either way ultimately still allows the library to used outside of leap.

(there is another option: add a subjective limit to the size of n; for example

int32_t interface::mod_exp(span<const char> base,
span<const char> exp,
span<const char> modulus,
span<char> out) const {
if (context.control.is_speculative_block()) {
unsigned int base_modulus_size = std::max(base.size(), modulus.size());
if (base_modulus_size < exp.size()) {
EOS_THROW(subjective_block_production_exception,
"mod_exp restriction: exponent bit size cannot exceed bit size of either base or modulus");
}

but I can't find any compelling reason to pick this choice due to our control of the bls lib)

Copy link
Contributor Author

@mschoenebeck mschoenebeck Jun 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Since there is a leap branch of the library now, let's try to tailor it perfectly for leap integration.

I added a yield function pointer parameter to pairing::calculate() (pairing::miller_loop() respectively) and g2::multiExp() since this one could also run for about 15ms in the 25ms-loop worst case. Here are the numbers for all three functions (my machine):

  • G1 multi-exponentiation: with v.size() == 161 the loop takes 25ms => g1::multiexp(v) then takes only 5.1ms
  • G2 multi-exponentiation: with v.size() == 96 the loop takes 25ms => g2::multiexp(v) then takes 15.3ms
  • Pairing: with v.size() == 58 the loop takes 25ms => pairing::calculate(v) then takes 14.7ms

The timings of g1::multiExp() should be fine, I guess, since 25ms + 5.1ms ≅ 30ms. So if 5ms to 7ms is an acceptable 'overhead' for the execution time of either pairing::calculate() or g1/g2::multiExp() after their corresponding loops (assuming the worst case vector sizes listed above with 25ms preceding loop execution time) then only one yield() call approximately in the middle of execution of pairing and multiexp should be fine, right? Worst case scenario is then about 25ms + ~7ms ≅ 32ms total execution time in case the transaction fails.

It was a bit tricky though, to integrate the yield() call accurately into the middle of the pairing and mutliexp functions because there are multiple loops (including nested loops) of which some depend on v.size(). However, I believe I found a good way to add it approximately in the middle of each function's execution path, so that the time check is performed once if v exceeds a certain size.

Copy link
Member

@arhag arhag Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mschoenebeck: I think we should pass the yield function into g1::multiExp() as well for consistency.

We should not assume that there will always be a limit of 30 ms in a transaction. For example, we are proposing that the network upgrade to a higher max transaction time. With a max transaction time of 150 ms, the user could pass a 5x larger array and thus increase the time of the g1::multiexp function call by 5x as well.

Additionally, I think the yield functions should be called frequently enough within the implementation so that the host functions cannot be abused to exceed the deadline by more than 5 ms even if we allow the transaction to execute indefinitely. We are proposing to raise the limit to 150 ms. We may push it to 250 ms in future. But in the longer term future, we may be able to take advantage of parallelism to even allow a transaction to execute for longer than even a block interval! And even if that does not happen, we may do that with read-only transactions which could potentially call these host functions. And we need to ensure read-only transactions respect our deadlines as well to maintain availability of the various features of the system.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added yield() call to g1::multiexp. Within the implementation yield() is called inside the loop that reads the parameters passed to the host function every 10 (g1) or 6 (g2) iterations. That should be frequent enough taking the above numbers into consideration, right?

r.toBytesLE({reinterpret_cast<uint8_t*>(result.data()), 576}, true);
return return_code::success;
}

int32_t interface::bls_g1_map(span<const char> e, span<char> result) const
{
if(e.size() != 48 || result.size() != 144)
return return_code::failure;
bls12_381::fp a = bls12_381::fp::fromBytesLE({reinterpret_cast<const uint8_t*>(e.data()), 48}, false, true);
bls12_381::g1 c = bls12_381::g1::mapToCurve(a);
c.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 144}, true);
return return_code::success;
}

int32_t interface::bls_g2_map(span<const char> e, span<char> result) const
{
if(e.size() != 96 || result.size() != 288)
return return_code::failure;
bls12_381::fp2 a = bls12_381::fp2::fromBytesLE({reinterpret_cast<const uint8_t*>(e.data()), 96}, false, true);
bls12_381::g2 c = bls12_381::g2::mapToCurve(a);
c.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 288}, true);
return return_code::success;
}

int32_t interface::bls_fp_mod(span<const char> s, span<char> result) const
{
if(s.size() != 64 || result.size() != 48)
return return_code::failure;
std::array<uint64_t, 8> k = bls12_381::scalar::fromBytesLE<8>({reinterpret_cast<const uint8_t*>(s.data()), 64});
bls12_381::fp e = bls12_381::fp::modPrime<8>(k);
e.toBytesLE({reinterpret_cast<uint8_t*>(result.data()), 48}, true);
return return_code::success;
}

}}} // ns eosio::chain::webassembly
12 changes: 12 additions & 0 deletions libraries/chain/webassembly/runtimes/eos-vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,18 @@ REGISTER_CF_HOST_FUNCTION( blake2_f );
REGISTER_CF_HOST_FUNCTION( sha3 );
REGISTER_CF_HOST_FUNCTION( k1_recover );

// bls_primitives protocol feature
REGISTER_CF_HOST_FUNCTION( bls_g1_add );
REGISTER_CF_HOST_FUNCTION( bls_g2_add );
REGISTER_CF_HOST_FUNCTION( bls_g1_mul );
REGISTER_CF_HOST_FUNCTION( bls_g2_mul );
REGISTER_CF_HOST_FUNCTION( bls_g1_exp );
REGISTER_CF_HOST_FUNCTION( bls_g2_exp );
REGISTER_CF_HOST_FUNCTION( bls_pairing );
REGISTER_CF_HOST_FUNCTION( bls_g1_map );
REGISTER_CF_HOST_FUNCTION( bls_g2_map );
REGISTER_CF_HOST_FUNCTION( bls_fp_mod );

} // namespace webassembly
} // namespace chain
} // namespace eosio
Loading