Skip to content

Commit

Permalink
host of tweaks (#3)
Browse files Browse the repository at this point in the history
* looking good

* black and flake8

* cleanup

* black

* simplify

* bump version

* accomplishment

Co-authored-by: Michael Flaxman <[email protected]>
  • Loading branch information
mflaxman and Michael Flaxman authored Nov 10, 2020
1 parent 808fa01 commit c45da7c
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 30 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,14 @@ python multiwallet_gui/app.py

## Roadmap:
* Allow user to select limit/offset for receive address verfication
* Add tooltips/explainers
* Mainnet/testnet toggle
* Add QR code generation on send/receive
* Support arbitrary paths
* Test/release on multiple OS
* Better form handling/validation
* Add libsec
* Add webcam on receive/send
* Sign release
* Sign binaries
* Dark mode
* Reproducible build

Expand Down
45 changes: 33 additions & 12 deletions multiwallet_gui/app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#! /usr/bin/env bash

import sys

from PyQt5.QtWidgets import (
QApplication,
QDialog,
QDialogButtonBox,
QTabWidget,
QVBoxLayout,
)

import sys

from multiwallet_gui.seedpicker import SeedpickerTab
from multiwallet_gui.receive import ReceiveTab
from multiwallet_gui.send import SendTab
Expand All @@ -18,25 +21,43 @@ def __init__(self):
self.setWindowTitle(
"Multiwallet - Stateless PSBT Multisig Wallet - ALPHA VERSION TESTNET ONLY"
)
self.setFixedWidth(800)

self.layout = QVBoxLayout()

self.tab_widget = QTabWidget()

vbox = QVBoxLayout()
tabWidget = QTabWidget()
if False:
# TODO: use something like this for a testnet toggle
self.buttonbox = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel
)
self.buttonbox.accepted.connect(self.accept)
self.buttonbox.rejected.connect(self.reject)
self.layout.addWidget(self.buttonbox)

for tab in (SeedpickerTab, ReceiveTab, SendTab):
tab_obj = tab()
tabWidget.addTab(tab_obj, tab_obj.TITLE)
# Initialize tab screen
self.seedpicker_tab = SeedpickerTab()
self.receive_tab = ReceiveTab()
self.send_tab = SendTab()

vbox.addWidget(tabWidget)
# Add tabs
for cnt, tab in enumerate(
[self.seedpicker_tab, self.receive_tab, self.send_tab]
):
self.tab_widget.addTab(tab, tab.TITLE)
self.tab_widget.setTabToolTip(cnt, tab.HOVER)

self.setLayout(vbox)
self.setFixedWidth(600)
# Add tabs to widget
self.layout.addWidget(self.tab_widget)
self.setLayout(self.layout)


def main():
app = QApplication(sys.argv)
qapp = QApplication(sys.argv)
my_app = MultiwalletApp()
my_app.show()
app.exec()
qapp.exec()


if __name__ == "__main__":
Expand Down
13 changes: 11 additions & 2 deletions multiwallet_gui/receive.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,28 @@ def get_addresses(pubkey_dicts, quorum_m, quorum_n, limit, offset, is_testnet):

class ReceiveTab(QWidget):
TITLE = "Receive"
HOVER = "Verify your bitcoin addresses belong to you qourum."

def __init__(self):
super().__init__()
vbox = QVBoxLayout()

self.descriptorLabel = QLabel("<b>Wallet Descriptor</b>")
self.descriptorLabel.setToolTip(
"This extended <i>public</i> key information is used to generate your bitcoin addresses."
)
self.descriptorEdit = QPlainTextEdit("")
self.descriptorEdit.setPlaceholderText("wsh(sortedmulti(2,...")
self.descriptorEdit.setPlaceholderText(
"Something like this:\n\nwsh(sortedmulti(2,[deadbeef/48h/1h/0h/2h]xpub.../0/*,"
)

self.descriptorSubmitButton = QPushButton("Derive Addresses")
self.descriptorSubmitButton.clicked.connect(self.process_submit)

self.addrResultsLabel = QLabel("")
self.addrResultsLabel.setToolTip(
"These bitcoin addresses belong to the quorum of extended public keys above. You may want to print this out for future reference."
)
self.addrResultsEdit = QPlainTextEdit("")
self.addrResultsEdit.setReadOnly(True)
self.addrResultsEdit.setHidden(True)
Expand Down Expand Up @@ -156,7 +165,7 @@ def process_submit(self):

results_label = f"<b>{pubkeys_info['quorum_m']}-of-{pubkeys_info['quorum_n']} Multisig Addresses</b>"
if not _is_libsec_enabled():
results_label += "\n(this is ~100x faster with libsec installed)"
results_label += "<br>(this is ~100x faster with libsec installed)"

self.addrResultsLabel.setText(results_label)
self.addrResultsEdit.setHidden(False)
Expand Down
39 changes: 29 additions & 10 deletions multiwallet_gui/seedpicker.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#! /usr/bin/env bash

from multiwallet_gui.helper import _clean_submisission, _msgbox_err

from PyQt5.QtWidgets import (
QVBoxLayout,
QWidget,
QLabel,
QPlainTextEdit,
QPushButton,
QWidget,
)

from buidl.hd import HDPrivateKey
Expand All @@ -33,39 +34,57 @@ def _get_all_valid_checksum_words(first_words, first_match=True):

class SeedpickerTab(QWidget):
TITLE = "Seedpicker"
HOVER = (
"<b>Protect yourself against a bad random number generator.</b> "
"Pick 23 words of your seed phrase and Seedpicker will calculate the last word."
)

def __init__(self):
super().__init__()
vbox = QVBoxLayout()
self.layout = QVBoxLayout()

self.firstWordsLabel = QLabel("<b>First 23 Words of Your Seed</b>")
self.firstWordsLabel.setToolTip(
"Pull words out of a hat so you don't have to trust a random number generator."
)
self.firstWordsEdit = QPlainTextEdit("")
self.firstWordsEdit.setPlaceholderText(
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"
)

# self.firstWordsSubmitButton = QPushButton(self)
self.firstWordsSubmitButton = QPushButton("Calculate Full Seed")
self.firstWordsSubmitButton.setText("Calculate Full Seed")
self.firstWordsSubmitButton.clicked.connect(self.process_submit)

self.privResultsLabel = QLabel("")
self.privResultsLabel.setToolTip(
"Write the full mnemonic <b>offline</b> and store in a <b>secure</b> place. This represents your bitcoin <i>private</i> keys."
)
self.privResultsEdit = QPlainTextEdit("")
self.privResultsEdit.setReadOnly(True)
self.privResultsEdit.setHidden(True)

self.pubResultsLabel = QLabel("")
self.pubResultsLabel.setToolTip(
"For export to your online computer and eventaully other hardware wallets. This represents your bitcoin <i>public</i> keys, which are neccesary-but-not-sufficient to spend your bitcoin."
)
self.pubResultsEdit = QPlainTextEdit("")
self.pubResultsEdit.setReadOnly(True)
self.pubResultsEdit.setHidden(True)

vbox.addWidget(self.firstWordsLabel)
vbox.addWidget(self.firstWordsEdit)
vbox.addWidget(self.firstWordsSubmitButton)
vbox.addWidget(self.privResultsLabel)
vbox.addWidget(self.privResultsEdit)
vbox.addWidget(self.pubResultsLabel)
vbox.addWidget(self.pubResultsEdit)
self.layout.addWidget(self.firstWordsLabel)
self.layout.addWidget(self.firstWordsEdit)
self.layout.addWidget(self.firstWordsSubmitButton)
self.layout.addWidget(self.privResultsLabel)
self.layout.addWidget(self.privResultsEdit)
self.layout.addWidget(self.pubResultsLabel)
self.layout.addWidget(self.pubResultsEdit)

self.setLayout(self.layout)

self.setLayout(vbox)
# show all the widgets # TODO: needed?
self.show()

def process_submit(self):
# Clear any previous submission in case of errors
Expand Down
21 changes: 18 additions & 3 deletions multiwallet_gui/send.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def _format_satoshis(sats, in_btc=False):

class SendTab(QWidget):
TITLE = "Send"
HOVER = "Use your seed to cosign a transaction."

# FIXME (add support and UX for this)
UNITS = "sats"
Expand All @@ -45,16 +46,26 @@ def __init__(self):
self.psbtLabel = QLabel(
"<b>Partially Signed Bitcoin Transaction</b> (required)"
)
self.psbtLabel.setToolTip(
"Transaction that your online computer is asking you to sign, in base64 format."
)
self.psbtEdit = QPlainTextEdit("")
self.psbtEdit.setPlaceholderText("cHNidP8BAH0CAAAAA...")
self.psbtEdit.setPlaceholderText("Something like this:\n\ncHNidP8BAH0CAAAAA...")

self.fullSeedLabel = QLabel("<b>Full 24-Word Seed Phrase</b>")
self.fullSeedLabel = QLabel("<b>Full 24-Word Seed Phrase</b> (optional)")
self.fullSeedLabel.setToolTip(
"Needed to sign the PSBT. You can first decode the transaction and inspect it without supplying your seed phrase."
)
self.fullSeedEdit = QPlainTextEdit("")
self.fullSeedEdit.setPlaceholderText(
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"
)

self.psbtDecodedLabel = QLabel("")
self.psbtDecodedLabel.setToolTip(
"The summary of what this transaction does. Multiwallet statelessly verifies all inputs belong to the same quorum and that any change is properly returned."
)

self.psbtDecodedEdit = QPlainTextEdit("")
self.psbtDecodedEdit.setReadOnly(True)
self.psbtDecodedEdit.setHidden(True)
Expand All @@ -66,6 +77,9 @@ def __init__(self):
self.fullSeedSubmitButton.clicked.connect(self.sign_psbt)

self.psbtSignedLabel = QLabel("")
self.psbtSignedLabel.setToolTip(
"Signed version for your online computer to broadcast to the bitcoin network (once you have collected enough signatures)."
)
self.psbtSignedEdit = QPlainTextEdit("")
self.psbtSignedEdit.setReadOnly(True)
self.psbtSignedEdit.setHidden(True)
Expand Down Expand Up @@ -268,6 +282,7 @@ def process_psbt(self, sign_tx=True):

TX_SUMMARY = " ".join(
[
inputs_desc[0]["quorum"],
"PSBT sends",
_format_satoshis(output_spend_sats, in_btc=self.UNITS == "btc"),
"to",
Expand Down Expand Up @@ -307,7 +322,7 @@ def process_psbt(self, sign_tx=True):
if not seed_phrase:
return _msgbox_err(
main_text="No Seed Phrase Supplied",
informative_text="Cannot Sign Transaction Without Seed Phrase",
informative_text="Cannot sign transaction without seed phrase",
)

seed_phrase_num = len(seed_phrase.split())
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

setup(
name="multiwallet",
version="0.3.2",
version="0.3.3",
author="Michael Flaxman",
author_email="[email protected]",
description="Stateless multisig bitcoin wallet",
Expand Down

0 comments on commit c45da7c

Please sign in to comment.