Merge pull request #139 from newtonick/urtypes

Add UR Types
This commit is contained in:
SeedSigner
2022-01-28 10:16:15 -06:00
committed by GitHub
11 changed files with 116 additions and 11 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "urtypes"]
path = src/vendor/urtypes
url = https://github.com/jreesun/urtypes.git
+8 -1
View File
@@ -223,7 +223,14 @@ mkvirtualenv --python=python3 seedsigner-env
Now download the SeedSigner code:
```
git clone https://github.com/SeedSigner/seedsigner
git clone --recursive https://github.com/SeedSigner/seedsigner
```
If you have already cloned in the past and just need to init/pull submodules:
```
cd seedsigner
git submodule update --init
cd ..
```
Install the necessary Python dependencies:
+1
View File
@@ -7,3 +7,4 @@ qrcode==6.1
RPi.GPIO==0.7.0
six==1.16.0
spidev==3.5
-e src/vendor/urtypes
+1 -1
View File
@@ -5,7 +5,7 @@ persistent_settings = False
[display]
text_color = ORANGE
qr_background_color = FFFFFF
qr_background_color = 555555
camera_rotation = 0
[wallet]
+4 -1
View File
@@ -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:
+83 -6
View File
@@ -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
@@ -63,6 +66,8 @@ class EncodeQR:
self.encoder = UREncodePSBTQR(self.psbt, self.qr_density)
elif self.qr_type == QRType.SEEDSSQR:
self.encoder = SeedSSQR(self.seed_phrase, self.wordlist)
elif self.qr_type == QRType.URXPUBQR:
self.encoder = URXPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.qr_density, self.wordlist)
elif self.qr_type == QRType.XPUBQR:
self.encoder = XPubQR(self.seed_phrase, self.passphrase, self.derivation, self.network, self.wordlist)
elif self.qr_type == QRType.SPECTERXPUBQR:
@@ -100,10 +105,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
@@ -228,7 +231,6 @@ class XPubQR:
raise Exception('Wordlist Required')
version = bip32.detect_version(self.derivation, default="xpub", network=NETWORKS[network])
print(self.derivation)
self.seed = Seed(mnemonic=self.seed_phrase, passphrase=self.passphrase, wordlist=self.wordlist)
self.root = bip32.HDKey.from_seed(self.seed.seed, version=NETWORKS[network]["xprv"])
self.fingerprint = self.root.child(0).fingerprint
@@ -313,4 +315,79 @@ class SpecterXPubQR(XPubQR):
return len(self.parts)
def isComplete(self):
return self.sent_complete
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, 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()
+1
View File
@@ -17,6 +17,7 @@ class QRType(IntEnum):
SEED4LETTERMNEMONIC = 9
XPUBQR = 20
SPECTERXPUBQR = 21
URXPUBQR = 22
BITCOINADDRESSQR = 30
SPECTERWALLETQR = 40
URWALLETQR = 41
+3 -1
View File
@@ -203,8 +203,10 @@ class Settings(Singleton):
def getXPubType(software):
if software == "Specter Desktop":
return QRType.SPECTERXPUBQR
else:
elif software == "Blue Wallet":
return QRType.XPUBQR
else:
return QRType.URXPUBQR
@property
def qr_density_name(self):
+1 -1
View File
@@ -5,7 +5,7 @@ persistent_settings = False
[display]
text_color = ORANGE
qr_background_color = FFFFFF
qr_background_color = 555555
camera_rotation = 0
[wallet]
Vendored Submodule
+1
Submodule src/vendor/urtypes added at 8d643aa7f5
+10
View File
@@ -85,4 +85,14 @@ def test_specter_xpub_qr():
assert e.nextPart() == "p3of4 MxfrVggXj5NJmaNEToWpax8GMMucozvAdqf1bW1J"
assert e.nextPart() == "p4of4 sZsfdBzsK3VUC5"
def test_ur_xpub_qr():
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.URXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQRDensity.MEDIUM, wordlist=bip39.WORDLIST)
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"