mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-06 15:38:25 +00:00
Refactored EncodeQR; pythonic refactors; Seed slots removed
This commit is contained in:
@@ -608,8 +608,8 @@ class QRDisplayScreen(BaseScreen):
|
||||
settings = Settings.get_instance()
|
||||
cur_brightness = settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS)
|
||||
|
||||
if self.qr_encoder.totalParts() == 1:
|
||||
image = self.qr_encoder.nextPartImage(240,240,1)
|
||||
if self.qr_encoder.total_parts() == 1:
|
||||
image = self.qr_encoder.next_part_image(240,240,1)
|
||||
self.renderer.show_image(image)
|
||||
self.hw_inputs.wait_for([B.KEY_RIGHT])
|
||||
|
||||
@@ -617,7 +617,7 @@ class QRDisplayScreen(BaseScreen):
|
||||
while True:
|
||||
# convert the cur_brightness integer (31-255) into hex triplets
|
||||
hex_color = (hex(cur_brightness).split('x')[1]) * 3
|
||||
image = self.qr_encoder.nextPartImage(240,240, border=2, background_color=hex_color)
|
||||
image = self.qr_encoder.next_part_image(240,240, border=2, background_color=hex_color)
|
||||
self.renderer.show_image(image)
|
||||
|
||||
# Target n held frames per second before rendering next QR image
|
||||
|
||||
@@ -26,7 +26,7 @@ logger = logging.getLogger(__name__)
|
||||
### Purpose: used to process images or string data from animated qr codes with psbt data to create binary/base64 psbt
|
||||
###
|
||||
class DecodeQR:
|
||||
def __init__(self, qr_type=None, wordlist_language_code=None):
|
||||
def __init__(self, qr_type=None, wordlist_language_code=SeedConstants.WORDLIST_LANGUAGE__ENGLISH):
|
||||
self.complete = False
|
||||
self.qr_type = qr_type
|
||||
self.wordlist_language_code = wordlist_language_code
|
||||
|
||||
+165
-184
@@ -16,6 +16,8 @@ from seedsigner.models import Seed, QRType
|
||||
from urtypes.crypto import PSBT as UR_PSBT
|
||||
from urtypes.crypto import Account, HDKey, Output, Keypath, PathComponent, SCRIPT_EXPRESSION_TAG_MAP
|
||||
|
||||
from seedsigner.models.seed import SeedConstants
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -25,117 +27,175 @@ class EncodeQR:
|
||||
"""
|
||||
# TODO: Refactor so that this is a base class with implementation classes for each
|
||||
# QR type. No reason exterior code can't directly instantiate the encoder it needs.
|
||||
psbt: PSBT = None
|
||||
seed_phrase: List[str] = None
|
||||
passphrase: str = None
|
||||
derivation: str = None
|
||||
network: str = None
|
||||
qr_type: str = None
|
||||
qr_density: str = None
|
||||
wordlist_language_code: str = None
|
||||
|
||||
# Class constants to be referenced internally and externally
|
||||
DENSITY__LOW = "Low"
|
||||
DENSITY__MEDIUM = "Medium"
|
||||
DENSITY__HIGH = "High"
|
||||
ALL_DENSITIES = [DENSITY__LOW, DENSITY__MEDIUM, DENSITY__HIGH]
|
||||
|
||||
WORDLIST = None
|
||||
|
||||
# Dataclass input vars on __init__()
|
||||
psbt: PSBT = None
|
||||
seed_phrase: List[str] = None
|
||||
passphrase: str = None
|
||||
derivation: str = None
|
||||
network: str = SeedConstants.MAINNET
|
||||
qr_type: str = None
|
||||
qr_density: str = DENSITY__MEDIUM
|
||||
wordlist_language_code: str = SeedConstants.WORDLIST_LANGUAGE__ENGLISH
|
||||
|
||||
def __post_init__(self):
|
||||
self.qr = QR()
|
||||
|
||||
if self.qr_type == None:
|
||||
raise Exception('Encoder Type Required')
|
||||
if not self.qr_type:
|
||||
raise Exception('qr_type is required')
|
||||
|
||||
if self.qr_density == None:
|
||||
self.qr_density = EncodeQR.DENSITY__MEDIUM
|
||||
|
||||
self.encoder = None
|
||||
self.encoder: BaseQrEncoder = None
|
||||
|
||||
# PSBT formats
|
||||
if self.qr_type == QRType.PSBTSPECTER:
|
||||
self.encoder = SpecterEncodePSBTQR(self.psbt, self.qr_density)
|
||||
self.encoder = SpecterPsbtQrEncoder(psbt=self.psbt, qr_density=self.qr_density)
|
||||
|
||||
elif self.qr_type == QRType.PSBTUR2:
|
||||
self.encoder = UREncodePSBTQR(self.psbt, self.qr_density)
|
||||
elif self.qr_type == QRType.URXPUBQR:
|
||||
self.encoder = URXPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.qr_density, self.wordlist_language_code)
|
||||
self.encoder = UrPsbtQrEncoder(psbt=self.psbt, qr_density=self.qr_density)
|
||||
|
||||
# XPUB formats
|
||||
elif self.qr_type == QRType.XPUBQR:
|
||||
self.encoder = XPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.wordlist_language_code)
|
||||
self.encoder = XpubQrEncoder(
|
||||
seed_phrase=self.seed_phrase,
|
||||
passphrase=self.passphrase,
|
||||
derivation=self.derivation,
|
||||
network=self.network,
|
||||
wordlist_language_code=self.wordlist_language_code
|
||||
)
|
||||
|
||||
elif self.qr_type == QRType.URXPUBQR:
|
||||
self.encoder = UrXpubQrEncoder(
|
||||
qr_density=self.qr_density,
|
||||
seed_phrase=self.seed_phrase,
|
||||
passphrase=self.passphrase,
|
||||
derivation=self.derivation,
|
||||
network=self.network,
|
||||
wordlist_language_code=self.wordlist_language_code
|
||||
)
|
||||
|
||||
elif self.qr_type == QRType.SPECTERXPUBQR:
|
||||
self.encoder = SpecterXPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.qr_density, self.wordlist_language_code)
|
||||
self.encoder = SpecterXPubQrEncoder(
|
||||
qr_density=self.qr_density,
|
||||
seed_phrase=self.seed_phrase,
|
||||
passphrase=self.passphrase,
|
||||
derivation=self.derivation,
|
||||
network=self.network,
|
||||
wordlist_language_code=self.wordlist_language_code
|
||||
)
|
||||
|
||||
|
||||
# SeedQR formats
|
||||
elif self.qr_type == QRType.SEEDQR:
|
||||
self.encoder = SeedQR(self.seed_phrase, self.wordlist_language_code)
|
||||
self.encoder = SeedQrEncoder(seed_phrase=self.seed_phrase,
|
||||
wordlist_language_code=self.wordlist_language_code)
|
||||
|
||||
elif self.qr_type == QRType.COMPACTSEEDQR:
|
||||
self.encoder = CompactSeedQR(self.seed_phrase, self.wordlist_language_code)
|
||||
self.encoder = CompactSeedQrEncoder(seed_phrase=self.seed_phrase,
|
||||
wordlist_language_code=self.wordlist_language_code)
|
||||
|
||||
else:
|
||||
raise Exception('Encoder Type not Supported')
|
||||
raise Exception('QR Type not supported')
|
||||
|
||||
|
||||
def totalParts(self):
|
||||
return self.encoder.seqLen()
|
||||
def total_parts(self):
|
||||
return self.encoder.seq_len()
|
||||
|
||||
|
||||
def nextPart(self):
|
||||
return self.encoder.nextPart()
|
||||
def next_part(self):
|
||||
return self.encoder.next_part()
|
||||
|
||||
|
||||
def part2Image(self, part, width=240, height=240, border=3):
|
||||
def part_to_image(self, part, width=240, height=240, border=3):
|
||||
return self.qr.qrimage_io(part, width, height, border)
|
||||
|
||||
|
||||
def nextPartImage(self, width=240, height=240, border=3, background_color="bdbdbd"):
|
||||
part = self.nextPart()
|
||||
def next_part_image(self, width=240, height=240, border=3, background_color="bdbdbd"):
|
||||
part = self.next_part()
|
||||
if self.qr_type == QRType.SEEDQR:
|
||||
return self.qr.qrimage(part, width, height, border)
|
||||
else:
|
||||
return self.qr.qrimage_io(part, width, height, border, background_color=background_color)
|
||||
|
||||
|
||||
def isComplete(self):
|
||||
return self.encoder.isComplete()
|
||||
# TODO: Make these properties?
|
||||
def is_complete(self):
|
||||
return self.encoder.is_complete
|
||||
|
||||
|
||||
def getQRDensity(self):
|
||||
def get_qr_density(self):
|
||||
return self.qr_density
|
||||
|
||||
|
||||
def getQRType(self):
|
||||
def get_qr_type(self):
|
||||
return self.qr_type
|
||||
|
||||
|
||||
|
||||
class UREncodePSBTQR:
|
||||
def __init__(self, p, qr_density):
|
||||
self.psbt = p
|
||||
self.qr_max_fragement_size = 20
|
||||
class BaseQrEncoder:
|
||||
def seq_len(self):
|
||||
raise Exception("Not implemented in child class")
|
||||
|
||||
def next_part(self) -> str:
|
||||
raise Exception("Not implemented in child class")
|
||||
|
||||
@property
|
||||
def is_complete(self):
|
||||
raise Exception("Not implemented in child class")
|
||||
|
||||
def _create_parts(self):
|
||||
raise Exception("Not implemented in child class")
|
||||
|
||||
|
||||
|
||||
class BasePsbtQrEncoder(BaseQrEncoder):
|
||||
def __init__(self, psbt: PSBT):
|
||||
self.psbt = psbt
|
||||
|
||||
|
||||
|
||||
class UrPsbtQrEncoder(BasePsbtQrEncoder):
|
||||
def __init__(self, psbt, qr_density):
|
||||
super().__init__(psbt)
|
||||
self.qr_max_fragment_size = 20
|
||||
|
||||
qr_ur_bytes = UR("crypto-psbt", UR_PSBT(self.psbt.serialize()).to_cbor())
|
||||
|
||||
if qr_density == EncodeQR.DENSITY__LOW:
|
||||
self.qr_max_fragement_size = 10
|
||||
self.qr_max_fragment_size = 10
|
||||
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||
self.qr_max_fragement_size = 30
|
||||
self.qr_max_fragment_size = 30
|
||||
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||
self.qr_max_fragement_size = 120
|
||||
self.qr_max_fragment_size = 120
|
||||
|
||||
self.ur2_encode = UREncoder(qr_ur_bytes,self.qr_max_fragement_size,0)
|
||||
self.ur2_encode = UREncoder(ur=qr_ur_bytes, max_fragment_len=self.qr_max_fragment_size)
|
||||
|
||||
|
||||
def seqLen(self):
|
||||
def seq_len(self):
|
||||
return self.ur2_encode.fountain_encoder.seq_len()
|
||||
|
||||
|
||||
def nextPart(self) -> str:
|
||||
def next_part(self) -> str:
|
||||
return self.ur2_encode.next_part().upper()
|
||||
|
||||
|
||||
def isComplete(self):
|
||||
@property
|
||||
def is_complete(self):
|
||||
return self.ur2_encode.is_complete()
|
||||
|
||||
|
||||
|
||||
class SpecterEncodePSBTQR:
|
||||
def __init__(self, p, qr_density):
|
||||
self.psbt = p
|
||||
class SpecterPsbtQrEncoder(BasePsbtQrEncoder):
|
||||
def __init__(self, psbt, qr_density):
|
||||
super().__init__(psbt)
|
||||
self.qr_max_fragement_size = 65
|
||||
self.parts = []
|
||||
self.part_num_sent = 0
|
||||
@@ -148,10 +208,10 @@ class SpecterEncodePSBTQR:
|
||||
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||
self.qr_max_fragement_size = 90
|
||||
|
||||
self.__createParts()
|
||||
self._create_parts()
|
||||
|
||||
|
||||
def __createParts(self):
|
||||
def _create_parts(self):
|
||||
base64_psbt = b2a_base64(self.psbt.serialize())
|
||||
|
||||
if base64_psbt[-1:] == b"\n":
|
||||
@@ -178,11 +238,11 @@ class SpecterEncodePSBTQR:
|
||||
cnt += 1
|
||||
|
||||
|
||||
def seqLen(self):
|
||||
def seq_len(self):
|
||||
return len(self.parts)
|
||||
|
||||
|
||||
def nextPart(self) -> str:
|
||||
def next_part(self) -> str:
|
||||
# if part num sent is gt number of parts, start at 0
|
||||
if self.part_num_sent > (len(self.parts) - 1):
|
||||
self.part_num_sent = 0
|
||||
@@ -199,13 +259,15 @@ class SpecterEncodePSBTQR:
|
||||
return part
|
||||
|
||||
|
||||
def isComplete(self):
|
||||
@property
|
||||
def is_complete(self):
|
||||
return self.sent_complete
|
||||
|
||||
|
||||
|
||||
class SeedQR:
|
||||
class SeedQrEncoder(BaseQrEncoder):
|
||||
def __init__(self, seed_phrase: List[str], wordlist_language_code: str):
|
||||
super().__init__()
|
||||
self.seed_phrase = seed_phrase
|
||||
self.wordlist = Seed.get_wordlist(wordlist_language_code)
|
||||
|
||||
@@ -213,11 +275,11 @@ class SeedQR:
|
||||
raise Exception('Wordlist Required')
|
||||
|
||||
|
||||
def seqLen(self):
|
||||
def seq_len(self):
|
||||
return 1
|
||||
|
||||
|
||||
def nextPart(self):
|
||||
def next_part(self):
|
||||
data = ""
|
||||
# Output as Numeric data format
|
||||
for word in self.seed_phrase:
|
||||
@@ -226,12 +288,14 @@ class SeedQR:
|
||||
return data
|
||||
|
||||
|
||||
def isComplete(self):
|
||||
@property
|
||||
def is_complete(self):
|
||||
return True
|
||||
|
||||
|
||||
class CompactSeedQR(SeedQR):
|
||||
def nextPart(self):
|
||||
|
||||
class CompactSeedQrEncoder(SeedQrEncoder):
|
||||
def next_part(self):
|
||||
# Output as binary data format
|
||||
binary_str = ""
|
||||
for word in self.seed_phrase:
|
||||
@@ -260,11 +324,12 @@ class CompactSeedQR(SeedQR):
|
||||
|
||||
|
||||
|
||||
class XPubQR:
|
||||
class XpubQrEncoder(BaseQrEncoder):
|
||||
def __init__(self, seed_phrase, passphrase, derivation, network, wordlist_language_code):
|
||||
self.seed_phrase = seed_phrase
|
||||
self.passphrase = passphrase
|
||||
self.derivation = derivation
|
||||
self.network = network
|
||||
self.wordlist = Seed.get_wordlist(wordlist_language_code)
|
||||
self.parts = []
|
||||
self.part_num_sent = 0
|
||||
@@ -273,61 +338,66 @@ class XPubQR:
|
||||
if self.wordlist == None:
|
||||
raise Exception('Wordlist Required')
|
||||
|
||||
version = bip32.detect_version(self.derivation, default="xpub", network=NETWORKS[network])
|
||||
self.seed = Seed(mnemonic=self.seed_phrase, passphrase=self.passphrase, wordlist_language_code=wordlist_language_code)
|
||||
self.root = bip32.HDKey.from_seed(self.seed.seed, version=NETWORKS[network]["xprv"])
|
||||
version = bip32.detect_version(self.derivation, default="xpub", network=NETWORKS[self.network])
|
||||
self.seed = Seed(mnemonic=self.seed_phrase,
|
||||
passphrase=self.passphrase,
|
||||
wordlist_language_code=wordlist_language_code)
|
||||
self.root = bip32.HDKey.from_seed(self.seed.seed_bytes, version=NETWORKS[self.network]["xprv"])
|
||||
self.fingerprint = self.root.child(0).fingerprint
|
||||
self.xprv = self.root.derive(self.derivation)
|
||||
self.xpub = self.xprv.to_public()
|
||||
self.xpub_base58 = self.xpub.to_string(version=version)
|
||||
self.network = network
|
||||
|
||||
self.xpubstring = "[%s%s]%s" % (hexlify(self.fingerprint).decode('utf-8'),self.derivation[1:],self.xpub_base58)
|
||||
self.xpubstring = "[{}{}]{}".format(
|
||||
hexlify(self.fingerprint).decode('utf-8'),
|
||||
self.derivation[1:],
|
||||
self.xpub_base58
|
||||
)
|
||||
|
||||
if isinstance(self, XPubQR):
|
||||
self.__createParts()
|
||||
self._create_parts()
|
||||
|
||||
|
||||
def __createParts(self):
|
||||
def _create_parts(self):
|
||||
self.parts = []
|
||||
self.parts.append(self.xpubstring)
|
||||
|
||||
|
||||
def nextPart(self) -> str:
|
||||
def next_part(self) -> str:
|
||||
if len(self.parts) > 0:
|
||||
self.sent_complete = True
|
||||
return self.parts[0]
|
||||
|
||||
|
||||
def seqLen(self):
|
||||
def seq_len(self):
|
||||
return len(self.parts)
|
||||
|
||||
|
||||
def isComplete(self):
|
||||
@property
|
||||
def is_complete(self):
|
||||
return self.sent_complete
|
||||
|
||||
|
||||
|
||||
class SpecterXPubQR(XPubQR):
|
||||
def __init__(self, seed_phrase, passphrase, derivation, network, qr_density, wordlist_language_code):
|
||||
self.qr_max_fragement_size = 65
|
||||
class SpecterXPubQrEncoder(XpubQrEncoder):
|
||||
def __init__(self, qr_density, **kwargs):
|
||||
# Must set up qr_max_fragment_size before calling super().__init__()
|
||||
self.qr_max_fragment_size = 65
|
||||
if qr_density == EncodeQR.DENSITY__LOW:
|
||||
self.qr_max_fragement_size = 40
|
||||
self.qr_max_fragment_size = 40
|
||||
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||
self.qr_max_fragement_size = 65
|
||||
self.qr_max_fragment_size = 65
|
||||
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||
self.qr_max_fragement_size = 90
|
||||
self.qr_max_fragment_size = 90
|
||||
|
||||
super().__init__(seed_phrase, passphrase, derivation, network, wordlist_language_code)
|
||||
self.__createParts()
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
def __createParts(self):
|
||||
def _create_parts(self):
|
||||
self.parts = []
|
||||
|
||||
start = 0
|
||||
stop = self.qr_max_fragement_size
|
||||
qr_cnt = ((len(self.xpubstring)-1) // self.qr_max_fragement_size) + 1
|
||||
stop = self.qr_max_fragment_size
|
||||
qr_cnt = ((len(self.xpubstring)-1) // self.qr_max_fragment_size) + 1
|
||||
|
||||
if qr_cnt == 1:
|
||||
self.parts.append(self.xpubstring[start:stop])
|
||||
@@ -337,14 +407,14 @@ class SpecterXPubQR(XPubQR):
|
||||
part = "p" + str(cnt+1) + "of" + str(qr_cnt) + " " + self.xpubstring[start:stop]
|
||||
self.parts.append(part)
|
||||
|
||||
start = start + self.qr_max_fragement_size
|
||||
stop = stop + self.qr_max_fragement_size
|
||||
start = start + self.qr_max_fragment_size
|
||||
stop = stop + self.qr_max_fragment_size
|
||||
if stop > len(self.xpubstring):
|
||||
stop = len(self.xpubstring)
|
||||
cnt += 1
|
||||
|
||||
|
||||
def nextPart(self) -> str:
|
||||
def next_part(self) -> str:
|
||||
# if part num sent is gt number of parts, start at 0
|
||||
if self.part_num_sent > (len(self.parts) - 1):
|
||||
self.part_num_sent = 0
|
||||
@@ -361,25 +431,17 @@ class SpecterXPubQR(XPubQR):
|
||||
return part
|
||||
|
||||
|
||||
def seqLen(self):
|
||||
return len(self.parts)
|
||||
|
||||
|
||||
def isComplete(self):
|
||||
return self.sent_complete
|
||||
|
||||
|
||||
|
||||
class URXPubQR(XPubQR):
|
||||
def __init__(self, seed_phrase, passphrase, derivation, network, qr_density, wordlist_language_code):
|
||||
super().__init__(seed_phrase, passphrase, derivation, network, wordlist_language_code)
|
||||
class UrXpubQrEncoder(XpubQrEncoder):
|
||||
def __init__(self, qr_density, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if qr_density == EncodeQR.DENSITY__LOW:
|
||||
self.qr_max_fragement_size = 10
|
||||
self.qr_max_fragment_size = 10
|
||||
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||
self.qr_max_fragement_size = 30
|
||||
self.qr_max_fragment_size = 30
|
||||
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||
self.qr_max_fragement_size = 120
|
||||
self.qr_max_fragment_size = 120
|
||||
|
||||
def derivation_to_keypath(path: str) -> list:
|
||||
arr = path.split("/")
|
||||
@@ -399,7 +461,7 @@ class URXPubQR(XPubQR):
|
||||
|
||||
return Keypath(arr, self.root.my_fingerprint, len(arr))
|
||||
|
||||
origin = derivation_to_keypath(derivation)
|
||||
origin = derivation_to_keypath(self.derivation)
|
||||
|
||||
self.ur_hdkey = HDKey({ 'key': self.xpub.key.serialize(),
|
||||
'chain_code': self.xpub.chain_code,
|
||||
@@ -432,93 +494,12 @@ class URXPubQR(XPubQR):
|
||||
|
||||
qr_ur_bytes = UR("crypto-account", ur_account.to_cbor())
|
||||
|
||||
self.ur2_encode = UREncoder(qr_ur_bytes, self.qr_max_fragement_size, 0)
|
||||
self.ur2_encode = UREncoder(ur=qr_ur_bytes, max_fragment_len=self.qr_max_fragment_size)
|
||||
|
||||
|
||||
def seqLen(self):
|
||||
return self.ur2_encode.fountain_encoder.seq_len()
|
||||
def seq_len(self):
|
||||
return self.ur2_encode.fountain_encoder.seq_len()
|
||||
|
||||
|
||||
def nextPart(self) -> str:
|
||||
def next_part(self) -> str:
|
||||
return self.ur2_encode.next_part().upper()
|
||||
|
||||
|
||||
def isComplete(self):
|
||||
return self.sent_complete
|
||||
|
||||
|
||||
|
||||
class URXPubQR(XPubQR):
|
||||
def __init__(self, seed_phrase, passphrase, derivation, network, qr_density, wordlist_language_code):
|
||||
super().__init__(self, seed_phrase, passphrase, derivation, network, wordlist_language_code)
|
||||
if qr_density == EncodeQR.LOW:
|
||||
self.qr_max_fragement_size = 10
|
||||
elif qr_density == EncodeQR.MEDIUM:
|
||||
self.qr_max_fragement_size = 30
|
||||
elif qr_density == EncodeQR.HIGH:
|
||||
self.qr_max_fragement_size = 120
|
||||
|
||||
def derivation_to_keypath(path: str) -> list:
|
||||
arr = path.split("/")
|
||||
if arr[0] == "m":
|
||||
arr = arr[1:]
|
||||
if len(arr) == 0:
|
||||
return Keypath([],self.root.my_fingerprint, None)
|
||||
if arr[-1] == "":
|
||||
# trailing slash
|
||||
arr = arr[:-1]
|
||||
|
||||
for i, e in enumerate(arr):
|
||||
if e[-1] == "h" or e[-1] == "'":
|
||||
arr[i] = PathComponent(int(e[:-1]), True)
|
||||
else:
|
||||
arr[i] = PathComponent(int(e), False)
|
||||
|
||||
return Keypath(arr, self.root.my_fingerprint, len(arr))
|
||||
|
||||
origin = derivation_to_keypath(derivation)
|
||||
|
||||
self.ur_hdkey = HDKey({ 'key': self.xpub.key.serialize(),
|
||||
'chain_code': self.xpub.chain_code,
|
||||
'origin': origin,
|
||||
'parent_fingerprint': self.xpub.fingerprint})
|
||||
|
||||
ur_outputs = []
|
||||
|
||||
if len(origin.components) > 0:
|
||||
if origin.components[0].index == 84: # Native Single Sig
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[404]],self.ur_hdkey))
|
||||
elif origin.components[0].index == 49: # Nested Single Sig
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[400], SCRIPT_EXPRESSION_TAG_MAP[404]],self.ur_hdkey))
|
||||
elif origin.components[0].index == 48: # Multisig
|
||||
if len(origin.components) >= 4:
|
||||
if origin.components[3].index == 2: # Native Multisig
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[401]],self.ur_hdkey))
|
||||
elif origin.components[3].index == 1: # Nested Multisig
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[400], SCRIPT_EXPRESSION_TAG_MAP[401]],self.ur_hdkey))
|
||||
|
||||
# If empty, add all script types
|
||||
if len(ur_outputs) == 0:
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[404]],self.ur_hdkey))
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[400], SCRIPT_EXPRESSION_TAG_MAP[404]],self.ur_hdkey))
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[401]],self.ur_hdkey))
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[400], SCRIPT_EXPRESSION_TAG_MAP[401]],self.ur_hdkey))
|
||||
ur_outputs.append(Output([SCRIPT_EXPRESSION_TAG_MAP[403]],self.ur_hdkey))
|
||||
|
||||
ur_account = Account(self.root.my_fingerprint, ur_outputs)
|
||||
|
||||
qr_ur_bytes = UR("crypto-account", ur_account.to_cbor())
|
||||
|
||||
self.ur2_encode = UREncoder(qr_ur_bytes, self.qr_max_fragement_size, 0)
|
||||
|
||||
|
||||
def seqLen(self):
|
||||
return self.ur2_encode.fountain_encoder.seq_len()
|
||||
|
||||
|
||||
def nextPart(self) -> str:
|
||||
return self.ur2_encode.next_part().upper()
|
||||
|
||||
|
||||
def isComplete(self):
|
||||
return self.ur2_encode.is_complete()
|
||||
|
||||
@@ -34,11 +34,13 @@ class PSBTParser():
|
||||
def get_change_data(self, change_num: int) -> dict:
|
||||
if change_num < len(self.change_data):
|
||||
return self.change_data[change_num]
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def num_change_outputs(self):
|
||||
return len(self.change_data)
|
||||
|
||||
|
||||
@property
|
||||
def is_multisig(self):
|
||||
"""
|
||||
@@ -46,14 +48,17 @@ class PSBTParser():
|
||||
"""
|
||||
return "m" in self.policy
|
||||
|
||||
|
||||
@property
|
||||
def num_destinations(self):
|
||||
return len(self.destination_addresses)
|
||||
|
||||
def __setRoot(self):
|
||||
self.root = bip32.HDKey.from_seed(self.seed.seed, version=NETWORKS[self.network]["xprv"])
|
||||
|
||||
def _set_root(self):
|
||||
self.root = bip32.HDKey.from_seed(self.seed.seed_bytes, version=NETWORKS[self.network]["xprv"])
|
||||
print(f"root: {self.root}")
|
||||
|
||||
|
||||
def parse(self):
|
||||
if self.psbt is None:
|
||||
print(f"self.psbt is None!!")
|
||||
@@ -63,33 +68,35 @@ class PSBTParser():
|
||||
print("self.seed is None!")
|
||||
return False
|
||||
|
||||
self.__setRoot()
|
||||
self._set_root()
|
||||
|
||||
rt = self.__parseInputs()
|
||||
rt = self._parse_inputs()
|
||||
if rt == False:
|
||||
return False
|
||||
|
||||
rt = self.__parseOutputs()
|
||||
rt = self._parse_outputs()
|
||||
if rt == False:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __parseInputs(self):
|
||||
|
||||
def _parse_inputs(self):
|
||||
self.input_amount = 0
|
||||
print(f"psbt.inputs: {self.psbt.inputs}")
|
||||
self.num_inputs = len(self.psbt.inputs)
|
||||
for inp in self.psbt.inputs:
|
||||
if inp.witness_utxo:
|
||||
self.input_amount += inp.witness_utxo.value
|
||||
inp_policy = PSBTParser.__get_policy(inp, inp.witness_utxo.script_pubkey, self.psbt.xpubs)
|
||||
inp_policy = PSBTParser._get_policy(inp, inp.witness_utxo.script_pubkey, self.psbt.xpubs)
|
||||
if self.policy == None:
|
||||
self.policy = inp_policy
|
||||
else:
|
||||
if self.policy != inp_policy:
|
||||
raise RuntimeError("Mixed inputs in the transaction")
|
||||
|
||||
def __parseOutputs(self):
|
||||
|
||||
def _parse_outputs(self):
|
||||
self.spend_amount = 0
|
||||
self.change_amount = 0
|
||||
self.change_data = []
|
||||
@@ -97,7 +104,7 @@ class PSBTParser():
|
||||
self.destination_addresses = []
|
||||
self.destination_amounts = []
|
||||
for i, out in enumerate(self.psbt.outputs):
|
||||
out_policy = PSBTParser.__get_policy(out, self.psbt.tx.vout[i].script_pubkey, self.psbt.xpubs)
|
||||
out_policy = PSBTParser._get_policy(out, self.psbt.tx.vout[i].script_pubkey, self.psbt.xpubs)
|
||||
is_change = False
|
||||
|
||||
# if policy is the same - probably change
|
||||
@@ -160,6 +167,7 @@ class PSBTParser():
|
||||
self.fee_amount = self.psbt.fee()
|
||||
return True
|
||||
|
||||
|
||||
@staticmethod
|
||||
def trim(tx):
|
||||
trimmed_psbt = psbt.PSBT(tx.tx)
|
||||
@@ -170,8 +178,9 @@ class PSBTParser():
|
||||
|
||||
return trimmed_psbt
|
||||
|
||||
|
||||
@staticmethod
|
||||
def sigCount(tx):
|
||||
def sig_count(tx):
|
||||
cnt = 0
|
||||
for i, inp in enumerate(tx.inputs):
|
||||
cnt += len(list(inp.partial_sigs.keys()))
|
||||
@@ -216,7 +225,7 @@ class PSBTParser():
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __get_policy(scope, scriptpubkey, xpubs):
|
||||
def _get_policy(scope, scriptpubkey, xpubs):
|
||||
"""Parse scope and get policy"""
|
||||
# we don't know the policy yet, let's parse it
|
||||
script_type = scriptpubkey.script_type()
|
||||
@@ -233,18 +242,18 @@ class PSBTParser():
|
||||
policy = {"type": script_type}
|
||||
# expected multisig
|
||||
if "p2wsh" in script_type and scope.witness_script is not None:
|
||||
m, n, pubkeys = PSBTParser.__parse_multisig(scope.witness_script)
|
||||
m, n, pubkeys = PSBTParser._parse_multisig(scope.witness_script)
|
||||
# check pubkeys are derived from cosigners
|
||||
try:
|
||||
cosigners = PSBTParser.__get_cosigners(pubkeys, scope.bip32_derivations, xpubs)
|
||||
cosigners = PSBTParser._get_cosigners(pubkeys, scope.bip32_derivations, xpubs)
|
||||
policy.update({"m": m, "n": n, "cosigners": cosigners})
|
||||
except:
|
||||
policy.update({"m": m, "n": n})
|
||||
return policy
|
||||
|
||||
# returns m, n, and pubkeys from multisig script
|
||||
|
||||
@staticmethod
|
||||
def __parse_multisig(sc):
|
||||
def _parse_multisig(sc):
|
||||
"""Takes a script and extracts m,n and pubkeys from it"""
|
||||
# OP_m <len:pubkey> ... <len:pubkey> OP_n OP_CHECKMULTISIG
|
||||
# check min size
|
||||
@@ -271,8 +280,9 @@ class PSBTParser():
|
||||
raise ValueError("Invalid multisig script")
|
||||
return m, n, pubkeys
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __get_cosigners(pubkeys, derivations, xpubs):
|
||||
def _get_cosigners(pubkeys, derivations, xpubs):
|
||||
"""Returns xpubs used to derive pubkeys using global xpub field from psbt"""
|
||||
cosigners = []
|
||||
for i, pubkey in enumerate(pubkeys):
|
||||
@@ -295,8 +305,8 @@ class PSBTParser():
|
||||
return sorted(cosigners)
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_input_fingerprints(cls, psbt: PSBT) -> List[str]:
|
||||
@staticmethod
|
||||
def get_input_fingerprints(psbt: PSBT) -> List[str]:
|
||||
"""
|
||||
Exctracts the fingerprint from each input's derivation path.
|
||||
|
||||
@@ -311,8 +321,8 @@ class PSBTParser():
|
||||
return list(fingerprints)
|
||||
|
||||
|
||||
@classmethod
|
||||
def has_matching_input_fingerprint(cls, psbt: PSBT, seed: Seed, network: str = "main"):
|
||||
@staticmethod
|
||||
def has_matching_input_fingerprint(psbt: PSBT, seed: Seed, network: str = "main"):
|
||||
"""
|
||||
Extracts the fingerprint from each psbt input utxo. Returns True if any match
|
||||
the current seed.
|
||||
|
||||
@@ -55,26 +55,31 @@ class SeedConstants:
|
||||
|
||||
|
||||
|
||||
class InvalidSeedException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class Seed:
|
||||
def __init__(self,
|
||||
mnemonic = None,
|
||||
passphrase = "",
|
||||
wordlist_language_code = SeedConstants.WORDLIST_LANGUAGE__ENGLISH) -> None:
|
||||
mnemonic: List[str] = None,
|
||||
passphrase: str = "",
|
||||
wordlist_language_code: str = SeedConstants.WORDLIST_LANGUAGE__ENGLISH) -> None:
|
||||
self.wordlist_language_code = wordlist_language_code
|
||||
|
||||
# The setters trigger a _generate_seed() call so we have to get the order right.
|
||||
self._passphrase = ""
|
||||
if not mnemonic:
|
||||
raise Exception("Must initialize a Seed with a mnemonic List[str]")
|
||||
self._mnemonic: List[str] = unicodedata.normalize("NFKD", " ".join(mnemonic).strip()).split()
|
||||
|
||||
# First set the mnemonic go through its setter's filtering
|
||||
self.mnemonic = mnemonic
|
||||
self._passphrase: str = ""
|
||||
self.set_passphrase(passphrase, regenerate_seed=False)
|
||||
|
||||
if passphrase:
|
||||
# Now run the passphrase's setter filter to change the final seed
|
||||
self.passphrase = passphrase
|
||||
self.seed_bytes: bytes = None
|
||||
self._generate_seed()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_wordlist(wordlist_language_code: str = SeedConstants.WORDLIST_LANGUAGE__ENGLISH):
|
||||
def get_wordlist(wordlist_language_code: str = SeedConstants.WORDLIST_LANGUAGE__ENGLISH) -> List[str]:
|
||||
# TODO: Support other bip-39 wordlist languages!
|
||||
if wordlist_language_code == SeedConstants.WORDLIST_LANGUAGE__ENGLISH:
|
||||
return bip39.WORDLIST
|
||||
@@ -84,41 +89,30 @@ class Seed:
|
||||
|
||||
def _generate_seed(self) -> bool:
|
||||
try:
|
||||
self.seed = bip39.mnemonic_to_seed(self.mnemonic, password=self.passphrase, wordlist=self.wordlist)
|
||||
return True
|
||||
self.seed_bytes = bip39.mnemonic_to_seed(self.mnemonic_str, password=self._passphrase, wordlist=self.wordlist)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
return False
|
||||
raise InvalidSeedException(repr(e))
|
||||
|
||||
|
||||
@property
|
||||
def mnemonic(self) -> str:
|
||||
return self._mnemonic
|
||||
def mnemonic_str(self) -> str:
|
||||
return " ".join(self._mnemonic)
|
||||
|
||||
|
||||
@property
|
||||
def mnemonic_list(self) -> List[str]:
|
||||
return self._mnemonic.split()
|
||||
return self._mnemonic
|
||||
|
||||
|
||||
@property
|
||||
def mnemonic_display(self) -> str:
|
||||
return unicodedata.normalize("NFC", self._mnemonic)
|
||||
def mnemonic_display_str(self) -> str:
|
||||
return unicodedata.normalize("NFC", " ".join(self._mnemonic))
|
||||
|
||||
|
||||
@property
|
||||
def mnemonic_display_list(self) -> List[str]:
|
||||
return unicodedata.normalize("NFC", self._mnemonic).split()
|
||||
|
||||
|
||||
@mnemonic.setter
|
||||
def mnemonic(self, value):
|
||||
if isinstance(value, list):
|
||||
self._mnemonic = unicodedata.normalize("NFKD", " ".join(value).strip())
|
||||
elif isinstance(value, str):
|
||||
self._mnemonic = unicodedata.normalize("NFKD", value.strip())
|
||||
|
||||
self._valid = self._generate_seed()
|
||||
return unicodedata.normalize("NFC", " ".join(self._mnemonic)).split()
|
||||
|
||||
|
||||
@property
|
||||
@@ -131,35 +125,36 @@ class Seed:
|
||||
return unicodedata.normalize("NFC", self._passphrase)
|
||||
|
||||
|
||||
@passphrase.setter
|
||||
def passphrase(self, value):
|
||||
if isinstance(value, str):
|
||||
self._passphrase = unicodedata.normalize("NFKD", value)
|
||||
def set_passphrase(self, passphrase: str, regenerate_seed: bool = True):
|
||||
if passphrase:
|
||||
self._passphrase = unicodedata.normalize("NFKD", passphrase)
|
||||
else:
|
||||
# Passphrase must always have a string value, even if it's just the empty
|
||||
# string.
|
||||
self._passphrase = ""
|
||||
|
||||
self._valid = self._generate_seed()
|
||||
if regenerate_seed:
|
||||
# Regenerate the internal seed since passphrase changes the result
|
||||
self._generate_seed()
|
||||
|
||||
|
||||
@property
|
||||
def wordlist(self):
|
||||
def wordlist(self) -> List[str]:
|
||||
return Seed.get_wordlist(self.wordlist_language_code)
|
||||
|
||||
|
||||
@wordlist.setter
|
||||
def wordlist(self, language_code: str):
|
||||
def set_wordlist_language_code(self, language_code: str):
|
||||
# TODO: Support other bip-39 wordlist languages!
|
||||
raise Exception("Not yet implemented!")
|
||||
|
||||
|
||||
def get_fingerprint(self, network: str = "main"):
|
||||
root = bip32.HDKey.from_seed(self.seed, version=NETWORKS[network]["xprv"])
|
||||
def get_fingerprint(self, network: str = SeedConstants.MAINNET) -> str:
|
||||
root = bip32.HDKey.from_seed(self.seed_bytes, version=NETWORKS[network]["xprv"])
|
||||
return hexlify(root.child(0).fingerprint).decode('utf-8')
|
||||
|
||||
|
||||
### override operators
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Seed):
|
||||
return self.seed == other.seed
|
||||
return self.seed_bytes == other.seed_bytes
|
||||
return False
|
||||
|
||||
def __bool__(self):
|
||||
return self._valid
|
||||
@@ -1,199 +1,47 @@
|
||||
# External Dependencies
|
||||
from typing import List
|
||||
from seedsigner.models import Seed
|
||||
from seedsigner.models.seed import InvalidSeedException
|
||||
|
||||
|
||||
|
||||
class SeedStorage:
|
||||
def __init__(self) -> None:
|
||||
self.seeds: List[Seed] = []
|
||||
self.pending_seed: Seed = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.seeds = []
|
||||
self.pending_seed = None
|
||||
|
||||
# to be removed once the concepts of slots is removed
|
||||
# this is a list of 3 indexes to the actual seed/mnemonic/passphrase data
|
||||
# -1 means the slot is empty
|
||||
self.slots = [-1,-1,-1]
|
||||
|
||||
def set_pending_seed(self, seed: Seed):
|
||||
self.pending_seed = seed
|
||||
def set_pending_seed(self, seed: Seed):
|
||||
self.pending_seed = seed
|
||||
|
||||
def get_pending_seed(self) -> Seed:
|
||||
return self.pending_seed
|
||||
|
||||
def finalize_pending_seed(self) -> int:
|
||||
# Finally store the pending seed and return its index
|
||||
self.seeds.append(self.pending_seed)
|
||||
self.pending_seed = None
|
||||
return len(self.seeds) - 1
|
||||
def get_pending_seed(self) -> Seed:
|
||||
return self.pending_seed
|
||||
|
||||
def clear_pending_seed(self):
|
||||
self.pending_seed = None
|
||||
|
||||
# only adds a seed if it's valid and does not already exist
|
||||
def add_mnemonic(self, mnemonic, passphrase: str = "", slot_num = -1) -> bool:
|
||||
self.seeds.append(Seed(mnemonic, passphrase))
|
||||
|
||||
# used to track virtual slot number, TODO: remove concept in UI of slots
|
||||
if slot_num > 0:
|
||||
self.slots[slot_num - 1] = len(self.seeds) - 1
|
||||
|
||||
def add_seed(self, seed: Seed, slot_num = -1):
|
||||
self.seeds.append(seed)
|
||||
|
||||
# used to track virtual slot number, TODO: remove concept in UI of slots
|
||||
if slot_num > 0:
|
||||
self.slots[slot_num - 1] = len(self.seeds) - 1
|
||||
|
||||
def add_passphrase_to_seed(self, seed, passphrase: str = ""):
|
||||
if not isinstance(passphrase, str):
|
||||
raise ValueError("Invalid passphrase format")
|
||||
|
||||
# find existing seed to add passphrase to
|
||||
if seed in self.seeds:
|
||||
idx = self.seeds.index(seed)
|
||||
seed.passphrase = passphrase
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def remove_passphrase_from_seed(self, seed):
|
||||
return self.add_passphrase_to_seed(seed, "")
|
||||
|
||||
# validates mnemonic part of a seed in list or string format
|
||||
def validate_mnemonic(self, seed_phrase) -> bool:
|
||||
try:
|
||||
seed = Seed(seed_phrase)
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def seed_count(self):
|
||||
return len(self.seeds)
|
||||
def finalize_pending_seed(self) -> int:
|
||||
# Finally store the pending seed and return its index
|
||||
if self.pending_seed in self.seeds:
|
||||
index = self.seeds.index(self.pending_seed)
|
||||
else:
|
||||
self.seeds.append(self.pending_seed)
|
||||
index = len(self.seeds) - 1
|
||||
self.pending_seed = None
|
||||
return index
|
||||
|
||||
#
|
||||
# Slot related methods to be removed once the concepts of slots is removed
|
||||
# TODO: remove concept in UI of slots
|
||||
#
|
||||
|
||||
def get_mnemonic_from_slot(self, slot_num = 0):
|
||||
if self.slots[slot_num - 1] == -1:
|
||||
raise ValueError("Slot is unexpectedly empty")
|
||||
|
||||
return self.seeds[self.slots[slot_num - 1]].mnemonic
|
||||
|
||||
def get_passphrase_from_slot(self, slot_num = 0):
|
||||
if self.slots[slot_num - 1] == -1:
|
||||
raise ValueError("Slot is unexpectedly empty")
|
||||
|
||||
return self.seeds[self.slots[slot_num - 1]].passphrase
|
||||
|
||||
def add_passphrase_to_slot(self, passphrase: str = "", slot_num = -1):
|
||||
if self.slots[slot_num - 1] == -1:
|
||||
raise ValueError("Slot is unexpectedly empty")
|
||||
|
||||
return self.add_passphrase_to_seed(self.seeds[self.slots[slot_num - 1]], passphrase)
|
||||
def clear_pending_seed(self):
|
||||
self.pending_seed = None
|
||||
|
||||
def remove_passphrase_from_slot(self, slot_num = -1):
|
||||
if self.slots[slot_num - 1] == -1:
|
||||
raise ValueError("Slot is unexpectedly empty")
|
||||
|
||||
return self.remove_passphrase_from_seed(self.seeds[self.slots[slot_num - 1]])
|
||||
|
||||
def get_first_seed_slot(self):
|
||||
if self.slots[0] != -1:
|
||||
return self.slots[0] + 1
|
||||
elif self.slots[1] != -1:
|
||||
return self.slots[1] + 1
|
||||
elif self.slots[2] != -1:
|
||||
return self.slots[2] + 1
|
||||
def validate_mnemonic(self, mnemonic: List[str]) -> bool:
|
||||
try:
|
||||
Seed(mnemonic=mnemonic)
|
||||
except InvalidSeedException as e:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def check_slot_1(self) -> bool:
|
||||
if self.slots[0] != -1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_slot_2(self) -> bool:
|
||||
if self.slots[1] != -1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_slot_3(self) -> bool:
|
||||
if self.slots[2] != -1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_slot(self, slot_num) -> bool:
|
||||
if self.slots[slot_num-1] != -1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_slot_passphrase(self, slot_num) -> bool:
|
||||
if self.slots[slot_num-1] != -1:
|
||||
if self.seeds[self.slots[slot_num-1]].passphrase != "":
|
||||
return True
|
||||
return False
|
||||
|
||||
def slot_avaliable(self) -> bool:
|
||||
if self.slots[0] == -1 or self.slots[1] == -1 or self.slots[2] == -1:
|
||||
return True
|
||||
return True
|
||||
|
||||
def num_of_saved_seeds(self) -> int:
|
||||
count = 0
|
||||
if self.slots[0] != -1:
|
||||
count += 1
|
||||
if self.slots[1] != -1:
|
||||
count += 1
|
||||
if self.slots[2] != -1:
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def num_of_free_slots(self) -> int:
|
||||
count = 0
|
||||
if self.slots[0] == -1:
|
||||
count += 1
|
||||
if self.slots[1] == -1:
|
||||
count += 1
|
||||
if self.slots[2] == -1:
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def save_seed_phrase(self, seed_phrase = [], slot_num = 0) -> bool:
|
||||
return self.add_mnemonic(seed_phrase,slot_num=slot_num)
|
||||
|
||||
def save_passphrase(self, passphrase, slot_num = 0) -> bool:
|
||||
return self.add_passphrase_to_slot(passphrase, slot_num=slot_num)
|
||||
|
||||
def get_seed_phrase(self, slot_num) -> list:
|
||||
if self.slots[slot_num - 1] == -1:
|
||||
return []
|
||||
|
||||
return self.seeds[self.slots[slot_num - 1]].mnemonic.split()
|
||||
|
||||
def get_passphrase(self, slot_num) -> str:
|
||||
if self.slots[slot_num - 1] == -1:
|
||||
return ""
|
||||
|
||||
return self.seeds[self.slots[slot_num - 1]].passphrase
|
||||
|
||||
def get_seed(self, slot_num) -> Seed:
|
||||
if self.slots[slot_num - 1] == -1:
|
||||
return Seed()
|
||||
|
||||
return self.seeds[self.slots[slot_num - 1]]
|
||||
|
||||
def delete_passphrase(self, slot_num) -> bool:
|
||||
if self.slots[slot_num - 1] == -1:
|
||||
return False
|
||||
|
||||
return self.remove_passphrase_from_seed(self.seeds[self.slots[slot_num - 1]])
|
||||
|
||||
def check_if_seed_valid(self, seed_phrase) -> bool:
|
||||
return self.validate_mnemonic(seed_phrase)
|
||||
|
||||
def num_seeds(self):
|
||||
return len(self.seeds)
|
||||
|
||||
@@ -294,13 +294,13 @@ class PSBTFinalizeView(View):
|
||||
loading_screen = LoadingScreenThread(text="Signing PSBT...")
|
||||
loading_screen.start()
|
||||
|
||||
sig_cnt = PSBTParser.sigCount(psbt)
|
||||
sig_cnt = PSBTParser.sig_count(psbt)
|
||||
psbt.sign_with(psbt_parser.root)
|
||||
trimmed_psbt = PSBTParser.trim(psbt)
|
||||
|
||||
loading_screen.stop()
|
||||
|
||||
if sig_cnt == PSBTParser.sigCount(trimmed_psbt):
|
||||
if sig_cnt == PSBTParser.sig_count(trimmed_psbt):
|
||||
# Signing failed / didn't do anything
|
||||
# TODO: Reserved for Nick. Are there different failure scenarios that we can detect?
|
||||
# Would be nice to alter the message on the next screen w/more detail.
|
||||
|
||||
@@ -742,7 +742,7 @@ class SeedToolsView(View):
|
||||
e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.COMPACTSEEDQR, wordlist=self.controller.settings.wordlist)
|
||||
else:
|
||||
e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.SEEDQR, wordlist=self.controller.settings.wordlist)
|
||||
data = e.nextPart()
|
||||
data = e.next_part()
|
||||
qr = QR()
|
||||
image = qr.qrimage(
|
||||
data=data,
|
||||
@@ -780,7 +780,7 @@ class SeedToolsView(View):
|
||||
|
||||
width = (qr_border + num_modules + qr_border) * pixels_per_block
|
||||
height = width
|
||||
data = e.nextPart()
|
||||
data = e.next_part()
|
||||
qr = QR()
|
||||
image = qr.qrimage(
|
||||
data,
|
||||
|
||||
@@ -412,7 +412,7 @@ class SeedExportXpubDetailsView(View):
|
||||
)
|
||||
|
||||
root = embit.bip32.HDKey.from_seed(
|
||||
self.seed.seed,
|
||||
self.seed.seed_bytes,
|
||||
version=embit.networks.NETWORKS[self.controller.settings.get_value(SettingsConstants.SETTING__NETWORK)]["xprv"]
|
||||
)
|
||||
|
||||
@@ -593,7 +593,7 @@ class SeedAddPassphraseView(View):
|
||||
return Destination(BackStackView)
|
||||
|
||||
# The new passphrase will be the return value
|
||||
self.seed.passphrase = ret
|
||||
self.seed.set_passphrase(ret)
|
||||
return Destination(SeedValidView)
|
||||
|
||||
|
||||
|
||||
+45
-43
@@ -1,8 +1,11 @@
|
||||
import pytest
|
||||
from mock import MagicMock
|
||||
from seedsigner.models import Seed, DecodeQR, DecodeQRStatus, QRType, EncodeQRDensity, PSBTParser
|
||||
from seedsigner.models import Seed, DecodeQR, DecodeQRStatus, QRType, PSBTParser
|
||||
from embit import psbt, bip39
|
||||
|
||||
from seedsigner.models.seed import SeedConstants
|
||||
|
||||
|
||||
|
||||
# this is an of this bug: https://github.com/Foundation-Devices/foundation-ur-py/issues/3
|
||||
def test_ur_qr_decode_multisig():
|
||||
@@ -39,7 +42,7 @@ def test_ur_qr_decode_multisig():
|
||||
, "UR:CRYPTO-PSBT/26-29/LPCSCYCSCACFCMCPCYBBRSPTSKHDSSKSKGDYBYDMPEYTWFDIROBZCHGWCEFMFECKZEDYAEAELAAEAEAELAAEAEAELAAOAEAELAADAEAEAEAEAEAEAECPAOAXREAMJNKKZCESYKSWJOJPTBRDEOKGBSHHDTIYPSBEPSVWKSYKJPCPDKUYIMPEOSFECEQZEOVTMDDYAEAELAAEAEAELAAEAEAELAAOAEAELAADAEAEAEAEAEAEAECPAOAXWTSOSKSKFNWYBYLBNSHTEEEEECTORFLRBYCWROTKVTEMBDPDQDTEVALECAWNWDVYCELPDNDYMYDYAEAELAAEAEAELAAEAEAELAAOAEAELAADAEAEAEAEAEAEAEAEADADTKGHCLAOOEWLYATYBAOYLKOLJESGHTGYJLCLRHSGLFKPBDCLSEHYMT"
|
||||
]
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
for i in qrcodes:
|
||||
d.add_data(i)
|
||||
assert d.qrType() == QRType.PSBTUR2
|
||||
@@ -55,13 +58,13 @@ def test_ur_qr_decode_multisig():
|
||||
|
||||
assert str(tx) == base64_psbt
|
||||
|
||||
def test_base64_single_frame_singlsig():
|
||||
|
||||
def test_base64_single_frame_singlsig():
|
||||
qrcodes = [
|
||||
"cHNidP8BAHICAAAAAQDo5ey+2HIrNUkExsFhsImv1OK1cYA9x/bRjYQD+0UaAQAAAAD9////Apg6AAAAAAAAF6kUVuVZEcdpQ2zgABa9dRUNYHD4VuaHgSYAAAAAAAAWABQaLE4t0JbDRg4pNnmcf+cAWIcyawAAAAAAAQEfqGEAAAAAAAAWABRyuw9od6yuS0yiZljV0X12wG9e5CIGA/ZlEZvQubb6PmcnK+vlnd8aftYnrQ8wHYSxsD8tDp61GIshjoFUAACAAQAAgAAAAIAAAAAAAAAAAAAAAA=="
|
||||
]
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
for i in qrcodes:
|
||||
d.add_data(i)
|
||||
assert d.qrType() == QRType.PSBTBASE64
|
||||
@@ -79,11 +82,11 @@ def test_base64_single_frame_singlsig():
|
||||
|
||||
mnemonic = "height demise useless trap grow lion found off key clown transfer enroll"
|
||||
pw = ""
|
||||
seed = Seed(mnemonic, passphrase=pw, wordlist=bip39.WORDLIST)
|
||||
seed = Seed(mnemonic, passphrase=pw)
|
||||
|
||||
assert seed.mnemonic == mnemonic
|
||||
assert seed.mnemonic_str == mnemonic
|
||||
|
||||
assert seed.seed != None
|
||||
assert seed.seed_bytes != None
|
||||
|
||||
pp = PSBTParser(tx,seed,"test")
|
||||
|
||||
@@ -97,8 +100,8 @@ def test_base64_single_frame_singlsig():
|
||||
|
||||
assert len(pp.destination_addresses) == 2
|
||||
|
||||
def test_ur2_sparrow_singlesig_to_self():
|
||||
|
||||
def test_ur2_sparrow_singlesig_to_self():
|
||||
qrcodes = [
|
||||
"UR:CRYPTO-PSBT/435-3/LPCFADQDAXCFAOOECYMSTBHDCSHDVYHTAXLATPBEOEDPAYIAWSYNAMSGSARSURCLDRKNFYZEWLHLMDTNATNYNSETJNZOKBGLFMRTMWOTLAFSSTYNTIPTLTTLWPVOZEHTAHTESNYADSHYVYFPDEBTBAIHIYTLWFUERDLKWTLSNLMWCHNDPSBWGRUTKSYLFLATVDCYNTDNGDTNURYLTTGUSSHNAEEEFZSRLEKEHDEMCYOTLDEMOTZORFDPKBCLBEHNREFWIDOSRSLRIOIAOXNLADDYENFMVOGLSGECDKBECSDSLUHNDTWYYTWFSNSPPESAZCGUDLDABAISLNJLWKJPRHBBRTEHYALRJYNYKPEOGLGLOLCPPYSEZTCHVLFMPELDGWCXGMSPHPGWAOKNBALOFXJLKNONPRBAPKYKIEVETEDWFXMDRTVWBYKSZTBTCLSGCSCPIYTPTYGYKIYNRTJLHLIEMNJYCTVYUEHGPRHN"
|
||||
, "UR:CRYPTO-PSBT/440-3/LPCFADROAXCFAOOECYMSTBHDCSHDVYAOAEAEAEAORTHKYLIDWSLTBGSGTBSNIHDMPKVSAAWYKNIACELDCMGRLULRKNLUMKRSBACNCPTBADAEAEAECNCPAECXJPRPLBLURFIHEMSWRFLNECGOTKGRMHLBCSAXTYJKREPKWEBWEYAYIYNDJPCWFSLSZCZMZMZMDLIHKNPYASHLWECEMEGSSSVTAEEEFZSRNSKEGSVYWNCMAMTNATMWCYMYWTUEPDTDAEAEAEAECNCPAECXONRSTTHSLBCPREBTRHBGJKSEWYPMTNPKEHIHHELPQDLSLPFRWNKBBTVAPMDTBEDAZCZMZMZMAOPKPEAOAEAEAEAEAECHPTBBCAWPEEVAOYIMDISSIDNNRFISRHBDGLATSASWAXGULTPDHSAEAEAEAEAEAECMAEBBJPRKBSISKTPSPLGRGSOEIYHDTLTTKIKORTJLHYVEMNKPCTAEDLFPBASG"
|
||||
@@ -106,7 +109,7 @@ def test_ur2_sparrow_singlesig_to_self():
|
||||
, "UR:CRYPTO-PSBT/443-3/LPCFADRKAXCFAOOECYMSTBHDCSHDVYAXADCTPDIARTHKYLIDWSLTAASGSARSUECLSANEPDFZEHDLRNWSGLNNHTYTBNGRYLVYWDCPCLTDAEAEAEAEADDKAXTBCHOSVEHPAHTESNYAUYOYCKRNDRTBGWIHIYTLWFUERDNYWTMSLSROHKRPKELPLONDKOUEJSKBKGIHKNDNAYHLWENSMEGSSSHNAEEEFZSRNSKEGSVYWNEEAATAPYIMPASNHPFWMETEAYBYHFRSBBZCDSRDTTLNADLBEMFTTSSOAHENPFNTVSVEBDHNDTWYKBBDEYTSLPFRJSLBBTVADPDTBEDAKIZEZMZMAOPKPEAOAEAECPAOAXWKGRNLJEVSZMWSIDSOTTEHHETYFTFXCASBMKGTURBWJLIMTLLUFWSTSSKIZMLAFXBALUECZTFTHPISKTDWPEGRGSCPIYHDTLGYKIKORTJLHLVEMNKPCTAERKWPCMJN"
|
||||
]
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
for i in qrcodes:
|
||||
d.add_data(i)
|
||||
assert d.qrType() == QRType.PSBTUR2
|
||||
@@ -122,11 +125,11 @@ def test_ur2_sparrow_singlesig_to_self():
|
||||
|
||||
assert str(tx) == d.getBase64PSBT()
|
||||
|
||||
def test_base64_2_input_p2wsh():
|
||||
|
||||
def test_base64_2_input_p2wsh():
|
||||
base64_psbt = "cHNidP8BALICAAAAAq1DhxRK+mUH4T6uUNob8bUaZ7MP+44MW4+Y9bOxpjhZAAAAAAD9////aWclWQ+45HKrI07r878E2UrAupT2paT4QurbmtNjYNQBAAAAAP3///8CQEIPAAAAAAAiACCpkDPDhmIzPlkJrjw9A71xjbIUWf3VUB7ooFJhTVm04tjSIQEAAAAAIgAgjQKFDauIXsV5u23LBdYgOwX1FwGGrLiQfWzBtFKZ7dIAAAAATwEENYfPBD5i336AAAACQStJhNVJul7vHKbo83VdmuAW2m0WaXLKDlFANn7dUNoCNbhLMdw4Knz7Q7o6exdL6UFhQegW9nJb0SUStbLEpawUAgjLdzAAAIABAACAAAAAgAIAAIBPAQQ1h88EnbHQAIAAAAI/2Nc7x7iMpJNapTe/OJTV4oifqzQcYY9KV2+PGRjCdQJoww1WnSNqfcxXGyux0q1PqfmzUqgJNqKJCpmqI9t47BQmu4PEMAAAgAEAAIAAAACAAgAAgE8BBDWHzwS6wUg5gAAAAh1Pvr3ZZ+GvcUwJl9OPz2cLXOnTAcBEC7zDtqIOt3IcA1aOofNgUZFu0baQw54SqOcGA7KAvTDOXygfKRilU2OqFHPF2gowAACAAQAAgAAAAIACAACAAAEBK4CWmAAAAAAAIgAgiYAxcG7dnrEiZ4VHFVHOo18XCalvhZYuMqBr9n7HESQBBWlSIQJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8iEDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMchA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHU64iBgJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8hwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAABAAAAIgYDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMccAgjLdzAAAIABAACAAAAAgAIAAIAAAAAAAQAAACIGA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHHHPF2gowAACAAQAAgAAAAIACAACAAAAAAAEAAAAAAQErgJaYAAAAAAAiACAzd60wM9EFnPHSNbsSJfyipL8myVLVP2/vwzotVUSNxQEFaVIhAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiIQKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdCEDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgFTriIGAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiHAIIy3cwAACAAQAAgAAAAIACAACAAAAAAAAAAAAiBgKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdBwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIgYDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgEcc8XaCjAAAIABAACAAAAAgAIAAIAAAAAAAAAAAAABAWlSIQKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/iEDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYhA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrU64iAgKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/hwCCMt3MAAAgAEAAIAAAACAAgAAgAEAAAAAAAAAIgIDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYcc8XaCjAAAIABAACAAAAAgAIAAIABAAAAAAAAACICA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrHCa7g8QwAACAAQAAgAAAAIACAACAAQAAAAAAAAAAAA=="
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(base64_psbt)
|
||||
assert d.qrType() == QRType.PSBTBASE64
|
||||
|
||||
@@ -135,7 +138,7 @@ def test_base64_2_input_p2wsh():
|
||||
assert str(tx) == base64_psbt
|
||||
|
||||
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
pp = PSBTParser(tx,Seed(mnemonic, wordlist=bip39.WORDLIST),"test")
|
||||
pp = PSBTParser(tx, Seed(mnemonic), "test")
|
||||
|
||||
assert tx.inputs[0].witness_utxo.value == 10000000 # input amount 1 in psbt
|
||||
|
||||
@@ -149,11 +152,11 @@ def test_base64_2_input_p2wsh():
|
||||
|
||||
assert len(pp.destination_addresses) == 1
|
||||
|
||||
def test_base64_1_input_p2sh_p2wsh():
|
||||
|
||||
def test_base64_1_input_p2sh_p2wsh():
|
||||
base64_psbt = "cHNidP8BAHICAAAAAR30J629i3Y/R8woRpLQ9JUa31rKxyM+Ny4NEsme48GWAAAAAAD9////Atw5XQUAAAAAF6kUdSESczdYagEyToVUSXyT8VTNz+OHgJaYAAAAAAAWABTmav7/w4OOcfCiewfjsA7eaujhYAAAAABPAQQ1h88EPmLffoAAAAHdXEj2dn8EYJ+rRdXEYu5laq6lJI5Mp+3t63ckwty05QKrJBNPewhwQaGPYRif6+XaxozFXvTXn7pU24H6fRy1FxQCCMt3MAAAgAEAAIAAAACAAQAAgE8BBDWHzwSdsdAAgAAAAeeOv56oeaaFTrNonMKDHk1C8brbWGFvdlecVue+v0/RAn/g4yI3oYsyen7OOcT7caYl4Mn7nQbyonHcusUR+GhzFCa7g8QwAACAAQAAgAAAAIABAACATwEENYfPBLrBSDmAAAABpzrb4oeEh2NNy/w/fr3osfyZTx7AaGDPAcP+LqeR3bYC5ioqmXPuazp69HwimlvecLylm9BLuyl/VpPXqiVrl20Uc8XaCjAAAIABAACAAAAAgAEAAIAAAQEgAOH1BQAAAAAXqRSv3gkn8731qcPbSDu4TJOlJJZ/PocBBCIAIOeiFBX5x0vX6CacrAUVovrs1DDCcKJS5qptFS3sjpDpAQVpUiECZ+pFYkOTVjB+eG+vQFA3MNjZWiA6DjRcs1Wl36A/zgMhA2Ygckuwjah29wiVRgA6wFx51+6ayrzeCIQ2eE4zfxPtIQOnUlBn22cn2CPCZkMSI6cDaZK2SlLV20rT6pqMoQCJsFOuIgYCZ+pFYkOTVjB+eG+vQFA3MNjZWiA6DjRcs1Wl36A/zgMcc8XaCjAAAIABAACAAAAAgAEAAIAAAAAAAAAAACIGA2Ygckuwjah29wiVRgA6wFx51+6ayrzeCIQ2eE4zfxPtHCa7g8QwAACAAQAAgAAAAIABAACAAAAAAAAAAAAiBgOnUlBn22cn2CPCZkMSI6cDaZK2SlLV20rT6pqMoQCJsBwCCMt3MAAAgAEAAIAAAACAAQAAgAAAAAAAAAAAAAEAIgAgZBwTq05RkpqKv6FV6LQjuM07Qv0/bYfWVc9NUQOFvwQBAWlSIQI32jVSdTgeu7+YZKrWfgOZ2J/LV36c5rBoApTzhrNlDCEC+hEqm3XmRt862AFFeyJ7p1m8A+V7czj6OajUNgCfg4EhA/MUVfxGh4k3Po3LB8CmMRsvIHcGNO0elUgETaITZA3UU64iAgI32jVSdTgeu7+YZKrWfgOZ2J/LV36c5rBoApTzhrNlDBwCCMt3MAAAgAEAAIAAAACAAQAAgAEAAAAAAAAAIgIC+hEqm3XmRt862AFFeyJ7p1m8A+V7czj6OajUNgCfg4EcJruDxDAAAIABAACAAAAAgAEAAIABAAAAAAAAACICA/MUVfxGh4k3Po3LB8CmMRsvIHcGNO0elUgETaITZA3UHHPF2gowAACAAQAAgAAAAIABAACAAQAAAAAAAAAAAA=="
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(base64_psbt)
|
||||
assert d.qrType() == QRType.PSBTBASE64
|
||||
|
||||
@@ -206,7 +209,7 @@ def test_ur_legacy():
|
||||
, "UR:BYTES/8OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/E0DKKUXEJDPX589HZPQ6SVQQQZQQZQQQSQQQQQYQQGQQPQQPQQQQQPQQQQQZYQSZ56D86Z6EZJEJALSG9KYCEJD4ES9TQR59AR7VDQQ9FKL44LSKUNL3E38ZAMFNQQQQSQQSQQYQQQQQPQQZQQQGQQGQQQQQGQQQQQQQ0P6SEH"
|
||||
]
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
for i in qrcodes:
|
||||
if d.add_data(i) == DecodeQRStatus.COMPLETE:
|
||||
break
|
||||
@@ -230,8 +233,8 @@ def test_ur_legacy():
|
||||
|
||||
assert pp.change_amount == 76318
|
||||
|
||||
def test_specter_multisig_animated_qr():
|
||||
|
||||
def test_specter_multisig_animated_qr():
|
||||
qrcodes = [
|
||||
"p2of5 OUsU2k9a6Valj6kLb/Bh2gB7YuxenNz7LY5PFJkKc60wAACAAQAAgAAAAIACAACATwEENYfPBEgFouKAAAACOeERUyCmIJuSdQNDB1mxkahw4tc2UCtjxJQoQGnRCtQCZUSXwQF+nA8dl/RG6U1b9ViWJxkzNrDCCagYEWch72cUrnhJ4jAAAIABAACAAAAAgAIAAIAAAQErckUPAAAAAAAiACDwHjagvyTvVDVn+RqxW0xaMLODBm2IVCwDBQm5uK8rDQEFR1IhAi/hk/Tz0aHcNwsVyv86bqQj30sUexPl"
|
||||
, "p3of5 c738CqJg+flLIQKmgeTdVjPh9P+MPnsTPbFRcGGLPAO3iTyYBR7eYQC4VlKuIgYCL+GT9PPRodw3CxXK/zpupCPfSxR7E+VzvfwKomD5+UscrnhJ4jAAAIABAACAAAAAgAIAAIABAAAACgAAACIGAqaB5N1WM+H0/4w+exM9sVFwYYs8A7eJPJgFHt5hALhWHJkKc60wAACAAQAAgAAAAIACAACAAQAAAAoAAAAAAQErMHUAAAAAAAAiACAUS+ztgOw4wxEOYDdQEZp42Ii3sA39MdXZMWolM3s9VgEFR1Ih"
|
||||
@@ -240,7 +243,7 @@ def test_specter_multisig_animated_qr():
|
||||
, "p1of5 cHNidP8BAKQCAAAAA6DLc9RAdKwWQb/7Nrq1FyAtDQ3e0w5E4LkLpcrwBL0aAAAAAAD9////78NG2vqjywKaa0QHfo6C44o1+odXeWZ43FfHy4bLdxABAAAAAP3///8rhueJha90HzgJ6alnJ1uvi0Zbq0JQoXnwDb0Cjn79DwAAAAAA/f///wHWCBAAAAAAABYAFDraKFKubXzlSvRQy8V4X+TwKhLXAAAAAE8BBDWHzwQJeplqgAAAAqmHnnoRR+XJd7gJt/PhLazKeWAfCBLJ4/OF/s0PqqF1AzK8fctt"
|
||||
]
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
for i in qrcodes:
|
||||
if d.add_data(i) == DecodeQRStatus.COMPLETE:
|
||||
break
|
||||
@@ -278,7 +281,7 @@ def test_specter_multisig_animated_qr():
|
||||
, "p2of5 OUsU2k9a6Valj6kLb/Bh2gB7YuxenNz7LY5PFJkKc60wAACAAQAAgAAAAIACAACATwEENYfPBEgFouKAAAACOeERUyCmIJuSdQNDB1mxkahw4tc2UCtjxJQoQGnRCtQCZUSXwQF+nA8dl/RG6U1b9ViWJxkzNrDCCagYEWch72cUrnhJ4jAAAIABAACAAAAAgAIAAIAAAQErckUPAAAAAAAiACDwHjagvyTvVDVn+RqxW0xaMLODBm2IVCwDBQm5uK8rDQEFR1IhAi/hk/Tz0aHcNwsVyv86bqQj30sUexPl"
|
||||
]
|
||||
|
||||
d2 = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d2 = DecodeQR()
|
||||
for i in qrcodes2:
|
||||
if d2.add_data(i) == DecodeQRStatus.COMPLETE:
|
||||
break
|
||||
@@ -308,26 +311,26 @@ def test_specter_multisig_animated_qr():
|
||||
|
||||
assert str(trimmed_tx2) == expected_signed_trimmed_base642
|
||||
|
||||
def test_mnemonic_qr():
|
||||
|
||||
def test_mnemonic_qr():
|
||||
mnemonic = "height demise useless trap grow lion found off key clown transfer enroll"
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
|
||||
assert d.add_data(mnemonic) == DecodeQRStatus.COMPLETE
|
||||
assert d.getSeedPhrase() == mnemonic.split()
|
||||
|
||||
bad_mnemonic = "height useless trap grow lion found off key clown transfer enroll"
|
||||
d2 = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d2 = DecodeQR()
|
||||
assert d2.add_data(bad_mnemonic) == DecodeQRStatus.INVALID
|
||||
|
||||
bad2_mnemonic = "words not in the list of bip39"
|
||||
d3 = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d3 = DecodeQR()
|
||||
assert d3.add_data(bad2_mnemonic) == DecodeQRStatus.INVALID
|
||||
|
||||
def test_short_4_letter_mnemonic_qr():
|
||||
|
||||
def test_short_4_letter_mnemonic_qr():
|
||||
short_nm = "heig demi usel trap grow lion foun off key clow tran enro"
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(short_nm)
|
||||
|
||||
assert d.isComplete() == True
|
||||
@@ -351,77 +354,78 @@ def test_bitcoin_address():
|
||||
main_bech32_address2 = "bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?amount=12000"
|
||||
main_bech32_address3 = "BITCOIN:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?junk"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(bad1)
|
||||
|
||||
assert d.qrType() == QRType.INVALID
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(legacy_address1)
|
||||
|
||||
assert d.getAddress() == legacy_address1
|
||||
assert d.getAddressType() == "P2PKH-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(legacy_address2)
|
||||
|
||||
assert d.getAddress() == legacy_address2
|
||||
assert d.getAddressType() == "P2PKH-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(main_bech32_address)
|
||||
|
||||
assert d.getAddress() == main_bech32_address
|
||||
assert d.getAddressType() == "Bech32-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(test_bech32_address)
|
||||
|
||||
assert d.getAddress() == test_bech32_address
|
||||
assert d.getAddressType() == "Bech32-test"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(main_nested_segwit_address)
|
||||
|
||||
assert d.getAddress() == main_nested_segwit_address
|
||||
assert d.getAddressType() == "P2SH-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(test_nested_segwit_address)
|
||||
|
||||
assert d.getAddress() == test_nested_segwit_address
|
||||
assert d.getAddressType() == "P2SH-test"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(main_bech32_address2)
|
||||
|
||||
assert d.getAddress() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
|
||||
assert d.getAddressType() == "Bech32-main"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(main_bech32_address3)
|
||||
|
||||
assert d.getAddress() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
|
||||
assert d.getAddressType() == "Bech32-main"
|
||||
|
||||
|
||||
def test_seed_qr():
|
||||
seed = "121802020768124106400009195602431595117715840445"
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(seed)
|
||||
|
||||
assert d.qrType() == QRType.SEEDQR
|
||||
assert d.getSeedPhrase() == "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash".split()
|
||||
|
||||
def test_specter_wallet_json():
|
||||
|
||||
|
||||
|
||||
def test_specter_wallet_json():
|
||||
parts = [
|
||||
'p1of3 {"label": "SeedSigner Dev Funds", "blockheight": 692143, "descriptor": "wsh(sortedmulti(4,[e0811b6b/48h/0h/0h/2h]xpub6E8v7uy63pCeJvHe5W8ea8zTnCtKMFgMRb5bueWWcUFMw6sWmUwTqxM8cFiKQRWkA2Fxth9HJZufJwjWTTvU1UGZNpTrh9khrswYMgeHiCt/0/*,[852b308f/48h/0h/0h/2h]xpub6ErhgAWfnEqW7xDBm1iLq5JjNyUS65YUFnjHLrRv9zmdDEtuE75bpWQ8o6bSBnpT6AkrrsA8eA5SmEFArZn11KEPaZJzx9mHTXPWZCsxLyh/0/*,[7edf9c59/48h/0h/0h/2h]xpub6DaFfKoe7Wpofr'
|
||||
, 'p2of3 bYeNo3Wv2AiLUMeyrPwotXfukFxUHbK4JxaLHTd5394QtH5wnjFzBgr2YnJpHhXv25Zsqv2APmMFvH1DsKHj5LCr3pmXs/0/*,[b433e095/48h/0h/0h/2h]xpub6EF51itHko2YhGTjVeuYbBgJjVbTzzpYzn2a3JwZHpDrMePRVgXGBHMx2Yv1KwgLsUn9i7ExcAo8uqMx4pDjVRY9J7qnceFAwRRj16dd5AS/0/*,[184d07eb/48h/0h/0h/2h]xpub6EEoTpcQu7N4R8D84pJjZ69j3mi'
|
||||
, 'p3of3 nevnYLDDoo2HBzYBXTQ4rGVf4XGTyCYFwJuZdsF9MyFYJNzYEjg5LGMA1ubTGWuDnjHAZz6ficVRDTSy/0/*,[3e451efe/48h/0h/0h/2h]xpub6ExQPvQxGBMaPxr8Fv7Vq91ztJFFX3VWvtpvex6UPZ1AptTeuAiJGCtKkgwJkrwpMZMagh9ex6rL4sM8axfFcdQbERoFCRUKTJxrBkJh56g/0/*))#c44hel9e", "devices": [{"type": "other", "label": "Keith"}, {"type": "other", "label": "Nick"}, {"type": "other", "label": "Richard"}, {"type": "other", "label": "Stephan"}, {"type": "other", "label": "SeedSigner 1"}, {"type": "other", "label": "SeedSigner 2"}]}'
|
||||
]
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(parts[1])
|
||||
d.add_data(parts[2])
|
||||
d.add_data(parts[0])
|
||||
@@ -429,15 +433,13 @@ def test_specter_wallet_json():
|
||||
assert d.qrType() == QRType.SPECTERWALLETQR
|
||||
assert d.isComplete() == True
|
||||
assert d.getWalletDescriptor() == "wsh(sortedmulti(4,[e0811b6b/48h/0h/0h/2h]xpub6E8v7uy63pCeJvHe5W8ea8zTnCtKMFgMRb5bueWWcUFMw6sWmUwTqxM8cFiKQRWkA2Fxth9HJZufJwjWTTvU1UGZNpTrh9khrswYMgeHiCt/0/*,[852b308f/48h/0h/0h/2h]xpub6ErhgAWfnEqW7xDBm1iLq5JjNyUS65YUFnjHLrRv9zmdDEtuE75bpWQ8o6bSBnpT6AkrrsA8eA5SmEFArZn11KEPaZJzx9mHTXPWZCsxLyh/0/*,[7edf9c59/48h/0h/0h/2h]xpub6DaFfKoe7WpofrbYeNo3Wv2AiLUMeyrPwotXfukFxUHbK4JxaLHTd5394QtH5wnjFzBgr2YnJpHhXv25Zsqv2APmMFvH1DsKHj5LCr3pmXs/0/*,[b433e095/48h/0h/0h/2h]xpub6EF51itHko2YhGTjVeuYbBgJjVbTzzpYzn2a3JwZHpDrMePRVgXGBHMx2Yv1KwgLsUn9i7ExcAo8uqMx4pDjVRY9J7qnceFAwRRj16dd5AS/0/*,[184d07eb/48h/0h/0h/2h]xpub6EEoTpcQu7N4R8D84pJjZ69j3minevnYLDDoo2HBzYBXTQ4rGVf4XGTyCYFwJuZdsF9MyFYJNzYEjg5LGMA1ubTGWuDnjHAZz6ficVRDTSy/0/*,[3e451efe/48h/0h/0h/2h]xpub6ExQPvQxGBMaPxr8Fv7Vq91ztJFFX3VWvtpvex6UPZ1AptTeuAiJGCtKkgwJkrwpMZMagh9ex6rL4sM8axfFcdQbERoFCRUKTJxrBkJh56g/0/*))#c44hel9e"
|
||||
|
||||
|
||||
|
||||
def test_specter_wallet_json2():
|
||||
|
||||
part = '{"label": "Testnet Single Zone", "blockheight": 2090512, "descriptor": "wpkh([990a73ad/84h/1h/0h]tpubDDHQMDnFdan2GyHBsG32VW9qiygbhVizGRTjiS3H79M49FSvpsvLXqLgp1yC7r43dXVHozWavi2Fc4WHUpZmQYmzoQbit28qJhLjScbAQWU/0/*)#ujr0xunp","devices": [{"type": "seedsigner", "label": "Single Seed Zone Testnet"}]}'
|
||||
|
||||
d = DecodeQR(wordlist=bip39.WORDLIST)
|
||||
d = DecodeQR()
|
||||
d.add_data(part)
|
||||
|
||||
assert d.qrType() == QRType.SPECTERWALLETQR
|
||||
assert d.isComplete() == True
|
||||
|
||||
|
||||
|
||||
+15
-15
@@ -16,8 +16,8 @@ def test_ur_qr_encode():
|
||||
|
||||
cnt = 0
|
||||
while cnt <= 10:
|
||||
fragment = e.nextPart()
|
||||
img = e.part2Image(fragment)
|
||||
fragment = e.next_part()
|
||||
img = e.part_to_image(fragment)
|
||||
cnt += 1
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ def test_specter_qr_encode():
|
||||
|
||||
cnt = 0
|
||||
while cnt <= 10:
|
||||
fragment = e.nextPart()
|
||||
fragment = e.next_part()
|
||||
if (cnt+1) == 1:
|
||||
assert fragment == "p1of117 cHNidP8BAIkCAAAAAaLlQ/VRNpx3IFtoRTOCnq2xfJwg/n7R9XB0TTTnlX/UHQAAA"
|
||||
elif (cnt+1) == 2:
|
||||
@@ -55,7 +55,7 @@ def test_specter_qr_encode():
|
||||
elif (cnt+1) == 11:
|
||||
assert fragment == "p11of117 REKtbWkqONAvETqIlMPWJ/f1uBvSCGFm+zzDYnnEBtuAYjZiQrzj9mFLQz4JUwAAC"
|
||||
|
||||
img = e.part2Image(fragment)
|
||||
img = e.part_to_image(fragment)
|
||||
cnt += 1
|
||||
|
||||
def test_seedsigner_qr():
|
||||
@@ -64,9 +64,9 @@ def test_seedsigner_qr():
|
||||
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), qr_type=QRType.SEEDQR)
|
||||
|
||||
print(e.nextPart())
|
||||
print(e.next_part())
|
||||
|
||||
assert e.nextPart() == "121802020768124106400009195602431595117715840445"
|
||||
assert e.next_part() == "121802020768124106400009195602431595117715840445"
|
||||
|
||||
def test_xpub_qr():
|
||||
|
||||
@@ -74,7 +74,7 @@ def test_xpub_qr():
|
||||
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.XPUBQR, network="test", derivation="m/48h/1h/0h/2h")
|
||||
|
||||
assert e.nextPart() == "[c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDNc5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBoMxfrVggXj5NJmaNEToWpax8GMMucozvAdqf1bW1JsZsfdBzsK3VUC5"
|
||||
assert e.next_part() == "[c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDNc5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBoMxfrVggXj5NJmaNEToWpax8GMMucozvAdqf1bW1JsZsfdBzsK3VUC5"
|
||||
|
||||
def test_specter_xpub_qr():
|
||||
|
||||
@@ -82,10 +82,10 @@ def test_specter_xpub_qr():
|
||||
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.SPECTERXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQR.DENSITY__LOW)
|
||||
|
||||
assert e.nextPart() == "p1of4 [c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDN"
|
||||
assert e.nextPart() == "p2of4 c5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBo"
|
||||
assert e.nextPart() == "p3of4 MxfrVggXj5NJmaNEToWpax8GMMucozvAdqf1bW1J"
|
||||
assert e.nextPart() == "p4of4 sZsfdBzsK3VUC5"
|
||||
assert e.next_part() == "p1of4 [c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDN"
|
||||
assert e.next_part() == "p2of4 c5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBo"
|
||||
assert e.next_part() == "p3of4 MxfrVggXj5NJmaNEToWpax8GMMucozvAdqf1bW1J"
|
||||
assert e.next_part() == "p4of4 sZsfdBzsK3VUC5"
|
||||
|
||||
def test_ur_xpub_qr():
|
||||
|
||||
@@ -93,8 +93,8 @@ def test_ur_xpub_qr():
|
||||
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.URXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQR.DENSITY__MEDIUM)
|
||||
|
||||
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/1-4/LPADAACSJKCYYNRDRTYAHDCAOEADCYSSMECPONAOLYTAADMETAADDLOXAXHDCLAOKSRLNLKPUEGYATHPMNRTAAOLAH"
|
||||
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/2-4/LPAOAACSJKCYYNRDRTYAHDCASNKKGHZMLUZORPVDGUOTECSTTKTOLPCWPTNTLKZTTIZTBEAAHDCXVDTPMYQDLAWNGL"
|
||||
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/3-4/LPAXAACSJKCYYNRDRTYAHDCARSTDSPZSBZSPGERLGDATUYNLPYBTGYIYYKBTWTAOSWKSVTSGCHBYDKYAVDSEPDJLAT"
|
||||
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/4-4/LPAAAACSJKCYYNRDRTYAHDCAAMTAADDYOEADLOCSDYYKADYKAEYKAOYKAOCYSSMECPONAYCYIOREKKJKAECADRRKIE"
|
||||
assert e.next_part() == "UR:CRYPTO-ACCOUNT/1-4/LPADAACSJKCYYNRDRTYAHDCAOEADCYSSMECPONAOLYTAADMETAADDLOXAXHDCLAOKSRLNLKPUEGYATHPMNRTAAOLAH"
|
||||
assert e.next_part() == "UR:CRYPTO-ACCOUNT/2-4/LPAOAACSJKCYYNRDRTYAHDCASNKKGHZMLUZORPVDGUOTECSTTKTOLPCWPTNTLKZTTIZTBEAAHDCXVDTPMYQDLAWNGL"
|
||||
assert e.next_part() == "UR:CRYPTO-ACCOUNT/3-4/LPAXAACSJKCYYNRDRTYAHDCARSTDSPZSBZSPGERLGDATUYNLPYBTGYIYYKBTWTAOSWKSVTSGCHBYDKYAVDSEPDJLAT"
|
||||
assert e.next_part() == "UR:CRYPTO-ACCOUNT/4-4/LPAAAACSJKCYYNRDRTYAHDCAAMTAADDYOEADLOCSDYYKADYKAEYKAOYKAOCYSSMECPONAYCYIOREKKJKAECADRRKIE"
|
||||
|
||||
|
||||
+16
-16
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type):
|
||||
assert len(seed_phrase) == mnemonic_length
|
||||
|
||||
e = EncodeQR(seed_phrase=seed_phrase, qr_type=qr_type, wordlist=bip39.WORDLIST)
|
||||
data = e.nextPart()
|
||||
data = e.next_part()
|
||||
print(data)
|
||||
|
||||
qr = QR()
|
||||
|
||||
Reference in New Issue
Block a user