Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add bip85 implementation #35

Merged
merged 1 commit into from
Jan 2, 2023
Merged
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
46 changes: 46 additions & 0 deletions src/embit/bip85.py
Original file line number Diff line number Diff line change
@@ -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]
1 change: 1 addition & 0 deletions tests/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down
40 changes: 40 additions & 0 deletions tests/tests/test_bip85.py
Original file line number Diff line number Diff line change
@@ -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))