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
six==1.16.0
spidev==3.5
opencv-python==4.5.5
opencv-python~=4.5.5
urtypes @ git+https://github.com/jreesun/urtypes.git@e0d0db277ec2339650343eaf7b220fffb9233241
+4 -2
View File
@@ -1,7 +1,9 @@
import os
import io
import numpy
from picamera import PiCamera
if not os.getenv("NOTAPI", False):
from picamera import PiCamera
from PIL import Image
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
# stream.seek(0)
# 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):
+156 -3
View File
@@ -11,6 +11,8 @@ from embit.descriptor import Descriptor
from binascii import hexlify
from threading import Thread
from queue import Queue
from datetime import datetime
from pathlib import Path as Pathlib
# Internal file class dependencies
from .views import (View, MenuView, SeedToolsView,SigningToolsView,
@@ -98,8 +100,8 @@ class Controller(Singleton):
# Views
controller.menu_view = MenuView(controller.disp, controller.q)
controller.seed_tools_view = SeedToolsView(controller.disp, controller.q)
# controller.io_test_view = IOTestView()
# controller.signing_tools_view = SigningToolsView(controller.storage)
controller.io_test_view = IOTestView(controller.disp, controller.q)
controller.signing_tools_view = SigningToolsView(controller.storage, controller.disp, controller.q)
controller.settings_tools_view = SettingsToolsView(controller.disp, controller.q)
controller.screensaver = ScreensaverView(controller.buttons, controller.disp, controller.q)
@@ -108,7 +110,7 @@ class Controller(Singleton):
@property
def camera(self):
if not os.getenv("NOTAPI", False):
# if not os.getenv("NOTAPI", False):
from .camera import Camera
return Camera.get_instance()
@@ -196,6 +198,9 @@ class Controller(Singleton):
ret_val = self.show_reset_tool()
elif ret_val == Path.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")
@@ -555,6 +560,14 @@ class Controller(Singleton):
xpub = xprv.to_public()
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.buttons.wait_for([B.KEY_RIGHT])
@@ -592,11 +605,146 @@ class Controller(Singleton):
return Path.MAIN_MENU
### 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):
seed = Seed(wordlist=self.settings.wordlist)
used_saved_seed = False
decoder = DecodeQR(wordlist=self.settings.wordlist)
decoder.qr_type = QRType.PSBTBASE64
# reusable qr scan function
def scan_qr(scan_text="Scan QR"):
# Scan QR using Camera
@@ -1002,6 +1150,11 @@ class Controller(Singleton):
if input == B.KEY_LEFT:
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
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)
+2
View File
@@ -7,11 +7,13 @@ class Path:
SEED_TOOLS_SUB_MENU = 10
SIGNING_TOOLS_SUB_MENU = 11
SETTINGS_SUB_MENU = 12
PSBT_SIGN_SUB_MENU = 13
SEED_WORD_12 = 20
SEED_WORD_24 = 21
POWER_OFF = 22
SEED_WORD_QR = 23
# Tools / Functions Routes
GEN_LAST_WORD = 40
DICE_GEN_SEED = 41
+4
View File
@@ -7,6 +7,7 @@ from threading import Thread
import time
import cv2
import numpy as np
from time import sleep
# Modified from: https://github.com/jrosebr1/imutils
class PiVideoStream:
@@ -45,7 +46,10 @@ class PiVideoStream:
# for f in self.stream:
while True:
ret, frame = self.cap.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.frame = frame
sleep(0.25)
# grab the frame from the stream and clear the stream in
# preparation for the next frame
# self.frame = f.array
+2 -2
View File
@@ -10,8 +10,8 @@ from seedsigner.helpers import B
class IOTestView(View):
def __init__(self) -> None:
View.__init__(self)
def __init__(self, disp, q) -> None:
View.__init__(self, disp, q)
self.redraw = False
self.redraw_complete = False
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:
ret_val = 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:
return self.display_seed_tools_menu()
@@ -53,8 +53,10 @@ class MenuView(View):
elif self.selected_menu_num == 2:
ret_val = Path.SIGN_TRANSACTION
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:
ret_val = self.display_settings_menu()
elif self.selected_menu_num == 5:
ret_val = Path.POWER_OFF
if ret_val != Path.MAIN_MENU: # When no main menu, return to controller
@@ -50,6 +50,18 @@ class SettingsToolsView(View):
else:
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
def display_qr_density_selection(self) -> str:
+2 -2
View File
@@ -8,8 +8,8 @@ import time
class SigningToolsView(View):
def __init__(self, seed_storage) -> None:
View.__init__(self)
def __init__(self, seed_storage, disp, q) -> None:
View.__init__(self, disp, q)
self.seed_storage = seed_storage
###
+1 -1
View File
@@ -6,7 +6,7 @@ persistent_settings = False
[display]
text_color = ORANGE
qr_background_color = 555555
camera_rotation = 0
camera_rotation = -90
[wallet]
network = main