Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion benchmark/key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void k1_benchmarking() {
void r1_benchmarking() {
auto payload = "Test Cases";
auto digest = sha256::hash(payload, const_strlen(payload));
auto key = private_key::generate<r1::private_key_shim>();
auto key = private_key::generate(private_key::key_type::r1);

auto sign_f = [&]() {
key.sign(digest);
Expand Down
23 changes: 2 additions & 21 deletions libraries/chain/include/sysio/chain/abi_serializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -647,27 +647,8 @@ namespace impl {
add(mvo, "context_free_actions", trx.context_free_actions, resolver, ctx);
add(mvo, "actions", trx.actions, resolver, ctx);

// process contents of block.transaction_extensions
auto exts = trx.validate_and_extract_extensions();
// Iterate through every extension entry and serialize known ones:
for ( auto const& kv : exts ) {
auto id = kv.first;
auto const& var = kv.second;
switch (id) {
case ed_pubkey_extension::extension_id(): {
// Unpack our ED25519 pubkey extension
const auto& edext = std::get<ed_pubkey_extension>(var);
// Emit the 32-byte pubkey (you can change the field name as needed)
mvo("ed_pubkey", edext.pubkey);
break;
}
// future case XXX_extension::extension_id(): …
default:
// Should never reach this as validate_and_extract_extensions() should only return known extensions
SYS_ASSERT( false, invalid_transaction_extension, "Transaction extension with id type {} is not supported", id);
break;
}
}
// No transaction extensions are currently supported.
// When extensions are added in the future, deserialize them here.

out(name, std::move(mvo));
}
Expand Down
17 changes: 7 additions & 10 deletions libraries/chain/include/sysio/chain/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@ namespace sysio { namespace chain {
using account_subjective_cpu_bill_t = flat_map<account_name, fc::microseconds>;
using action_payers_t = flat_set<account_name>;

/**
* This extension is for including an ED25519 public key in a transaction for signature verification. Generic public_key_type was used to future proof
* in the scenario where another ED25519 curve variant is added. We don't want to add another extension per variant.
*/
struct ed_pubkey_extension {
static constexpr uint16_t extension_id() { return 0x8000; } // 32768 in decimal
static constexpr bool enforce_unique() { return false; }
public_key_type pubkey; // 32-byte public key
// No transaction extensions remain. Keep the type infrastructure for future use.
// Remove placeholder when a transaction extension is needed.
struct transaction_extension_placeholder {
static constexpr uint16_t extension_id() { return 0xFFFF; }
static constexpr bool enforce_unique() { return true; }
};

namespace detail {
Expand All @@ -28,7 +25,7 @@ namespace sysio { namespace chain {
}

using transaction_extension_types = detail::transaction_extension_types<
ed_pubkey_extension
transaction_extension_placeholder
>;

using transaction_extension = transaction_extension_types::transaction_extension_t;
Expand Down Expand Up @@ -214,4 +211,4 @@ FC_REFLECT_DERIVED( sysio::chain::signed_transaction, (sysio::chain::transaction
FC_REFLECT_ENUM( sysio::chain::packed_transaction::compression_type, (none)(zlib))
// @ignore unpacked_trx
FC_REFLECT( sysio::chain::packed_transaction, (signatures)(compression)(packed_context_free_data)(packed_trx) )
FC_REFLECT( sysio::chain::ed_pubkey_extension, (pubkey) )
FC_REFLECT( sysio::chain::transaction_extension_placeholder, )
39 changes: 2 additions & 37 deletions libraries/chain/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include <sysio/chain/transaction.hpp>

#include <fc/static_variant.hpp>
#include <fc/crypto/elliptic_ed.hpp>
#include <fc/crypto/signature.hpp>
namespace sysio { namespace chain {

Expand Down Expand Up @@ -56,26 +55,7 @@ fc::microseconds transaction::get_signature_keys( const vector<signature_type>&
auto start = fc::time_point::now();
recovered_pub_keys.clear();

// 1) Extract and validate extensions
auto validated_ext = validate_and_extract_extensions();
vector<public_key_type> ed_pubkeys;
for ( auto const& item : validated_ext ) {
if ( auto* e = std::get_if<ed_pubkey_extension>(&item.second) ) {
ed_pubkeys.emplace_back(e->pubkey);
}
}

auto to_pk_str = [&](const auto& pk){ try { return pk.to_string([&]() {FC_CHECK_DEADLINE(deadline); }); } catch (...) { return std::string("unknown"); } };
if( !allow_duplicate_keys ) {
flat_set<public_key_type> seen;
for( auto& pk : ed_pubkeys ) {
auto [it, inserted] = seen.emplace(pk);
SYS_ASSERT( inserted, tx_duplicate_sig, "duplicate ED public-key extension for key {}", to_pk_str(pk) );
}
}

// Prepare index for public key extensions.
size_t pubkey_idx = 0;

if ( !signatures.empty() ) {
const digest_type digest = sig_digest(chain_id, cfd);
Expand All @@ -85,35 +65,20 @@ fc::microseconds transaction::get_signature_keys( const vector<signature_type>&
SYS_ASSERT( now < deadline, tx_cpu_usage_exceeded,
"sig verification timed out {}us", now-start );

// dynamic dispatch into the correct path
sig.visit([&](auto const& shim){
using Shim = std::decay_t<decltype(shim)>;

if constexpr ( std::is_same_v<Shim, fc::crypto::bls::signature_shim>) {
SYS_THROW(fc::unsupported_exception, "BLS signatures can not be used to recover public keys.");
} else if constexpr( Shim::is_recoverable ) {
// If public key can be recovered from signature
} else {
static_assert( Shim::is_recoverable, "All non-BLS signature types must be recoverable" );
auto [itr, ok] = recovered_pub_keys.emplace(fc::crypto::public_key::recover(sig, digest));
SYS_ASSERT( allow_duplicate_keys || ok, tx_duplicate_sig, "duplicate signature for key {}", to_pk_str(*itr) );
} else {
// If public key cannot be recovered from signature, we need to get it from transaction extensions and use verify.
SYS_ASSERT( pubkey_idx < ed_pubkeys.size(), unsatisfied_authorization, "missing ED pubkey extension for signature #{}", pubkey_idx );

const auto& pubkey = ed_pubkeys[pubkey_idx++];
const auto& pubkey_shim = pubkey.template get<typename Shim::public_key_type>();

SYS_ASSERT( shim.verify(digest, pubkey_shim), unsatisfied_authorization, "non-recoverable signature #{} failed", pubkey_idx-1 );

recovered_pub_keys.emplace(pubkey);
}
});
}
}

// Ensure no extra ED pubkey extensions were provided
SYS_ASSERT( pubkey_idx == ed_pubkeys.size(), unsatisfied_authorization,
"got {} ED public-key extensions but only {} ED signatures", ed_pubkeys.size(), pubkey_idx );

return fc::time_point::now() - start;
} FC_CAPTURE_AND_RETHROW("") }

Expand Down
4 changes: 3 additions & 1 deletion libraries/chain/transaction_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,12 @@ namespace sysio::chain {
void transaction_context::init_for_input_trx()
{
const transaction& trx = packed_trx.get_transaction();
// delayed and compressed transactions are not supported by wire
// delayed, compressed, and extension-bearing transactions are not supported by wire
SYS_ASSERT( trx.delay_sec.value == 0, transaction_exception, "transaction cannot be delayed" );
SYS_ASSERT( packed_trx.get_compression() == packed_transaction::compression_type::none,
tx_compression_not_allowed, "packed transaction cannot be compressed");
SYS_ASSERT( trx.transaction_extensions.empty(), invalid_transaction_extension,
"transaction extensions are not currently supported" );

is_input = true;
if (!control.skip_trx_checks()) {
Expand Down
22 changes: 11 additions & 11 deletions libraries/chain/webassembly/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,29 @@ namespace sysio::chain::webassembly {
sig_variable_size_limit_exception, "signature variable length component size greater than subjective maximum");
// Check if the signature is ED25519
if( s.contains<fc::crypto::ed::signature_shim>() ) {
// a) Extract 32 raw bytes from fc::sha256
auto sha_data = digest->data();
// Extract 32 raw bytes from fc::sha256
auto sha_data = digest->data();
const unsigned char* msgptr = reinterpret_cast<const unsigned char*>(sha_data);

// b) Extract 64-byte signature (skip the 1-byte “which” prefix)
const unsigned char* sigptr = reinterpret_cast<const unsigned char*>(sig.data()) + 1;

// c) Extract 32-byte pubkey (skip the 1-byte “which” prefix)
// Extract 32-byte pubkey (skip the 1-byte “which” prefix)
const unsigned char* pubptr = reinterpret_cast<const unsigned char*>(pub.data()) + 1;

// Extract 64-byte signature (skip 1-byte variant index + 32-byte embedded pubkey)
const unsigned char* sigptr = reinterpret_cast<const unsigned char*>(sig.data()) + 1 + crypto_sign_PUBLICKEYBYTES;

// d) Call libsodium’s raw ED25519 detached-verify
int ok = crypto_sign_verify_detached( sigptr,
msgptr,
32,
pubptr );
msgptr,
32,
pubptr );
SYS_ASSERT( ok == 0,
crypto_api_exception,
"ED25519 signature verify failed" );
return;
}

// otherwise, fall back to the existing ECDSA‐style recover→compare path
auto check = fc::crypto::public_key::recover( s, *digest);
auto check = fc::crypto::public_key::recover( s, *digest );
SYS_ASSERT( check == p,
crypto_api_exception,
"Error expected key different than recovered key" );
Expand All @@ -94,7 +94,7 @@ namespace sysio::chain::webassembly {
fc::raw::unpack(ds, s);

using sig_type = fc::crypto::signature::sig_type;
SYS_ASSERT(s.contains_type(sig_type::k1, sig_type::r1, sig_type::wa, sig_type::em), unactivated_signature_type,
SYS_ASSERT(s.contains_type(sig_type::k1, sig_type::r1, sig_type::wa, sig_type::em, sig_type::ed), unactivated_signature_type,
"Unactivated signature type used during recover_key");

if(context.control.is_speculative_block())
Expand Down
24 changes: 15 additions & 9 deletions libraries/libfc/include/fc/crypto/elliptic_ed.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,19 @@ struct public_key_shim {
};

/**
* ED25519 signature (64 bytes)
* ED25519 signature (96 bytes = 32 embedded pubkey + 64 sig)
*
* The public key is embedded directly in the signature blob, making
* ED25519 signatures self-contained and recoverable. Layout:
* [0..31] - 32-byte ED25519 public key
* [32..95] - 64-byte ED25519 signature
*/
struct signature_shim {
static constexpr size_t size = crypto_sign_BYTES;
static constexpr bool is_recoverable = false;
static constexpr size_t size = crypto_sign_BYTES + crypto_sign_PUBLICKEYBYTES;
// ED25519 signatures are not mathematically recoverable like ECDSA (K1/R1),
// but we embed the public key in the signature blob so recover() can extract
// and verify it, allowing uniform treatment in get_signature_keys().
static constexpr bool is_recoverable = true;

using data_type = std::array<uint8_t, size>;
data_type _data{};
Expand All @@ -72,9 +80,7 @@ struct signature_shim {
}

using public_key_type = public_key_shim;
public_key_shim recover(const sha256&) const {
FC_THROW_EXCEPTION(exception, "ED25519 signature recovery not supported");
}
public_key_shim recover(const sha256& digest) const;

bool verify(const sha256& digest, const public_key_shim& pub) const;
bool verify_solana(const uint8_t* data, size_t len, const public_key_shim& pub) const;
Expand All @@ -85,7 +91,7 @@ struct signature_shim {
}

static signature_shim from_base58_string(const std::string& str) {
constexpr size_t max_sig_len = 88;
constexpr size_t max_sig_len = 132;
FC_ASSERT( str.size() <= max_sig_len, "Invalid ED25519 signature string length {}", str.size());
auto bytes = from_base58(str);
FC_ASSERT(bytes.size() == size, "Invalid ED25519 signature bytes length {}", bytes.size());
Expand Down Expand Up @@ -206,13 +212,13 @@ DataStream& operator>>(DataStream& ds, crypto::ed::public_key_shim& pk) {

template <typename DataStream>
DataStream& operator<<(DataStream& ds, const crypto::ed::signature_shim& sig) {
ds.write(reinterpret_cast<const char*>(sig._data.data()), crypto_sign_BYTES);
ds.write(reinterpret_cast<const char*>(sig._data.data()), crypto::ed::signature_shim::size);
return ds;
}

template <typename DataStream>
DataStream& operator>>(DataStream& ds, crypto::ed::signature_shim& sig) {
ds.read(reinterpret_cast<char*>(sig._data.data()), crypto_sign_BYTES);
ds.read(reinterpret_cast<char*>(sig._data.data()), crypto::ed::signature_shim::size);
return ds;
}

Expand Down
10 changes: 1 addition & 9 deletions libraries/libfc/include/fc/crypto/private_key.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,7 @@ namespace fc { namespace crypto {

signature sign(const sha256& digest) const;

template< typename KeyType = ecc::private_key_shim >
static private_key generate() {
return private_key(storage_type(KeyType::generate()));
}

template< typename KeyType = r1::private_key_shim >
static private_key generate_r1() {
return private_key(storage_type(KeyType::generate()));
}
static private_key generate(key_type t = key_type::k1);

template< typename KeyType = ecc::private_key_shim >
static private_key regenerate( const typename KeyType::data_type& data ) {
Expand Down
31 changes: 24 additions & 7 deletions libraries/libfc/src/crypto/elliptic_ed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ namespace fc { namespace crypto { namespace ed {
FC_THROW_EXCEPTION(exception, "Failed to create ED25519 signature");
}

// 4) Pack into your signature_shim
// 4) Pack into signature_shim: 32-byte pubkey + 64-byte sig
signature_shim out;
// zero‑pad entire buffer then copy
memset(out._data.data(), 0, out.size);
memcpy(out._data.data(), sigbuf, crypto_sign_BYTES);
auto pub = get_public_key();
memcpy(out._data.data(), pub._data.data(), crypto_sign_PUBLICKEYBYTES);
memcpy(out._data.data() + crypto_sign_PUBLICKEYBYTES, sigbuf, crypto_sign_BYTES);
return out;
}

Expand All @@ -69,9 +70,9 @@ namespace fc { namespace crypto { namespace ed {
// 1) Convert raw digest to ASCII hex, added due to Phantom wallet limitations which doesn't allow signing raw binary data. Guard rails so users don't unknowingly sign malicious transactions.
const std::string hex = fc::to_hex(digest.data(), digest.data_size());

// 2) Verify signature on hex payload
// 2) Verify signature on hex payload (sig starts at offset 32)
return crypto_sign_verify_detached(
_data.data(),
_data.data() + crypto_sign_PUBLICKEYBYTES,
reinterpret_cast<const unsigned char*>(hex.data()),
hex.size(),
pub._data.data()
Expand All @@ -95,18 +96,34 @@ namespace fc { namespace crypto { namespace ed {
FC_THROW_EXCEPTION(exception, "Failed to create ED25519 signature");
}

// Pack: 32-byte pubkey + 64-byte sig
signature_shim out;
memset(out._data.data(), 0, out.size);
memcpy(out._data.data(), sigbuf, crypto_sign_BYTES);
auto pub = get_public_key();
memcpy(out._data.data(), pub._data.data(), crypto_sign_PUBLICKEYBYTES);
memcpy(out._data.data() + crypto_sign_PUBLICKEYBYTES, sigbuf, crypto_sign_BYTES);
return out;
}

// Recover public key from embedded bytes, verifying signature first
public_key_shim signature_shim::recover(const sha256& digest) const {
// Extract the embedded public key from [0..31]
public_key_shim pub;
memcpy(pub._data.data(), _data.data(), crypto_sign_PUBLICKEYBYTES);

// Verify the signature against the embedded key
FC_ASSERT( verify(digest, pub), "ED25519 signature verification failed during recovery" );

return pub;
}

// Verify raw bytes directly (for Solana transaction verification)
bool signature_shim::verify_solana(const uint8_t* data, size_t len, const public_key_shim& pub) const {
sodium_init_guard();

// Sig starts at offset 32
return crypto_sign_verify_detached(
_data.data(),
_data.data() + crypto_sign_PUBLICKEYBYTES,
data,
len,
pub._data.data()
Expand Down
15 changes: 15 additions & 0 deletions libraries/libfc/src/crypto/private_key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ namespace fc { namespace crypto {
}, _storage));
}

private_key private_key::generate(key_type t) {
// Compile-time dispatch table indexed by key_type
using gen_fn = private_key(*)();
static constexpr gen_fn generators[] = {
[] { return private_key(storage_type(ecc::private_key_shim::generate())); }, // k1
[] { return private_key(storage_type(r1::private_key_shim::generate())); }, // r1
[] { return private_key(storage_type(em::private_key_shim::generate())); }, // em
[] { return private_key(storage_type(ed::private_key_shim::generate())); }, // ed
[] { return private_key(storage_type(bls::private_key_shim::generate())); }, // bls
};
auto idx = static_cast<size_t>(t);
FC_ASSERT(idx < std::size(generators), "Key type does not support generation");
return generators[idx]();
}

private_key private_key::from_string(const std::string& str, key_type type) {
switch (type) {
case key_type::k1:
Expand Down
5 changes: 3 additions & 2 deletions libraries/libfc/src/crypto/solana/solana_crypto_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ solana_signature solana_signature::from_base58(const std::string& str) {

solana_signature solana_signature::from_ed_signature(const fc::crypto::ed::signature_shim& sig) {
solana_signature result;
static_assert(sizeof(sig._data) == SIZE, "ED25519 signature size mismatch");
std::ranges::copy(sig._data, result.data.begin());
// Copy the 64-byte signature from offset 32 (after the embedded pubkey)
static_assert(SIZE == crypto_sign_BYTES, "Solana signature size must be 64 bytes");
std::memcpy(result.data.data(), sig._data.data() + crypto_sign_PUBLICKEYBYTES, SIZE);
return result;
}

Expand Down
Loading
Loading