-
Notifications
You must be signed in to change notification settings - Fork 70
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
Changes from 15 commits
17eb804
ece632e
10776fc
9b42070
ff765e0
66773fd
53e267d
201a0f7
06ab49e
22656ad
89773d7
bcbd4a4
362c3ea
c931460
1a35039
e920c99
8f7d439
c4b0e19
76c82ae
14d9bdb
06411fa
8a0e21f
81020c3
7159dc2
5f827ac
1ca2373
0d3203b
3b77508
233ae01
76e4fd9
9007161
e050376
1e5822d
712b1a0
59cc737
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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) | ||||||||||||||||||||||||||
|
@@ -250,4 +251,105 @@ namespace eosio { namespace chain { namespace webassembly { | |||||||||||||||||||||||||
return return_code::success; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_g1_add(span<const char> op1, span<const char> op2, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_g2_add(span<const char> op1, span<const char> op2, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_g1_mul(span<const char> point, span<const char> scalar, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_g2_mul(span<const char> point, span<const char> scalar, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_g1_exp(span<const char> points, span<const char> scalars, const uint32_t n, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before using the Consider, for example, a contract currently with 64KB of memory calling Host functions must ensure they only access memory within the passed leap/libraries/chain/webassembly/crypto.cpp Lines 119 to 126 in ba919c7
notice how if op1 is not 64 bytes an error is returned. Since bls_g1_exp() does not return anything, the only reasonable option for bls_g1_exp may be to assert that points.size() == n*144 (i.e. fail the transaction).
This same guidance applies to (this feedback applies to all the added host functions) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the detailed explanation and example scenario. I was expecting the execution to fail in those cases but didn't consider potential memory leaks. A pretty big deal, especially for network nodes that run indefinitely. Of course, these range checks are very important. Updates in my most recent commit(s) include:
|
||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
bls12_381::g1 r = bls12_381::g1::multiExp(pv, sv); | ||||||||||||||||||||||||||
r.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 144}, true); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_g2_exp(span<const char> points, span<const char> scalars, const uint32_t n, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
bls12_381::g2 r = bls12_381::g2::multiExp(pv, sv); | ||||||||||||||||||||||||||
r.toJacobianBytesLE({reinterpret_cast<uint8_t*>(result.data()), 288}, true); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_pairing(span<const char> g1_points, span<const char> g2_points, const uint32_t n, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
bls12_381::fp12 r = bls12_381::pairing::calculate(v); | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whenever seeing a loop in a host function, need to be mindful on if a malicious contract can cause the loop to take "too long". In this case, how long does We don't have hard rules, but historical guidance is that a host function should "yield" (allow the deadline timer to cancel the transaction) at least once per millisecond. But this is a manual yield the host function must perform. As an example, leap/libraries/chain/webassembly/crypto.cpp Lines 216 to 227 in ba919c7
see how every eosio::chain::config::hashing_checktime_block_size worth of input to hash will call context.trx_context.checktime();
And, general historical guidance is that host functions with conditions that fail to yield in 5+ms would be considered a security defect and must be fixed. So, we may need to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, thanks for taking the time explaining everything so well. I really appreciate it. I already noticed calls to There are three host functions with loops. According to your rule of thumb (approx. one check per ms) I did some time measuring on my machine to get an idea on how long each one of the loop iterations run. Here the results and the reasoning behind the numbers I chose for the
Assuming significantly better HW on BP nodes I thought aiming for Updates in the most recent commit(s) include:
|
||||||||||||||||||||||||||
r.toBytesLE({reinterpret_cast<uint8_t*>(result.data()), 576}, true); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_g1_map(span<const char> e, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_g2_map(span<const char> e, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
void interface::bls_fp_mod(span<const char> s, span<char> result) const | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
}}} // ns eosio::chain::webassembly |
There was a problem hiding this comment.
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 toinit
only needs to happen once whennodeos
starts. It is a live dispatcher that checks ifadc
andbmi2
cpu features are available and if so sets the faster asm routines.