Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion shared/ccc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ async def sssp_enable():
First step is to define a new PIN code that is used when you want to bypass or \
disable this feature.
''',
title="Spending Policy")
title="Spending Policy" if version.has_qwerty else "Spend Policy")

if ch != 'y':
# just a tourist
Expand Down
9 changes: 9 additions & 0 deletions shared/chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,4 +511,13 @@ def verify_recover_pubkey(sig, digest):
except:
raise ValueError('invalid signature')


def type_from_xpub_version(xpub_ver):
# https://github.com/satoshilabs/slips/blob/master/slip-0132.md
if xpub_ver in [0x0488b21e, 0x049d7cb2, 0x04b24746, 0x0295b43f, 0x02aa7ed3]:
return "BTC"
else:
assert xpub_ver in [0x043587cf, 0x044a5262, 0x045f1cf6, 0x024289ef, 0x02575483]
return "XTN"

# EOF
45 changes: 16 additions & 29 deletions shared/desc_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,26 +282,9 @@ def compile(self):
@classmethod
def parse_key(cls, key_str):
assert key_str[1:4].lower() == b"pub", "only extended pubkeys allowed"
# extended key
# or xpub or tpub as we use descriptors (SLIP-132 NOT allowed)
hint = key_str[0:1].lower()
if hint == b"x":
chain_type = "BTC"
elif hint == b"t":
chain_type = "XTN"
else:
# slip (ignore any implied address format)
chain_type = "BTC" if hint in b"yz" else "XTN"

node = ngu.hdnode.HDNode()
node.deserialize(key_str)
try:
assert node.privkey() is None, "no privkeys"
except ValueError:
# ValueError is thrown from libngu if key is public
pass

return node, chain_type
version = node.deserialize(key_str)
return node, chains.type_from_xpub_version(version)

def validate(self, my_xfp, disable_checks=False):
assert self.chain_type == chains.current_key_chain().ctype, "wrong chain"
Expand All @@ -311,9 +294,16 @@ def validate(self, my_xfp, disable_checks=False):
xfp = self.origin.cc_fp
is_mine = (xfp == my_xfp)

# raises ValueError on invalid pubkey (should be in libngu)
# raises ValueError on invalid pubkey in libngu
# https://github.com/switck/libngu/blob/master/ngu/hdnode.c#L148
# invalid public key not allowed even with disable checks
ngu.secp256k1.pubkey(self.node.pubkey())
# ngu.secp256k1.pubkey(self.node.pubkey())

try:
assert self.node.privkey() is None, "no privkeys"
except ValueError:
# ValueError is thrown from libngu if key is public
pass

if not disable_checks:
depth = self.node.depth()
Expand Down Expand Up @@ -410,21 +400,18 @@ def from_cc_json(cls, vals, af_str):
# new firmware, prefer key expression
return cls.from_string(vals[key_exp])

# TODO
node, _, _, _ = chains.slip132_deserialize(vals[af_str])
ek = chains.current_chain().serialize_public(node)
return cls.from_cc_data(vals["xfp"], vals["%s_deriv" % af_str], ek)

@classmethod
def from_psbt_xpub(cls, ek_bytes, xfp_path):
xfp, *path = xfp_path
koi = KeyOriginInfo(a2b_hex(xfp2str(xfp)), path)
# TODO this should be done by C code, no need to base58 encode/decode
# byte-serialized key should be decodable
ek = ngu.codecs.b58_encode(ek_bytes)
node, chain_type = cls.parse_key(ek.encode())

return cls(node, koi, KeyDerivationInfo(), chain_type=chain_type)
koi = KeyOriginInfo(ustruct.pack("<I", xfp), path)
node = ngu.hdnode.HDNode()
version = node.deser_bytes(ek_bytes)
return cls(node, koi, KeyDerivationInfo(),
chain_type=chains.type_from_xpub_version(version))

@property
def is_provably_unspendable(self):
Expand Down
21 changes: 15 additions & 6 deletions testing/test_miniscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -3178,9 +3178,11 @@ def test_same_key_set_miniscript(get_cc_key, bitcoin_core_signer, create_core_wa

title, story = cap_story()
if 'OK TO SEND' not in title:
pick_menu_item(fname)
time.sleep(0.1)
title, story = cap_story()
try:
pick_menu_item(fname)
time.sleep(0.1)
title, story = cap_story()
except: pass

assert title == "OK TO SEND?"
assert "msc2" in story
Expand All @@ -3194,8 +3196,9 @@ def test_same_key_set_miniscript(get_cc_key, bitcoin_core_signer, create_core_wa
@pytest.mark.parametrize("orig_der", [False, True])
def test_specific_wallet_signing_xpubs(orig_der, get_cc_key, bitcoin_core_signer, create_core_wallet,
offer_minsc_import, press_select, bitcoind, start_sign,
cap_story, end_sign, clear_miniscript, goto_home):
cap_story, end_sign, clear_miniscript, goto_home, use_regtest):
goto_home()
use_regtest()
clear_miniscript()

msc = "wsh(or_d(pk(@D),and_v(v:multi(2,@A,@B,@C),older(65535))))"
Expand Down Expand Up @@ -3255,8 +3258,14 @@ def test_specific_wallet_signing_xpubs(orig_der, get_cc_key, bitcoin_core_signer
end_sign(accept=True)

item = po.xpubs[0]
# wrong key
key_wrong = item[0][:-1] + b"\x10"
# wrong key - but has to be valid - otherwise "bad pubkey is raised"
node = BIP32Node.from_master_secret(os.urandom(32))
if orig_der:
a, _ = bk.split("]")
der = "/".join(a[1:].split("/")[1:])
node = node.subkey_for_path(der)

key_wrong = node.node.serialize_public()
po.xpubs[0] = (key_wrong, item[1])

start_sign(po.as_bytes(), miniscript="msc")
Expand Down
2 changes: 1 addition & 1 deletion testing/test_sssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def doit(pin=None, mag=None, vel=None, whitelist=None, w2fa=None, has_violation=
title, story = cap_story()

# it is possible that PIN was set beforehand
if title == "Spending Policy":
if title == ("Spending Policy" if is_q1 else "Spend Policy"):
assert "stops you from signing transactions unless conditions are met" in story
assert "locked into a special mode" in story
assert "First step is to define a new PIN" in story
Expand Down