diff --git a/.gitignore b/.gitignore index c4d159d2..9ada4fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ CMakeCache.txt CMakeFiles Makefile compile_commands.json +moc_* +*.moc libraries/utilities/git_revision.cpp diff --git a/libraries/app/include/graphene/app/full_account.hpp b/libraries/app/include/graphene/app/full_account.hpp index a9e12a1a..5428030d 100644 --- a/libraries/app/include/graphene/app/full_account.hpp +++ b/libraries/app/include/graphene/app/full_account.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace graphene { namespace app { using namespace graphene::chain; diff --git a/programs/light_client/Account.cpp b/programs/light_client/Account.cpp index 96785e5a..95d75e9e 100644 --- a/programs/light_client/Account.cpp +++ b/programs/light_client/Account.cpp @@ -1,5 +1,6 @@ #include "Balance.hpp" #include "ChainDataModel.hpp" +#include "Wallet.hpp" #include @@ -15,17 +16,54 @@ QQmlListProperty Account::balances() return QQmlListProperty(this, this, count, at); } +double Account::getActiveControl( Wallet* w )const +{ + if( m_account.active.num_auths() == 0 ) return 0; + if( m_account.active.weight_threshold == 0 ) return 0; + + uint64_t weight = 0; + for( auto& key : m_account.active.key_auths ) + { + if( w->hasPrivateKey( toQString(key.first) ) ) weight += key.second; + } + for( auto& acnt : m_account.active.account_auths ) + { + // TODO: lookup Account, check to see if we have full control of it, and + // add its weight if we do. Be sure to limit recursion depth + } + + return double(weight) / double( m_account.active.weight_threshold ); +} + +double Account::getOwnerControl( Wallet* w )const +{ + if( m_account.owner.num_auths() == 0 ) return 0; + if( m_account.owner.weight_threshold == 0 ) return 0; + uint64_t weight = 0; + for( auto& key : m_account.owner.key_auths ) + { + if( w->hasPrivateKey( toQString(key.first) ) ) weight += key.second; + } + for( auto& acnt : m_account.owner.account_auths ) + { + // TODO: lookup Account, check to see if we have full *ACTIVE* control of it, and + // add its weight if we do. Be sure to limit recursion depth + } + + return double(weight) / double( m_account.owner.weight_threshold ); +} + void Account::update(const graphene::chain::account_balance_object& balance) { auto balanceItr = std::find_if(m_balances.begin(), m_balances.end(), [&balance](Balance* b) { return b->type()->id() == balance.asset_type.instance.value; }); if (balanceItr != m_balances.end()) { - ilog("Updating ${a}'s balance: ${b}", ("a", m_name.toStdString())("b", balance)); + ilog("Updating ${a}'s balance: ${b}", ("a", name().toStdString())("b", balance)); (*balanceItr)->update(balance); Q_EMIT balancesChanged(); } else { - ilog("Adding to ${a}'s new balance: ${b}", ("a", m_name.toStdString())("b", balance)); + ilog("Adding to ${a}'s new balance: ${b}", ("a", name().toStdString())("b", balance)); Balance* newBalance = new Balance; newBalance->setParent(this); auto model = qobject_cast(parent()); diff --git a/programs/light_client/Account.hpp b/programs/light_client/Account.hpp index dbdfaa9b..f816d6d2 100644 --- a/programs/light_client/Account.hpp +++ b/programs/light_client/Account.hpp @@ -4,27 +4,42 @@ #include "GrapheneObject.hpp" #include +#include namespace graphene { namespace chain { class account_balance_object; }} +using graphene::chain::account_object; +using graphene::chain::account_balance_object; + class Balance; +class Wallet; + class Account : public GrapheneObject { Q_OBJECT - Q_PROPERTY(QString name MEMBER m_name READ name NOTIFY nameChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QQmlListProperty balances READ balances NOTIFY balancesChanged) - QString m_name; + account_object m_account; QList m_balances; public: Account(ObjectId id = -1, QString name = QString(), QObject* parent = nullptr) - : GrapheneObject(id, parent), m_name(name) - {} + : GrapheneObject(id, parent) + { + m_account.name = name.toStdString(); + } + void setAccountObject( const account_object& obj ) + { + auto old_name = m_account.name; + m_account = obj; + if( old_name != m_account.name ) + Q_EMIT nameChanged(); + } - QString name()const { return m_name; } + QString name()const { return toQString(m_account.name); } QQmlListProperty balances(); void setBalances(QList balances) { @@ -34,7 +49,18 @@ public: } } - void update(const graphene::chain::account_balance_object& balance); + void update(const account_balance_object& balance); + + + /** + * Anything greater than 1.0 means full authority. + * Anything between (0 and 1.0) means partial authority + * 0 means no authority. + * + * @return the percent of direct control the wallet has over the account. + */ + Q_INVOKABLE double getOwnerControl( Wallet* w )const; + Q_INVOKABLE double getActiveControl( Wallet* w )const; Q_SIGNALS: void nameChanged(); diff --git a/programs/light_client/ChainDataModel.cpp b/programs/light_client/ChainDataModel.cpp index 9a877abc..9f8c9ebe 100644 --- a/programs/light_client/ChainDataModel.cpp +++ b/programs/light_client/ChainDataModel.cpp @@ -79,21 +79,21 @@ void ChainDataModel::processUpdatedObject(const fc::variant& update) auto id = update.as()["id"].as(); if (id.space() == protocol_ids) { switch (id.type()) { - default: - wlog("Update procedure for ${update} is not yet implemented.", ("update", update)); - break; + default: + wlog("Update procedure for ${update} is not yet implemented.", ("update", update)); + break; } } else if (id.space() == implementation_ids) { switch (id.type()) { - case impl_account_balance_object_type: { - account_balance_object balance = update.as(); - auto owner = m_accounts.find(balance.owner.instance.value); - if (owner != m_accounts.end()) - (*owner)->update(balance); - else - elog("Got unexpected balance update:\n${u}\nfor an account I don't have.", - ("u", update)); - break; + case impl_account_balance_object_type: { + account_balance_object balance = update.as(); + auto owner = m_accounts.find(balance.owner.instance.value); + if (owner != m_accounts.end()) + (*owner)->update(balance); + else + elog("Got unexpected balance update:\n${u}\nfor an account I don't have.", + ("u", update)); + break; } default: @@ -195,7 +195,7 @@ void ChainDataModel::getAccountImpl(QString accountIdentifier, Account* const * } else { m_accounts.modify(itr, [this,&accountPackage](Account* a){ a->setProperty("id", ObjectId(accountPackage->account.id.instance())); - a->setProperty("name", QString::fromStdString(accountPackage->account.name)); + a->setAccountObject( accountPackage->account ); // Set balances QList balances; diff --git a/programs/light_client/GrapheneApplication.cpp b/programs/light_client/GrapheneApplication.cpp index 93f0c4c5..4a91bb71 100644 --- a/programs/light_client/GrapheneApplication.cpp +++ b/programs/light_client/GrapheneApplication.cpp @@ -1,5 +1,6 @@ #include "GrapheneApplication.hpp" #include "ChainDataModel.hpp" +#include "Wallet.hpp" #include "Operations.hpp" #include @@ -17,6 +18,7 @@ GrapheneApplication::GrapheneApplication(QObject* parent) m_model = new ChainDataModel(m_thread, this); m_operationBuilder = new OperationBuilder(*m_model, this); + m_wallet = new Wallet( this ); connect(m_model, &ChainDataModel::queueExecute, this, &GrapheneApplication::execute); diff --git a/programs/light_client/GrapheneApplication.hpp b/programs/light_client/GrapheneApplication.hpp index 10253925..4d40386a 100644 --- a/programs/light_client/GrapheneApplication.hpp +++ b/programs/light_client/GrapheneApplication.hpp @@ -7,24 +7,28 @@ #include + namespace fc { namespace http { class websocket_client; }} class ChainDataModel; class OperationBuilder; +class Wallet; class GrapheneApplication : public QObject { Q_OBJECT Q_PROPERTY(ChainDataModel* model READ model CONSTANT) Q_PROPERTY(OperationBuilder* operationBuilder READ operationBuilder CONSTANT) + Q_PROPERTY(Wallet* wallet READ wallet CONSTANT) Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged) - fc::thread m_thread; - ChainDataModel* m_model = nullptr; + fc::thread m_thread; + ChainDataModel* m_model = nullptr; + Wallet* m_wallet = nullptr; OperationBuilder* m_operationBuilder = nullptr; - bool m_isConnected = false; + bool m_isConnected = false; boost::signals2::scoped_connection m_connectionClosed; @@ -40,6 +44,8 @@ public: GrapheneApplication(QObject* parent = nullptr); ~GrapheneApplication(); + Wallet* wallet()const { return m_wallet; } + ChainDataModel* model() const { return m_model; } diff --git a/programs/light_client/GrapheneObject.hpp b/programs/light_client/GrapheneObject.hpp index 0c53c562..a14c89fd 100644 --- a/programs/light_client/GrapheneObject.hpp +++ b/programs/light_client/GrapheneObject.hpp @@ -5,6 +5,8 @@ #include +QString toQString( const std::string& s ); + using ObjectId = qint64; Q_DECLARE_METATYPE(ObjectId) diff --git a/programs/light_client/Wallet.cpp b/programs/light_client/Wallet.cpp index bfee7db6..42ed3bb7 100644 --- a/programs/light_client/Wallet.cpp +++ b/programs/light_client/Wallet.cpp @@ -6,7 +6,8 @@ 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() +Wallet::Wallet( QObject* parent ) +:QObject(parent) { } @@ -35,6 +36,7 @@ bool Wallet::open( QString file_path ) } _wallet_file_path = p; + Q_EMIT isOpenChanged( true ); return true; } @@ -48,6 +50,7 @@ bool Wallet::close() if( !isOpen() ) return false; save(); _wallet_file_path = fc::path(); + Q_EMIT isOpenChanged( false ); return false; } @@ -158,6 +161,8 @@ bool Wallet::unlock( QString password ) _decrypted_master_key = fc::raw::unpack(plain_txt); if( _data.master_key_digest != fc::sha512::hash(_decrypted_master_key) ) _decrypted_master_key = fc::sha512(); + + Q_EMIT isLockedChanged( isLocked() ); return !isLocked(); } @@ -166,6 +171,8 @@ bool Wallet::lock() if( !isOpen() ) return false; _brain_key = QString(); _decrypted_master_key = fc::sha512(); + + Q_EMIT isLockedChanged( isLocked() ); return true; } bool Wallet::changePassword( QString new_password ) @@ -181,6 +188,22 @@ bool Wallet::changePassword( QString new_password ) return true; } +bool Wallet::hasPrivateKey( QString pubkey, bool include_with_brain_key ) +{ + auto pub = fc::variant( pubkey.toStdString() ).as(); + auto itr = _data.encrypted_private_keys.find(pub); + if( itr == _data.encrypted_private_keys.end() ) + return false; + if( itr->second.encrypted_private_key.size() ) + return true; + if( include_with_brain_key && itr->second.brain_sequence >= 0 ) + { + if( !itr->second.owner ) + return true; + return hasPrivateKey( toQString( *itr->second.owner ), include_with_brain_key ); + } + return false; +} QString Wallet::getPrivateKey( QString pubkey ) { @@ -203,6 +226,7 @@ QString Wallet::getPublicKey( QString wif_private_key )const if( !priv ) return QString(); auto pub = public_key_type(priv->get_public_key()); + return toQString( fc::variant( pub ).as_string() ); } @@ -223,6 +247,8 @@ QString Wallet::getActivePrivateKey( QString owner_pub_key, uint32_t seq ) 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 ) ); + _data.encrypted_private_keys[active_pub_key].owner = fc::variant( owner_pub_key.toStdString() ).as(); + _data.encrypted_private_keys[active_pub_key].brain_sequence = seq; _available_private_keys.insert( active_pub_key ); return toQString(wif); @@ -244,6 +270,7 @@ QString Wallet::getOwnerPrivateKey( uint32_t seq ) 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 ) ); + _data.encrypted_private_keys[owner_pub_key].brain_sequence = seq; _available_private_keys.insert( owner_pub_key ); return toQString( wif ); diff --git a/programs/light_client/Wallet.hpp b/programs/light_client/Wallet.hpp index d5d6e879..3e00b007 100644 --- a/programs/light_client/Wallet.hpp +++ b/programs/light_client/Wallet.hpp @@ -16,13 +16,18 @@ using graphene::chain::digest_type; using graphene::chain::signature_type; using fc::optional; +QString toQString( const std::string& s ); +QString toQString( public_key_type k ); + 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; + vector encrypted_private_key; + int32_t brain_sequence = -1; + optional owner; /// if this key was derived from an owner key + sequence }; -FC_REFLECT( key_data, (label)(encrypted_private_key) ); +FC_REFLECT( key_data, (label)(encrypted_private_key)(brain_sequence)(owner) ); struct wallet_file { @@ -51,10 +56,9 @@ FC_REFLECT( wallet_file, */ class Wallet : public QObject { + Q_OBJECT public: - Q_OBJECT - - Wallet(); + Wallet( QObject* parent = nullptr ); ~Wallet(); Q_INVOKABLE bool open( QString file_path ); @@ -101,6 +105,7 @@ class Wallet : public QObject Q_INVOKABLE bool setKeyLabel( QString pubkey, QString label ); Q_INVOKABLE QString getPublicKey( QString label ); Q_INVOKABLE QString getPrivateKey( QString pubkey ); + Q_INVOKABLE bool hasPrivateKey( QString pubkey, bool include_with_brain_key = false ); /** imports a public key and assigns it a label */ Q_INVOKABLE bool importPublicKey( QString pubkey, QString label = QString() ); @@ -131,6 +136,10 @@ class Wallet : public QObject const flat_set& getAvailablePrivateKeys()const; + Q_SIGNALS: + void isLockedChanged( bool state ); + void isOpenChanged( bool state ); + private: fc::path _wallet_file_path; wallet_file _data;