Skip to content
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

Add sign_message command to cli_wallet #1878

Merged
merged 8 commits into from
Aug 7, 2019
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
57 changes: 57 additions & 0 deletions libraries/wallet/include/graphene/wallet/wallet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,22 @@ struct vesting_balance_object_with_info : public vesting_balance_object
fc::time_point_sec allowed_withdraw_time;
};

struct signed_message_meta {
string account;
public_key_type memo_key;
uint32_t block;
string time;
};

class signed_message {
public:
string message;
signed_message_meta meta;
fc::optional<fc::ecc::compact_signature> signature;

fc::sha256 digest()const;
};

namespace detail {
class wallet_api_impl;
}
Expand Down Expand Up @@ -1055,6 +1071,40 @@ class wallet_api
string read_memo(const memo_data& memo);


/** Sign a message using an account's memo key. The signature is generated as in
* in https://github.com/xeroc/python-graphenelib/blob/d9634d74273ebacc92555499eca7c444217ecba0/graphenecommon/message.py#L64 .
*
* @param signer the name or id of signing account
* @param message text to sign
* @return the signed message in an abstract format
*/
signed_message sign_message(string signer, string message);

/** Verify a message signed with sign_message using the given account's memo key.
*
* @param message the message text
* @param account the account name of the message
* @param block the block number of the message
* @param time the timestamp of the message
* @param sig the message signature
* @return true if signature matches
*/
bool verify_message( string message, string account, int block, const string& time, compact_signature sig );

/** Verify a message signed with sign_message
*
* @param message the signed_message structure containing message, meta data and signature
* @return true if signature matches
*/
bool verify_signed_message( signed_message message );

/** Verify a message signed with sign_message, in its encapsulated form.
*
* @param message the complete encapsulated message string including separators and line feeds
* @return true if signature matches
*/
bool verify_encapsulated_message( string message );

/** These methods are used for stealth transfers */
///@{
/**
Expand Down Expand Up @@ -2034,6 +2084,9 @@ FC_REFLECT(graphene::wallet::operation_detail_ex,
FC_REFLECT( graphene::wallet::account_history_operation_detail,
(total_count)(result_count)(details))

FC_REFLECT( graphene::wallet::signed_message_meta, (account)(memo_key)(block)(time) )
FC_REFLECT( graphene::wallet::signed_message, (message)(meta)(signature) )

FC_API( graphene::wallet::wallet_api,
(help)
(gethelp)
Expand Down Expand Up @@ -2151,6 +2204,10 @@ FC_API( graphene::wallet::wallet_api,
(network_get_connected_peers)
(sign_memo)
(read_memo)
(sign_message)
(verify_message)
(verify_signed_message)
(verify_encapsulated_message)
(set_key_label)
(get_key_label)
(get_public_key)
Expand Down
162 changes: 162 additions & 0 deletions libraries/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ using std::endl;

namespace detail {

static const string ENC_HEADER( "-----BEGIN BITSHARES SIGNED MESSAGE-----\n" );
static const string ENC_META( "-----BEGIN META-----\n" );
static const string ENC_SIG( "-----BEGIN SIGNATURE-----\n" );
static const string ENC_FOOTER( "-----END BITSHARES SIGNED MESSAGE-----" );

struct operation_result_printer
{
public:
Expand Down Expand Up @@ -297,6 +302,26 @@ class htlc_hash_to_string_visitor
}
};

/* meta contains lines of the form "key=value".
* Returns the value for the corresponding key, throws if key is not present. */
static string meta_extract( const string& meta, const string& key )
{
FC_ASSERT( meta.size() > key.size(), "Key '${k}' not found!", ("k",key) );
size_t start;
if( meta.substr( 0, key.size() ) == key && meta[key.size()] == '=' )
start = 0;
else
{
start = meta.find( "\n" + key + "=" );
FC_ASSERT( start != string::npos, "Key '${k}' not found!", ("k",key) );
++start;
}
start += key.size() + 1;
size_t lf = meta.find( "\n", start );
if( lf == string::npos ) lf = meta.size();
return meta.substr( start, lf - start );
}

class wallet_api_impl
{
public:
Expand Down Expand Up @@ -2244,6 +2269,81 @@ class wallet_api_impl
return clear_text;
}

signed_message sign_message(string signer, string message)
{
FC_ASSERT( !self.is_locked() );

const account_object from_account = get_account(signer);
auto dynamic_props = get_dynamic_global_properties();

signed_message msg;
msg.message = message;
msg.meta.account = from_account.name;
msg.meta.memo_key = from_account.options.memo_key;
msg.meta.block = dynamic_props.head_block_number;
msg.meta.time = dynamic_props.time.to_iso_string() + "Z";
msg.signature = get_private_key( from_account.options.memo_key ).sign_compact( msg.digest() );
return msg;
}

bool verify_message( const string& message, const string& account, int block, const string& time,
const compact_signature& sig )
{
const account_object from_account = get_account( account );

signed_message msg;
msg.message = message;
msg.meta.account = from_account.name;
msg.meta.memo_key = from_account.options.memo_key;
msg.meta.block = block;
msg.meta.time = time;
msg.signature = sig;

return verify_signed_message( msg );
}

bool verify_signed_message( const signed_message& message )
{
if( !message.signature.valid() ) return false;

const account_object from_account = get_account( message.meta.account );

const public_key signer( *message.signature, message.digest() );
if( !( message.meta.memo_key == signer ) ) return false;
FC_ASSERT( from_account.options.memo_key == signer,
"Message was signed by contained key, but it doesn't belong to the contained account!" );

return true;
}

bool verify_encapsulated_message( const string& message )
{
signed_message msg;
size_t begin_p = message.find( ENC_HEADER );
FC_ASSERT( begin_p != string::npos, "BEGIN MESSAGE line not found!" );
size_t meta_p = message.find( ENC_META, begin_p );
FC_ASSERT( meta_p != string::npos, "BEGIN META line not found!" );
FC_ASSERT( meta_p >= begin_p + ENC_HEADER.size() + 1, "Missing message!?" );
size_t sig_p = message.find( ENC_SIG, meta_p );
FC_ASSERT( sig_p != string::npos, "BEGIN SIGNATURE line not found!" );
FC_ASSERT( sig_p >= meta_p + ENC_META.size(), "Missing metadata?!" );
size_t end_p = message.find( ENC_FOOTER, meta_p );
FC_ASSERT( end_p != string::npos, "END MESSAGE line not found!" );
FC_ASSERT( end_p >= sig_p + ENC_SIG.size() + 1, "Missing signature?!" );

msg.message = message.substr( begin_p + ENC_HEADER.size(), meta_p - begin_p - ENC_HEADER.size() - 1 );
const string meta = message.substr( meta_p + ENC_META.size(), sig_p - meta_p - ENC_META.size() );
const string sig = message.substr( sig_p + ENC_SIG.size(), end_p - sig_p - ENC_SIG.size() - 1 );

msg.meta.account = meta_extract( meta, "account" );
msg.meta.memo_key = public_key_type( meta_extract( meta, "memokey" ) );
msg.meta.block = boost::lexical_cast<uint32_t>( meta_extract( meta, "block" ) );
msg.meta.time = meta_extract( meta, "timestamp" );
msg.signature = variant(sig).as< fc::ecc::compact_signature >( 5 );

return verify_signed_message( msg );
}

signed_transaction sell_asset(string seller_account,
string amount_to_sell,
string symbol_to_sell,
Expand Down Expand Up @@ -2637,6 +2737,25 @@ class wallet_api_impl
return ss.str();
};

m["sign_message"] = [this](variant result, const fc::variants& a)
{
auto r = result.as<signed_message>( GRAPHENE_MAX_NESTED_OBJECTS );

fc::stringstream encapsulated;
encapsulated << ENC_HEADER;
encapsulated << r.message << '\n';
encapsulated << ENC_META;
encapsulated << "account=" << r.meta.account << '\n';
encapsulated << "memokey=" << std::string( r.meta.memo_key ) << '\n';
encapsulated << "block=" << r.meta.block << '\n';
encapsulated << "timestamp=" << r.meta.time << '\n';
encapsulated << ENC_SIG;
encapsulated << fc::to_hex( (const char*)r.signature->data, r.signature->size() ) << '\n';
encapsulated << ENC_FOOTER;

return encapsulated.str();
};

return m;
}

Expand Down Expand Up @@ -3136,6 +3255,17 @@ std::string operation_result_printer::operator()(const asset& a)
}}}

namespace graphene { namespace wallet {
fc::sha256 signed_message::digest()const
{
fc::stringstream to_sign;
to_sign << message << '\n';
to_sign << "account=" << meta.account << '\n';
to_sign << "memokey=" << std::string( meta.memo_key ) << '\n';
to_sign << "block=" << meta.block << '\n';
to_sign << "timestamp=" << meta.time;

return fc::sha256::hash( to_sign.str() );
}
vector<brain_key_info> utility::derive_owner_keys_from_brain_key(string brain_key, int number_of_desired_keys)
{
// Safety-check
Expand Down Expand Up @@ -4480,6 +4610,38 @@ string wallet_api::read_memo(const memo_data& memo)
return my->read_memo(memo);
}

signed_message wallet_api::sign_message(string signer, string message)
{
FC_ASSERT(!is_locked());
return my->sign_message(signer, message);
}

bool wallet_api::verify_message( string message, string account, int block, const string& time, compact_signature sig )
{
return my->verify_message( message, account, block, time, sig );
}

/** Verify a message signed with sign_message
*
* @param message the signed_message structure containing message, meta data and signature
* @return true if signature matches
*/
abitmore marked this conversation as resolved.
Show resolved Hide resolved
bool wallet_api::verify_signed_message( signed_message message )
{
return my->verify_signed_message( message );
}

/** Verify a message signed with sign_message, in its encapsulated form.
*
* @param message the complete encapsulated message string including separators and line feeds
* @return true if signature matches
*/
bool wallet_api::verify_encapsulated_message( string message )
{
return my->verify_encapsulated_message( message );
}


string wallet_api::get_key_label( public_key_type key )const
{
auto key_itr = my->_wallet.labeled_keys.get<by_key>().find(key);
Expand Down
Loading