Files
coopos/libraries/chain/webassembly/crypto.cpp
T

435 lines
21 KiB
C++

#include <eosio/chain/webassembly/interface.hpp>
#include <eosio/chain/protocol_state_object.hpp>
#include <eosio/chain/transaction_context.hpp>
#include <eosio/chain/apply_context.hpp>
#include <fc/io/datastream.hpp>
#include <fc/crypto/modular_arithmetic.hpp>
#include <fc/crypto/blake2.hpp>
#include <fc/crypto/sha3.hpp>
#include <fc/crypto/k1_recover.hpp>
#include <bn256/bn256.h>
#include <bls12-381/bls12-381.hpp>
// local helpers
namespace {
uint32_t ceil_log2(uint32_t n)
{
if (n <= 1) {
return 0;
}
return 32 - __builtin_clz(n - 1);
};
}
// bls implementation
namespace {
using eosio::chain::span;
using eosio::chain::webassembly::return_code;
using bls12_381::from_mont;
}
namespace eosio::chain::webassembly {
void interface::assert_recover_key( legacy_ptr<const fc::sha256> digest,
legacy_span<const char> sig,
legacy_span<const char> pub ) const {
fc::crypto::signature s;
fc::crypto::public_key p;
fc::datastream<const char*> ds( sig.data(), sig.size() );
fc::datastream<const char*> pubds ( pub.data(), pub.size() );
fc::raw::unpack( ds, s );
fc::raw::unpack( pubds, p );
EOS_ASSERT(s.which() < context.db.get<protocol_state_object>().num_supported_key_types, unactivated_signature_type,
"Unactivated signature type used during assert_recover_key");
EOS_ASSERT(p.which() < context.db.get<protocol_state_object>().num_supported_key_types, unactivated_key_type,
"Unactivated key type used when creating assert_recover_key");
if(context.control.is_speculative_block())
EOS_ASSERT(s.variable_size() <= context.control.configured_subjective_signature_length_limit(),
sig_variable_size_limit_exception, "signature variable length component size greater than subjective maximum");
auto check = fc::crypto::public_key( s, *digest, false );
EOS_ASSERT( check == p, crypto_api_exception, "Error expected key different than recovered key" );
}
int32_t interface::recover_key( legacy_ptr<const fc::sha256> digest,
legacy_span<const char> sig,
legacy_span<char> pub ) const {
fc::crypto::signature s;
fc::datastream<const char*> ds( sig.data(), sig.size() );
fc::raw::unpack(ds, s);
EOS_ASSERT(s.which() < context.db.get<protocol_state_object>().num_supported_key_types, unactivated_signature_type,
"Unactivated signature type used during recover_key");
if(context.control.is_speculative_block())
EOS_ASSERT(s.variable_size() <= context.control.configured_subjective_signature_length_limit(),
sig_variable_size_limit_exception, "signature variable length component size greater than subjective maximum");
auto recovered = fc::crypto::public_key(s, *digest, false);
// the key types newer than the first 2 may be varible in length
if (s.which() >= config::genesis_num_supported_key_types ) {
EOS_ASSERT(pub.size() >= 33, wasm_execution_error,
"destination buffer must at least be able to hold an ECC public key");
auto packed_pubkey = fc::raw::pack(recovered);
auto copy_size = std::min<size_t>(pub.size(), packed_pubkey.size());
std::memcpy(pub.data(), packed_pubkey.data(), copy_size);
return packed_pubkey.size();
} else {
// legacy behavior, key types 0 and 1 always pack to 33 bytes.
// this will do one less copy for those keys while maintaining the rules of
// [0..33) dest sizes: assert (asserts in fc::raw::pack)
// [33..inf) dest sizes: return packed size (always 33)
fc::datastream<char*> out_ds( pub.data(), pub.size() );
fc::raw::pack(out_ds, recovered);
return out_ds.tellp();
}
}
void interface::assert_sha256(legacy_span<const char> data, legacy_ptr<const fc::sha256> hash_val) const {
auto result = context.trx_context.hash_with_checktime<fc::sha256>( data.data(), data.size() );
EOS_ASSERT( result == *hash_val, crypto_api_exception, "hash mismatch" );
}
void interface::assert_sha1(legacy_span<const char> data, legacy_ptr<const fc::sha1> hash_val) const {
auto result = context.trx_context.hash_with_checktime<fc::sha1>( data.data(), data.size() );
EOS_ASSERT( result == *hash_val, crypto_api_exception, "hash mismatch" );
}
void interface::assert_sha512(legacy_span<const char> data, legacy_ptr<const fc::sha512> hash_val) const {
auto result = context.trx_context.hash_with_checktime<fc::sha512>( data.data(), data.size() );
EOS_ASSERT( result == *hash_val, crypto_api_exception, "hash mismatch" );
}
void interface::assert_ripemd160(legacy_span<const char> data, legacy_ptr<const fc::ripemd160> hash_val) const {
auto result = context.trx_context.hash_with_checktime<fc::ripemd160>( data.data(), data.size() );
EOS_ASSERT( result == *hash_val, crypto_api_exception, "hash mismatch" );
}
void interface::sha1(legacy_span<const char> data, legacy_ptr<fc::sha1> hash_val) const {
*hash_val = context.trx_context.hash_with_checktime<fc::sha1>( data.data(), data.size() );
}
void interface::sha256(legacy_span<const char> data, legacy_ptr<fc::sha256> hash_val) const {
*hash_val = context.trx_context.hash_with_checktime<fc::sha256>( data.data(), data.size() );
}
void interface::sha512(legacy_span<const char> data, legacy_ptr<fc::sha512> hash_val) const {
*hash_val = context.trx_context.hash_with_checktime<fc::sha512>( data.data(), data.size() );
}
void interface::ripemd160(legacy_span<const char> data, legacy_ptr<fc::ripemd160> hash_val) const {
*hash_val = context.trx_context.hash_with_checktime<fc::ripemd160>( data.data(), data.size() );
}
int32_t interface::alt_bn128_add(span<const char> op1, span<const char> op2, span<char> result ) const {
if (op1.size() != 64 || op2.size() != 64 || result.size() < 64 ||
bn256::g1_add(std::span<const uint8_t, 64>{(const uint8_t*)op1.data(), 64},
std::span<const uint8_t, 64>{(const uint8_t*)op2.data(), 64},
std::span<uint8_t, 64>{ (uint8_t*)result.data(), 64}) == -1)
return return_code::failure;
return return_code::success;
}
int32_t interface::alt_bn128_mul(span<const char> g1_point, span<const char> scalar, span<char> result) const {
if (g1_point.size() != 64 || scalar.size() != 32 || result.size() < 64 ||
bn256::g1_scalar_mul(std::span<const uint8_t, 64>{(const uint8_t*)g1_point.data(), 64},
std::span<const uint8_t, 32>{(const uint8_t*)scalar.data(), 32},
std::span<uint8_t, 64>{ (uint8_t*)result.data(), 64}) == -1)
return return_code::failure;
return return_code::success;
}
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);
if (res == -1)
return return_code::failure;
else
return res? 0 : 1;
}
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");
}
static constexpr uint64_t bit_calc_limit = 106;
uint64_t bit_calc = 5 * ceil_log2(exp.size()) + 8 * ceil_log2(base_modulus_size);
if (bit_calc_limit < bit_calc) {
EOS_THROW(subjective_block_production_exception,
"mod_exp restriction: bit size too large for input arguments");
}
}
bytes bbase(base.data(), base.data() + base.size());
bytes bexp(exp.data(), exp.data() + exp.size());
bytes bmod(modulus.data(), modulus.data() + modulus.size());
auto maybe_err = fc::modexp(bbase, bexp, bmod);
if(std::holds_alternative<fc::modular_arithmetic_error>(maybe_err)) {
return return_code::failure;
}
const auto& res = std::get<bytes>(maybe_err);
if( out.size() < res.size() )
return return_code::failure;
std::memcpy( out.data(), res.data(), res.size() );
return return_code::success;
}
int32_t interface::blake2_f( uint32_t rounds,
span<const char> state,
span<const char> message,
span<const char> t0_offset,
span<const char> t1_offset,
int32_t final,
span<char> out) const {
bool _final = final == 1;
bytes bstate(state.data(), state.data() + state.size());
bytes bmessage(message.data(), message.data() + message.size());
bytes bt0_offset(t0_offset.data(), t0_offset.data() + t0_offset.size());
bytes bt1_offset(t1_offset.data(), t1_offset.data() + t1_offset.size());
auto checktime = [this]() { context.trx_context.checktime(); };
auto maybe_err = fc::blake2b(rounds, bstate, bmessage, bt0_offset, bt1_offset, _final, checktime);
if(std::holds_alternative<fc::blake2b_error>(maybe_err)) {
return return_code::failure;
}
const auto& res = std::get<bytes>(maybe_err);
if( out.size() < res.size() )
return return_code::failure;
std::memcpy( out.data(), res.data(), res.size() );
return return_code::success;
}
void interface::sha3( span<const char> input, span<char> output, int32_t keccak ) const {
bool _keccak = keccak == 1;
const size_t bs = eosio::chain::config::hashing_checktime_block_size;
const char* data = input.data();
uint32_t datalen = input.size();
fc::sha3::encoder enc;
while ( datalen > bs ) {
enc.write( data, bs);
data += bs;
datalen -= bs;
context.trx_context.checktime();
}
enc.write( data, datalen);
auto res = enc.result(!_keccak);
auto copy_size = std::min( output.size(), res.data_size() );
std::memcpy( output.data(), res.data(), copy_size );
}
int32_t interface::k1_recover( span<const char> signature, span<const char> digest, span<char> pub) const {
bytes bsignature(signature.data(), signature.data() + signature.size());
bytes bdigest(digest.data(), digest.data() + digest.size());
auto maybe_err = fc::k1_recover(bsignature, bdigest);
if( std::holds_alternative<fc::k1_recover_error>(maybe_err)) {
return return_code::failure;
}
const auto& res = std::get<bytes>(maybe_err);
if( pub.size() < res.size() )
return return_code::failure;
std::memcpy( pub.data(), res.data(), res.size() );
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() != 96 || op2.size() != 96 || result.size() != 96)
return return_code::failure;
std::optional<bls12_381::g1> a = bls12_381::g1::fromAffineBytesLE(std::span<const uint8_t, 96>((const uint8_t*)op1.data(), 96), {.check_valid = true, .to_mont = true});
std::optional<bls12_381::g1> b = bls12_381::g1::fromAffineBytesLE(std::span<const uint8_t, 96>((const uint8_t*)op2.data(), 96), {.check_valid = true, .to_mont = true});
if(!a || !b)
return return_code::failure;
bls12_381::g1 c = a->add(*b);
c.toAffineBytesLE(std::span<uint8_t, 96>((uint8_t*)result.data(), 96), from_mont::yes);
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() != 192 || op2.size() != 192 || result.size() != 192)
return return_code::failure;
std::optional<bls12_381::g2> a = bls12_381::g2::fromAffineBytesLE(std::span<const uint8_t, 192>((const uint8_t*)op1.data(), 192), {.check_valid = true, .to_mont = true});
std::optional<bls12_381::g2> b = bls12_381::g2::fromAffineBytesLE(std::span<const uint8_t, 192>((const uint8_t*)op2.data(), 192), {.check_valid = true, .to_mont = true});
if(!a || !b)
return return_code::failure;
bls12_381::g2 c = a->add(*b);
c.toAffineBytesLE(std::span<uint8_t, 192>((uint8_t*)result.data(), 192), from_mont::yes);
return return_code::success;
}
int32_t interface::bls_g1_weighted_sum(span<const char> points, span<const char> scalars, const uint32_t n, span<char> result) const {
if(n == 0 || points.size() != n*96 || scalars.size() != n*32 || result.size() != 96)
return return_code::failure;
// Use much efficient scale for the special case of n == 1.
if (1 == n) {
std::optional<bls12_381::g1> a = bls12_381::g1::fromAffineBytesLE(std::span<const uint8_t, 96>((const uint8_t*)points.data(), 96), {.check_valid = true, .to_mont = true});
if(!a)
return return_code::failure;
std::array<uint64_t, 4> b = bls12_381::scalar::fromBytesLE<4>(std::span<uint8_t, 32>((uint8_t*)scalars.data(), 32));
bls12_381::g1 c = a->scale(b);
c.toAffineBytesLE(std::span<uint8_t, 96>((uint8_t*)result.data(), 96), from_mont::yes);
return return_code::success;
}
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++)
{
std::optional<bls12_381::g1> p = bls12_381::g1::fromAffineBytesLE(std::span<const uint8_t, 96>((const uint8_t*)points.data() + i*96, 96), {.check_valid = true, .to_mont = true});
if(!p.has_value())
return return_code::failure;
std::array<uint64_t, 4> s = bls12_381::scalar::fromBytesLE<4>(std::span<const uint8_t, 32>((const uint8_t*)scalars.data() + i*32, 32));
pv.push_back(p.value());
sv.push_back(s);
if(i%10 == 0)
context.trx_context.checktime();
}
bls12_381::g1 r = bls12_381::g1::weightedSum(pv, sv, [this](){ context.trx_context.checktime();}); // accessing value is safe
r.toAffineBytesLE(std::span<uint8_t, 96>((uint8_t*)result.data(), 96), from_mont::yes);
return return_code::success;
}
int32_t interface::bls_g2_weighted_sum(span<const char> points, span<const char> scalars, const uint32_t n, span<char> result) const {
if(n == 0 || points.size() != n*192 || scalars.size() != n*32 || result.size() != 192)
return return_code::failure;
// Use much efficient scale for the special case of n == 1.
if (1 == n) {
std::optional<bls12_381::g2> a = bls12_381::g2::fromAffineBytesLE(std::span<const uint8_t, 192>((const uint8_t*)points.data(), 192), {.check_valid = true, .to_mont = true});
if(!a)
return return_code::failure;
std::array<uint64_t, 4> b = bls12_381::scalar::fromBytesLE<4>(std::span<uint8_t, 32>((uint8_t*)scalars.data(), 32));
bls12_381::g2 c = a->scale(b);
c.toAffineBytesLE(std::span<uint8_t, 192>((uint8_t*)result.data(), 192), from_mont::yes);
return return_code::success;
}
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++)
{
std::optional<bls12_381::g2> p = bls12_381::g2::fromAffineBytesLE(std::span<const uint8_t, 192>((const uint8_t*)points.data() + i*192, 192), {.check_valid = true, .to_mont = true});
if(!p)
return return_code::failure;
std::array<uint64_t, 4> s = bls12_381::scalar::fromBytesLE<4>(std::span<const uint8_t, 32>((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::weightedSum(pv, sv, [this](){ context.trx_context.checktime();}); // accessing value is safe
r.toAffineBytesLE(std::span<uint8_t, 192>((uint8_t*)result.data(), 192), from_mont::yes);
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(n == 0 || g1_points.size() != n*96 || g2_points.size() != n*192 || 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++)
{
std::optional<bls12_381::g1> p_g1 = bls12_381::g1::fromAffineBytesLE(std::span<const uint8_t, 96>((const uint8_t*)g1_points.data() + i*96, 96), {.check_valid = true, .to_mont = true});
std::optional<bls12_381::g2> p_g2 = bls12_381::g2::fromAffineBytesLE(std::span<const uint8_t, 192>((const uint8_t*)g2_points.data() + i*192, 192), {.check_valid = true, .to_mont = true});
if(!p_g1 || !p_g2)
return return_code::failure;
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, [this](){ context.trx_context.checktime();});
r.toBytesLE(std::span<uint8_t, 576>((uint8_t*)result.data(), 576), from_mont::yes);
return return_code::success;
}
int32_t interface::bls_g1_map(span<const char> e, span<char> result) const {
if(e.size() != 48 || result.size() != 96)
return return_code::failure;
std::optional<bls12_381::fp> a = bls12_381::fp::fromBytesLE(std::span<const uint8_t, 48>((const uint8_t*)e.data(), 48), {.check_valid = true, .to_mont = true});
if(!a)
return return_code::failure;
bls12_381::g1 c = bls12_381::g1::mapToCurve(*a);
c.toAffineBytesLE(std::span<uint8_t, 96>((uint8_t*)result.data(), 96), from_mont::yes);
return return_code::success;
}
int32_t interface::bls_g2_map(span<const char> e, span<char> result) const {
if(e.size() != 96 || result.size() != 192)
return return_code::failure;
std::optional<bls12_381::fp2> a = bls12_381::fp2::fromBytesLE(std::span<const uint8_t, 96>((const uint8_t*)e.data(), 96), {.check_valid = true, .to_mont = true});
if(!a)
return return_code::failure;
bls12_381::g2 c = bls12_381::g2::mapToCurve(*a);
c.toAffineBytesLE(std::span<uint8_t, 192>((uint8_t*)result.data(), 192), from_mont::yes);
return return_code::success;
}
int32_t interface::bls_fp_mod(span<const char> s, span<char> result) const {
// s is scalar.
if(s.size() != 64 || result.size() != 48)
return return_code::failure;
std::array<uint64_t, 8> k = bls12_381::scalar::fromBytesLE<8>(std::span<const uint8_t, 64>((const uint8_t*)s.data(), 64));
bls12_381::fp e = bls12_381::fp::modPrime<8>(k);
e.toBytesLE(std::span<uint8_t, 48>((uint8_t*)result.data(), 48), from_mont::yes);
return return_code::success;
}
int32_t interface::bls_fp_mul(span<const char> op1, span<const char> op2, span<char> result) const {
if(op1.size() != 48 || op2.size() != 48 || result.size() != 48)
return return_code::failure;
std::optional<bls12_381::fp> a = bls12_381::fp::fromBytesLE(std::span<const uint8_t, 48>((const uint8_t*)op1.data(), 48), {.check_valid = true, .to_mont = true});
std::optional<bls12_381::fp> b = bls12_381::fp::fromBytesLE(std::span<const uint8_t, 48>((const uint8_t*)op2.data(), 48), {.check_valid = true, .to_mont = true});
if(!a || !b)
return return_code::failure;
bls12_381::fp c = a->multiply(*b);
c.toBytesLE(std::span<uint8_t, 48>((uint8_t*)result.data(), 48), from_mont::yes);
return return_code::success;
}
int32_t interface::bls_fp_exp(span<const char> base, span<const char> exp, span<char> result) const {
// exp is scalar.
if(base.size() != 48 || exp.size() != 64 || result.size() != 48)
return return_code::failure;
std::optional<bls12_381::fp> a = bls12_381::fp::fromBytesLE(std::span<const uint8_t, 48>((const uint8_t*)base.data(), 48), {.check_valid = true, .to_mont = true});
if(!a)
return return_code::failure;
std::array<uint64_t, 8> b = bls12_381::scalar::fromBytesLE<8>(std::span<const uint8_t, 64>((const uint8_t*)exp.data(), 64));
bls12_381::fp c = a->exp<8>(b);
c.toBytesLE(std::span<uint8_t, 48>((uint8_t*)result.data(), 48), from_mont::yes);
return return_code::success;
}
} // ns eosio::chain::webassembly