Skip to content

Commit

Permalink
Merge #606: Ledger: Use ' instead of h for hardened character in wall…
Browse files Browse the repository at this point in the history
…et policies

a633731 tests: Update bitcoind patches (Andrew Chow)
b947b7e ledger: Use ' as hardened char (Andrew Chow)
680a9ea descriptor: allow setting custom hardened char (Andrew Chow)

Pull request description:

  Ledgers appear to not like `h` as the hardened character in wallet policies, so change it to use `'`. To make this happen, all of the descriptor `to_string` functions which eventually call `KeyOriginInfo.to_string()` will now take a `hardened_char` argument which defaults to `h`. This allows us to change the hardened char as needed while retaining a default of `h`.

  Fixes #605

Top commit has no ACKs.

Tree-SHA512: 1d74d41671628a0eb7b7b9849840688e3520ba2967c698ad451d55ee3f647a1edb7c5e63e5732f076d38eccb617ad92a82d0953d740a0339aab4ac307a0fa6e3
  • Loading branch information
achow101 committed May 23, 2022
2 parents b2e6e11 + a633731 commit 50d3b85
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 165 deletions.
24 changes: 12 additions & 12 deletions hwilib/descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,15 @@ def parse(cls, s: str) -> 'PubkeyProvider':

return cls(origin, pubkey, deriv_path)

def to_string(self) -> str:
def to_string(self, hardened_char: str = "h") -> str:
"""
Serialize the pubkey expression to a string to be used in a descriptor
:return: The pubkey expression as a string
"""
s = ""
if self.origin:
s += "[{}]".format(self.origin.to_string())
s += "[{}]".format(self.origin.to_string(hardened_char))
s += self.pubkey
if self.deriv_path:
s += self.deriv_path
Expand Down Expand Up @@ -229,25 +229,25 @@ def __init__(
self.subdescriptors = subdescriptors
self.name = name

def to_string_no_checksum(self) -> str:
def to_string_no_checksum(self, hardened_char: str = "h") -> str:
"""
Serializes the descriptor as a string without the descriptor checksum
:return: The descriptor string
"""
return "{}({}{})".format(
self.name,
",".join([p.to_string() for p in self.pubkeys]),
self.subdescriptors[0].to_string_no_checksum() if len(self.subdescriptors) > 0 else ""
",".join([p.to_string(hardened_char) for p in self.pubkeys]),
self.subdescriptors[0].to_string_no_checksum(hardened_char) if len(self.subdescriptors) > 0 else ""
)

def to_string(self) -> str:
def to_string(self, hardened_char: str = "h") -> str:
"""
Serializes the descriptor as a string with the checksum
:return: The descriptor with a checksum
"""
return AddChecksum(self.to_string_no_checksum())
return AddChecksum(self.to_string_no_checksum(hardened_char))

def expand(self, pos: int) -> "ExpandedScripts":
"""
Expand Down Expand Up @@ -327,8 +327,8 @@ def __init__(
if self.is_sorted:
self.pubkeys.sort()

def to_string_no_checksum(self) -> str:
return "{}({},{})".format(self.name, self.thresh, ",".join([p.to_string() for p in self.pubkeys]))
def to_string_no_checksum(self, hardened_char: str = "h") -> str:
return "{}({},{})".format(self.name, self.thresh, ",".join([p.to_string(hardened_char) for p in self.pubkeys]))

def expand(self, pos: int) -> "ExpandedScripts":
if self.thresh > 16:
Expand Down Expand Up @@ -405,16 +405,16 @@ def __init__(
super().__init__([internal_key], subdescriptors, "tr")
self.depths = depths

def to_string_no_checksum(self) -> str:
r = f"{self.name}({self.pubkeys[0].to_string()}"
def to_string_no_checksum(self, hardened_char: str = "h") -> str:
r = f"{self.name}({self.pubkeys[0].to_string(hardened_char)}"
path: List[bool] = [] # Track left or right for each depth
for p, depth in enumerate(self.depths):
r += ","
while len(path) <= depth:
if len(path) > 0:
r += "{"
path.append(False)
r += self.subdescriptors[p].to_string_no_checksum()
r += self.subdescriptors[p].to_string_no_checksum(hardened_char)
while len(path) > 0 and path[-1]:
if len(path) > 0:
r += "}"
Expand Down
6 changes: 3 additions & 3 deletions hwilib/devices/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def legacy_sign_tx() -> PSBT:
for xpub_bytes, xpub_origin in psbt2.xpub.items():
xpub = ExtendedKey.from_bytes(xpub_bytes)
if (xpub_origin.fingerprint == pk_origin.fingerprint) and (xpub_origin.path == pk_origin.path[:len(xpub_origin.path)]):
key_exprs.append(PubkeyProvider(xpub_origin, xpub.to_string(), "/**").to_string())
key_exprs.append(PubkeyProvider(xpub_origin, xpub.to_string(), "/**").to_string(hardened_char="'"))
break
else:
# No xpub, Ledger will not accept this multisig
Expand Down Expand Up @@ -448,7 +448,7 @@ def _get_singlesig_default_wallet_policy(self, addr_type: AddressType, account:
# Build a PubkeyProvider for the key we're going to use
origin = KeyOriginInfo(self.get_master_fingerprint(), path)
pk_prov = PubkeyProvider(origin, self.get_pubkey_at_path(f"m{origin._path_string()}").to_string(), "/**")
key_str = pk_prov.to_string()
key_str = pk_prov.to_string(hardened_char="'")

# Make the Wallet object
return PolicyMapWallet(name="", policy_map=template, keys_info=[key_str])
Expand All @@ -474,7 +474,7 @@ def display_singlesig_address(
BadArgumentError("Unknown address type")

origin = KeyOriginInfo(self.get_master_fingerprint(), path)
wallet = PolicyMapWallet(name="", policy_map=template, keys_info=[f"[{origin.to_string()}]"])
wallet = PolicyMapWallet(name="", policy_map=template, keys_info=["[{}]".format(origin.to_string(hardened_char="'"))])
else:
if not is_standard_path(path, addr_type, self.chain):
raise BadArgumentError("Ledger requires BIP 44 standard paths")
Expand Down
8 changes: 4 additions & 4 deletions hwilib/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,23 +280,23 @@ def serialize(self) -> bytes:
r += struct.pack("<" + "I" * len(self.path), *self.path)
return r

def _path_string(self) -> str:
def _path_string(self, hardened_char: str = "h") -> str:
s = ""
for i in self.path:
hardened = is_hardened(i)
i &= ~HARDENED_FLAG
s += "/" + str(i)
if hardened:
s += "h"
s += hardened_char
return s

def to_string(self) -> str:
def to_string(self, hardened_char: str = "h") -> str:
"""
Return the KeyOriginInfo as a string in the form <fingerprint>/<index>/<index>/...
This is the same way that KeyOriginInfo is shown in descriptors
"""
s = binascii.hexlify(self.fingerprint).decode()
s += self._path_string()
s += self._path_string(hardened_char)
return s

@classmethod
Expand Down

0 comments on commit 50d3b85

Please sign in to comment.