Skip to content

Conversation

@heifner
Copy link
Contributor

@heifner heifner commented Jan 9, 2026

This PR changes standardized string serialization and deserialization for Solana Ed25519 (ED) and Ethereum (EM) cryptographic types. It enables conversion between native string formats (e.g., Solana-style Base58 for ED25519 and Hex for Ethereum) and the project's internal public_key, private_key, and signature storage types.

Changes

fc::crypto type changes

  • fc::crypto public_key, private_key, signature types now have a from_string() instead of constructors for converting from a string. The from_string takes an optional type for specification of the type of key/signature to create. If the default unknown is provided then the prefix strings like PUB_ED_ is required so that the type can be inferred from the string.
  • to_string() now takes a bool include_prefix defaulting to false. If true then the type prefix, e.g. PUB_ED_ is added to the string. This is useful for knowing how to parse the string according to its type. If the default false is provided then the prefix is not included. This currently only affects ED and EM as other existing Wire types like R1 and BLS always provide the prefix since that is part of their "native" string representation.

Ed25519 String Formatting

  • Implementation: Added to_string and from_string methods to public_key_shim, private_key_shim, and signature_shim using standard Ed25519 Base58 encoding.
  • Native Support: Integrated Solana-compatible native string parsing through from_native_string_to_signature<chain_key_type_solana> in key_serdes.hpp.

Ethereum (EM) String Formatting

  • Hex Support: Implemented to_string and from_string for Ethereum types, supporting standard hexadecimal representations for keys and signatures.
  • Routing: Updated signature.cpp and private_key.cpp to correctly route em types to Ethereum-specific hex utilities via key_serdes.hpp.

Unified Prefix Support

Updated parsing logic to support the following project-standardized prefixes:

  • Ed25519: PVT_ED_, PUB_ED_, and SIG_ED_.
  • Ethereum: PVT_EM_, PUB_EM_, and SIG_EM_.
  • These prefixes are added to unmolested ED base58 and EM hex strings. Clients can simply remove the prefixes. For example, PUB_EM_0x04e68a... => 0x04e68a....

clio updates

  • Add new option clio create key --k1 which will generate K1 keys with PUB_K1_ & PVT_K1_ prefix.
  • Add new option clio convert k1_public_key which will read either form of K1 key and output both.
  • Add new option clio convert k1_private_key which will read either form of K1 key and output private key and public key in both formats.

PR also removes unneeded padding from ED signature_shim.

Noticed this: AntelopeIO/spring#1561 after creating the pull request. Need to consider how these changes can co-exist.

…Ethereum norm.

Use a from_string instead of a constructor.
Optionally allow for string prefixes on EM strings to encode the type.
ED string format not implemented yet.
… signature.

Remove unneeded padding of ED25519 signature_shim.
@heifner heifner requested a review from jglanz January 9, 2026 19:01
Copy link
Collaborator

@jglanz jglanz left a comment

Choose a reason for hiding this comment

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

Generally it looks solid; it's to large to go thru every line, but as long as the test suites are running successfully then all is good

Comments/Notes

  • nits
  • Use fc::split for string tokenizing instead of boilerplate std::find
  • Method naming for parse wire [priv|pub|sig] unknown

inline public_key_data deserialize_bls_base64url(const std::string& base64urlstr) {
using namespace fc::crypto::bls::constants;
auto res = std::mismatch(bls_public_key_prefix.begin(), bls_public_key_prefix.end(), base64urlstr.begin());
FC_ASSERT(res.first == bls_public_key_prefix.end(), "BLS Public Key has invalid format : ${str}", ("str", base64urlstr));
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: _FMT macro

Copy link
Contributor Author

@heifner heifner Jan 12, 2026

Choose a reason for hiding this comment

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

Not available on this branch.

static bls12_381::g1 from_affine_bytes_le(const public_key_data& affine_non_montgomery_le);
private:
public_key_data _affine_non_montgomery_le{};
public_key_data _affine_non_montgomery_le{};
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: whitespace? could just be browser

}

static public_key_shim from_string(const std::string& str) {
constexpr size_t max_key_len = 44;
Copy link
Collaborator

Choose a reason for hiding this comment

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

is this equivalent to key data_size literals? If yes, move up to key_type types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are max lengths for base58 encoding. Probably best thing would be to rename this to from_base58_string()

}

// Given PUB_K1_xxx returns <'PUB','K1','xxx'>
inline std::tuple<std::string, std::string, std::string> parse_base_prefixes(std::string_view str) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use fc::split

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fc::split actually doesn't work because BLS strings can have _ in them. For example: PUB_BLS_no1gZTuy-0iL_FVrc6q4ow5KtTtdv6ZQ3Cq73Jwl32qRNC9AEQaWsESaoN4Y9NAVRmRGnbEekzgo6YlwbztPeoWhWzvHiOALTFKegRXlRxVbM4naOg33cZOSdS25i_MXywteRA


std::string to_native_string(const fc::yield_function_t& yield) const;
// If include_prefix is true, the prefix will be included in the string representation.
// Note for Wire native types (k1, r1, wa, bls) the prefix is always included.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: doc format /**

case key_type::r1:
case key_type::bls: {
private_key k(parse_unknown_wire_private_key_str(str));
FC_ASSERT( k.type() == type, "Parsed type ${pt} does not match specified type ${t} for ${k}",
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: _FMT

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not available on this branch.

Copy link
Collaborator

@jglanz jglanz left a comment

Choose a reason for hiding this comment

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

read other notes first

@heifner
Copy link
Contributor Author

heifner commented Jan 12, 2026

  • Method naming for parse wire [priv|pub|sig] unknown

Not sure what this is referring to?

Base automatically changed from feature/trx-block-key-enforcement to master January 12, 2026 15:10
@heifner heifner merged commit 7513746 into master Jan 16, 2026
21 of 27 checks passed
@heifner heifner deleted the feature/key-string-formats branch January 16, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants