From 1fb21ae5ea7e3eefeaba067009730dfe5411c4fe Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Sun, 20 Nov 2022 19:13:56 +0100 Subject: [PATCH] add bip85 implementation --- src/embit/bip85.py | 46 +++++++++++++++++++++++++++++++++++++++ tests/tests/__init__.py | 1 + tests/tests/test_bip85.py | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 src/embit/bip85.py create mode 100644 tests/tests/test_bip85.py diff --git a/src/embit/bip85.py b/src/embit/bip85.py new file mode 100644 index 0000000..5733848 --- /dev/null +++ b/src/embit/bip85.py @@ -0,0 +1,46 @@ +from . import bip32, bip39, ec +import hmac + +BIP85_MAGIC = b"bip-entropy-from-k" +H = 0x80000000 + +class LANGUAGES: + """ + Tuples: (bip85 derivation index, wordlist). + """ + ENGLISH = (0, bip39.WORDLIST) + +def derive_entropy(root, app_index, path): + """ + Derive app-specific bip85 entropy using path m/83696968'/app_index'/...path' + """ + assert max(path) < H + derivation = [ H+83696968 ,H+app_index ] + [ p+H for p in path ] + derived = root.derive(derivation) + return hmac.new(BIP85_MAGIC, derived.secret, digestmod='sha512').digest() + +def derive_mnemonic(root, num_words=12, index=0, language=LANGUAGES.ENGLISH): + """Derive a new mnemonic with num_words using language (code, wordlist)""" + assert num_words in [12, 18, 24] + langcode, wordlist = language + path = [langcode, num_words, index] + entropy = derive_entropy(root, 39, path) + entropy_part = entropy[:num_words*4//3] + return bip39.mnemonic_from_bytes(entropy_part, wordlist=wordlist) + +def derive_wif(root, index=0): + """Derive ec.PrivateKey""" + entropy = derive_entropy(root, 2, [index]) + return ec.PrivateKey(entropy[:32]) + +def derive_xprv(root, index=0): + """Derive bip32.HDKey""" + entropy = derive_entropy(root, 32, [index]) + return bip32.HDKey(ec.PrivateKey(entropy[32:]),entropy[:32]) + +def derive_hex(root, num_bytes=32, index=0): + """Derive raw entropy from 16 to 64 bytes long""" + assert num_bytes <= 64 + assert num_bytes >= 16 + entropy = derive_entropy(root, 128169, [num_bytes, index]) + return entropy[:num_bytes] diff --git a/tests/tests/__init__.py b/tests/tests/__init__.py index 4f7c25b..365d340 100644 --- a/tests/tests/__init__.py +++ b/tests/tests/__init__.py @@ -10,6 +10,7 @@ from .test_psbtview import * from .test_taproot import * from .test_script import * +from .test_bip85 import * if sys.implementation.name != "micropython": from .test_ripemd160 import * diff --git a/tests/tests/test_bip85.py b/tests/tests/test_bip85.py new file mode 100644 index 0000000..7eee01a --- /dev/null +++ b/tests/tests/test_bip85.py @@ -0,0 +1,40 @@ +from unittest import TestCase +from embit import bip85, bip32 +from binascii import unhexlify + +ROOT = bip32.HDKey.from_string("xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb") + +VECTORS_BIP39 = [ + (12, 0, bip85.LANGUAGES.ENGLISH, "girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose"), + (18, 0, bip85.LANGUAGES.ENGLISH, "near account window bike charge season chef number sketch tomorrow excuse sniff circle vital hockey outdoor supply token"), + (24, 0, bip85.LANGUAGES.ENGLISH, "puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano"), +] + +VECTORS_WIF = [ + (0, "Kzyv4uF39d4Jrw2W7UryTHwZr1zQVNk4dAFyqE6BuMrMh1Za7uhp"), +] + +VECTORS_XPRV = [ + (0, "xprv9s21ZrQH143K2srSbCSg4m4kLvPMzcWydgmKEnMmoZUurYuBuYG46c6P71UGXMzmriLzCCBvKQWBUv3vPB3m1SATMhp3uEjXHJ42jFg7myX"), +] + +VECTORS_HEX = [ + (64, 0, "492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e71aa2d645442f878555d078fd1f1f67e368976f04137b1f7a0d19232136ca50c44614af72b5582a5c"), +] + +class Bip85Test(TestCase): + def test_bip39(self): + for num_words, index, lang, expected in VECTORS_BIP39: + self.assertEqual(bip85.derive_mnemonic(ROOT, num_words, index, language=lang), expected) + + def test_wif(self): + for idx, expected in VECTORS_WIF: + self.assertEqual(bip85.derive_wif(ROOT, idx).wif(), expected) + + def test_xprv(self): + for idx, expected in VECTORS_XPRV: + self.assertEqual(bip85.derive_xprv(ROOT, idx).to_string(), expected) + + def test_hex(self): + for num_bytes, idx, expected in VECTORS_HEX: + self.assertEqual(bip85.derive_hex(ROOT, num_bytes, idx), unhexlify(expected))