diff --git a/src/seedsigner/models/decode_qr.py b/src/seedsigner/models/decode_qr.py index 07f90b9c..5f1e2462 100644 --- a/src/seedsigner/models/decode_qr.py +++ b/src/seedsigner/models/decode_qr.py @@ -10,6 +10,8 @@ from seedsigner.helpers.ur2.ur_decoder import URDecoder from seedsigner.helpers.bcur import (cbor_decode, bc32decode) from seedsigner.models import QRType, Seed +from urtypes.crypto import PSBT as UR_PSBT + ### ### DecodeQR Class ### Purpose: used to process images or string data from animated qr codes with psbt data to create binary/base64 psbt @@ -141,7 +143,8 @@ class DecodeQR: if self.complete: if self.qr_type == QRType.PSBTUR2: cbor = self.ur_decoder.result_message().cbor - return cbor_decode(cbor) + return UR_PSBT.from_cbor(cbor).data + # return cbor_decode(cbor) elif self.qr_type == QRType.PSBTSPECTER: return self.specter_qr.getData() elif self.qr_type == QRType.PSBTURLEGACY: diff --git a/src/seedsigner/models/encode_qr.py b/src/seedsigner/models/encode_qr.py index 394113e2..788efd8b 100644 --- a/src/seedsigner/models/encode_qr.py +++ b/src/seedsigner/models/encode_qr.py @@ -9,6 +9,9 @@ from seedsigner.helpers.bcur import (bc32encode, cbor_encode, bcur_encode) from seedsigner.helpers.qr import QR from seedsigner.models import Seed, QRType, EncodeQRDensity +from urtypes.crypto import PSBT as UR_PSBT +from urtypes.crypto import Account, HDKey, Output, Keypath, PathComponent, SCRIPT_EXPRESSION_TAG_MAP + ### ### EncodeQR Class ### Purpose: used to encode psbt for displaying as qr image @@ -64,7 +67,7 @@ class EncodeQR: elif self.qr_type == QRType.SEEDSSQR: self.encoder = SeedSSQR(self.seed_phrase, self.wordlist) elif self.qr_type == QRType.XPUBQR: - self.encoder = XPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.wordlist) + self.encoder = URXPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.qr_density, self.wordlist) elif self.qr_type == QRType.SPECTERXPUBQR: self.encoder = SpecterXPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.qr_density, self.wordlist) else: @@ -100,10 +103,8 @@ class UREncodePSBTQR: def __init__(self, p, qr_density): self.psbt = p self.qr_max_fragement_size = 20 - - cbor_encoder = CBOREncoder() - cbor_encoder.encodeBytes(self.psbt.serialize()) - qr_ur_bytes = UR("crypto-psbt", cbor_encoder.get_bytes()) + + qr_ur_bytes = UR("crypto-psbt", UR_PSBT(self.psbt.serialize()).to_cbor()) if qr_density == EncodeQRDensity.LOW: self.qr_max_fragement_size = 10 @@ -313,4 +314,79 @@ class SpecterXPubQR(XPubQR): return len(self.parts) def isComplete(self): - return self.sent_complete \ No newline at end of file + return self.sent_complete + +class URXPubQR(XPubQR): + + def __init__(self, seed_phrase, passphrase, derivation, network, qr_density, wordlist): + + XPubQR.__init__(self, seed_phrase, passphrase, derivation, network, wordlist) + + if qr_density == EncodeQRDensity.LOW: + self.qr_max_fragement_size = 10 + elif qr_density == EncodeQRDensity.MEDIUM: + self.qr_max_fragement_size = 30 + elif qr_density == EncodeQRDensity.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, None) + + 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() \ No newline at end of file