mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-05 15:08:25 +00:00
working camera and psbt sign
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,9 +110,9 @@ class Controller(Singleton):
|
||||
|
||||
@property
|
||||
def camera(self):
|
||||
if not os.getenv("NOTAPI", False):
|
||||
from .camera import Camera
|
||||
return Camera.get_instance()
|
||||
# if not os.getenv("NOTAPI", False):
|
||||
from .camera import Camera
|
||||
return Camera.get_instance()
|
||||
|
||||
|
||||
def start(self) -> None:
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -6,7 +6,7 @@ persistent_settings = False
|
||||
[display]
|
||||
text_color = ORANGE
|
||||
qr_background_color = 555555
|
||||
camera_rotation = 0
|
||||
camera_rotation = -90
|
||||
|
||||
[wallet]
|
||||
network = main
|
||||
|
||||
Reference in New Issue
Block a user