working camera and psbt sign

This commit is contained in:
Sam Korn
2022-03-11 13:59:20 -07:00
parent ad87f235f3
commit b7cc40a6e8
10 changed files with 190 additions and 15 deletions
+1 -1
View File
@@ -5,5 +5,5 @@ Pillow==8.2.0
qrcode==7.3.1 qrcode==7.3.1
six==1.16.0 six==1.16.0
spidev==3.5 spidev==3.5
opencv-python==4.5.5 opencv-python~=4.5.5
urtypes @ git+https://github.com/jreesun/urtypes.git@e0d0db277ec2339650343eaf7b220fffb9233241 urtypes @ git+https://github.com/jreesun/urtypes.git@e0d0db277ec2339650343eaf7b220fffb9233241
+4 -2
View File
@@ -1,7 +1,9 @@
import os
import io import io
import numpy import numpy
from picamera import PiCamera if not os.getenv("NOTAPI", False):
from picamera import PiCamera
from PIL import Image from PIL import Image
from seedsigner.helpers import PiVideoStream, Singleton from seedsigner.helpers import PiVideoStream, Singleton
@@ -78,7 +80,7 @@ class Camera(Singleton):
# "Rewind" the stream to the beginning so we can read its content # "Rewind" the stream to the beginning so we can read its content
# stream.seek(0) # stream.seek(0)
# return Image.open(stream) # return Image.open(stream)
return Image.fromarray(self._video_stream.frame.astype('uint8'), 'RGB').rotate(90) return Image.fromarray(self._video_stream.frame.astype('uint8'), 'BGR')
def stop_single_frame_mode(self): def stop_single_frame_mode(self):
+158 -5
View File
@@ -11,6 +11,8 @@ from embit.descriptor import Descriptor
from binascii import hexlify from binascii import hexlify
from threading import Thread from threading import Thread
from queue import Queue from queue import Queue
from datetime import datetime
from pathlib import Path as Pathlib
# Internal file class dependencies # Internal file class dependencies
from .views import (View, MenuView, SeedToolsView,SigningToolsView, from .views import (View, MenuView, SeedToolsView,SigningToolsView,
@@ -98,8 +100,8 @@ class Controller(Singleton):
# Views # Views
controller.menu_view = MenuView(controller.disp, controller.q) controller.menu_view = MenuView(controller.disp, controller.q)
controller.seed_tools_view = SeedToolsView(controller.disp, controller.q) controller.seed_tools_view = SeedToolsView(controller.disp, controller.q)
# controller.io_test_view = IOTestView() controller.io_test_view = IOTestView(controller.disp, controller.q)
# controller.signing_tools_view = SigningToolsView(controller.storage) controller.signing_tools_view = SigningToolsView(controller.storage, controller.disp, controller.q)
controller.settings_tools_view = SettingsToolsView(controller.disp, controller.q) controller.settings_tools_view = SettingsToolsView(controller.disp, controller.q)
controller.screensaver = ScreensaverView(controller.buttons, controller.disp, controller.q) controller.screensaver = ScreensaverView(controller.buttons, controller.disp, controller.q)
@@ -108,9 +110,9 @@ class Controller(Singleton):
@property @property
def camera(self): def camera(self):
if not os.getenv("NOTAPI", False): # if not os.getenv("NOTAPI", False):
from .camera import Camera from .camera import Camera
return Camera.get_instance() return Camera.get_instance()
def start(self) -> None: def start(self) -> None:
@@ -196,6 +198,9 @@ class Controller(Singleton):
ret_val = self.show_reset_tool() ret_val = self.show_reset_tool()
elif ret_val == Path.POWER_OFF: elif ret_val == Path.POWER_OFF:
ret_val = self.show_power_off() ret_val = self.show_power_off()
elif ret_val == Path.PSBT_SIGN_SUB_MENU:
ret_val = self.sign_psbt()
raise Exception("Unhandled case") raise Exception("Unhandled case")
@@ -555,6 +560,14 @@ class Controller(Singleton):
xpub = xprv.to_public() xpub = xprv.to_public()
xpub_base58 = xpub.to_string(version=version) xpub_base58 = xpub.to_string(version=version)
with open(f"/home/skorn/Desktop/xpub-{fingerprint}.txt", "w") as f:
f.write(xpub_base58)
f.write("\n")
f.write(derivation)
f.write("\n")
f.write(fingerprint)
f.write("\n")
self.signing_tools_view.display_xpub_info(fingerprint, derivation, xpub_base58) self.signing_tools_view.display_xpub_info(fingerprint, derivation, xpub_base58)
self.buttons.wait_for([B.KEY_RIGHT]) self.buttons.wait_for([B.KEY_RIGHT])
@@ -592,11 +605,146 @@ class Controller(Singleton):
return Path.MAIN_MENU return Path.MAIN_MENU
### Sign Transactions ### Sign Transactions
def sign_psbt(self):
seed = Seed(wordlist=self.settings.wordlist)
used_saved_seed = False
base_path = Pathlib("/home/skorn/Desktop/psbt/")
psbt_names = [p.name for p in list(base_path.rglob("*.psbt")) ]
psbt_names.append("... [ Return to Main ]")
r = self.menu_view.display_generic_selection_menu(psbt_names, "Select PSBT?")
print(f"got selected: {r}")
exit_index = len(psbt_names)
if r == exit_index:
return Path.MAIN_MENU
else:
psbt_filename = psbt_names[r-1]
with open(base_path.joinpath(psbt_filename),'r') as f:
psbtdata = f.read()
decoder = DecodeQR(wordlist=self.settings.wordlist)
decoder.qr_type = QRType.PSBTBASE64
decoder.add_data(psbtdata)
self.menu_view.draw_modal(["Validating PSBT"])
psbt = decoder.getPSBT()
# show transaction information before sign
self.menu_view.draw_modal(["Parsing PSBT"])
p = PSBTParser(psbt,seed,self.settings.network)
self.signing_tools_view.display_transaction_information(p)
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT], False)
if input == B.KEY_LEFT:
return Path.MAIN_MENU
# validate single sig using seed
# No valid seed yet, If there is a saved seed, ask to use saved seed
if self.storage.num_of_saved_seeds() > 0:
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Use Saved Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,3,None)
if slot_num == 0:
return Path.MAIN_MENU
seed = self.storage.get_seed(slot_num)
used_saved_seed = True
if not seed:
# no valid seed yet, gather seed phrase
# display menu to select 12 or 24 word seed for last word
ret_val = self.menu_view.display_qr_12_24_word_menu("... [ Cancel ]")
if ret_val == Path.SEED_WORD_12:
seed.mnemonic = self.seed_tools_view.display_manual_seed_entry(12)
elif ret_val == Path.SEED_WORD_24:
seed.mnemonic = self.seed_tools_view.display_manual_seed_entry(24)
elif ret_val == Path.SEED_WORD_QR:
seed.mnemonic = self.seed_tools_view.read_seed_phrase_qr()
else:
return Path.MAIN_MENU
if not seed:
return Path.MAIN_MENU
# check if seed phrase is valid
self.menu_view.draw_modal(["Validating Seed ..."])
if not seed:
self.menu_view.draw_modal(["Seed Invalid", "check seed phrase", "and try again"], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
return Path.MAIN_MENU
if len(seed.passphrase) == 0:
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Add Seed Passphrase?")
if r == 1:
# display a tool to pick letters/numbers to make a passphrase
seed.passphrase = self.seed_tools_view.draw_passphrase_keyboard_entry()
if len(seed.passphrase) == 0:
self.menu_view.draw_modal(["No passphrase added", "to seed words"], "", "Left to Exit, Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT])
if input == B.KEY_LEFT:
return Path.MAIN_MENU
else:
self.menu_view.draw_modal(["Optional passphrase", "added to seed words", seed.passphrase], "", "Right to Continue")
self.buttons.wait_for([B.KEY_RIGHT])
# display seed phrase
while True:
r = self.seed_tools_view.display_seed_phrase(seed.mnemonic_list, seed.passphrase, "Right to Continue")
if r == True:
break
else:
# Cancel
return Path.MAIN_MENU
# Ask to save seed
if self.storage.slot_avaliable() and used_saved_seed == False:
r = self.menu_view.display_generic_selection_menu(["Yes", "No"], "Save Seed?")
if r == 1: #Yes
slot_num = self.menu_view.display_saved_seed_menu(self.storage,2,None)
if slot_num in (1,2,3):
self.storage.add_seed(seed, slot_num)
self.menu_view.draw_modal(["Seed Valid", "Saved to Slot #" + str(slot_num)], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
self.menu_view.draw_modal(["Parsing PSBT"])
p = PSBTParser(psbt, seed, self.settings.network)
self.signing_tools_view.display_transaction_information(p)
# Sign PSBT
self.menu_view.draw_modal(["PSBT Signing ..."])
sig_cnt = PSBTParser.sigCount(psbt)
psbt.sign_with(p.root)
trimmed_psbt = PSBTParser.trim(psbt)
if sig_cnt == PSBTParser.sigCount(trimmed_psbt):
self.menu_view.draw_modal(["Signing failed", "left to exit", "or right to continue", "to display PSBT QR"], "", "")
input = self.buttons.wait_for([B.KEY_RIGHT, B.KEY_LEFT], False)
if input == B.KEY_LEFT:
return Path.MAIN_MENU
# TODO: handle multisig case. Increment filename smarter
signed_filename = f"{psbt_filename}.signed"
with open(base_path.joinpath(signed_filename), "wb") as f:
f.write(trimmed_psbt.serialize())
self.menu_view.draw_modal(["PSBT Signed", f"Saved to Filename: {signed_filename}"], "", "Right to Continue")
input = self.buttons.wait_for([B.KEY_RIGHT])
def show_sign_transaction(self): def show_sign_transaction(self):
seed = Seed(wordlist=self.settings.wordlist) seed = Seed(wordlist=self.settings.wordlist)
used_saved_seed = False used_saved_seed = False
decoder = DecodeQR(wordlist=self.settings.wordlist)
decoder.qr_type = QRType.PSBTBASE64
# reusable qr scan function # reusable qr scan function
def scan_qr(scan_text="Scan QR"): def scan_qr(scan_text="Scan QR"):
# Scan QR using Camera # Scan QR using Camera
@@ -1002,6 +1150,11 @@ class Controller(Singleton):
if input == B.KEY_LEFT: if input == B.KEY_LEFT:
return Path.MAIN_MENU return Path.MAIN_MENU
with open(f"/home/skorn/Desktop/txn-{datetime.utcnow().replace(microsecond=0).isoformat()}-signed.psbt", "wb") as f:
f.write(trimmed_psbt.serialize())
# Display Animated QR Code # Display Animated QR Code
self.menu_view.draw_modal(["Generating PSBT QR ..."]) self.menu_view.draw_modal(["Generating PSBT QR ..."])
e = EncodeQR(psbt=trimmed_psbt, qr_type=self.settings.qr_psbt_type, qr_density=self.settings.qr_density, wordlist=self.settings.wordlist) e = EncodeQR(psbt=trimmed_psbt, qr_type=self.settings.qr_psbt_type, qr_density=self.settings.qr_density, wordlist=self.settings.wordlist)
+2
View File
@@ -7,11 +7,13 @@ class Path:
SEED_TOOLS_SUB_MENU = 10 SEED_TOOLS_SUB_MENU = 10
SIGNING_TOOLS_SUB_MENU = 11 SIGNING_TOOLS_SUB_MENU = 11
SETTINGS_SUB_MENU = 12 SETTINGS_SUB_MENU = 12
PSBT_SIGN_SUB_MENU = 13
SEED_WORD_12 = 20 SEED_WORD_12 = 20
SEED_WORD_24 = 21 SEED_WORD_24 = 21
POWER_OFF = 22 POWER_OFF = 22
SEED_WORD_QR = 23 SEED_WORD_QR = 23
# Tools / Functions Routes # Tools / Functions Routes
GEN_LAST_WORD = 40 GEN_LAST_WORD = 40
DICE_GEN_SEED = 41 DICE_GEN_SEED = 41
+4
View File
@@ -7,6 +7,7 @@ from threading import Thread
import time import time
import cv2 import cv2
import numpy as np import numpy as np
from time import sleep
# Modified from: https://github.com/jrosebr1/imutils # Modified from: https://github.com/jrosebr1/imutils
class PiVideoStream: class PiVideoStream:
@@ -45,7 +46,10 @@ class PiVideoStream:
# for f in self.stream: # for f in self.stream:
while True: while True:
ret, frame = self.cap.read() ret, frame = self.cap.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.frame = frame self.frame = frame
sleep(0.25)
# grab the frame from the stream and clear the stream in # grab the frame from the stream and clear the stream in
# preparation for the next frame # preparation for the next frame
# self.frame = f.array # self.frame = f.array
+2 -2
View File
@@ -10,8 +10,8 @@ from seedsigner.helpers import B
class IOTestView(View): class IOTestView(View):
def __init__(self) -> None: def __init__(self, disp, q) -> None:
View.__init__(self) View.__init__(self, disp, q)
self.redraw = False self.redraw = False
self.redraw_complete = False self.redraw_complete = False
self.qr_text = "Scan ANY QR Code" self.qr_text = "Scan ANY QR Code"
+4 -2
View File
@@ -25,7 +25,7 @@ class MenuView(View):
def display_main_menu(self, sub_menu = None) -> int: def display_main_menu(self, sub_menu = None) -> int:
ret_val = 0 ret_val = 0
input = 0 input = 0
lines = ["Seed Tools", "Scan QR", "Settings", "Power Off"] lines = ["Seed Tools", "Scan QR", "Sign PSBT", "Settings", "Power Off"]
if sub_menu == Path.SEED_TOOLS_SUB_MENU: if sub_menu == Path.SEED_TOOLS_SUB_MENU:
return self.display_seed_tools_menu() return self.display_seed_tools_menu()
@@ -53,8 +53,10 @@ class MenuView(View):
elif self.selected_menu_num == 2: elif self.selected_menu_num == 2:
ret_val = Path.SIGN_TRANSACTION ret_val = Path.SIGN_TRANSACTION
elif self.selected_menu_num == 3: elif self.selected_menu_num == 3:
ret_val = self.display_settings_menu() ret_val = Path.PSBT_SIGN_SUB_MENU
elif self.selected_menu_num == 4: elif self.selected_menu_num == 4:
ret_val = self.display_settings_menu()
elif self.selected_menu_num == 5:
ret_val = Path.POWER_OFF ret_val = Path.POWER_OFF
if ret_val != Path.MAIN_MENU: # When no main menu, return to controller if ret_val != Path.MAIN_MENU: # When no main menu, return to controller
@@ -50,6 +50,18 @@ class SettingsToolsView(View):
else: else:
return None return None
def display_saved_psbts(self) -> str:
r = self.controller.menu_view.display_generic_selection_menu(["... [ Return to Settings ]", "Low", "Medium", "High"], "Which QR Density?")
if r == 2:
return EncodeQRDensity.LOW
elif r == 3:
return EncodeQRDensity.MEDIUM
elif r == 4:
return EncodeQRDensity.HIGH
else:
return None
### Display QR Density Selection ### Display QR Density Selection
def display_qr_density_selection(self) -> str: def display_qr_density_selection(self) -> str:
+2 -2
View File
@@ -8,8 +8,8 @@ import time
class SigningToolsView(View): class SigningToolsView(View):
def __init__(self, seed_storage) -> None: def __init__(self, seed_storage, disp, q) -> None:
View.__init__(self) View.__init__(self, disp, q)
self.seed_storage = seed_storage self.seed_storage = seed_storage
### ###
+1 -1
View File
@@ -6,7 +6,7 @@ persistent_settings = False
[display] [display]
text_color = ORANGE text_color = ORANGE
qr_background_color = 555555 qr_background_color = 555555
camera_rotation = 0 camera_rotation = -90
[wallet] [wallet]
network = main network = main