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