From 3f67f1f0ea40971a68ae416ae067a017b7560c16 Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Sun, 10 Jul 2022 10:38:05 +0200 Subject: [PATCH] BIP-85 passwords --- bip85/__init__.py | 10 ++++++++++ bip85/app.py | 9 ++++++++- bip85/cli.py | 10 ++++++++++ bip85/tests/test_bip85.py | 20 ++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/bip85/__init__.py b/bip85/__init__.py index 2c033e1..610200e 100644 --- a/bip85/__init__.py +++ b/bip85/__init__.py @@ -25,6 +25,7 @@ from pycoin.symbols.btc import network as BTC from pycoin.encoding.bytes32 import from_bytes_32, to_bytes_32 import base58 +import base64 class BIP85(object): @@ -59,6 +60,15 @@ def bip32_xprv_to_hex(self, path, width, xprv_string): ent = self.bip32_xprv_to_entropy(path, xprv_string) return ent[0:width].hex() + def bip32_xprv_to_pwd(self, path, pwd_len, xprv_string): + # export entropy as hex + if not 20 <= pwd_len <= 86: + raise ValueError("'pwd_len' has to be in closed interval <20..86>") + path = self._decorate_path(path) + ent = self.bip32_xprv_to_entropy(path, xprv_string) + ent_b64 = base64.b64encode(ent).decode().strip() + return ent_b64[:pwd_len] + def bip32_xprv_to_xprv(self, path, xprv_string): path = self._decorate_path(path) ent = self.bip32_xprv_to_entropy(path, xprv_string) diff --git a/bip85/app.py b/bip85/app.py index d76665e..cd65ab7 100644 --- a/bip85/app.py +++ b/bip85/app.py @@ -53,12 +53,19 @@ def wif(xprv_string, index): def hex(xprv_string, index, width): - # m/83696968'/128169p'/index' + # m/83696968'/128169p'/width'/index' bip85 = BIP85() path = f"83696968p/128169p/{width}p/{index}p" return bip85.bip32_xprv_to_hex(path, width, xprv_string) +def pwd(xprv_string, index, pwd_len): + # m/83696968'/707764'/pwd_len'/index' + bip85 = BIP85() + path = f"83696968p/707764p/{pwd_len}p/{index}p" + return bip85.bip32_xprv_to_pwd(path, pwd_len, xprv_string) + + def xprv(xprv_string, index): # 83696968'/32'/index' bip85 = BIP85() diff --git a/bip85/cli.py b/bip85/cli.py index 645d60a..9b82310 100644 --- a/bip85/cli.py +++ b/bip85/cli.py @@ -66,6 +66,14 @@ def main(): required=True, help='Number of bytes to generate') subparsers.add_parser('xprv', help='Derive an XPRV (master private key)') + app_pwd_parser = subparsers.add_parser('pwd', + help='Derive a password') + app_pwd_parser.add_argument('--pwd-len', + type=int, + choices=range(20,87), + metavar="[20-86]", + required=True, + help='Desired password length') args = parser.parse_args() xprv = _get_xprv_from_args(args) print(f"Using master private key: {xprv}") @@ -77,6 +85,8 @@ def main(): print(app.hex(xprv, args.index, args.num_bytes)) elif args.bip85_app == 'xprv': print(app.xprv(xprv, args.index)) + elif args.bip85_app == "pwd": + print(app.pwd(xprv, args.index, args.pwd_len)) if __name__ == "__main__": diff --git a/bip85/tests/test_bip85.py b/bip85/tests/test_bip85.py index c81ca35..ece6282 100644 --- a/bip85/tests/test_bip85.py +++ b/bip85/tests/test_bip85.py @@ -100,6 +100,26 @@ def test_hex(path, width, expect): bip85 = BIP85() assert bip85.bip32_xprv_to_hex(path, width, XPRV) == expect +@pytest.mark.parametrize('path, pwd_len, expect', [ + ("83696968'/707764'/20'/0'", 20, "RrH7uVI0XlpddCbiuYV+"), + ("83696968'/707764'/21'/0'", 21, "dKLoepugzdVJvdL56ogNV"), + ("83696968'/707764'/24'/0'", 24, "vtV6sdNQTKpuefUMOHOKwUp1"), + ("83696968'/707764'/32'/1234'", 32, "mBhJgXCJd6IpdOu1cc/D1wU+5sxj/1tK"), + ("83696968'/707764'/64'/1234'", 64, "HBqosVLBhKneX8ZCZgLdvmA8biOdUV2S/AteE5Rs8sMT0pfG3aItk/IrHGEpY9um"), + ("83696968'/707764'/86'/1234'", 86, "7n3VQ63qjgY6OJBQxqWYToNRfzzN5J8DwN1D8JqlZfnsF+1LdPXG3gkOXighX4iKyKip8nRIhVVVObh/G41F7g"), + ]) +def test_pwd(path, pwd_len, expect): + bip85 = BIP85() + assert bip85.bip32_xprv_to_pwd(path, pwd_len, XPRV) == expect + +def test_pwd_out_of_range(): + bip85 = BIP85() + with pytest.raises(ValueError): + bip85.bip32_xprv_to_pwd("83696968'/707764'/87'/0'", 87, XPRV) + + with pytest.raises(ValueError): + bip85.bip32_xprv_to_pwd("83696968'/707764'/19'/0'", 19, XPRV) + def test_bipentropy_applications(): assert app.bip39(XPRV, 'english', 18, 0) == \ 'near account window bike charge season chef number sketch tomorrow excuse sniff circle vital hockey outdoor supply token'