From e2bf5d1270b1dc33f308ab134e7e7d3f4f64b7d4 Mon Sep 17 00:00:00 2001 From: Maran Date: Thu, 10 Apr 2014 14:53:12 -0400 Subject: [PATCH 1/2] Implemented key importing/generation for the GUI --- ethereal/assets/qml/first_run.qml | 155 ++++++++++++++++++++++++++++++ ethereal/assets/qml/wallet.qml | 29 ++++++ ethereal/ethereum.go | 2 - ethereal/ui/gui.go | 34 +++++-- ethereal/ui/library.go | 28 ++++++ 5 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 ethereal/assets/qml/first_run.qml diff --git a/ethereal/assets/qml/first_run.qml b/ethereal/assets/qml/first_run.qml new file mode 100644 index 0000000000..0bd3b4ce10 --- /dev/null +++ b/ethereal/assets/qml/first_run.qml @@ -0,0 +1,155 @@ +import QtQuick 2.0 +import Ethereum 1.0 + +// Which ones do we actually need? +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.1 + +ApplicationWindow { + id: wizardRoot + width: 500 + height: 400 + title: "Ethereal first run setup" + + Column { + spacing: 5 + anchors.leftMargin: 10 + anchors.left: parent.left + + Text { + visible: true + text: "

Ethereal setup

" + } + + Column { + id: restoreColumn + spacing: 5 + Text { + visible: true + font.pointSize: 14 + text: "Restore your Ethereum account" + id: restoreLabel + } + + TextField { + id: txPrivKey + width: 480 + placeholderText: "Private key or mnemonic words" + focus: true + onTextChanged: { + if(this.text.length == 64){ + detailLabel.text = "Private (hex) key detected." + actionButton.enabled = true + } + else if(this.text.split(" ").length == 24){ + detailLabel.text = "Mnemonic key detected." + actionButton.enabled = true + }else{ + detailLabel.text = "" + actionButton.enabled = false + } + } + } + Row { + spacing: 10 + Button { + id: actionButton + text: "Restore" + enabled: false + onClicked: { + var success = eth.importAndSetPrivKey(txPrivKey.text) + if(success){ + importedDetails.visible = true + restoreColumn.visible = false + newKey.visible = false + wizardRoot.height = 120 + } + } + } + Text { + id: detailLabel + font.pointSize: 12 + anchors.topMargin: 10 + } + } + } + Column { + id: importedDetails + visible: false + Text { + text: "Your account has been imported. Please close the application and restart it again to let the changes take effect." + wrapMode: Text.WordWrap + width: 460 + } + } + Column { + spacing: 5 + id: newDetailsColumn + visible: false + Text { + font.pointSize: 14 + text: "Your account details" + } + Label { + text: "Address" + } + TextField { + id: addressInput + readOnly:true + width: 480 + } + Label { + text: "Private key" + } + TextField { + id: privkeyInput + readOnly:true + width: 480 + } + Label { + text: "Mnemonic words" + } + TextField { + id: mnemonicInput + readOnly:true + width: 480 + } + Label { + text: "A new account has been created. Please take the time to write down the 24 words. You can use those to restore your account at a later date." + wrapMode: Text.WordWrap + width: 480 + } + Label { + text: "Please restart the application once you have completed the steps above." + wrapMode: Text.WordWrap + width: 480 + } + } + + } + Button { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: 10 + anchors.bottomMargin: 10 + id: newKey + text: "I don't have an account yet" + onClicked: { + var res = eth.createAndSetPrivKey() + mnemonicInput.text = res[0] + addressInput.text = res[1] + privkeyInput.text = res[2] + + // Hide restore + restoreColumn.visible = false + + // Show new details + newDetailsColumn.visible = true + newKey.visible = false + } + } +} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index b22e82f9a4..58ba3e3dc1 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -25,6 +25,35 @@ ApplicationWindow { } } + Menu { + title: "Test" + MenuItem { + text: "Test test" + shortcut: "Ctrl+t" + onTriggered: { + var win + function finishedLoading(){ + console.log("Trigged") + win = wizard.createObject(root) + } + console.log("Loading wizard") + + var wizard = Qt.createComponent("first_run.qml") + if(wizard.status== Component.Ready){ + console.log("Component is ready") + finishedLoading() + }else if( wizard.status == Component.Error){ + console.log("Error loading component:", wizard.errorString()) + } + else{ + wizard.statusChanged.connect(finishedLoading) + console.log("Component is NOT ready") + win = wizard.createObject(root) + } + } + } + } + Menu { title: "Network" MenuItem { diff --git a/ethereal/ethereum.go b/ethereal/ethereum.go index 99f3b0b525..9cc52039d1 100644 --- a/ethereal/ethereum.go +++ b/ethereal/ethereum.go @@ -84,8 +84,6 @@ func main() { utils.ImportPrivateKey(ImportKey) os.Exit(0) } - } else { - utils.CreateKeyPair(false) } } diff --git a/ethereal/ui/gui.go b/ethereal/ui/gui.go index 89736ac29c..6184baee6a 100644 --- a/ethereal/ui/gui.go +++ b/ethereal/ui/gui.go @@ -53,7 +53,6 @@ type Gui struct { txDb *ethdb.LDBDatabase addr []byte - } // Create GUI, but doesn't start it @@ -64,10 +63,16 @@ func New(ethereum *eth.Ethereum) *Gui { panic(err) } - key := ethutil.Config.Db.GetKeys()[0] - addr := key.Address() + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + // On first run we won't have any keys yet, so this would crash. + // Therefor we check if we are ready to actually start this process + var addr []byte + if len(data) > 0 { + key := ethutil.Config.Db.GetKeys()[0] + addr = key.Address() - ethereum.StateManager().WatchAddr(addr) + ethereum.StateManager().WatchAddr(addr) + } return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} } @@ -94,9 +99,18 @@ func (ui *Gui) Start(assetPath string) { context.SetVar("ui", uiLib) // Load the main QML interface - component, err := ui.engine.LoadFile(uiLib.AssetPath("qml/wallet.qml")) + data, _ := ethutil.Config.Db.Get([]byte("KeyRing")) + var err error + var component qml.Object + firstRun := len(data) == 0 + + if firstRun { + component, err = ui.engine.LoadFile(uiLib.AssetPath("qml/first_run.qml")) + } else { + component, err = ui.engine.LoadFile(uiLib.AssetPath("qml/wallet.qml")) + } if err != nil { - ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") + ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'") panic(err) } ui.engine.LoadFile(uiLib.AssetPath("qml/transactions.qml")) @@ -111,9 +125,11 @@ func (ui *Gui) Start(assetPath string) { ethutil.Config.Log.AddLogSystem(ui) // Loads previous blocks - go ui.setInitialBlockChain() - go ui.readPreviousTransactions() - go ui.update() + if firstRun == false { + go ui.setInitialBlockChain() + go ui.readPreviousTransactions() + go ui.update() + } ui.win.Show() ui.win.Wait() diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index 9a74694267..b8ecf3b14a 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -5,7 +5,9 @@ import ( "fmt" "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethutil" + "github.com/ethereum/go-ethereum/utils" "github.com/obscuren/mutan" + "github.com/obscuren/secp256k1-go" "strings" ) @@ -15,6 +17,32 @@ type EthLib struct { txPool *ethchain.TxPool } +func (lib *EthLib) ImportAndSetPrivKey(privKey string) bool { + fmt.Println(privKey) + mnemonic := strings.Split(privKey, " ") + if len(mnemonic) == 24 { + fmt.Println("Got mnemonic key, importing.") + key := ethutil.MnemonicDecode(mnemonic) + utils.ImportPrivateKey(key) + } else if len(mnemonic) == 1 { + fmt.Println("Got hex key, importing.") + utils.ImportPrivateKey(privKey) + } else { + fmt.Println("Did not recognise format, exiting.") + return false + } + return true +} + +func (lib *EthLib) CreateAndSetPrivKey() (string, string, string, string) { + pub, prv := secp256k1.GenerateKeyPair() + pair := ðutil.Key{PrivateKey: prv, PublicKey: pub} + ethutil.Config.Db.Put([]byte("KeyRing"), pair.RlpEncode()) + mne := ethutil.MnemonicEncode(ethutil.Hex(prv)) + mnemonicString := strings.Join(mne, " ") + return mnemonicString, fmt.Sprintf("%x", pair.Address()), fmt.Sprintf("%x", prv), fmt.Sprintf("%x", pub) +} + func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data string) (string, error) { var hash []byte var contractCreation bool From cf1ae41bc0bedeb5208dc00696c538c13f2183c6 Mon Sep 17 00:00:00 2001 From: Maran Date: Fri, 11 Apr 2014 13:26:14 -0400 Subject: [PATCH 2/2] Improved (hopefully) the send transaction tab --- ethereal/assets/qml/wallet.qml | 269 ++++++++++++++++++++++++++------- ethereal/ui/library.go | 14 +- 2 files changed, 226 insertions(+), 57 deletions(-) diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 58ba3e3dc1..742a1283a4 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -179,70 +179,233 @@ ApplicationWindow { visible: false anchors.fill: parent color: "#00000000" - - ColumnLayout { - width: 400 - anchors.left: parent.left - anchors.top: parent.top + TabView{ + anchors.fill: parent + anchors.rightMargin: 5 anchors.leftMargin: 5 anchors.topMargin: 5 - TextField { - id: txRecipient - placeholderText: "Recipient address (or empty for contract)" - Layout.fillWidth: true - } - - TextField { - id: txValue - width: 200 - placeholderText: "Amount" + anchors.bottomMargin: 5 + id: newTransactionTab + Component.onCompleted:{ + addTab("Send ether", newTransaction) + addTab("Create contract", newContract) } - TextField { - id: txGas - width: 200 - placeholderText: "Gas" - anchors.left: txValue + } + Component { + id: newTransaction + Column { + spacing: 5 anchors.leftMargin: 5 + anchors.topMargin: 5 + anchors.top: parent.top + anchors.left: parent.left + TextField { + id: txSimpleRecipient + placeholderText: "Recipient address" + Layout.fillWidth: true + validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } + width: 530 + } + TextField { + id: txSimpleValue + placeholderText: "Amount" + anchors.rightMargin: 5 + validator: IntValidator { } + } + Button { + id: txSimpleButton + text: "Send" + onClicked: { + //this.enabled = false + var res = eth.createTx(txSimpleRecipient.text, txSimpleValue.text,"","","") + if(res[1]) { + txSimpleResult.text = "There has been an error broadcasting your transaction:" + res[1].error() + txSimpleResult.visible = true + } else { + txSimpleResult.text = "Your transaction has been broadcasted over the network.\nYour transaction id is:" + txSimpleOutput.text = res[0] + txSimpleOutput.visible = true + txSimpleResult.visible = true + txSimpleValue.visible = false + txSimpleRecipient.visible = false + txSimpleValue.text = "" + txSimpleRecipient.text = "" + txSimpleRecipient.focus = true + newSimpleTxButton.visible = true + this.visible = false + } + } + } + Text { + id: txSimpleResult + visible: false + + } + TextField { + id: txSimpleOutput + visible: false + width: 530 + } + Button { + id: newSimpleTxButton + visible: false + text: "Create an other transaction" + onClicked: { + this.visible = false + txSimpleResult.text = "" + txSimpleOutput.text = "" + txSimpleResult.visible = false + txSimpleOutput.visible = false + txSimpleValue.visible = true + txSimpleRecipient.visible = true + txSimpleButton.visible = true + } + } } - TextField { - id: txGasPrice - width: 200 - placeholderText: "Gas price" - anchors.left: txGas + } + Component { + id: newContract + Column { + id: mainContractColumn + function contractFormReady(){ + if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { + txButton.state = "READY" + }else{ + txButton.state = "NOTREADY" + } + } + states: [ + State{ + name: "ERROR" + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: codeView; visible:true} + }, + State { + name: "DONE" + PropertyChanges { target: txValue; visible:false} + PropertyChanges { target: txGas; visible:false} + PropertyChanges { target: txGasPrice; visible:false} + PropertyChanges { target: codeView; visible:false} + PropertyChanges { target: txButton; visible:false} + PropertyChanges { target: txDataLabel; visible:false} + + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: txOutput; visible:true} + PropertyChanges { target: newTxButton; visible:true} + }, + State { + name: "SETUP" + PropertyChanges { target: txValue; visible:true; text: ""} + PropertyChanges { target: txGas; visible:true; text: ""} + PropertyChanges { target: txGasPrice; visible:true; text: ""} + PropertyChanges { target: codeView; visible:true; text: ""} + PropertyChanges { target: txButton; visible:true} + PropertyChanges { target: txDataLabel; visible:true} + + PropertyChanges { target: txResult; visible:false} + PropertyChanges { target: txOutput; visible:false} + PropertyChanges { target: newTxButton; visible:false} + } + ] + width: 400 + spacing: 5 + anchors.left: parent.left + anchors.top: parent.top anchors.leftMargin: 5 - } + anchors.topMargin: 5 - Label { - text: "Transaction data" - } + TextField { + id: txValue + width: 200 + placeholderText: "Amount" + validator: IntValidator { } + onTextChanged: { + contractFormReady() + } + } + TextField { + id: txGas + width: 200 + validator: IntValidator { } + placeholderText: "Gas" + onTextChanged: { + contractFormReady() + } + } + TextField { + id: txGasPrice + width: 200 + placeholderText: "Gas price" + validator: IntValidator { } + onTextChanged: { + contractFormReady() + } + } - TextArea { - id: codeView - anchors.topMargin: 5 - Layout.fillWidth: true - width: parent.width /2 - } + Label { + id: txDataLabel + text: "Transaction data" + } - Button { - id: txButton - text: "Send" - onClicked: { - //this.enabled = false - var res = eth.createTx(txRecipient.text, txValue.text, txGas.text, txGasPrice.text, codeView.text) - if(res[1]) { - txOutput.text = "Output:\n" + res[1].error() - } else { - txOutput.text = "Output:\n" + res[0] + TextArea { + id: codeView + anchors.topMargin: 5 + Layout.fillWidth: true + width: parent.width /2 + onTextChanged: { + contractFormReady() + } + } + + Button { + id: txButton + states: [ + State { + name: "READY" + PropertyChanges { target: txButton; enabled: true} + }, + State { + name: "NOTREADY" + PropertyChanges { target: txButton; enabled:false} + } + ] + text: "Send" + enabled: false + onClicked: { + //this.enabled = false + var res = eth.createTx("", txValue.text, txGas.text, txGasPrice.text, codeView.text) + if(res[1]) { + txResult.text = "Your contract could not be send over the network:\n" + txResult.text += res[1].error() + txResult.text += "" + mainContractColumn.state = "ERROR" + } else { + txResult.text = "Your contract has been submitted:\n" + txOutput.text = res[0] + mainContractColumn.state = "DONE" + } + } + } + Text { + id: txResult + visible: false + } + TextField { + id: txOutput + visible: false + width: 530 + } + Button { + id: newTxButton + visible: false + text: "Create an other contract" + onClicked: { + this.visible = false + txResult.text = "" + txOutput.text = "" + mainContractColumn.state = "SETUP" } - txOutput.visible = true } - } - TextArea { - id: txOutput - visible: false - Layout.fillWidth: true - height: 40 - anchors.bottom: parent.bottom } } } diff --git a/ethereal/ui/library.go b/ethereal/ui/library.go index b8ecf3b14a..d4800bf1dc 100644 --- a/ethereal/ui/library.go +++ b/ethereal/ui/library.go @@ -63,15 +63,21 @@ func (lib *EthLib) CreateTx(recipient, valueStr, gasStr, gasPriceStr, data strin var tx *ethchain.Transaction // Compile and assemble the given data if contractCreation { - asm, err := mutan.Compile(strings.NewReader(data), false) - if err != nil { - return "", err + asm, errors := mutan.Compile(strings.NewReader(data), false) + if len(errors) > 0 { + var errs string + for _, er := range errors { + if er != nil { + errs += er.Error() + } + } + return "", fmt.Errorf(errs) } code := ethutil.Assemble(asm...) tx = ethchain.NewContractCreationTx(value, gasPrice, code) } else { - tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, []string{}) + tx = ethchain.NewTransactionMessage(hash, value, gasPrice, gas, nil) } acc := lib.stateManager.GetAddrState(keyPair.Address()) tx.Nonce = acc.Nonce