diff --git a/LeftPanel.qml b/LeftPanel.qml index 4b14ca76..79206fb4 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -48,6 +48,7 @@ Rectangle { signal transferClicked() signal receiveClicked() signal txkeyClicked() + signal sharedringdbClicked() signal settingsClicked() signal addressBookClicked() signal miningClicked() @@ -63,6 +64,7 @@ Rectangle { else if(pos === "AddressBook") menuColumn.previousButton = addressBookButton else if(pos === "Mining") menuColumn.previousButton = miningButton else if(pos === "TxKey") menuColumn.previousButton = txkeyButton + else if(pos === "SharedRingDB") menuColumn.previousButton = sharedringdbButton else if(pos === "Sign") menuColumn.previousButton = signButton else if(pos === "Settings") menuColumn.previousButton = settingsButton else if(pos === "Advanced") menuColumn.previousButton = advancedButton @@ -456,6 +458,30 @@ Rectangle { color: "#505050" height: 1 } + // ------------- Shared RingDB tab --------------- + MenuButton { + id: sharedringdbButton + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Shared RingDB") + translationManager.emptyString + symbol: qsTr("S") + translationManager.emptyString + dotColor: "#FFD781" + under: advancedButton + onClicked: { + parent.previousButton.checked = false + parent.previousButton = sharedringdbButton + panel.sharedringdbClicked() + } + } + Rectangle { + visible: sharedringdbButton.present + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + color: "#505050" + height: 1 + } + // ------------- Sign/verify tab --------------- MenuButton { diff --git a/MiddlePanel.qml b/MiddlePanel.qml index b543171f..38c9bb2c 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -54,6 +54,7 @@ Rectangle { property Transfer transferView: Transfer { } property Receive receiveView: Receive { } property TxKey txkeyView: TxKey { } + property SharedRingDB sharedringdbView: SharedRingDB { } property History historyView: History { } property Sign signView: Sign { } property Settings settingsView: Settings { } @@ -117,6 +118,10 @@ Rectangle { name: "TxKey" PropertyChanges { target: root; currentView: txkeyView } PropertyChanges { target: mainFlickable; contentHeight: minHeight } + }, State { + name: "SharedRingDB" + PropertyChanges { target: root; currentView: sharedringdbView } + PropertyChanges { target: mainFlickable; contentHeight: minHeight } }, State { name: "AddressBook" PropertyChanges { target: root; currentView: addressBookView } diff --git a/components/HistoryTable.qml b/components/HistoryTable.qml index b846865b..cac884ae 100644 --- a/components/HistoryTable.qml +++ b/components/HistoryTable.qml @@ -39,7 +39,7 @@ ListView { property int rowSpacing: 12 property var addressBookModel: null - function buildTxDetailsString(tx_id, paymentId, tx_key,tx_note, destinations) { + function buildTxDetailsString(tx_id, paymentId, tx_key,tx_note, destinations, rings) { var trStart = '', trMiddle = '', trEnd = ""; @@ -50,6 +50,7 @@ ListView { + (tx_key ? trStart + qsTr("Tx key:") + trMiddle + tx_key + trEnd : "") + (tx_note ? trStart + qsTr("Tx note:") + trMiddle + tx_note + trEnd : "") + (destinations ? trStart + qsTr("Destinations:") + trMiddle + destinations + trEnd : "") + + (rings ? trStart + qsTr("Rings:") + trMiddle + rings + trEnd : "") + "" + translationManager.emptyString; } @@ -103,8 +104,11 @@ ListView { onClicked: { var tx_key = currentWallet.getTxKey(hash) var tx_note = currentWallet.getUserNote(hash) + var rings = currentWallet.getRings(hash) + if (rings) + rings = rings.replace(/\|/g, '\n') informationPopup.title = "Transaction details"; - informationPopup.content = buildTxDetailsString(hash,paymentId,tx_key,tx_note,destinations); + informationPopup.content = buildTxDetailsString(hash,paymentId,tx_key,tx_note,destinations, rings); informationPopup.onCloseCallback = null informationPopup.open(); } diff --git a/components/HistoryTableMobile.qml b/components/HistoryTableMobile.qml index d3f39bf9..3b1dc31c 100644 --- a/components/HistoryTableMobile.qml +++ b/components/HistoryTableMobile.qml @@ -38,7 +38,7 @@ ListView { property var previousItem property var addressBookModel: null - function buildTxDetailsString(tx_id, paymentId, tx_key,tx_note, destinations) { + function buildTxDetailsString(tx_id, paymentId, tx_key,tx_note, destinations, rings) { var trStart = '', trMiddle = '', trEnd = ""; @@ -49,6 +49,7 @@ ListView { + (tx_key ? trStart + qsTr("Tx key:") + trMiddle + tx_key + trEnd : "") + (tx_note ? trStart + qsTr("Tx note:") + trMiddle + tx_note + trEnd : "") + (destinations ? trStart + qsTr("Destinations:") + trMiddle + destinations + trEnd : "") + + (rings ? trStart + qsTr("Rings:") + trMiddle + rings + trEnd : "") + "" + translationManager.emptyString; } @@ -91,9 +92,11 @@ ListView { onClicked: { var tx_key = currentWallet.getTxKey(hash) var tx_note = currentWallet.getUserNote(hash) - + var rings = currentWallet.getRings(hash) + if (rings) + rings = rings.replace(/\|/g, '\n') informationPopup.title = "Transaction details"; - informationPopup.text = buildTxDetailsString(hash,paymentId,tx_key,tx_note,destinations); + informationPopup.text = buildTxDetailsString(hash,paymentId,tx_key,tx_note,destinations, rings); informationPopup.open(); informationPopup.onCloseCallback = null } diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 96e0ac41..b44f1ab5 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -230,6 +230,9 @@ eval make -C $MONERO_DIR/build/$BUILD_TYPE/contrib/epee all install # install easylogging eval make -C $MONERO_DIR/build/$BUILD_TYPE/external/easylogging++ all install +# install lmdb +eval make -C $MONERO_DIR/build/$BUILD_TYPE/external/db_drivers/liblmdb all install + # Install libunbound echo "Installing libunbound..." pushd $MONERO_DIR/build/$BUILD_TYPE/external/unbound diff --git a/main.qml b/main.qml index 7e351bb0..f8c6af06 100644 --- a/main.qml +++ b/main.qml @@ -100,6 +100,7 @@ ApplicationWindow { if(seq === "Ctrl+S") middlePanel.state = "Transfer" else if(seq === "Ctrl+R") middlePanel.state = "Receive" else if(seq === "Ctrl+K") middlePanel.state = "TxKey" + else if(seq === "Ctrl+S") middlePanel.state = "SharedRingDB" else if(seq === "Ctrl+H") middlePanel.state = "History" else if(seq === "Ctrl+B") middlePanel.state = "AddressBook" else if(seq === "Ctrl+M") middlePanel.state = "Mining" @@ -111,7 +112,8 @@ ApplicationWindow { if(middlePanel.state === "Dashboard") middlePanel.state = "Transfer" else if(middlePanel.state === "Transfer") middlePanel.state = "Receive" else if(middlePanel.state === "Receive") middlePanel.state = "TxKey" - else if(middlePanel.state === "TxKey") middlePanel.state = "History" + else if(middlePanel.state === "TxKey") middlePanel.state = "SharedRingDB" + else if(middlePanel.state === "SharedRingDB") middlePanel.state = "History" else if(middlePanel.state === "History") middlePanel.state = "AddressBook" else if(middlePanel.state === "AddressBook") middlePanel.state = "Mining" else if(middlePanel.state === "Mining") middlePanel.state = "Sign" @@ -121,7 +123,8 @@ ApplicationWindow { if(middlePanel.state === "Settings") middlePanel.state = "Transfer" else if(middlePanel.state === "Transfer") middlePanel.state = "Receive" else if(middlePanel.state === "Receive") middlePanel.state = "TxKey" - else if(middlePanel.state === "TxKey") middlePanel.state = "History" + else if(middlePanel.state === "TxKey") middlePanel.state = "SharedRingDB" + else if(middlePanel.state === "SharedRingDB") middlePanel.state = "History" else if(middlePanel.state === "History") middlePanel.state = "AddressBook" else if(middlePanel.state === "AddressBook") middlePanel.state = "Sign" else if(middlePanel.state === "Sign") middlePanel.state = "Settings" @@ -132,7 +135,8 @@ ApplicationWindow { else if(middlePanel.state === "Sign") middlePanel.state = "Mining" else if(middlePanel.state === "Mining") middlePanel.state = "AddressBook" else if(middlePanel.state === "AddressBook") middlePanel.state = "History" - else if(middlePanel.state === "History") middlePanel.state = "TxKey" + else if(middlePanel.state === "History") middlePanel.state = "SharedRingDB" + else if(middlePanel.state === "SharedRingDB") middlePanel.state = "TxKey" else if(middlePanel.state === "TxKey") middlePanel.state = "Receive" else if(middlePanel.state === "Receive") middlePanel.state = "Transfer" else if(middlePanel.state === "Transfer") middlePanel.state = "Dashboard" @@ -140,7 +144,8 @@ ApplicationWindow { if(middlePanel.state === "Settings") middlePanel.state = "Sign" else if(middlePanel.state === "Sign") middlePanel.state = "AddressBook" else if(middlePanel.state === "AddressBook") middlePanel.state = "History" - else if(middlePanel.state === "History") middlePanel.state = "TxKey" + else if(middlePanel.state === "History") middlePanel.state = "SharedRingDB" + else if(middlePanel.state === "SharedRingDB") middlePanel.state = "TxKey" else if(middlePanel.state === "TxKey") middlePanel.state = "Receive" else if(middlePanel.state === "Receive") middlePanel.state = "Transfer" else if(middlePanel.state === "Transfer") middlePanel.state = "Settings" @@ -998,6 +1003,9 @@ ApplicationWindow { property bool useRemoteNode: false property string remoteNodeAddress: "" property string bootstrapNodeAddress: "" + property bool segregatePreForkOutputs: true + property bool keyReuseMitigation2: true + property int segregationHeight: 0 } // Information dialog @@ -1280,6 +1288,7 @@ ApplicationWindow { onTransferClicked: { middlePanel.state = "Transfer"; if(isMobile) hideMenu(); updateBalance(); } onReceiveClicked: { middlePanel.state = "Receive"; if(isMobile) hideMenu(); updateBalance(); } onTxkeyClicked: { middlePanel.state = "TxKey"; if(isMobile) hideMenu(); updateBalance(); } + onSharedringdbClicked: { middlePanel.state = "SharedRingDB"; if(isMobile) hideMenu(); updateBalance(); } onHistoryClicked: { middlePanel.state = "History"; if(isMobile) hideMenu(); updateBalance(); } onAddressBookClicked: { middlePanel.state = "AddressBook"; if(isMobile) hideMenu(); updateBalance(); } onMiningClicked: { middlePanel.state = "Mining"; if(isMobile) hideMenu(); updateBalance(); } diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro index 3d2c7f4b..90b95e55 100644 --- a/monero-wallet-gui.pro +++ b/monero-wallet-gui.pro @@ -102,6 +102,7 @@ ios:arm64 { !ios:!android { LIBS += -L$$WALLET_ROOT/lib \ -lwallet_merged \ + -llmdb \ -lepee \ -lunbound \ -leasylogging \ @@ -111,6 +112,7 @@ android { message("Host is Android") LIBS += -L$$WALLET_ROOT/lib \ -lwallet_merged \ + -llmdb \ -lepee \ -lunbound \ -leasylogging @@ -129,6 +131,7 @@ ios { CONFIG += arm64 LIBS += -L$$WALLET_ROOT/lib-ios \ -lwallet_merged \ + -llmdb \ -lepee \ -lunbound \ -leasylogging diff --git a/pages/SharedRingDB.qml b/pages/SharedRingDB.qml new file mode 100644 index 00000000..53ea34e1 --- /dev/null +++ b/pages/SharedRingDB.qml @@ -0,0 +1,420 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 + +import "../components" +import moneroComponents.Clipboard 1.0 + +Rectangle { + + color: "#F0EEEE" + + Clipboard { id: clipboard } + + function validHex32(s) { + if (s.length != 64) + return false + for (var i = 0; i < s.length; ++i) + if ("0123456789abcdefABCDEF".indexOf(s[i]) == -1) + return false + return true + } + + function validRing(str, relative) { + var outs = str.split(" "); + if (outs.length == 0) + return false + for (var i = 1; i < outs.length; ++i) { + if (relative) { + if (outs[i] <= 0) + return false + } + else { + if (outs[i] <= outs[i-1]) + return false + } + } + return true + } + + /* main layout */ + ColumnLayout { + id: mainLayout + anchors.margins: 40 + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + + spacing: 20 + property int labelWidth: 120 + property int editWidth: 400 + property int lineEditFontSize: 12 + + MessageDialog { + id: sharedRingDBDialog + standardButtons: StandardButton.Ok + } + + Text { + text: qsTr("This page allows you to interact with the shared ring database.
" + + "This database is meant for use by Monero wallets as well as wallets from Monero clones which reuse the Monero keys.") + translationManager.emptyString + wrapMode: Text.Wrap + Layout.fillWidth: true; + } + + Text { + textFormat: Text.RichText + text: "" + + "" + qsTr("Blackballed outputs") + "" + " (" + qsTr("help") + ")
" + + qsTr("This sets which outputs are known to be spent, and thus not to be used as privacy placeholders in ring signatures.
") + + qsTr("You should only have to load a file when you want to refresh the list. Manual adding/removing is possible if needed.") + translationManager.emptyString + wrapMode: Text.Wrap + Layout.fillWidth: true; + onLinkActivated: { + sharedRingDBDialog.title = qsTr("Blackballed outputs") + translationManager.emptyString; + sharedRingDBDialog.text = qsTr( + "In order to obscure which inputs in a Monero transaction are being spent, a third party should not be able " + + "to tell which inputs in a ring are already known to be spent. Being able to do so would weaken the protection " + + "afforded by ring signatures. If all but one of the inputs are known to be already spent, then the input being " + + "actually spent becomes apparent, thereby nullifying the effect of ring signatures, one of the three main layers " + + "of privacy protection Monero uses.
" + + "To help transactions avoid those inputs, a list of known spent ones can be used to avoid using them in new " + + "transactions. Such a list is maintained by the Monero project and is available on the getmonero.org website, " + + "and you can import this list here.
" + + "Alternatively, you can scan the blockchain (and the blockchain of key-reusing Monero clones) yourself " + + "using the monero-blockchain-blackball tool to create a list of known spent outputs.
" + ) + sharedRingDBDialog.icon = StandardIcon.Information + sharedRingDBDialog.open() + } + } + + RowLayout { + id: loadBlackballFileRow + anchors.topMargin: 17 + anchors.left: parent.left + anchors.right: parent.right + + FileDialog { + id: loadBlackballFileDialog + title: qsTr("Please choose a file to load blackballed outputs from") + translationManager.emptyString; + folder: "file://" + nameFilters: [ "*"] + + onAccepted: { + loadBlackballFileLine.text = walletManager.urlToLocalPath(loadBlackballFileDialog.fileUrl) + } + } + + StandardButton { + id: selectBlackballFileButton + anchors.rightMargin: 17 * scaleRatio + text: qsTr("Select") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: true + onClicked: { + loadBlackballFileDialog.open() + } + } + + LineEdit { + id: loadBlackballFileLine + anchors.left: selectBlackballFileButton.right + anchors.right: loadBlackballFileButton.left + placeholderText: qsTr("Filename with outputs to blackball") + translationManager.emptyString; + readOnly: false + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (loadBlackballFileLine.text.length > 0) { + clipboard.setText(loadBlackballFileLine.text) + } + } + } + } + + StandardButton { + id: loadBlackballFileButton + anchors.right: parent.right + text: qsTr("Load") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: !!appWindow.currentWallet + onClicked: appWindow.currentWallet.blackballOutputs(walletManager.urlToLocalPath(loadBlackballFileDialog.fileUrl), true) + } + } + + Label { + fontSize: 14 + text: qsTr("Or manually blackball or unblackball a single output:") + translationManager.emptyString + width: mainLayout.labelWidth + } + + RowLayout { + LineEdit { + id: blackballOutputLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("Paste output public key") + translationManager.emptyString + readOnly: false + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (blackballOutputLine.text.length > 0) { + clipboard.setText(blackballOutputLine.text) + } + } + } + } + + StandardButton { + id: blackballButton + text: qsTr("Blackball") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: !!appWindow.currentWallet && validHex32(blackballOutputLine.text) + onClicked: appWindow.currentWallet.blackballOutput(blackballOutputLine.text) + } + + StandardButton { + id: unblackballButton + anchors.right: parent.right + text: qsTr("Unblackball") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: !!appWindow.currentWallet && validHex32(blackballOutputLine.text) + onClicked: appWindow.currentWallet.unblackballOutput(blackballOutputLine.text) + } + } + + Text { + textFormat: Text.RichText + text: "" + + "" + qsTr("Rings") + "" + " (" + qsTr("help") + ")
" + + qsTr("This records rings used by outputs spent on Monero on a key reusing chain, so that the same ring may be reused to avoid privacy issues.") + translationManager.emptyString + wrapMode: Text.Wrap + Layout.fillWidth: true; + onLinkActivated: { + sharedRingDBDialog.title = qsTr("Rings") + translationManager.emptyString; + sharedRingDBDialog.text = qsTr( + "In order to avoid nullifying the protection afforded by Monero's ring signatures, an output should not " + + "be spent with different rings on different blockchains. While this is normally not a concern, it can become one " + + "when a key-reusing Monero clone allows you do spend existing outputs. In this case, you need to ensure this " + + "existing outputs uses the same ring on both chains.
" + + "This will be done automatically by Monero and any key-reusing software which is not trying to actively strip " + + "you of your privacy.
" + + "If you are using a key-reusing Monero clone too, and this clone does not include this protection, you can still " + + "ensure your transactions are protected by spending on the clone first, then manually adding the ring on this page, " + + "which allows you to then spend your Monero safely.
" + + "If you do not use a key-reusing Monero clone without these safety features, then you do not need to do anything " + + "as it is all automated.
" + ) + sharedRingDBDialog.icon = StandardIcon.Information + sharedRingDBDialog.open() + } + } + + RowLayout { + LineEdit { + id: keyImageLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("Paste key image") + translationManager.emptyString + readOnly: false + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (keyImageLine.text.length > 0) { + clipboard.setText(keyImageLine.text) + } + } + } + } + } + + RowLayout { + StandardButton { + id: getRingButton + text: qsTr("Get Ring") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: !!appWindow.currentWallet && validHex32(keyImageLine.text) + onClicked: { + var ring = appWindow.currentWallet.getRing(keyImageLine.text) + if (ring === "") + { + getRingLine.text = qsTr("No ring found"); + } + else + { + getRingLine.text = ring; + } + } + } + LineEdit { + id: getRingLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("") + translationManager.emptyString + readOnly: true + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (getRingLine.text.length > 0) { + clipboard.setText(getRingLine.text) + } + } + } + } + } + + RowLayout { + CheckBox { + id: setRingRelative + checked: true + text: qsTr("Relative") + translationManager.emptyString + checkedIcon: "../images/checkedVioletIcon.png" + uncheckedIcon: "../images/uncheckedIcon.png" + } + LineEdit { + id: setRingLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("") + translationManager.emptyString + readOnly: false + width: mainLayout.editWidth + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (getRingLine.text.length > 0) { + clipboard.setText(getRingLine.text) + } + } + } + } + StandardButton { + id: setRingButton + text: qsTr("Set Ring") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: !!appWindow.currentWallet && validHex32(keyImageLine.text) && validRing(setRingLine.text.trim(), setRingRelative.checked) + onClicked: { + var outs = setRingLine.text.trim() + appWindow.currentWallet.setRing(keyImageLine.text, outs, setRingRelative.checked) + } + } + } + + CheckBox { + id: segregatePreForkOutputs + checked: persistentSettings.segregatePreForkOutputs + text: qsTr("I intend to spend on key-reusing fork(s)") + translationManager.emptyString + checkedIcon: "../images/checkedVioletIcon.png" + uncheckedIcon: "../images/uncheckedIcon.png" + onClicked: { + persistentSettings.segregatePreForkOutputs = segregatePreForkOutputs.checked + if (appWindow.currentWallet) { + appWindow.currentWallet.segregatePreForkOutputs(segregatePreForkOutputs.checked) + } + } + } + CheckBox { + id: keyReuseMitigation2 + checked: persistentSettings.keyReuseMitigation2 + text: qsTr("I might want to spend on key-reusing fork(s)") + translationManager.emptyString + checkedIcon: "../images/checkedVioletIcon.png" + uncheckedIcon: "../images/uncheckedIcon.png" + onClicked: { + persistentSettings.keyReuseMitigation2 = keyReuseMitigation2.checked + if (appWindow.currentWallet) { + appWindow.currentWallet.keyReuseMitigation2(keyReuseMitigation2.checked) + } + } + } + RowLayout { + id: segregationHeightRow + anchors.topMargin: 17 + anchors.left: parent.left + anchors.right: parent.right + + Label { + id: segregationHeightLabel + fontSize: 14 + text: qsTr("Segregation height:") + translationManager.emptyString + } + LineEdit { + id: segregationHeightLine + readOnly: false + Layout.fillWidth: true + validator: IntValidator { bottom: 0 } + onEditingFinished: { + persistentSettings.segregationHeight = segregationHeightLine.text + if (appWindow.currentWallet) { + appWindow.currentWallet.segregationHeight(segregationHeightLine.text) + } + } + } + } + + } + + function onPageCompleted() { + console.log("RingDB page loaded"); + appWindow.currentWallet.segregatePreForkOutputs(persistentSettings.segregatePreForkOutputs) + appWindow.currentWallet.segregationHeight(persistentSettings.segregationHeight) + segregationHeightLine.text = persistentSettings.segregationHeight + appWindow.currentWallet.keyReuseMitigation2(persistentSettings.keyReuseMitigation2) + } + +} diff --git a/qml.qrc b/qml.qrc index 409c3a60..86f7dde3 100644 --- a/qml.qrc +++ b/qml.qrc @@ -140,6 +140,7 @@ wizard/utils.js pages/Receive.qml pages/TxKey.qml + pages/SharedRingDB.qml components/IconButton.qml components/PasswordDialog.qml components/NewPasswordDialog.qml diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 14541f65..74649ce7 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -733,6 +733,117 @@ QString Wallet::getWalletLogPath() const #endif } +bool Wallet::blackballOutput(const QString &pubkey) +{ + QList list; + list.push_back(pubkey); + return blackballOutputs(list, true); +} + +bool Wallet::blackballOutputs(const QList &pubkeys, bool add) +{ + std::vector std_pubkeys; + foreach (const QString &pubkey, pubkeys) { + std_pubkeys.push_back(pubkey.toStdString()); + } + return m_walletImpl->blackballOutputs(std_pubkeys, add); +} + +bool Wallet::blackballOutputs(const QString &filename, bool add) +{ + QFile file(filename); + + try { + if (!file.open(QIODevice::ReadOnly)) + return false; + QList outputs; + QTextStream in(&file); + while (!in.atEnd()) { + outputs.push_back(in.readLine()); + } + file.close(); + return blackballOutputs(outputs, add); + } + catch (const std::exception &e) { + file.close(); + return false; + } +} + +bool Wallet::unblackballOutput(const QString &pubkey) +{ + return m_walletImpl->unblackballOutput(pubkey.toStdString()); +} + +QString Wallet::getRing(const QString &key_image) +{ + std::vector cring; + if (!m_walletImpl->getRing(key_image.toStdString(), cring)) + return ""; + QString ring = ""; + for (uint64_t out: cring) + { + if (!ring.isEmpty()) + ring = ring + " "; + QString s; + s.setNum(out); + ring = ring + s; + } + return ring; +} + +QString Wallet::getRings(const QString &txid) +{ + std::vector>> crings; + if (!m_walletImpl->getRings(txid.toStdString(), crings)) + return ""; + QString ring = ""; + for (const auto &cring: crings) + { + if (!ring.isEmpty()) + ring = ring + "|"; + ring = ring + QString::fromStdString(cring.first) + " absolute"; + for (uint64_t out: cring.second) + { + ring = ring + " "; + QString s; + s.setNum(out); + ring = ring + s; + } + } + return ring; +} + +bool Wallet::setRing(const QString &key_image, const QString &ring, bool relative) +{ + std::vector cring; + QStringList strOuts = ring.split(" "); + foreach(QString str, strOuts) + { + uint64_t out; + bool ok; + out = str.toULong(&ok); + if (ok) + cring.push_back(out); + } + return m_walletImpl->setRing(key_image.toStdString(), cring, relative); +} + +void Wallet::segregatePreForkOutputs(bool segregate) +{ + m_walletImpl->segregatePreForkOutputs(segregate); +} + +void Wallet::segregationHeight(quint64 height) +{ + m_walletImpl->segregationHeight(height); +} + +void Wallet::keyReuseMitigation2(bool mitigation) +{ + m_walletImpl->keyReuseMitigation2(mitigation); +} + Wallet::Wallet(Monero::Wallet *w, QObject *parent) : QObject(parent) , m_walletImpl(w) diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 82e11ec3..c4b1dceb 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "wallet/api/wallet2_api.h" // we need to have an access to the Monero::Wallet::Status enum here; @@ -276,6 +277,22 @@ public: QString getDaemonLogPath() const; QString getWalletLogPath() const; + // Blackalled outputs + Q_INVOKABLE bool blackballOutput(const QString &pubkey); + Q_INVOKABLE bool blackballOutputs(const QList &pubkeys, bool add); + Q_INVOKABLE bool blackballOutputs(const QString &filename, bool add); + Q_INVOKABLE bool unblackballOutput(const QString &pubkey); + + // Rings + Q_INVOKABLE QString getRing(const QString &key_image); + Q_INVOKABLE QString getRings(const QString &txid); + Q_INVOKABLE bool setRing(const QString &key_image, const QString &ring, bool relative); + + // key reuse mitigation options + Q_INVOKABLE void segregatePreForkOutputs(bool segregate); + Q_INVOKABLE void segregationHeight(quint64 height); + Q_INVOKABLE void keyReuseMitigation2(bool mitigation); + // TODO: setListenter() when it implemented in API signals: // emitted on every event happened with wallet