-
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 17 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,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); | ||||||||||||||||||||||||||||||
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. In the case of 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. 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 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.
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 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. Good question. So I did some measuring again: It takes 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. 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. 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 leap/libraries/chain/webassembly/crypto.cpp Lines 137 to 139 in 7c0694b
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 leap/libraries/chain/webassembly/crypto.cpp Lines 146 to 156 in 7c0694b
but I can't find any compelling reason to pick this choice due to our control of the bls lib) 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. Agree. Since there is a I added a
The timings of It was a bit tricky though, to integrate the 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. @mschoenebeck: I think we should pass the yield function into 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 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. 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. Added |
||||||||||||||||||||||||||||||
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 |
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.