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
|
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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user