diff --git a/libraries/fc b/libraries/fc index 17b64bb3..25937606 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 17b64bb38f73e9bbb9de2c18b6a9dd92bca5b7bd +Subproject commit 2593760687e89c8878f0e537ae9b9963fa46b210 diff --git a/libraries/utilities/include/graphene/utilities/key_conversion.hpp b/libraries/utilities/include/graphene/utilities/key_conversion.hpp index 0c140b9d..ad4d66f5 100644 --- a/libraries/utilities/include/graphene/utilities/key_conversion.hpp +++ b/libraries/utilities/include/graphene/utilities/key_conversion.hpp @@ -23,6 +23,7 @@ namespace graphene { namespace utilities { +std::string key_to_wif(const fc::sha256& private_secret ); std::string key_to_wif(const fc::ecc::private_key& key); fc::optional wif_to_key( const std::string& wif_key ); diff --git a/libraries/utilities/key_conversion.cpp b/libraries/utilities/key_conversion.cpp index 6c8d8a5f..5ee62070 100644 --- a/libraries/utilities/key_conversion.cpp +++ b/libraries/utilities/key_conversion.cpp @@ -21,9 +21,8 @@ namespace graphene { namespace utilities { -std::string key_to_wif(const fc::ecc::private_key& key) +std::string key_to_wif(const fc::sha256& secret ) { - fc::sha256 secret = key.get_secret(); const size_t size_of_data_to_hash = sizeof(secret) + 1; const size_t size_of_hash_bytes = 4; char data[size_of_data_to_hash + size_of_hash_bytes]; @@ -34,6 +33,10 @@ std::string key_to_wif(const fc::ecc::private_key& key) memcpy(data + size_of_data_to_hash, (char*)&digest, size_of_hash_bytes); return fc::to_base58(data, sizeof(data)); } +std::string key_to_wif(const fc::ecc::private_key& key) +{ + return key_to_wif( key.get_secret() ); +} fc::optional wif_to_key( const std::string& wif_key ) { diff --git a/programs/light_client/Wallet.cpp b/programs/light_client/Wallet.cpp index dfdcb370..bfee7db6 100644 --- a/programs/light_client/Wallet.cpp +++ b/programs/light_client/Wallet.cpp @@ -1,7 +1,11 @@ #include "Wallet.hpp" +#include #include #include +QString toQString( const std::string& s ) { QString result; result.fromStdString( s ); return result; } +QString toQString( public_key_type k ) { return toQString( fc::variant(k).as_string() ); } + Wallet::Wallet() { } @@ -19,7 +23,18 @@ bool Wallet::open( QString file_path ) ilog( "Unable to open wallet file '${f}', it does not exist", ("f",p) ); return false; } + + _data = fc::json::from_file( p ).as(); + + for( const auto& key : _data.encrypted_private_keys ) + { + if( key.second.label != string() ) + _label_to_key[toQString(key.second.label)] = toQString( key.first ); + if( key.second.encrypted_private_key.size() ) + _available_private_keys.insert( key.first ); + } _wallet_file_path = p; + return true; } @@ -47,7 +62,10 @@ bool Wallet::save() bool Wallet::saveAs( QString file_path ) { if( !isOpen() ) return false; - return false; + fc::path p(file_path.toStdString()); + if( fc::exists(p) ) return false; + fc::json::save_to_file( _data, _wallet_file_path ); + return true; } bool Wallet::create( QString file_path, QString password, QString brain_key ) @@ -164,60 +182,208 @@ bool Wallet::changePassword( QString new_password ) return true; } +QString Wallet::getPrivateKey( QString pubkey ) +{ + if( !isOpen() ) return QString(); + if( isLocked() ) return QString(); + + auto pub = fc::variant( pubkey.toStdString() ).as(); + auto itr = _data.encrypted_private_keys.find( pub ); + if( itr == _data.encrypted_private_keys.end() ) + return QString(); + if( itr->second.encrypted_private_key.size() == 0 ) + return QString(); + auto plain = fc::aes_decrypt( _decrypted_master_key, itr->second.encrypted_private_key ); + return toQString( fc::raw::unpack(plain) ); +} + +QString Wallet::getPublicKey( QString wif_private_key )const +{ + auto priv = graphene::utilities::wif_to_key( wif_private_key.toStdString() ); + if( !priv ) return QString(); + + auto pub = public_key_type(priv->get_public_key()); + return toQString( fc::variant( pub ).as_string() ); +} + QString Wallet::getActivePrivateKey( QString owner_pub_key, uint32_t seq ) { if( !isOpen() ) return QString(); - return QString(); + if( isLocked() ) return QString(); + + auto owner_wif_private_key = getPrivateKey( owner_pub_key ); + if( owner_wif_private_key == QString() ) return QString(); + + auto seed = (owner_wif_private_key + " " + QString::number(seq)).toStdString(); + auto secret = fc::sha256::hash( fc::sha512::hash( seed.c_str(), seed.size() ) ); + + auto wif = graphene::utilities::key_to_wif( secret ); + auto priv_key = graphene::utilities::wif_to_key( wif ); + if( !priv_key ) return QString(); + + public_key_type active_pub_key(priv_key->get_public_key()); + _data.encrypted_private_keys[active_pub_key].encrypted_private_key = fc::aes_encrypt( _decrypted_master_key, fc::raw::pack( wif ) ); + _available_private_keys.insert( active_pub_key ); + + return toQString(wif); } QString Wallet::getOwnerPrivateKey( uint32_t seq ) { if( !isOpen() ) return QString(); - return QString(); + if( isLocked() ) return QString(); + if( !hasBrainKey() ) return QString(); + + auto seed = (getBrainKey() + " " + QString::number(seq)).toStdString(); + + auto secret = fc::sha256::hash( fc::sha512::hash( seed.c_str(), seed.size() ) ); + + auto wif = graphene::utilities::key_to_wif( secret ); + auto priv_key = graphene::utilities::wif_to_key( wif ); + if( !priv_key ) return QString(); + + public_key_type owner_pub_key(priv_key->get_public_key()); + _data.encrypted_private_keys[owner_pub_key].encrypted_private_key = fc::aes_encrypt( _decrypted_master_key, fc::raw::pack( wif ) ); + _available_private_keys.insert( owner_pub_key ); + + return toQString( wif ); +} + +QString Wallet::getActivePublicKey( QString active_pub, uint32_t seq ) +{ + return getPublicKey( getActivePrivateKey( active_pub, seq ) ); +} + +QString Wallet::getOwnerPublicKey( uint32_t seq ) +{ + return getPublicKey( getOwnerPrivateKey( seq ) ); } QString Wallet::getKeyLabel( QString pubkey ) { if( !isOpen() ) return QString(); - return QString(); + public_key_type key = fc::variant( pubkey.toStdString() ).as(); + auto itr = _data.encrypted_private_keys.find( key ); + if( itr == _data.encrypted_private_keys.end() ) + return QString(); + return toQString( itr->second.label ); +} +/** + * The same label may not be assigned to more than one key, this method will + * fail if a key with the same label already exists. + * + * @return true if the label was set + */ +bool Wallet::setKeyLabel( QString pubkey, QString label ) +{ + if( label == QString() ) // clear the label + { + auto pub = fc::variant( pubkey.toStdString() ).as(); + auto old_label = _data.encrypted_private_keys[pub].label; + _data.encrypted_private_keys[pub].label = string(); + if( old_label.size() ) + _label_to_key.erase( toQString( old_label ) ); + + return true; + } + + auto itr = _label_to_key.find( label ); + if( itr != _label_to_key.end() ) + return false; + + _label_to_key[label] = pubkey; + + auto pub = fc::variant( pubkey.toStdString() ).as(); + _data.encrypted_private_keys[pub].label = label.toStdString(); + + return true; +} + + +QList > Wallet::getAllPublicKeys( bool only_if_private )const +{ + QList< QPair > result; + if( !isOpen() ) return result; + + for( const auto& item : _data.encrypted_private_keys ) + { + if( only_if_private && !item.second.encrypted_private_key.size() ) continue; + result.push_back( qMakePair( toQString( item.first ), toQString( item.second.label ) ) ); + } + + return result; } QString Wallet::getPublicKey( QString label ) { if( !isOpen() ) return QString(); - return QString(); + + auto itr = _label_to_key.find( label ); + if( itr != _label_to_key.end() ) + return QString(); + + return itr->second; } /** imports a public key and assigns it a label */ -bool Wallet::importPublicKey( QString pubkey, QString label) +bool Wallet::importPublicKey( QString pubkey, QString label ) { - if( !isOpen() ) return false; - return false; + return setKeyLabel( pubkey, label ); } /** * @param wifkey a private key in (WIF) Wallet Import Format * @pre !isLocked() **/ -bool Wallet::importPrivateKey( QString wifkey, QString label ) +bool Wallet::importPrivateKey( QString wifkey, QString label ) { if( !isOpen() ) return false; - return false; + if( isLocked() ) return false; + + auto pub = getPublicKey( wifkey ); + if( pub == QString() ) return false; + + importPublicKey( pub, label ); + + auto p = fc::variant( pub.toStdString() ).as(); + _data.encrypted_private_keys[p].encrypted_private_key = fc::aes_encrypt( _decrypted_master_key, fc::raw::pack( wifkey.toStdString() ) ); + _available_private_keys.insert(p); + + return true; } /** removes the key, its lablel and its private key */ bool Wallet::removePublicKey( QString pubkey ) { if( !isOpen() ) return false; + auto pub = fc::variant( pubkey.toStdString() ).as(); + _available_private_keys.erase(pub); + + auto itr = _data.encrypted_private_keys.find(pub); + if( itr != _data.encrypted_private_keys.end() ) + { + _label_to_key.erase( toQString(itr->second.label) ); + _data.encrypted_private_keys.erase(itr); + return true; + } return false; } -/** removes only the private key, keeping the public key and label */ +/** removes only the private key, keeping the public key and label + * + * @pre isOpen() && !isLocked() + **/ bool Wallet::removePrivateKey( QString pubkey ) { if( !isOpen() ) return false; - return false; + if( isLocked() ) return false; + + auto pub = fc::variant( pubkey.toStdString() ).as(); + _data.encrypted_private_keys[pub].encrypted_private_key.resize(0); + _available_private_keys.erase(pub); + + return true; } /** @@ -227,6 +393,28 @@ vector Wallet::signDigest( const digest_type& d, const set& keys )const { vector result; + if( !isOpen() ) return result; + if( isLocked() ) return result; + + result.reserve( keys.size() ); + + vector priv_keys; + for( const auto& key : keys ) + { + auto itr = _data.encrypted_private_keys.find( key ); + if( itr == _data.encrypted_private_keys.end() ) + return vector(); + if( itr->second.encrypted_private_key.size() == 0 ) + return vector(); + + auto plain_wif = fc::aes_decrypt( _decrypted_master_key, itr->second.encrypted_private_key ); + auto wif = fc::raw::unpack(plain_wif); + auto priv = graphene::utilities::wif_to_key( wif ); + if( !priv ) return vector(); + + result.push_back( priv->sign_compact( d ) ); + } + return result; } diff --git a/programs/light_client/Wallet.hpp b/programs/light_client/Wallet.hpp index 35ae7011..d5d6e879 100644 --- a/programs/light_client/Wallet.hpp +++ b/programs/light_client/Wallet.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include using std::string; using std::vector; @@ -17,6 +19,7 @@ using fc::optional; struct key_data { string label; /** unique label assigned to this key */ + /** encrypted as a packed std::string containing a wif private key */ vector encrypted_private_key; }; FC_REFLECT( key_data, (label)(encrypted_private_key) ); @@ -42,6 +45,9 @@ FC_REFLECT( wallet_file, /** * @class Wallet * @brief Securely maintains a set of labeled public and private keys + * + * @note this API is designed for use with QML which does not support exceptions so + * all errors are passed via return values. */ class Wallet : public QObject { @@ -79,6 +85,7 @@ class Wallet : public QObject * @return WIF private key */ Q_INVOKABLE QString getActivePrivateKey( QString owner_public_key, uint32_t sequence ); + Q_INVOKABLE QString getActivePublicKey( QString owner_public_key, uint32_t sequence ); /** * @pre !isLocked(); @@ -87,9 +94,13 @@ class Wallet : public QObject * @return WIF private key */ Q_INVOKABLE QString getOwnerPrivateKey( uint32_t sequence ); + Q_INVOKABLE QString getOwnerPublicKey( uint32_t sequence ); + Q_INVOKABLE QString getPublicKey( QString wif_private_key )const; Q_INVOKABLE QString getKeyLabel( QString pubkey ); + Q_INVOKABLE bool setKeyLabel( QString pubkey, QString label ); Q_INVOKABLE QString getPublicKey( QString label ); + Q_INVOKABLE QString getPrivateKey( QString pubkey ); /** imports a public key and assigns it a label */ Q_INVOKABLE bool importPublicKey( QString pubkey, QString label = QString() ); @@ -106,6 +117,11 @@ class Wallet : public QObject /** removes only the private key, keeping the public key and label */ Q_INVOKABLE bool removePrivateKey( QString pubkey ); + /** + * @param only_if_private filter any public keys for which the wallet lacks a private key + * @return a list of PUBLICKEY, LABEL for all known public keys + */ + Q_INVOKABLE QList > getAllPublicKeys( bool only_if_private )const; /** * @pre !isLocked()