Skip to content

Commit

Permalink
Merge pull request #1878 from pmconrad/sign_msg
Browse files Browse the repository at this point in the history
Add `sign_message` command to `cli_wallet`
  • Loading branch information
pmconrad committed Aug 7, 2019
2 parents da0cbb6 + 098b02c commit fc1c972
Show file tree
Hide file tree
Showing 3 changed files with 317 additions and 0 deletions.
57 changes: 57 additions & 0 deletions libraries/wallet/include/graphene/wallet/wallet.hpp
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
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
*/
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

0 comments on commit fc1c972

Please sign in to comment.