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
96 changes: 21 additions & 75 deletions ledgerwallet/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Adapter,
Byte,
Construct,
Default,
Enum,
FlagsEnum,
GreedyBytes,
Expand Down Expand Up @@ -137,7 +138,10 @@ def _decode(self, obj, context, path):
if element & 0x80000000:
out.append(str(element & 0x7FFFFFFF) + "'")
else:
out.append(str(element))
if element == 0x7FFFFFFF:
out.append("*")
else:
out.append(str(element))
return "/".join(out)

def _encode(self, obj, context, path):
Expand All @@ -151,8 +155,21 @@ def _encode(self, obj, context, path):
for element in elements:
if element.endswith("'"):
out.append(0x80000000 | int(element[:-1]))
elif element == "*":
out.append(0x7FFFFFFF)
else:
out.append(int(element))
value = int(element)
if value == 0x7FFFFFFF:
raise ValueError(
f"Invalid BIP32 path: {element} value is reserved for wildcard"
" symbol"
)
elif value > 0x7FFFFFFF:
raise ValueError(
f"Invalid BIP32 path: {element} value is too large. For"
" hardened paths, use the quote symbol"
)
out.append(value)
return out


Expand Down Expand Up @@ -186,8 +203,8 @@ def _encode(self, obj, context, path):
Asn1Length,
Struct(
curve=Curve,
paths=Optional(GreedyRange(Bip32Path)),
paths_slip21=Optional(GreedyRange(Slip21Path)),
paths=Default(GreedyRange(Bip32Path), []),
paths_slip21=Default(GreedyRange(Slip21Path), []),
),
)

Expand Down Expand Up @@ -221,74 +238,3 @@ class BolosTag(enum.IntEnum):
)

AppParams = GreedyRange(Param)


def main():
params1 = AppParams.build(
[
{"type_": "BOLOS_TAG_APPNAME", "value": "SSH/PGP Agent"},
{"type_": "BOLOS_TAG_APPVERSION", "value": "0.0.4"},
{
"type_": "BOLOS_TAG_ICON",
"value": (
b"\x01\x00\x00\x00\x00\xFF\xFF\xFF\x00\x00\x18\xFC\x24\x02"
b"\x24\x0A\x24\x1A\x7E\x32\x66\x62\x6E\x62\x7E\x32\x00\x1A"
b"\x40\x0A\x5F\x02\x5F\x02\x40\x02\x40\xFE\x7F\x00\x00"
),
},
{
"type_": "BOLOS_TAG_DERIVEPATH",
"value": {
"curve": Curve.prime256r1 | Curve.ed25519 | Curve.slip21,
"paths": ["44'/535348'", "13'", "17'"],
"paths_slip21": ["MYPATH"],
},
},
]
)

params1_expected = (
b"\x01\x0D\x53\x53\x48\x2F\x50\x47\x50\x20\x41\x67\x65\x6E\x74"
b"\x02\x05\x30\x2E\x30\x2E\x34\x03\x29\x01\x00\x00\x00\x00\xFF"
b"\xFF\xFF\x00\x00\x18\xFC\x24\x02\x24\x0A\x24\x1A\x7E\x32\x66"
b"\x62\x6E\x62\x7E\x32\x00\x1A\x40\x0A\x5F\x02\x5F\x02\x40\x02"
b"\x40\xFE\x7F\x00\x00\x04\x1c\x0E\x02\x80\x00\x00\x2C\x80\x08"
b"\x2B\x34\x01\x80\x00\x00\x0D\x01\x80\x00\x00\x11\x87\x00\x4D"
b"\x59\x50\x41\x54\x48"
)
assert params1 == params1_expected

params2 = AppParams.build(
[
{"type_": "BOLOS_TAG_APPNAME", "value": "Vanadium"},
{"type_": "BOLOS_TAG_APPVERSION", "value": "0.0.1"},
{
"type_": "BOLOS_TAG_ICON",
"value": (
b"\x01\x00\x00\x00\x00\xFF\xFF\xFF\x00\x00\x18\xFC\x24\x02"
b"\x24\x0A\x24\x1A\x7E\x32\x66\x62\x6E\x62\x7E\x32\x00\x1A"
b"\x40\x0A\x5F\x02\x5F\x02\x40\x02\x40\xFE\x7F\x00\x00"
),
},
{
"type_": "BOLOS_TAG_DERIVEPATH",
"value": {
"curve": Curve.secp256k1 | Curve.slip21,
"paths": [""],
"paths_slip21": ["VANADIUM"],
},
},
]
)
params2_expected = (
b"\x01\x08\x56\x61\x6E\x61\x64\x69\x75\x6D\x02\x05\x30\x2E\x30"
b"\x2E\x31\x03\x29\x01\x00\x00\x00\x00\xFF\xFF\xFF\x00\x00\x18"
b"\xFC\x24\x02\x24\x0A\x24\x1A\x7E\x32\x66\x62\x6E\x62\x7E\x32"
b"\x00\x1A\x40\x0A\x5F\x02\x5F\x02\x40\x02\x40\xFE\x7F\x00\x00"
b"\x04\x0C\x09\x00\x89\x00\x56\x41\x4E\x41\x44\x49\x55\x4D"
)
assert params2 == params2_expected


if __name__ == "__main__":
main()
128 changes: 128 additions & 0 deletions tests/unit/test_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from construct import StreamError

from ledgerwallet.params import (
AppParams,
Asn1Length,
Bip32Path,
Curve,
Dependencies,
Dependency,
DerivationPath,
Expand Down Expand Up @@ -180,3 +182,129 @@ def test_parse(self):
self.assertEqual(result[0].version, version)
self.assertEqual(result[1].name, name2)
self.assertIsNone(result[1].version)


class AppParamsTest(TestCase):
def test_build_ssh_agent_params(self):
params = AppParams.build(
[
{"type_": "BOLOS_TAG_APPNAME", "value": "SSH/PGP Agent"},
{"type_": "BOLOS_TAG_APPVERSION", "value": "0.0.4"},
{
"type_": "BOLOS_TAG_ICON",
"value": (
b"\x01\x00\x00\x00\x00\xFF\xFF\xFF\x00\x00\x18\xFC\x24\x02"
b"\x24\x0A\x24\x1A\x7E\x32\x66\x62\x6E\x62\x7E\x32\x00\x1A"
b"\x40\x0A\x5F\x02\x5F\x02\x40\x02\x40\xFE\x7F\x00\x00"
),
},
{
"type_": "BOLOS_TAG_DERIVEPATH",
"value": {
"curve": Curve.prime256r1 | Curve.ed25519 | Curve.slip21,
"paths": ["44'/535348'", "13'", "17'"],
"paths_slip21": ["MYPATH"],
},
},
]
)

params_expected = (
b"\x01\x0D\x53\x53\x48\x2F\x50\x47\x50\x20\x41\x67\x65\x6E\x74"
b"\x02\x05\x30\x2E\x30\x2E\x34\x03\x29\x01\x00\x00\x00\x00\xFF"
b"\xFF\xFF\x00\x00\x18\xFC\x24\x02\x24\x0A\x24\x1A\x7E\x32\x66"
b"\x62\x6E\x62\x7E\x32\x00\x1A\x40\x0A\x5F\x02\x5F\x02\x40\x02"
b"\x40\xFE\x7F\x00\x00\x04\x1c\x0E\x02\x80\x00\x00\x2C\x80\x08"
b"\x2B\x34\x01\x80\x00\x00\x0D\x01\x80\x00\x00\x11\x87\x00\x4D"
b"\x59\x50\x41\x54\x48"
)
self.assertEqual(params, params_expected)

def test_build_vanadium_params(self):
params = AppParams.build(
[
{"type_": "BOLOS_TAG_APPNAME", "value": "Vanadium"},
{"type_": "BOLOS_TAG_APPVERSION", "value": "0.0.1"},
{
"type_": "BOLOS_TAG_ICON",
"value": (
b"\x01\x00\x00\x00\x00\xFF\xFF\xFF\x00\x00\x18\xFC\x24\x02"
b"\x24\x0A\x24\x1A\x7E\x32\x66\x62\x6E\x62\x7E\x32\x00\x1A"
b"\x40\x0A\x5F\x02\x5F\x02\x40\x02\x40\xFE\x7F\x00\x00"
),
},
{
"type_": "BOLOS_TAG_DERIVEPATH",
"value": {
"curve": Curve.secp256k1 | Curve.slip21,
"paths": [""],
"paths_slip21": ["VANADIUM"],
},
},
]
)
params_expected = (
b"\x01\x08\x56\x61\x6E\x61\x64\x69\x75\x6D\x02\x05\x30\x2E\x30"
b"\x2E\x31\x03\x29\x01\x00\x00\x00\x00\xFF\xFF\xFF\x00\x00\x18"
b"\xFC\x24\x02\x24\x0A\x24\x1A\x7E\x32\x66\x62\x6E\x62\x7E\x32"
b"\x00\x1A\x40\x0A\x5F\x02\x5F\x02\x40\x02\x40\xFE\x7F\x00\x00"
b"\x04\x0C\x09\x00\x89\x00\x56\x41\x4E\x41\x44\x49\x55\x4D"
)
self.assertEqual(params, params_expected)

def test_build_wildcard_params(self):
params = AppParams.build(
[
{
"type_": "BOLOS_TAG_DERIVEPATH",
"value": {
"curve": Curve.secp256k1,
"paths": ["44'/*"],
},
},
]
)
params_expected = b"\x04\x0A\x01\x02\x80\x00\x00\x2C\x7F\xFF\xFF\xFF"
self.assertEqual(params, params_expected)

def test_build_unauthorized_value(self):
try:
AppParams.build(
[
{
"type_": "BOLOS_TAG_DERIVEPATH",
"value": {
"curve": Curve.secp256k1,
"paths": ["2147483647"],
},
},
]
)
except ValueError as e:
self.assertIn("value is reserved for wildcard symbol", str(e))
except Exception as e:
self.fail(f"Raised unexpected exception: {type(e)} {e}")
else:
self.fail("Did not raise ValueError")

def test_build_too_large_value(self):
try:
AppParams.build(
[
{
"type_": "BOLOS_TAG_DERIVEPATH",
"value": {
"curve": Curve.secp256k1,
"paths": ["2147483648"],
},
},
]
)
except ValueError as e:
self.assertIn(
"value is too large. For hardened paths, use the quote symbol", str(e)
)
except Exception as e:
self.fail(f"Raised unexpected exception: {type(e)} {e}")
else:
self.fail("Did not raise ValueError")