Merge branch 'master' of github.com:cryptonomex/graphene
This commit is contained in:
commit
8b0a22d849
20 changed files with 446 additions and 191 deletions
|
|
@ -33,7 +33,7 @@ void memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::pub
|
|||
string memo_data::get_message(const fc::ecc::private_key& priv,
|
||||
const fc::ecc::public_key& pub)const
|
||||
{
|
||||
if( from != public_key_type() )
|
||||
if( from != public_key_type() )
|
||||
{
|
||||
auto secret = priv.get_shared_secret(pub);
|
||||
auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit d11b48a015e0c339cc20f739ef1f8a5b3c57b913
|
||||
Subproject commit 088dadd12481e75829b83c098f3bc36b7e296ca0
|
||||
|
|
@ -4,6 +4,29 @@
|
|||
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
|
||||
void Account::setAccountObject(const graphene::chain::account_object& obj)
|
||||
{
|
||||
auto oldName = m_account.name;
|
||||
auto oldMemoKey = memoKey();
|
||||
|
||||
m_account = obj;
|
||||
if (oldName != m_account.name)
|
||||
Q_EMIT nameChanged();
|
||||
if (oldMemoKey != memoKey())
|
||||
Q_EMIT memoKeyChanged();
|
||||
|
||||
if (!m_loaded) {
|
||||
m_loaded = true;
|
||||
Q_EMIT loaded();
|
||||
qDebug() << name() << "loaded.";
|
||||
}
|
||||
}
|
||||
|
||||
QString Account::memoKey() const
|
||||
{
|
||||
return toQString(m_account.options.memo_key);
|
||||
}
|
||||
|
||||
QQmlListProperty<Balance> Account::balances()
|
||||
{
|
||||
auto count = [](QQmlListProperty<Balance>* list) {
|
||||
|
|
@ -16,41 +39,60 @@ QQmlListProperty<Balance> Account::balances()
|
|||
return QQmlListProperty<Balance>(this, this, count, at);
|
||||
}
|
||||
|
||||
double Account::getActiveControl( Wallet* w )const
|
||||
double Account::getActiveControl(Wallet* w, int depth)const
|
||||
{
|
||||
if( m_account.active.num_auths() == 0 ) return 0;
|
||||
if( m_account.active.weight_threshold == 0 ) return 0;
|
||||
if (depth >= GRAPHENE_MAX_SIG_CHECK_DEPTH) return 0;
|
||||
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 )
|
||||
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
|
||||
if (w->hasPrivateKey(toQString(key.first))) weight += key.second;
|
||||
}
|
||||
|
||||
return double(weight) / double( m_account.active.weight_threshold );
|
||||
ChainDataModel* model = qobject_cast<ChainDataModel*>(parent());
|
||||
for (auto& acnt : m_account.active.account_auths)
|
||||
{
|
||||
Account* account = model->getAccount(acnt.first.instance.value);
|
||||
if (!account->m_loaded) {
|
||||
QEventLoop el;
|
||||
connect(account, &Account::loaded, &el, &QEventLoop::quit);
|
||||
QTimer::singleShot(1000, &el, SLOT(quit()));
|
||||
el.exec();
|
||||
if (!account->m_loaded)
|
||||
// We don't have this account loaded yet... Oh well, move along
|
||||
continue;
|
||||
}
|
||||
if (account->getActiveControl(w, depth + 1) >= 1.0)
|
||||
weight += acnt.second;
|
||||
}
|
||||
|
||||
return double(weight) / double(m_account.active.weight_threshold);
|
||||
}
|
||||
|
||||
double Account::getOwnerControl( Wallet* w )const
|
||||
double Account::getOwnerControl(Wallet* w)const
|
||||
{
|
||||
if( m_account.owner.num_auths() == 0 ) return 0;
|
||||
if( m_account.owner.weight_threshold == 0 ) return 0;
|
||||
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 )
|
||||
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
|
||||
if (w->hasPrivateKey(toQString(key.first))) weight += key.second;
|
||||
}
|
||||
|
||||
return double(weight) / double( m_account.owner.weight_threshold );
|
||||
ChainDataModel* model = qobject_cast<ChainDataModel*>(parent());
|
||||
for (auto& acnt : m_account.owner.account_auths)
|
||||
{
|
||||
Account* account = model->getAccount(acnt.first.instance.value);
|
||||
if (!account->m_loaded)
|
||||
// We don't have this account loaded yet... Oh well, move along
|
||||
continue;
|
||||
if (account->getActiveControl(w) >= 1.0)
|
||||
weight += acnt.second;
|
||||
}
|
||||
|
||||
return double(weight) / double(m_account.owner.weight_threshold);
|
||||
}
|
||||
|
||||
void Account::update(const graphene::chain::account_balance_object& balance)
|
||||
|
|
|
|||
|
|
@ -21,9 +21,12 @@ class Account : public GrapheneObject {
|
|||
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QQmlListProperty<Balance> balances READ balances NOTIFY balancesChanged)
|
||||
Q_PROPERTY(QString memoKey READ memoKey NOTIFY memoKeyChanged)
|
||||
Q_PROPERTY(bool isLoaded MEMBER m_loaded NOTIFY loaded)
|
||||
|
||||
account_object m_account;
|
||||
QList<Balance*> m_balances;
|
||||
bool m_loaded = false;
|
||||
|
||||
public:
|
||||
Account(ObjectId id = -1, QString name = QString(), QObject* parent = nullptr)
|
||||
|
|
@ -31,15 +34,10 @@ public:
|
|||
{
|
||||
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();
|
||||
}
|
||||
void setAccountObject(const account_object& obj);
|
||||
|
||||
QString name()const { return QString::fromStdString(m_account.name); }
|
||||
QString memoKey()const;
|
||||
QQmlListProperty<Balance> balances();
|
||||
|
||||
void setBalances(QList<Balance*> balances) {
|
||||
|
|
@ -58,10 +56,12 @@ public:
|
|||
*
|
||||
* @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_INVOKABLE double getOwnerControl(Wallet* w)const;
|
||||
Q_INVOKABLE double getActiveControl(Wallet* w , int depth = 0)const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void nameChanged();
|
||||
void balancesChanged();
|
||||
void memoKeyChanged();
|
||||
void loaded();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <fc/rpc/websocket_api.hpp>
|
||||
|
||||
#include <QStandardPaths>
|
||||
|
||||
using graphene::app::login_api;
|
||||
using graphene::app::database_api;
|
||||
|
||||
|
|
@ -19,7 +21,7 @@ GrapheneApplication::GrapheneApplication(QObject* parent)
|
|||
|
||||
m_model = new ChainDataModel(m_thread, this);
|
||||
m_operationBuilder = new OperationBuilder(*m_model, this);
|
||||
m_wallet = new Wallet( this );
|
||||
m_wallet = new Wallet(this);
|
||||
|
||||
connect(m_model, &ChainDataModel::queueExecute,
|
||||
this, &GrapheneApplication::execute);
|
||||
|
|
@ -75,6 +77,11 @@ void GrapheneApplication::start(QString apiurl, QString user, QString pass)
|
|||
}
|
||||
}
|
||||
|
||||
QString GrapheneApplication::defaultDataPath()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
}
|
||||
|
||||
Transaction* GrapheneApplication::createTransaction() const
|
||||
{
|
||||
return new Transaction;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ public:
|
|||
return m_isConnected;
|
||||
}
|
||||
|
||||
Q_INVOKABLE static QString defaultDataPath();
|
||||
|
||||
/// Convenience method to get a Transaction in QML. Caller takes ownership of the new Transaction.
|
||||
Q_INVOKABLE Transaction* createTransaction() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#include "Operations.hpp"
|
||||
#include "Wallet.hpp"
|
||||
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
|
||||
|
|
@ -31,6 +34,38 @@ QString TransferOperation::memo() const {
|
|||
return memo;
|
||||
}
|
||||
|
||||
bool TransferOperation::canEncryptMemo(Wallet* wallet, ChainDataModel* model) const
|
||||
{
|
||||
if (!m_op.memo) return false;
|
||||
auto pub = model->getAccount(sender())->memoKey();
|
||||
if (!wallet->hasPrivateKey(pub)) return false;
|
||||
return graphene::utilities::wif_to_key(wallet->getPrivateKey(pub).toStdString()).valid();
|
||||
}
|
||||
|
||||
bool TransferOperation::canDecryptMemo(Wallet* wallet, ChainDataModel* model) const
|
||||
{
|
||||
if (!m_op.memo) return false;
|
||||
auto pub = model->getAccount(receiver())->memoKey();
|
||||
if (!wallet->hasPrivateKey(pub)) return false;
|
||||
return graphene::utilities::wif_to_key(wallet->getPrivateKey(pub).toStdString()).valid();
|
||||
}
|
||||
|
||||
QString TransferOperation::decryptedMemo(Wallet* wallet, ChainDataModel* model) const
|
||||
{
|
||||
fc::ecc::private_key privateKey;
|
||||
fc::ecc::public_key publicKey;
|
||||
|
||||
if (canEncryptMemo(wallet, model)) {
|
||||
privateKey = *graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(sender())->memoKey()).toStdString());
|
||||
publicKey = m_op.memo->to;
|
||||
} else if (canDecryptMemo(wallet, model)) {
|
||||
privateKey = *graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(receiver())->memoKey()).toStdString());
|
||||
publicKey = m_op.memo->from;
|
||||
} else return QString::null;
|
||||
|
||||
return QString::fromStdString(m_op.memo->get_message(privateKey, publicKey));
|
||||
}
|
||||
|
||||
void TransferOperation::setMemo(QString memo) {
|
||||
if (memo == this->memo())
|
||||
return;
|
||||
|
|
@ -41,3 +76,12 @@ void TransferOperation::setMemo(QString memo) {
|
|||
m_op.memo->set_message({}, {}, memo.toStdString());
|
||||
Q_EMIT memoChanged();
|
||||
}
|
||||
|
||||
void TransferOperation::encryptMemo(Wallet* wallet, ChainDataModel* model)
|
||||
{
|
||||
if (!canEncryptMemo(wallet, model)) return;
|
||||
auto privateKey = graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(sender())->memoKey()).toStdString());
|
||||
if (!privateKey) return;
|
||||
m_op.memo->set_message(*privateKey, public_key_type(model->getAccount(receiver())->memoKey().toStdString()), memo().toStdString());
|
||||
Q_EMIT memoChanged();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,9 +57,13 @@ public:
|
|||
qint64 amount() const { return m_op.amount.amount.value; }
|
||||
ObjectId amountType() const { return m_op.amount.asset_id.instance.value; }
|
||||
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
|
||||
/// performed elsewhere.
|
||||
/// performed by calling encryptMemo()
|
||||
QString memo() const;
|
||||
|
||||
Q_INVOKABLE bool canEncryptMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||
Q_INVOKABLE bool canDecryptMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||
Q_INVOKABLE QString decryptedMemo(Wallet* wallet, ChainDataModel* model) const;
|
||||
|
||||
const graphene::chain::transfer_operation& operation() const { return m_op; }
|
||||
graphene::chain::transfer_operation& operation() { return m_op; }
|
||||
|
||||
|
|
@ -101,8 +105,9 @@ public Q_SLOTS:
|
|||
Q_EMIT amountTypeChanged();
|
||||
}
|
||||
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
|
||||
/// performed elsewhere.
|
||||
/// performed by calling encryptMemo()
|
||||
void setMemo(QString memo);
|
||||
void encryptMemo(Wallet* wallet, ChainDataModel* model);
|
||||
|
||||
Q_SIGNALS:
|
||||
void feeChanged();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ public:
|
|||
return m_transaction.operations.size();
|
||||
}
|
||||
|
||||
graphene::chain::signed_transaction& internalTransaction() {
|
||||
return m_transaction;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void setStatus(Status status)
|
||||
{
|
||||
|
|
@ -51,5 +55,5 @@ private:
|
|||
Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged)
|
||||
|
||||
Status m_status = Unbroadcasted;
|
||||
graphene::chain::transaction m_transaction;
|
||||
graphene::chain::signed_transaction m_transaction;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
#include "Transaction.hpp"
|
||||
#include "Wallet.hpp"
|
||||
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
#include <graphene/chain/protocol/fee_schedule.hpp>
|
||||
|
||||
#include <fc/crypto/aes.hpp>
|
||||
#include <fc/io/json.hpp>
|
||||
|
||||
|
|
@ -20,22 +24,22 @@ bool Wallet::open(QString file_path)
|
|||
fc::path p(file_path.toStdString());
|
||||
if( !fc::exists(p) )
|
||||
{
|
||||
ilog( "Unable to open wallet file '${f}', it does not exist", ("f",p) );
|
||||
ilog("Unable to open wallet file '${f}'; it does not exist", ("f",p));
|
||||
return false;
|
||||
}
|
||||
|
||||
_data = fc::json::from_file( p ).as<wallet_file>();
|
||||
_data = fc::json::from_file(p).as<wallet_file>();
|
||||
|
||||
for( const auto& key : _data.encrypted_private_keys )
|
||||
{
|
||||
if( key.second.label != string() )
|
||||
_label_to_key[QString::fromStdString(key.second.label)] = toQString(key.first);
|
||||
if( key.second.encrypted_private_key.size() )
|
||||
_available_private_keys.insert( key.first );
|
||||
_available_private_keys.insert(key.first);
|
||||
}
|
||||
_wallet_file_path = p;
|
||||
|
||||
Q_EMIT isOpenChanged( true );
|
||||
Q_EMIT isOpenChanged(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +53,7 @@ bool Wallet::close()
|
|||
if( !isOpen() ) return false;
|
||||
save();
|
||||
_wallet_file_path = fc::path();
|
||||
Q_EMIT isOpenChanged( false );
|
||||
Q_EMIT isOpenChanged(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -57,28 +61,28 @@ bool Wallet::save()
|
|||
{
|
||||
if( !isOpen() ) return false;
|
||||
/// TODO: backup existing wallet file first.
|
||||
fc::json::save_to_file( _data, _wallet_file_path );
|
||||
fc::json::save_to_file(_data, _wallet_file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Wallet::saveAs( QString file_path )
|
||||
bool Wallet::saveAs(QString file_path)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
fc::path p(file_path.toStdString());
|
||||
if( fc::exists(p) ) return false;
|
||||
fc::json::save_to_file( _data, _wallet_file_path );
|
||||
fc::json::save_to_file(_data, _wallet_file_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::create( QString file_path, QString password, QString brain_key )
|
||||
bool Wallet::create(QString file_path, QString password, QString brain_key)
|
||||
{
|
||||
if( isOpen() ) return false;
|
||||
if( password == QString() ) return false;
|
||||
|
||||
fc::path p( file_path.toStdString() );
|
||||
if( fc::exists( p ) )
|
||||
fc::path p(file_path.toStdString());
|
||||
if( fc::exists(p) )
|
||||
{
|
||||
ilog( "Unable to create wallet file '${f}' because a file with that name already exists.", ("f",p) );
|
||||
ilog("Unable to create wallet file '${f}' because a file with that name already exists.", ("f",p));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -90,28 +94,30 @@ bool Wallet::create( QString file_path, QString password, QString brain_key )
|
|||
auto brainkey = brain_key.toStdString();
|
||||
auto pw_str = password.toStdString();
|
||||
|
||||
auto password_hash = fc::sha512::hash( pw_str.c_str(), pw_str.size() );
|
||||
_decrypted_master_key = fc::sha512::hash( fc::ecc::private_key::generate().get_secret() );
|
||||
_data.master_key_digest = fc::sha512::hash( _decrypted_master_key );
|
||||
_data.encrypted_master_key = fc::aes_encrypt( password_hash, fc::raw::pack(_decrypted_master_key) );
|
||||
auto password_hash = fc::sha512::hash(pw_str.c_str(), pw_str.size());
|
||||
_decrypted_master_key = fc::sha512::hash(fc::ecc::private_key::generate().get_secret());
|
||||
_data.master_key_digest = fc::sha512::hash(_decrypted_master_key);
|
||||
_data.encrypted_master_key = fc::aes_encrypt(password_hash, fc::raw::pack(_decrypted_master_key));
|
||||
|
||||
_data.brain_key_digest = fc::sha512::hash( brainkey.c_str(), brainkey.size() );
|
||||
_data.encrypted_brain_key = fc::aes_encrypt( _decrypted_master_key, fc::raw::pack( brainkey ) );
|
||||
_data.brain_key_digest = fc::sha512::hash(brainkey.c_str(), brainkey.size());
|
||||
_data.encrypted_brain_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(brainkey));
|
||||
|
||||
fc::json::save_to_file( _data, p );
|
||||
QFileInfo(file_path).absoluteDir().mkpath(".");
|
||||
fc::json::save_to_file(_data, p);
|
||||
_wallet_file_path = p;
|
||||
ilog("Created wallet file '${f}'", ("f", p));
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::loadBrainKey( QString brain_key )
|
||||
bool Wallet::loadBrainKey(QString brain_key)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
|
||||
if( brain_key == QString() ) return false;
|
||||
auto brainkey = brain_key.toStdString();
|
||||
|
||||
if( _data.brain_key_digest != fc::sha512::hash( brainkey.c_str(), brainkey.size() ) )
|
||||
if( _data.brain_key_digest != fc::sha512::hash(brainkey.c_str(), brainkey.size()) )
|
||||
return false;
|
||||
|
||||
_brain_key = brain_key;
|
||||
|
|
@ -140,29 +146,34 @@ QString Wallet::getBrainKey()
|
|||
if( _brain_key != QString() )
|
||||
return _brain_key;
|
||||
|
||||
auto dec_brain_key = fc::aes_decrypt( _decrypted_master_key, _data.encrypted_brain_key );
|
||||
auto dec_brain_key = fc::aes_decrypt(_decrypted_master_key, _data.encrypted_brain_key);
|
||||
auto dec_brain_key_str = fc::raw::unpack<string>(dec_brain_key);
|
||||
_brain_key.fromStdString( dec_brain_key_str );
|
||||
_brain_key.fromStdString(dec_brain_key_str);
|
||||
return _brain_key;
|
||||
}
|
||||
|
||||
bool Wallet::isLocked()const
|
||||
{
|
||||
if( !isOpen() ) return true;
|
||||
return false;
|
||||
return _decrypted_master_key == fc::sha512();
|
||||
}
|
||||
bool Wallet::unlock( QString password )
|
||||
bool Wallet::unlock(QString password)
|
||||
{
|
||||
if( !isLocked() ) return true;
|
||||
auto pw_str = password.toStdString();
|
||||
auto password_hash = fc::sha512::hash( pw_str.c_str(), pw_str.size() );
|
||||
auto plain_txt = fc::aes_decrypt( password_hash, _data.encrypted_master_key );
|
||||
_decrypted_master_key = fc::raw::unpack<fc::sha512>(plain_txt);
|
||||
if( _data.master_key_digest != fc::sha512::hash(_decrypted_master_key) )
|
||||
_decrypted_master_key = fc::sha512();
|
||||
try {
|
||||
if( !isLocked() ) return true;
|
||||
auto pw_str = password.toStdString();
|
||||
auto password_hash = fc::sha512::hash(pw_str.c_str(), pw_str.size());
|
||||
auto plain_txt = fc::aes_decrypt(password_hash, _data.encrypted_master_key);
|
||||
_decrypted_master_key = fc::raw::unpack<fc::sha512>(plain_txt);
|
||||
if( _data.master_key_digest != fc::sha512::hash(_decrypted_master_key) )
|
||||
_decrypted_master_key = fc::sha512();
|
||||
|
||||
Q_EMIT isLockedChanged( isLocked() );
|
||||
return !isLocked();
|
||||
Q_EMIT isLockedChanged(isLocked());
|
||||
return !isLocked();
|
||||
} catch (const fc::exception& e) {
|
||||
elog(e.to_detail_string());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Wallet::lock()
|
||||
|
|
@ -171,25 +182,25 @@ bool Wallet::lock()
|
|||
_brain_key = QString();
|
||||
_decrypted_master_key = fc::sha512();
|
||||
|
||||
Q_EMIT isLockedChanged( isLocked() );
|
||||
Q_EMIT isLockedChanged(isLocked());
|
||||
return true;
|
||||
}
|
||||
bool Wallet::changePassword( QString new_password )
|
||||
bool Wallet::changePassword(QString new_password)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
if( isLocked() ) return false;
|
||||
|
||||
auto pw_str = new_password.toStdString();
|
||||
auto password_hash = fc::sha512::hash( pw_str.c_str(), pw_str.size() );
|
||||
_data.encrypted_master_key = fc::aes_encrypt( password_hash, fc::raw::pack(_decrypted_master_key) );
|
||||
auto password_hash = fc::sha512::hash(pw_str.c_str(), pw_str.size());
|
||||
_data.encrypted_master_key = fc::aes_encrypt(password_hash, fc::raw::pack(_decrypted_master_key));
|
||||
|
||||
save();
|
||||
|
||||
return true;
|
||||
}
|
||||
bool Wallet::hasPrivateKey( QString pubkey, bool include_with_brain_key )
|
||||
bool Wallet::hasPrivateKey(QString pubkey, bool include_with_brain_key)
|
||||
{
|
||||
auto pub = fc::variant( pubkey.toStdString() ).as<public_key_type>();
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
auto itr = _data.encrypted_private_keys.find(pub);
|
||||
if( itr == _data.encrypted_private_keys.end() )
|
||||
return false;
|
||||
|
|
@ -199,61 +210,61 @@ bool Wallet::hasPrivateKey( QString pubkey, bool include_with_brain_key )
|
|||
{
|
||||
if( !itr->second.owner )
|
||||
return true;
|
||||
return hasPrivateKey( toQString( *itr->second.owner ), include_with_brain_key );
|
||||
return hasPrivateKey(toQString(*itr->second.owner), include_with_brain_key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Wallet::getPrivateKey( QString pubkey )
|
||||
QString Wallet::getPrivateKey(QString pubkey)
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
if( isLocked() ) return QString();
|
||||
|
||||
auto pub = fc::variant( pubkey.toStdString() ).as<public_key_type>();
|
||||
auto itr = _data.encrypted_private_keys.find( pub );
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
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 QString::fromStdString( fc::raw::unpack<std::string>(plain) );
|
||||
auto plain = fc::aes_decrypt(_decrypted_master_key, itr->second.encrypted_private_key);
|
||||
return QString::fromStdString(fc::raw::unpack<std::string>(plain));
|
||||
}
|
||||
|
||||
QString Wallet::getPublicKey( QString wif_private_key )const
|
||||
QString Wallet::getPublicKey(QString wif_private_key)const
|
||||
{
|
||||
auto priv = graphene::utilities::wif_to_key( wif_private_key.toStdString() );
|
||||
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 QString::fromStdString( fc::variant( pub ).as_string() );
|
||||
return QString::fromStdString(fc::variant(pub).as_string());
|
||||
}
|
||||
|
||||
QString Wallet::getActivePrivateKey( QString owner_pub_key, uint32_t seq )
|
||||
QString Wallet::getActivePrivateKey(QString owner_pub_key, uint32_t seq)
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
if( isLocked() ) return QString();
|
||||
|
||||
auto owner_wif_private_key = getPrivateKey( owner_pub_key );
|
||||
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 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 );
|
||||
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 ) );
|
||||
_data.encrypted_private_keys[active_pub_key].owner = fc::variant( owner_pub_key.toStdString() ).as<public_key_type>();
|
||||
_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<public_key_type>();
|
||||
_data.encrypted_private_keys[active_pub_key].brain_sequence = seq;
|
||||
_available_private_keys.insert( active_pub_key );
|
||||
|
||||
return QString::fromStdString(wif);
|
||||
}
|
||||
|
||||
QString Wallet::getOwnerPrivateKey( uint32_t seq )
|
||||
QString Wallet::getOwnerPrivateKey(uint32_t seq)
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
if( isLocked() ) return QString();
|
||||
|
|
@ -261,39 +272,39 @@ QString Wallet::getOwnerPrivateKey( uint32_t seq )
|
|||
|
||||
auto seed = (getBrainKey() + " " + QString::number(seq)).toStdString();
|
||||
|
||||
auto secret = fc::sha256::hash( fc::sha512::hash( seed.c_str(), seed.size() ) );
|
||||
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 );
|
||||
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 ) );
|
||||
_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 );
|
||||
_available_private_keys.insert(owner_pub_key);
|
||||
|
||||
return QString::fromStdString( wif );
|
||||
return QString::fromStdString(wif);
|
||||
}
|
||||
|
||||
QString Wallet::getActivePublicKey( QString active_pub, uint32_t seq )
|
||||
QString Wallet::getActivePublicKey(QString active_pub, uint32_t seq)
|
||||
{
|
||||
return getPublicKey( getActivePrivateKey( active_pub, seq ) );
|
||||
return getPublicKey(getActivePrivateKey(active_pub, seq));
|
||||
}
|
||||
|
||||
QString Wallet::getOwnerPublicKey( uint32_t seq )
|
||||
QString Wallet::getOwnerPublicKey(uint32_t seq)
|
||||
{
|
||||
return getPublicKey( getOwnerPrivateKey( seq ) );
|
||||
return getPublicKey(getOwnerPrivateKey(seq));
|
||||
}
|
||||
|
||||
|
||||
QString Wallet::getKeyLabel( QString pubkey )
|
||||
QString Wallet::getKeyLabel(QString pubkey)
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
public_key_type key = fc::variant( pubkey.toStdString() ).as<public_key_type>();
|
||||
auto itr = _data.encrypted_private_keys.find( key );
|
||||
auto itr = _data.encrypted_private_keys.find(key);
|
||||
if( itr == _data.encrypted_private_keys.end() )
|
||||
return QString();
|
||||
return QString::fromStdString( itr->second.label );
|
||||
return QString::fromStdString(itr->second.label);
|
||||
}
|
||||
/**
|
||||
* The same label may not be assigned to more than one key, this method will
|
||||
|
|
@ -301,86 +312,97 @@ QString Wallet::getKeyLabel( QString pubkey )
|
|||
*
|
||||
* @return true if the label was set
|
||||
*/
|
||||
bool Wallet::setKeyLabel( QString pubkey, QString label )
|
||||
bool Wallet::setKeyLabel(QString pubkey, QString label)
|
||||
{
|
||||
if( label == QString() ) // clear the label
|
||||
{
|
||||
auto pub = fc::variant( pubkey.toStdString() ).as<public_key_type>();
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
auto old_label = _data.encrypted_private_keys[pub].label;
|
||||
_data.encrypted_private_keys[pub].label = string();
|
||||
if( old_label.size() )
|
||||
_label_to_key.erase( QString::fromStdString( old_label ) );
|
||||
_label_to_key.erase(QString::fromStdString(old_label));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto itr = _label_to_key.find( label );
|
||||
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<public_key_type>();
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
_data.encrypted_private_keys[pub].label = label.toStdString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
QList<QPair<QString,QString> > Wallet::getAllPublicKeys( bool only_if_private )const
|
||||
QList<QPair<QString,QString>> Wallet::getAllPublicKeys(bool only_if_private)const
|
||||
{
|
||||
QList< QPair<QString,QString> > result;
|
||||
QList< QPair<QString,QString>> 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 ), QString::fromStdString( item.second.label ) ) );
|
||||
result.push_back(qMakePair(toQString(item.first), QString::fromStdString(item.second.label)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Wallet::getPublicKey( QString label )
|
||||
void Wallet::sign(Transaction* transaction) const
|
||||
{
|
||||
if( !isOpen() ) return QString();
|
||||
if (transaction == nullptr) return;
|
||||
|
||||
auto itr = _label_to_key.find( label );
|
||||
auto& trx = transaction->internalTransaction();
|
||||
flat_set<public_key_type> pubKeys = getAvailablePrivateKeys();
|
||||
trx.signatures = signDigest(trx.digest(), set<public_key_type>(pubKeys.begin(), pubKeys.end()));
|
||||
idump((trx));
|
||||
}
|
||||
|
||||
QString Wallet::getPublicKey(QString label)
|
||||
{
|
||||
if( !isOpen() ) return QString::null;
|
||||
|
||||
auto itr = _label_to_key.find(label);
|
||||
if( itr != _label_to_key.end() )
|
||||
return QString();
|
||||
return QString::null;
|
||||
|
||||
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)
|
||||
{
|
||||
return setKeyLabel( pubkey, label );
|
||||
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;
|
||||
if( isLocked() ) return false;
|
||||
|
||||
auto pub = getPublicKey( wifkey );
|
||||
if( pub == QString() ) return false;
|
||||
auto priv = graphene::utilities::wif_to_key(wifkey.toStdString());
|
||||
if (!priv) return false;
|
||||
|
||||
importPublicKey( pub, label );
|
||||
auto p = priv->get_public_key();
|
||||
auto pub = toQString(p);
|
||||
importPublicKey(pub, label);
|
||||
|
||||
auto p = fc::variant( pub.toStdString() ).as<public_key_type>();
|
||||
_data.encrypted_private_keys[p].encrypted_private_key = fc::aes_encrypt( _decrypted_master_key, fc::raw::pack( wifkey.toStdString() ) );
|
||||
_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 )
|
||||
bool Wallet::removePublicKey(QString pubkey)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
auto pub = fc::variant( pubkey.toStdString() ).as<public_key_type>();
|
||||
|
|
@ -389,7 +411,7 @@ bool Wallet::removePublicKey( QString pubkey )
|
|||
auto itr = _data.encrypted_private_keys.find(pub);
|
||||
if( itr != _data.encrypted_private_keys.end() )
|
||||
{
|
||||
_label_to_key.erase( QString::fromStdString(itr->second.label) );
|
||||
_label_to_key.erase(QString::fromStdString(itr->second.label));
|
||||
_data.encrypted_private_keys.erase(itr);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -400,12 +422,12 @@ bool Wallet::removePublicKey( QString pubkey )
|
|||
*
|
||||
* @pre isOpen() && !isLocked()
|
||||
**/
|
||||
bool Wallet::removePrivateKey( QString pubkey )
|
||||
bool Wallet::removePrivateKey(QString pubkey)
|
||||
{
|
||||
if( !isOpen() ) return false;
|
||||
if( isLocked() ) return false;
|
||||
|
||||
auto pub = fc::variant( pubkey.toStdString() ).as<public_key_type>();
|
||||
auto pub = fc::variant(pubkey.toStdString()).as<public_key_type>();
|
||||
_data.encrypted_private_keys[pub].encrypted_private_key.resize(0);
|
||||
_available_private_keys.erase(pub);
|
||||
|
||||
|
|
@ -415,8 +437,7 @@ bool Wallet::removePrivateKey( QString pubkey )
|
|||
/**
|
||||
* @pre !isLocked()
|
||||
*/
|
||||
vector<signature_type> Wallet::signDigest( const digest_type& d,
|
||||
const set<public_key_type>& keys )const
|
||||
vector<signature_type> Wallet::signDigest(const digest_type& d, const set<public_key_type>& keys)const
|
||||
{
|
||||
vector<signature_type> result;
|
||||
if( !isOpen() ) return result;
|
||||
|
|
@ -424,21 +445,20 @@ vector<signature_type> Wallet::signDigest( const digest_type& d,
|
|||
|
||||
result.reserve( keys.size() );
|
||||
|
||||
vector<fc::ecc::private_key> priv_keys;
|
||||
for( const auto& key : keys )
|
||||
{
|
||||
auto itr = _data.encrypted_private_keys.find( key );
|
||||
auto itr = _data.encrypted_private_keys.find(key);
|
||||
if( itr == _data.encrypted_private_keys.end() )
|
||||
return vector<signature_type>();
|
||||
if( itr->second.encrypted_private_key.size() == 0 )
|
||||
return vector<signature_type>();
|
||||
|
||||
auto plain_wif = fc::aes_decrypt( _decrypted_master_key, itr->second.encrypted_private_key );
|
||||
auto plain_wif = fc::aes_decrypt(_decrypted_master_key, itr->second.encrypted_private_key);
|
||||
auto wif = fc::raw::unpack<std::string>(plain_wif);
|
||||
auto priv = graphene::utilities::wif_to_key( wif );
|
||||
auto priv = graphene::utilities::wif_to_key(wif);
|
||||
if( !priv ) return vector<signature_type>();
|
||||
|
||||
result.push_back( priv->sign_compact( d ) );
|
||||
result.push_back(priv->sign_compact(d));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -448,5 +468,3 @@ const flat_set<public_key_type>& Wallet::getAvailablePrivateKeys()const
|
|||
{
|
||||
return _available_private_keys;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
|
||||
|
||||
#include <graphene/chain/protocol/types.hpp>
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QtQml>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
|
@ -16,7 +18,9 @@ using graphene::chain::digest_type;
|
|||
using graphene::chain::signature_type;
|
||||
using fc::optional;
|
||||
|
||||
QString toQString( public_key_type k );
|
||||
class Transaction;
|
||||
|
||||
QString toQString(public_key_type k);
|
||||
|
||||
struct key_data
|
||||
{
|
||||
|
|
@ -26,7 +30,6 @@ struct key_data
|
|||
int32_t brain_sequence = -1;
|
||||
optional<public_key_type> owner; /// if this key was derived from an owner key + sequence
|
||||
};
|
||||
FC_REFLECT( key_data, (label)(encrypted_private_key)(brain_sequence)(owner) );
|
||||
|
||||
struct wallet_file
|
||||
{
|
||||
|
|
@ -37,14 +40,6 @@ struct wallet_file
|
|||
map<public_key_type, key_data> encrypted_private_keys;
|
||||
};
|
||||
|
||||
FC_REFLECT( wallet_file,
|
||||
(encrypted_brain_key)
|
||||
(brain_key_digest)
|
||||
(encrypted_master_key)
|
||||
(master_key_digest)
|
||||
(encrypted_private_keys)
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @class Wallet
|
||||
|
|
@ -62,15 +57,15 @@ class Wallet : public QObject
|
|||
Wallet( QObject* parent = nullptr );
|
||||
~Wallet();
|
||||
|
||||
Q_INVOKABLE bool open( QString file_path );
|
||||
Q_INVOKABLE bool open(QString file_path);
|
||||
Q_INVOKABLE bool close();
|
||||
Q_INVOKABLE bool isOpen()const;
|
||||
bool isOpen()const;
|
||||
Q_INVOKABLE bool save();
|
||||
Q_INVOKABLE bool saveAs( QString file_path );
|
||||
Q_INVOKABLE bool create( QString file_path, QString password, QString brain_key = QString() );
|
||||
Q_INVOKABLE bool saveAs(QString file_path);
|
||||
Q_INVOKABLE bool create(QString file_path, QString password, QString brain_key = QString());
|
||||
|
||||
/** required to generate new owner keys */
|
||||
Q_INVOKABLE bool loadBrainKey( QString brain_key );
|
||||
Q_INVOKABLE bool loadBrainKey(QString brain_key);
|
||||
|
||||
/** removes brain key to secure owner keys */
|
||||
Q_INVOKABLE bool purgeBrainKey();
|
||||
|
|
@ -79,18 +74,18 @@ class Wallet : public QObject
|
|||
/** @pre hasBrainKey() */
|
||||
Q_INVOKABLE QString getBrainKey();
|
||||
|
||||
Q_INVOKABLE bool isLocked()const;
|
||||
Q_INVOKABLE bool unlock( QString password );
|
||||
bool isLocked()const;
|
||||
Q_INVOKABLE bool unlock(QString password);
|
||||
Q_INVOKABLE bool lock();
|
||||
Q_INVOKABLE bool changePassword( QString new_password );
|
||||
Q_INVOKABLE bool changePassword(QString new_password);
|
||||
|
||||
/**
|
||||
* @pre !isLocked();
|
||||
* @post save()
|
||||
* @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 );
|
||||
Q_INVOKABLE QString getActivePrivateKey(QString owner_public_key, uint32_t sequence);
|
||||
Q_INVOKABLE QString getActivePublicKey(QString owner_public_key, uint32_t sequence);
|
||||
|
||||
/**
|
||||
* @pre !isLocked();
|
||||
|
|
@ -98,48 +93,49 @@ class Wallet : public QObject
|
|||
* @post save()
|
||||
* @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 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 );
|
||||
Q_INVOKABLE bool hasPrivateKey( QString pubkey, bool include_with_brain_key = false );
|
||||
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);
|
||||
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() );
|
||||
Q_INVOKABLE bool importPublicKey(QString pubkey, QString label = QString());
|
||||
|
||||
/**
|
||||
* @param wifkey a private key in (WIF) Wallet Import Format
|
||||
* @pre !isLocked()
|
||||
**/
|
||||
Q_INVOKABLE bool importPrivateKey( QString wifkey, QString label = QString() );
|
||||
Q_INVOKABLE bool importPrivateKey(QString wifkey, QString label = QString());
|
||||
|
||||
/** removes the key, its lablel and its private key */
|
||||
Q_INVOKABLE bool removePublicKey( QString pubkey );
|
||||
Q_INVOKABLE bool removePublicKey(QString pubkey);
|
||||
|
||||
/** removes only the private key, keeping the public key and label */
|
||||
Q_INVOKABLE bool removePrivateKey( QString pubkey );
|
||||
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<QPair<QString,QString> > getAllPublicKeys( bool only_if_private )const;
|
||||
Q_INVOKABLE QList<QPair<QString,QString>> getAllPublicKeys(bool only_if_private)const;
|
||||
|
||||
/**
|
||||
* @pre !isLocked()
|
||||
*/
|
||||
vector<signature_type> signDigest( const digest_type& d,
|
||||
const set<public_key_type>& keys )const;
|
||||
Q_INVOKABLE void sign(Transaction* transaction) const;
|
||||
vector<signature_type> signDigest(const digest_type& d,
|
||||
const set<public_key_type>& keys)const;
|
||||
|
||||
const flat_set<public_key_type>& getAvailablePrivateKeys()const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void isLockedChanged( bool state );
|
||||
void isOpenChanged( bool state );
|
||||
void isLockedChanged(bool state);
|
||||
void isOpenChanged(bool state);
|
||||
|
||||
private:
|
||||
fc::path _wallet_file_path;
|
||||
|
|
@ -150,3 +146,13 @@ class Wallet : public QObject
|
|||
map<QString,QString> _label_to_key;
|
||||
QString _brain_key;
|
||||
};
|
||||
QML_DECLARE_TYPE(Wallet)
|
||||
|
||||
FC_REFLECT( key_data, (label)(encrypted_private_key)(brain_sequence)(owner) )
|
||||
FC_REFLECT( wallet_file,
|
||||
(encrypted_brain_key)
|
||||
(brain_key_digest)
|
||||
(encrypted_master_key)
|
||||
(master_key_digest)
|
||||
(encrypted_private_keys)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "Transaction.hpp"
|
||||
#include "Operations.hpp"
|
||||
#include "Balance.hpp"
|
||||
#include "Wallet.hpp"
|
||||
|
||||
class Crypto {
|
||||
Q_GADGET
|
||||
|
|
@ -37,6 +38,7 @@ int main(int argc, char *argv[])
|
|||
qmlRegisterType<Balance>("Graphene.Client", 0, 1, "Balance");
|
||||
qmlRegisterType<Account>("Graphene.Client", 0, 1, "Account");
|
||||
qmlRegisterType<ChainDataModel>("Graphene.Client", 0, 1, "DataModel");
|
||||
qmlRegisterType<Wallet>("Graphene.Client", 0, 1, "Wallet");
|
||||
qmlRegisterType<GrapheneApplication>("Graphene.Client", 0, 1, "GrapheneApplication");
|
||||
qmlRegisterType<Transaction>("Graphene.Client", 0, 1, "Transaction");
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ RowLayout {
|
|||
|
||||
/// The Account object the user has selected
|
||||
property Account account
|
||||
/// A real in the range [0,1] representing the amount of control the wallet has over this account
|
||||
property real accountControlLevel: account && account.isLoaded? account.getActiveControl(app.wallet) : 0
|
||||
/// An Array of Balance objects held by account
|
||||
property var balances: account? Object.keys(account.balances).map(function(key){return account.balances[key]})
|
||||
.filter(function(balance){return balance.amount > 0})
|
||||
: null
|
||||
|
||||
/// Set the name field to have active focus
|
||||
|
|
@ -32,6 +35,8 @@ RowLayout {
|
|||
name: account && account.name == accountNameField.text? accountNameField.text : ""
|
||||
width: Scaling.cm(2)
|
||||
height: Scaling.cm(2)
|
||||
showOwnership: accountControlLevel > 0
|
||||
fullOwnership: accountControlLevel >= 1
|
||||
}
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -65,8 +70,8 @@ RowLayout {
|
|||
text = Qt.binding(function() {
|
||||
if (account == null)
|
||||
return qsTr("Account does not exist.")
|
||||
var text = qsTr("Account ID: %1").arg(account.id < 0? qsTr("Loading...")
|
||||
: account.id)
|
||||
var text = qsTr("Account ID: %1").arg(!account.isLoaded? qsTr("Loading...")
|
||||
: account.id)
|
||||
if (showBalance >= 0) {
|
||||
var bal = balances[showBalance]
|
||||
text += "<br/>" + qsTr("Balance: <a href='balance'>%1</a> %2").arg(String(bal.amountReal()))
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ Rectangle {
|
|||
greySheet.closed()
|
||||
formContainer.data = []
|
||||
if (internal.callback instanceof Function)
|
||||
internal.callback()
|
||||
internal.callback.apply(this, internal.callbackArgs)
|
||||
internal.callback = undefined
|
||||
internal.callbackArgs = []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ Canvas {
|
|||
contextType: "2d"
|
||||
|
||||
property var name
|
||||
property bool showOwnership: false
|
||||
property bool fullOwnership: false
|
||||
|
||||
onNameChanged: requestPaint()
|
||||
|
||||
onPaint: {
|
||||
|
|
@ -36,4 +39,28 @@ Canvas {
|
|||
draw_circle(context, 2*radius, centerY, radius)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: parent.height / 4
|
||||
color: fullOwnership? "green" : "blue"
|
||||
opacity: .6
|
||||
visible: showOwnership
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
color: "white"
|
||||
font.pixelSize: parent.height * 2/3
|
||||
font.bold: true
|
||||
text: fullOwnership? qsTr("FULL") : qsTr("PARTIAL")
|
||||
}
|
||||
TooltipArea {
|
||||
text: fullOwnership? qsTr("You have full control of this account, and can use it to perform actions directly.")
|
||||
: qsTr("You have partial control of this account, and can vote for it to take an action.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
programs/light_client/qml/TooltipArea.qml
Normal file
23
programs/light_client/qml/TooltipArea.qml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Controls.Private 1.0
|
||||
|
||||
// TooltipArea.qml
|
||||
// This file contains private Qt Quick modules that might change in future versions of Qt
|
||||
// Tested on: Qt 5.4.1
|
||||
|
||||
MouseArea {
|
||||
id: _root
|
||||
property string text: ""
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: _root.enabled
|
||||
|
||||
onExited: Tooltip.hideText()
|
||||
onCanceled: Tooltip.hideText()
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: _root.enabled && _root.containsMouse && _root.text.length
|
||||
onTriggered: Tooltip.showText(_root, Qt.point(_root.mouseX, _root.mouseY), _root.text)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,10 +24,8 @@ FormBase {
|
|||
|
||||
onDisplay: {
|
||||
trx = app.createTransaction()
|
||||
console.log(JSON.stringify(arg))
|
||||
for (var op in arg)
|
||||
trx.appendOperation(arg[op])
|
||||
console.log(JSON.stringify(trx))
|
||||
}
|
||||
|
||||
Component {
|
||||
|
|
@ -64,4 +62,37 @@ FormBase {
|
|||
Loader {
|
||||
sourceComponent: trx && Array.prototype.slice.call(trx.operations).length > 0? transactionDelegate : undefined
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Item { Layout.fillWidth: true }
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
onClicked: {
|
||||
canceled({})
|
||||
trx = null
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: passwordField
|
||||
Layout.preferredWidth: app.wallet.isLocked? Scaling.cm(4) : 0
|
||||
echoMode: TextInput.Password
|
||||
placeholderText: qsTr("Wallet password")
|
||||
visible: width > 0
|
||||
onAccepted: finishButton.clicked()
|
||||
|
||||
Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
|
||||
}
|
||||
Button {
|
||||
id: finishButton
|
||||
text: app.wallet.isLocked? qsTr("Unlock") : qsTr("Finish")
|
||||
onClicked: {
|
||||
if (app.wallet.isLocked)
|
||||
app.wallet.unlock(passwordField.text)
|
||||
else {
|
||||
app.wallet.sign(trx)
|
||||
completed(trx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,19 +92,21 @@ FormBase {
|
|||
Text {
|
||||
font.pixelSize: assetField.height / 2.5
|
||||
text: {
|
||||
if (!senderPicker.account)
|
||||
if (!senderPicker.account || !amountField.maxBalance)
|
||||
return ""
|
||||
return qsTr("Fee:<br/>") + operation().fee / amountField.precisionAdjustment + " CORE"
|
||||
return qsTr("Fee:<br/>") + amountField.maxBalance.type.formatAmount(operation().fee) + " CORE"
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
onClicked: canceled()
|
||||
onClicked: canceled({})
|
||||
}
|
||||
Button {
|
||||
id: transferButton
|
||||
text: qsTr("Transfer")
|
||||
text: !senderAccount ||
|
||||
!senderAccount.isLoaded ||
|
||||
senderPicker.accountControlLevel >= 1? qsTr("Transfer") : qsTr("Propose")
|
||||
enabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value
|
||||
onClicked: completed([operation()])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,13 +31,21 @@ ApplicationWindow {
|
|||
statusBar: StatusBar {
|
||||
Label {
|
||||
anchors.right: parent.right
|
||||
text: app.isConnected? qsTr("Connected") : qsTr("Disconnected")
|
||||
text: qsTr("Network: %1 Wallet: %2").arg(app.isConnected? qsTr("Connected") : qsTr("Disconnected"))
|
||||
.arg(app.wallet.isLocked? qsTr("Locked") : qsTr("Unlocked"))
|
||||
}
|
||||
}
|
||||
|
||||
GrapheneApplication {
|
||||
id: app
|
||||
}
|
||||
Component.onCompleted: {
|
||||
var walletFile = appSettings.walletPath + "/wallet.json"
|
||||
if (!wallet.open(walletFile)) {
|
||||
// TODO: onboarding experience
|
||||
wallet.create(walletFile, "default password", "default brain key")
|
||||
}
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
running: !app.isConnected
|
||||
interval: 5000
|
||||
|
|
@ -48,6 +56,8 @@ ApplicationWindow {
|
|||
Settings {
|
||||
id: appSettings
|
||||
category: "appSettings"
|
||||
|
||||
property string walletPath: app.defaultDataPath()
|
||||
}
|
||||
Connections {
|
||||
target: app
|
||||
|
|
@ -65,8 +75,8 @@ ApplicationWindow {
|
|||
// TODO: make back into a preview and confirm dialog
|
||||
var back = Qt.createComponent("TransactionConfirmationForm.qml")
|
||||
formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back},
|
||||
function() {
|
||||
console.log("Closed form")
|
||||
function(arg) {
|
||||
console.log("Closed form: " + JSON.stringify(arg))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -152,6 +162,32 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: passwordField
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
Button {
|
||||
text: app.wallet.isLocked? "Unlock wallet" : "Lock wallet"
|
||||
onClicked: {
|
||||
if (app.wallet.isLocked)
|
||||
app.wallet.unlock(passwordField.text)
|
||||
else
|
||||
app.wallet.lock()
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: keyField
|
||||
placeholderText: "Private key"
|
||||
}
|
||||
TextField {
|
||||
id: keyLabelField
|
||||
placeholderText: "Key label"
|
||||
}
|
||||
Button {
|
||||
text: "Import key"
|
||||
enabled: !app.wallet.isLocked && keyField.text && keyLabelField.text
|
||||
onClicked: app.wallet.importPrivateKey(keyField.text, keyLabelField.text)
|
||||
}
|
||||
}
|
||||
|
||||
FormBox {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<file>Scaling.qml</file>
|
||||
<file>Identicon.qml</file>
|
||||
<file>AccountPicker.qml</file>
|
||||
<file>TooltipArea.qml</file>
|
||||
<file>jdenticon/jdenticon-1.0.1.min.js</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
|||
Loading…
Reference in a new issue