def parse_key(key: str) -> BIP32: """ Try to parse an extended key, whether it is in xpub, xpriv or mnemonic format. """ try: private_key = BIP32.from_xpriv(key) print('🔑 Read master private key successfully') return private_key except Exception: pass try: public_key = BIP32.from_xpub(key) print('🔑 Read master public key successfully') return public_key except Exception: pass try: language = Mnemonic.detect_language(key) seed = Mnemonic(language).to_seed(key) private_key = BIP32.from_seed(seed) print('🔑 Read mnemonic successfully') return private_key except Exception: pass raise ValueError( 'The key is invalid or the format isn\'t recognized. Make sure it\'s a mnemonic, xpriv or xpub.' )
def test_sanity_tests(): seed = bytes.fromhex( "1077a46dc8545d372f22d9e110ae6c5c2bf7620fe9c4c911f5404d112233e1aa270567dd3554092e051ba3ba86c303590b0309116ac89964ff284db2219d7511" ) first_bip32 = BIP32.from_seed(seed) sec_bip32 = BIP32.from_xpriv( "xprv9s21ZrQH143K3o4KUs47P2x9afhH31ekMo2foNTYwrU9wwZ8g5EatR9bn6YmCacdvnHWMnPFUqieQrnunrzuF5UfgGbhbEW43zRnhpPDBUL" ) assert first_bip32.get_master_xpriv() == sec_bip32.get_master_xpriv() assert first_bip32.get_master_xpub() == sec_bip32.get_master_xpub() # Fuzz it a bit for i in range(50): path = [int.from_bytes(os.urandom(3), "big") for _ in range(5)] h_path = [ HARDENED_INDEX + int.from_bytes(os.urandom(3), "big") for _ in range(5) ] mixed_path = [int.from_bytes(os.urandom(3), "big") for _ in range(5)] for i in mixed_path: if int.from_bytes(os.urandom(32), "big") % 2: i += HARDENED_INDEX assert first_bip32.get_xpriv_from_path( path) == sec_bip32.get_xpriv_from_path(path) assert first_bip32.get_xpub_from_path( path) == sec_bip32.get_xpub_from_path(path) assert first_bip32.get_xpriv_from_path( h_path) == sec_bip32.get_xpriv_from_path(h_path) assert first_bip32.get_xpub_from_path( h_path) == sec_bip32.get_xpub_from_path(h_path) assert first_bip32.get_xpriv_from_path( mixed_path) == sec_bip32.get_xpriv_from_path(mixed_path) assert first_bip32.get_xpub_from_path( mixed_path) == sec_bip32.get_xpub_from_path(mixed_path) # Taken from iancoleman's website bip32 = BIP32.from_seed( bytes.fromhex( "ac8c2377e5cde867d7e420fbe04d8906309b70d51b8fe58d6844930621a9bc223929155dcfebb4da9d62c86ec0d15adf936a663f4f0cf39cbb0352e7dac073d6" )) assert bip32.get_master_xpriv() == bip32.get_xpriv_from_path( [] ) == "xprv9s21ZrQH143K2GzaKJsW7DQsxeDpY3zqgusaSx6owWGC19k4mhwnVAsm4qPsCw43NkY2h1BzVLyxWHt9NKF86QRyBj53vModdGcNxtpD6KX" assert bip32.get_master_xpub() == bip32.get_xpub_from_path( [] ) == "xpub661MyMwAqRbcEm53RLQWUMMcWg4JwWih48oBFLWRVqoAsx5DKFG32yCEv8iH29TWpmo5KTcpsjXcea6Zx4Hc6PAbGnHjEDCf3yHbj7qdpnf" # Sanity checks for m/0'/0'/14/0'/18 xpriv = bip32.get_xpriv_from_path( [HARDENED_INDEX, HARDENED_INDEX, 14, HARDENED_INDEX, 18]) xpub = bip32.get_xpub_from_path( [HARDENED_INDEX, HARDENED_INDEX, 14, HARDENED_INDEX, 18]) assert xpriv == "xprvA2YVbLvEeKaPedw7F6RLwG3RgYnTq1xGCyDNMgZNWdEQnSUBQmKEuLyA6TSPsggt5xvyJHLD9L25tNLpQiP4Q8ZkQNo8ueAgeYj5zYq8hSm" assert xpub == "xpub6FXqzrT8Uh8gs81aM7xMJPzAEacxEUg7aC8yA4xz4xmPfEoKxJdVT9Hdwm3LwVQrSos2rhGDt8aGGHvdLr5LLAjK8pXFkbSpzGoGTXjd4z9" # Now if we our master is m/0'/0'/14, we should derive the same keys for # m/0'/18 ! xpriv2 = bip32.get_xpriv_from_path([HARDENED_INDEX, HARDENED_INDEX, 14]) assert xpriv2 == "xprv9yQJmvQMywM5i7UNuZ4RQ1A9rEMwAJCExPardkmBCB46S3vBqNEatSwLUrwLNLHBu1Kd9aGxGKDD5YAfs6hRzpYthciAHjtGadxgV2PeqY9" bip32 = BIP32.from_xpriv(xpriv2) assert bip32.get_master_xpriv() == xpriv2 assert bip32.get_xpriv_from_path([HARDENED_INDEX, 18]) == xpriv assert bip32.get_xpub_from_path([HARDENED_INDEX, 18]) == xpub
def __init__(self, master_key: BIP32, utxos: List[scanner.Utxo], address: str, amount_in_sat: int): """ Craft and sign a transaction that spends all the UTXOs and sends the requested funds to a specific address. """ output_script = scripts.build_output_script_from_address(address) if output_script is None: raise ValueError( 'The address is invalid or the format isn\'t recognized.') if amount_in_sat < NON_SEGWIT_DUST: raise ValueError('Not enough funds to create a sweep transaction.') self.outputs = [(amount_in_sat, output_script)] self.inputs = [] for index in range(len(utxos)): utxo = utxos[index] # Build the inputs for signing: they should all have empty scripts, save for the input that we are signing, # which should have the output script of a P2PKH output. pubkey = master_key.get_pubkey_from_path(utxo.path.to_list()) script = scripts.ScriptType.LEGACY.build_output_script(pubkey) inputs = [(u, script if u == utxo else b'', []) for u in utxos] if utxo.script_type == scripts.ScriptType.LEGACY: # If this is a legacy input, then the transaction digest is just the wire format serialization. tx = _serialize_tx(inputs, self.outputs, include_witness=False) else: # If this is a segwit input (native or not), then the transaction digest is the one defined in BIP143. tx = _serialize_tx_for_segwit_signing(index, inputs, self.outputs) # To produce the final message digest we need to append the sig-hash type, and double sha256 the message. tx.extend(SIGHASH_ALL.to_bytes(4, 'little')) hash = scripts.sha256(scripts.sha256(bytes(tx))) privkey = master_key.get_privkey_from_path(utxo.path.to_list()) signature = coincurve.PrivateKey(privkey).sign(hash, hasher=None) extended_signature = bytearray(signature) extended_signature.append(SIGHASH_ALL) extended_signature = bytes(extended_signature) self.inputs.append( (utxo, utxo.script_type.build_input_script(pubkey, extended_signature), utxo.script_type.build_witness(pubkey, extended_signature)))
def Init(self, request, context): node = Node() node.hsm_secret = request.hsm_secret.data # on chain wallet hkdf = HKDF(key=node.hsm_secret) r = hkdf.extract_key(info='bip32 seed'.encode(), length=32) logger.debug("bip32_key seed: %s" % r.hex()) node.bip32_key = BIP32.from_seed(r, network=self.network) # node pubkey, node msg sign r = hkdf.extract_key(info='nodeid'.encode(), length=32) node.node_privkey = r node.node_pk = r logger.info("new node privkey %s" % r.hex()) node_pk = coincurve.PrivateKey(secret=r) node.pubkey = node_pk.public_key.format() node.nodeid = binascii.hexlify(node.pubkey) logger.info("new node id %s" % node.nodeid) # channel secret_base node.secret_base = hkdf.extract_key(info='peer seed'.encode(), length=32) logger.debug("new channel secret base %s" % node.secret_base.hex()) if not self.nodes.get(node.pubkey) or request.coldstart: self.nodes[node.pubkey] = node reply = remotesigner_pb2.InitReply() reply.node_id.data = node.pubkey return reply
def test_vector_3(): seed = bytes.fromhex( "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be" ) bip32 = BIP32.from_seed(seed) # Chain m assert ( bip32.get_xpub_from_path([]) == "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13" ) assert ( bip32.get_xpriv_from_path([]) == "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6" ) assert (bip32.get_xpub_from_path("m") == bip32.get_xpub_from_path([])) assert (bip32.get_xpriv_from_path("m") == bip32.get_xpriv_from_path([])) # Chain m/0H assert ( bip32.get_xpub_from_path([HARDENED_INDEX]) == "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y" ) assert ( bip32.get_xpriv_from_path([HARDENED_INDEX]) == "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L" ) assert (bip32.get_xpub_from_path("m/0H") == bip32.get_xpub_from_path( [HARDENED_INDEX])) assert (bip32.get_xpriv_from_path("m/0H") == bip32.get_xpriv_from_path( [HARDENED_INDEX]))
def test_vector_1(): seed = bytes.fromhex("000102030405060708090a0b0c0d0e0f") bip32 = BIP32.from_seed(seed) # Chain m assert ( bip32.get_master_xpub() == "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" ) assert ( bip32.get_master_xpriv() == "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" ) # Chain m/0H assert ( bip32.get_xpub_from_path([HARDENED_INDEX]) == "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw" ) assert ( bip32.get_xpriv_from_path([HARDENED_INDEX]) == "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7" ) # m/0H/1 assert ( bip32.get_xpub_from_path([HARDENED_INDEX, 1]) == "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ" ) assert ( bip32.get_xpriv_from_path([HARDENED_INDEX, 1]) == "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" ) # m/0H/1/2H assert ( bip32.get_xpub_from_path([HARDENED_INDEX, 1, HARDENED_INDEX + 2]) == "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5" ) assert ( bip32.get_xpriv_from_path([HARDENED_INDEX, 1, HARDENED_INDEX + 2]) == "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM" ) # m/0H/1/2H/2 assert ( bip32.get_xpub_from_path([HARDENED_INDEX, 1, HARDENED_INDEX + 2, 2]) == "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV" ) assert ( bip32.get_xpriv_from_path([HARDENED_INDEX, 1, HARDENED_INDEX + 2, 2]) == "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" ) # m/0H/1/2H/2/1000000000 assert ( bip32.get_xpub_from_path( [HARDENED_INDEX, 1, HARDENED_INDEX + 2, 2, 1000000000]) == "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" ) assert ( bip32.get_xpriv_from_path( [HARDENED_INDEX, 1, HARDENED_INDEX + 2, 2, 1000000000]) == "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" )
def load_from_mnemonic(self, words): print 'mnemonic', words seed = mnemonic.Mnemonic('english').decode(words) print 'seed', seed xprv = BIP32.get_xprv_from_seed(seed) self.struct.seed.CopyFrom(xprv)
def _script_at(self, master_key: BIP32, index: int, account: int) -> Script: """ Render the script at a specific index and account pair. """ pubkey = master_key.get_pubkey_from_path(self.path.to_list(index, account)) script = self.script_type.build_output_script(pubkey) return Script(script, index, account, self)
def get_address(words, strange=10): address_list = [] mnemo = Mnemonic("english") seed = mnemo.to_seed(words, passphrase="") bip32 = BIP32.from_seed(seed) for i in range(0, strange): path = "m/44'/313'/0'/0/%i" % i pab_key = bip32.get_pubkey_from_path(path) key = zilkey.ZilKey(public_key=pab_key.hex()) address_list.append(key.address) return address_list
def __init__(self, mnemonic: str, language: str = "english", passphrase: str = ""): """ BIP44 HD wallet with a master mnemonic. :param mnemonic (str): The master mnemonic to derive keys :param language (str, optional): The mnemonic's language, default: "english" :param passphrase (str, optional): The mnemonic's passphrase, default: "" """ self._seed = Mnemonic(language).to_seed(mnemonic, passphrase) self._bip32 = BIP32.from_seed(self._seed)
def create_wallet(testnet=False): path = "m/44'/1'/0'/0/0" if testnet else "m/44'/0'/0'/0/0" mnemo = Mnemonic("english") words = mnemo.generate() seed = mnemo.to_seed(words, passphrase="") bip32 = BIP32.from_seed(seed) privkey = bip32.get_privkey_from_path(path) pubkey = base64.b64encode(bip32.get_pubkey_from_path(path)).decode('utf-8') definition = ['sig', {"pubkey": pubkey}] print(f"privkey: {privkey}") print(f"pubkey: {pubkey}") print(f"address: {get_chash_160(definition)}")
def from_mnemonic(cls, words: str, path="m/44'/494'/0'/0/0") -> PrivateKey: """ Create a PrivateKey instance from a given mnemonic phrase and a HD derivation path. If path is not given, default to Band's HD prefix 494 and all other indexes being zeroes. :param words: the mnemonic phrase for recover private key :param path: the HD path that follows the BIP32 standard :return: Initialized PrivateKey object """ seed = Mnemonic("english").to_seed(words) self = cls(_error_do_not_use_init_directly=True) self.signing_key = SigningKey.from_string( BIP32.from_seed(seed).get_privkey_from_path(path), curve=SECP256k1, hashfunc=hashlib.sha256, ) return self
def test_vault_address(vault_factory): wallets = vault_factory.get_wallets() # FIXME: separate the Bitcoin backends !! bitcoind = wallets[0].bitcoind for wallet in wallets: # It's burdensome to our xpub to be None in the list, but it allows us # to know which of the stakeholders we are, so.. all_xpubs = [keychain.get_master_xpub() if keychain else wallet.our_bip32.get_master_xpub() for keychain in wallet.keychains] # bitcoind should always return the same address as us for i in range(3): wallet_first_address = wallet.getnewaddress() bitcoind_first_address = bitcoind.addmultisigaddress(4, [ b2x(BIP32.from_xpub(xpub).get_pubkey_from_path([i])) for xpub in all_xpubs ])["address"] assert wallet_first_address == bitcoind_first_address
def get_wallets(self, emergency_privkeys=None): """Get 4 vaults, one for each stakeholder. Spin up the servers.""" bip32s = [BIP32.from_seed(os.urandom(32), "test") for _ in range(4)] xpubs = [bip32.get_master_xpub() for bip32 in bip32s] if emergency_privkeys is None: emergency_privkeys = [CKey(os.urandom(32)) for _ in range(4)] emergency_pubkeys = [k.pub for k in emergency_privkeys] self.vaults = [] # Generate some random 'OK' addresses acked_addresses = [self.bitcoind.getnewaddress() for _ in range(5)] for bip32 in bip32s: xpriv = bip32.get_master_xpriv() conf = self.bitcoind.rpc.__btc_conf_file__ cosigner_url = "http://localhost:{}".format(self.cosigning_port) sigserv_url = "http://localhost:{}".format(self.sigserver_port) self.vaults.append( Vault(xpriv, xpubs, emergency_pubkeys, conf, cosigner_url, sigserv_url, acked_addresses)) return self.vaults
def next_script( self, master_key: BIP32) -> Optional[Tuple[bytes, Path, ScriptType]]: """ Fetch the next script for the current descriptor. """ if self.index > self.max_index or self.account > self.max_account: return None # derive the next script path = self.path.with_account(self.account) pubkey = master_key.get_pubkey_from_path(path.to_list(self.index)) script = self.script_type.build_output_script(pubkey) # Since traversing the entire [0; MAX_INDEX] x [0; MAX_ACCOUNT] space of combinations might take a while, we # walk the (index, account) grid in diagonal order. This order prioritizes the most probable combinations # (ie. low index, low account), while letting us explore a large space in the long run. # # 0 1 2 # ↙ ↙ ↙ # (0,0) (1,0) (2,0) 3 # ↙ ↙ ↙ ↙ # (0,1) (1,1) (2,1) 4 # ↙ ↙ ↙ ↙ # (0,2) (1,2) (2,2) 5 # ↙ ↙ ↙ ↙ # (0,3) (1,3) (2,3) # ↙ ↙ ↙ if self.index == 0 or self.account == self.max_account: # if we reached the border, start in the next diagonal diagonal_total = self.index + self.account + 1 self.index = min(diagonal_total, self.max_index) self.account = diagonal_total - self.index else: # go down the diagonal self.index -= 1 self.account += 1 return script, path, self.script_type
def get_node(self): """Return decrypted HDNodeType (from stored mnemonic or encrypted HDNodeType)""" if not self.is_initialized(): raise NotInitializedException("Device not initalized") if self.is_locked(): raise Exception("Passphrase required") if self.struct.HasField("mnemonic") and self.struct.HasField("node"): raise Exception("Cannot have both mnemonic and node at the same time") if self.session.has_node(): # If we've already unlocked node, let's use it return self.session.node if self.struct.HasField("mnemonic"): print "Loading mnemonic" seed = Mnemonic(self.struct.language).to_seed( self.struct.mnemonic, passphrase=self.session.get_passphrase() ) self.session.set_node(BIP32.get_node_from_seed(seed)) else: print "Loading node" passphrase = self.session.get_passphrase() if passphrase: secret = PBKDF2( passphrase, "TREZORHD", iterations=2048, macmodule=hmac, digestmodule=hashlib.sha512 ).read(64) node = types.HDNodeType() node.CopyFrom(self.struct.node) aes_key = secret[:32] aes_iv = secret[32:48] aescbc = pyaes.AESModeOfOperationCBC(key=aes_key, iv=aes_iv) node.chain_code = aescbc.decrypt(node.chain_code[:16]) + aescbc.decrypt(node.chain_code[16:]) node.private_key = aescbc.decrypt(node.private_key[:16]) + aescbc.decrypt(node.private_key[16:]) self.session.set_node(node) else: self.session.set_node(self.struct.node) return self.session.node
def get_node(self): '''Return decrypted HDNodeType (from stored mnemonic or encrypted HDNodeType)''' if not self.is_initialized(): raise NotInitializedException("Device not initalized") if self.is_locked(): raise Exception("Passphrase required") if self.struct.HasField('mnemonic') and self.struct.HasField('node'): raise Exception("Cannot have both mnemonic and node at the same time") if self.session.has_node(): # If we've already unlocked node, let's use it return self.session.node if self.struct.HasField('mnemonic'): print "Loading mnemonic" seed = Mnemonic(self.struct.language).to_seed(self.struct.mnemonic, passphrase=self.session.get_passphrase()) self.session.set_node(BIP32.get_node_from_seed(seed)) else: print "Loading node" self.session.set_node(self.struct.node) return self.session.node
def seed_to_privkey(seed: str, path: str = DEFAULT_DERIVATION_PATH) -> bytes: """Get a private key from a mnemonic seed and a derivation path. Assumes a BIP39 mnemonic seed with no passphrase. Raises `ecdsa.MalformedPointError` if the resulting private key is invalid. """ seed_bytes = mnemonic.Mnemonic.to_seed(seed, passphrase="") # Generate wallet master key as per BIP32 bip32_magic_string = b"Bitcoin seed" seed_hmac = hmac.new(bip32_magic_string, seed_bytes, digestmod=hashlib.sha512).digest() master_key = seed_hmac[:32] chain_code = seed_hmac[32:] # Derive a private key from the given path derived_privkey = BIP32(chain_code, privkey=master_key).get_privkey_from_path(path) # Validate the derived private key. Can raise ecdsa.MalformedPointError here. ecdsa.SigningKey.from_string(derived_privkey, curve=ecdsa.SECP256k1) return derived_privkey
def __init__(self, xpriv, xpubs, emergency_pubkeys, bitcoin_conf_path, cosigning_url, sigserver_url, acked_addresses, current_index=0, birthdate=None): """ We need the xpub of all the other stakeholders to derive their pubkeys. :param xpriv: Who am I ? Has to correspond to one of the following xpub. As str. :param xpubs: A list of the xpub of all the stakeholders (as str), in the following order: 1) first trader 2) second trader 3) first "normie" stakeholder 4) second "normie" stakeholder. :param emergency_pubkeys: A list of the four offline keys of the stakeholders, as bytes. :param bitcoin_conf_path: Path to bitcoin.conf. :param cosigning_url: The url of the cosigning server. :param sigserver_url: The url of the server to post / get the sigs from other stakeholders. :param acked_addresses: Addresses to which we accept to spend. :param birthdate: The timestamp at which this wallet has been created. If not passed, will assume newly-created wallet. """ assert len(xpubs) == 4 self.our_bip32 = BIP32.from_xpriv(xpriv) self.keychains = [] for xpub in xpubs: if xpub != self.our_bip32.get_master_xpub(): self.keychains.append(BIP32.from_xpub(xpub)) else: self.keychains.append(None) self.all_xpubs = xpubs self.emergency_pubkeys = emergency_pubkeys # Ok, shitload of indexes. The current one is the lower bound of the # range we will import to bitcoind as watchonly. The max one is the # upper bond, the current "gen" one is to generate new addresses. self.current_index = current_index self.current_gen_index = self.current_index self.max_index = current_index + 500 self.index_treshold = self.max_index self.birthdate = int(time.time()) if birthdate is None else birthdate self.bitcoind = BitcoindApi(bitcoin_conf_path) # First of all, watch the emergency vault self.watch_emergency_vault() # And store the corresponding address.. txo = emergency_txout(self.emergency_pubkeys, 0) self.emergency_address = str( CBitcoinAddress.from_scriptPubKey(txo.scriptPubKey)) # The cosigning server, asked for its signature for the spend_tx self.cosigner = CosigningApi(cosigning_url) self.cosigner_pubkey = self.cosigner.get_pubkey() # The "sig" server, used to store and exchange signatures between # vaults and which provides us a feerate. # Who am I ? stk_id = self.keychains.index(None) + 1 self.sigserver = ServerApi(sigserver_url, stk_id) self.vault_addresses = [] self.unvault_addresses = [] self.update_watched_addresses() # We keep track of each vault, see below when we fill it for details # about what it contains. Basically all the transactions, the # signatures and some useful fields (like "are all txs signed ?"). self.vaults = [] self.vaults_lock = threading.Lock() # Poll for funds until we die self.funds_poller_stop = threading.Event() self.funds_poller = threading.Thread(target=self.poll_for_funds, daemon=True) self.funds_poller.start() # Poll for spends until we die self.acked_addresses = acked_addresses self.acked_spends = [] self.spends_poller_stop = threading.Event() self.spends_poller = threading.Thread(target=self.poll_for_spends, daemon=True) self.spends_poller.start() # Don't start polling for signatures just yet, we don't have any vault! self.update_sigs_stop = threading.Event() self.update_sigs_thread =\ threading.Thread(target=self.update_all_signatures, daemon=True) self.stopped = False
def test_vector_2(): seed = bytes.fromhex( "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" ) bip32 = BIP32.from_seed(seed) # Chain m assert ( bip32.get_master_xpub() == "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" ) assert ( bip32.get_master_xpriv() == "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" ) # Chain m/0 assert ( bip32.get_xpub_from_path([0]) == "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH" ) assert ( bip32.get_xpriv_from_path([0]) == "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt" ) assert (bip32.get_xpriv_from_path("m/0") == bip32.get_xpriv_from_path([0])) assert (bip32.get_xpub_from_path("m/0") == bip32.get_xpub_from_path([0])) # Chain m/0/2147483647H assert ( bip32.get_xpub_from_path([0, HARDENED_INDEX + 2147483647]) == "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a" ) assert ( bip32.get_xpriv_from_path([0, HARDENED_INDEX + 2147483647]) == "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9" ) assert (bip32.get_xpub_from_path("m/0/2147483647H") == bip32.get_xpub_from_path([0, HARDENED_INDEX + 2147483647])) assert (bip32.get_xpriv_from_path("m/0/2147483647H") == bip32.get_xpriv_from_path([0, HARDENED_INDEX + 2147483647])) # Chain m/0/2147483647H/1 assert ( bip32.get_xpub_from_path([0, HARDENED_INDEX + 2147483647, 1]) == "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon" ) assert ( bip32.get_xpriv_from_path([0, HARDENED_INDEX + 2147483647, 1]) == "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" ) assert (bip32.get_xpub_from_path("m/0/2147483647H/1") == bip32.get_xpub_from_path([0, HARDENED_INDEX + 2147483647, 1])) assert (bip32.get_xpriv_from_path("m/0/2147483647H/1") == bip32.get_xpriv_from_path([0, HARDENED_INDEX + 2147483647, 1])) # Chain m/0/2147483647H/1/2147483646H assert ( bip32.get_xpub_from_path( [0, HARDENED_INDEX + 2147483647, 1, HARDENED_INDEX + 2147483646]) == "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" ) assert ( bip32.get_xpriv_from_path( [0, HARDENED_INDEX + 2147483647, 1, HARDENED_INDEX + 2147483646]) == "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc" ) assert (bip32.get_xpub_from_path( "m/0/2147483647H/1/2147483646H") == bip32.get_xpub_from_path( [0, HARDENED_INDEX + 2147483647, 1, HARDENED_INDEX + 2147483646])) assert (bip32.get_xpriv_from_path( "m/0/2147483647H/1/2147483646H") == bip32.get_xpriv_from_path( [0, HARDENED_INDEX + 2147483647, 1, HARDENED_INDEX + 2147483646])) # Chain m/0/2147483647H/1/2147483646H/2 assert ( bip32.get_xpub_from_path([ 0, HARDENED_INDEX + 2147483647, 1, HARDENED_INDEX + 2147483646, 2 ]) == "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt" ) assert ( bip32.get_xpriv_from_path([ 0, HARDENED_INDEX + 2147483647, 1, HARDENED_INDEX + 2147483646, 2 ]) == "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" ) assert (bip32.get_xpub_from_path( "m/0/2147483647H/1/2147483646H/2") == bip32.get_xpub_from_path([ 0, HARDENED_INDEX + 2147483647, 1, HARDENED_INDEX + 2147483646, 2 ])) assert (bip32.get_xpriv_from_path( "m/0/2147483647H/1/2147483646H/2") == bip32.get_xpriv_from_path([ 0, HARDENED_INDEX + 2147483647, 1, HARDENED_INDEX + 2147483646, 2 ]))
def test_sanity_tests(): seed = bytes.fromhex( "1077a46dc8545d372f22d9e110ae6c5c2bf7620fe9c4c911f5404d112233e1aa270567dd3554092e051ba3ba86c303590b0309116ac89964ff284db2219d7511" ) first_bip32 = BIP32.from_seed(seed) sec_bip32 = BIP32.from_xpriv( "xprv9s21ZrQH143K3o4KUs47P2x9afhH31ekMo2foNTYwrU9wwZ8g5EatR9bn6YmCacdvnHWMnPFUqieQrnunrzuF5UfgGbhbEW43zRnhpPDBUL" ) assert first_bip32.get_master_xpriv() == sec_bip32.get_master_xpriv() assert first_bip32.get_master_xpub() == sec_bip32.get_master_xpub() # Fuzz it a bit for i in range(50): path = [int.from_bytes(os.urandom(3), "big") for _ in range(5)] h_path = [ HARDENED_INDEX + int.from_bytes(os.urandom(3), "big") for _ in range(5) ] mixed_path = [int.from_bytes(os.urandom(3), "big") for _ in range(5)] for i in mixed_path: if int.from_bytes(os.urandom(32), "big") % 2: i += HARDENED_INDEX assert first_bip32.get_xpriv_from_path( path) == sec_bip32.get_xpriv_from_path(path) assert first_bip32.get_xpub_from_path( path) == sec_bip32.get_xpub_from_path(path) assert first_bip32.get_xpriv_from_path( h_path) == sec_bip32.get_xpriv_from_path(h_path) assert first_bip32.get_xpub_from_path( h_path) == sec_bip32.get_xpub_from_path(h_path) assert first_bip32.get_xpriv_from_path( mixed_path) == sec_bip32.get_xpriv_from_path(mixed_path) assert first_bip32.get_xpub_from_path( mixed_path) == sec_bip32.get_xpub_from_path(mixed_path) # Taken from iancoleman's website bip32 = BIP32.from_seed( bytes.fromhex( "ac8c2377e5cde867d7e420fbe04d8906309b70d51b8fe58d6844930621a9bc223929155dcfebb4da9d62c86ec0d15adf936a663f4f0cf39cbb0352e7dac073d6" )) assert bip32.get_master_xpriv() == bip32.get_xpriv_from_path( [] ) == "xprv9s21ZrQH143K2GzaKJsW7DQsxeDpY3zqgusaSx6owWGC19k4mhwnVAsm4qPsCw43NkY2h1BzVLyxWHt9NKF86QRyBj53vModdGcNxtpD6KX" assert bip32.get_master_xpub() == bip32.get_xpub_from_path( [] ) == "xpub661MyMwAqRbcEm53RLQWUMMcWg4JwWih48oBFLWRVqoAsx5DKFG32yCEv8iH29TWpmo5KTcpsjXcea6Zx4Hc6PAbGnHjEDCf3yHbj7qdpnf" # Sanity checks for m/0'/0'/14/0'/18 xpriv = bip32.get_xpriv_from_path( [HARDENED_INDEX, HARDENED_INDEX, 14, HARDENED_INDEX, 18]) xpub = bip32.get_xpub_from_path( [HARDENED_INDEX, HARDENED_INDEX, 14, HARDENED_INDEX, 18]) assert xpriv == "xprvA2YVbLvEeKaPedw7F6RLwG3RgYnTq1xGCyDNMgZNWdEQnSUBQmKEuLyA6TSPsggt5xvyJHLD9L25tNLpQiP4Q8ZkQNo8ueAgeYj5zYq8hSm" assert xpub == "xpub6FXqzrT8Uh8gs81aM7xMJPzAEacxEUg7aC8yA4xz4xmPfEoKxJdVT9Hdwm3LwVQrSos2rhGDt8aGGHvdLr5LLAjK8pXFkbSpzGoGTXjd4z9" # Now if we our master is m/0'/0'/14, we should derive the same keys for # m/0'/18 ! xpriv2 = bip32.get_xpriv_from_path([HARDENED_INDEX, HARDENED_INDEX, 14]) assert xpriv2 == "xprv9yQJmvQMywM5i7UNuZ4RQ1A9rEMwAJCExPardkmBCB46S3vBqNEatSwLUrwLNLHBu1Kd9aGxGKDD5YAfs6hRzpYthciAHjtGadxgV2PeqY9" bip32 = BIP32.from_xpriv(xpriv2) assert bip32.get_master_xpriv() == xpriv2 assert bip32.get_xpriv_from_path([HARDENED_INDEX, 18]) == xpriv assert bip32.get_xpub_from_path([HARDENED_INDEX, 18]) == xpub # We should recognize the networks.. # .. for xprivs: bip32 = BIP32.from_xpriv( "xprv9wHokC2KXdTSpEepFcu53hMDUHYfAtTaLEJEMyxBPAMf78hJg17WhL5FyeDUQH5KWmGjGgEb2j74gsZqgupWpPbZgP6uFmP8MYEy5BNbyET" ) assert bip32.network == "main" bip32 = BIP32.from_xpriv( "tprv8ZgxMBicQKsPeCBsMzQCCb5JcW4S49MVL3EwhdZMF1RF71rgisZU4ZRvrHX6PZQEiNUABDLvYqpx8Lsccq8aGGR59qHAoLoE3iXYuDa8JTP" ) assert bip32.network == "test" # .. for xpubs: bip32 = BIP32.from_xpub( "xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU" ) assert bip32.network == "main" bip32 = BIP32.from_xpub( "tpubD6NzVbkrYhZ4WN3WiKRjeo2eGyYNiKNg8vcQ1UjLNJJaDvoFhmR1XwJsbo5S4vicSPoWQBThR3Rt8grXtP47c1AnoiXMrEmFdRZupxJzH1j" ) assert bip32.network == "test" # We should create valid network encoding.. assert BIP32.from_seed(os.urandom(32), "test").get_master_xpub().startswith("tpub") assert BIP32.from_seed(os.urandom(32), "test").get_master_xpriv().startswith("tprv") assert BIP32.from_seed(os.urandom(32), "main").get_master_xpub().startswith("xpub") assert BIP32.from_seed(os.urandom(32), "main").get_master_xpriv().startswith("xprv")
def setup_device(self, mnemonic, passphrase, wallet_manager, testnet): seed = Mnemonic.to_seed(mnemonic) xprv = seed_to_hd_master_key(seed, testnet=testnet) wallet_name = os.path.join(wallet_manager.cli_path + '_hotstorage', self.alias) wallet_manager.cli.createwallet(wallet_name, False, True) cli = wallet_manager.cli.wallet(wallet_name) # TODO: Maybe more than 1000? Maybe add mechanism to add more later. ## NOTE: This will work only on the network the device was added, so hot devices should be filtered out by network. coin = int(testnet) cli.importmulti([ { 'desc': AddChecksum('sh(wpkh({}/49h/{}h/0h/0/*))'.format(xprv, coin)), 'range': 1000, 'timestamp': 'now'}, { 'desc': AddChecksum('sh(wpkh({}/49h/{}h/0h/1/*))'.format(xprv, coin)), 'range': 1000, 'timestamp': 'now'}, { 'desc': AddChecksum('wpkh({}/84h/{}h/0h/0/*)'.format(xprv, coin)), 'range': 1000, 'timestamp': 'now'}, { 'desc': AddChecksum('wpkh({}/84h/{}h/0h/1/*)'.format(xprv, coin)), 'range': 1000, 'timestamp': 'now'}, { 'desc': AddChecksum('sh(wpkh({}/48h/{}h/0h/1h/0/*))'.format(xprv, coin)), 'range': 1000, 'timestamp': 'now'}, { 'desc': AddChecksum('sh(wpkh({}/48h/{}h/0h/1h/1/*))'.format(xprv, coin)), 'range': 1000, 'timestamp': 'now'}, { 'desc': AddChecksum('wpkh({}/48h/{}h/0h/2h/0/*)'.format(xprv, coin)), 'range': 1000, 'timestamp': 'now'}, { 'desc': AddChecksum('wpkh({}/48h/{}h/0h/2h/1/*)'.format(xprv, coin)), 'range': 1000, 'timestamp': 'now'}, ]) if passphrase: cli.encryptwallet(passphrase) bip32 = BIP32.from_seed(seed) xpubs = "" master_fpr = get_xpub_fingerprint(bip32.get_xpub_from_path('m/0h')).hex() if not testnet: # Nested Segwit xpub = bip32.get_xpub_from_path('m/49h/0h/0h') ypub = convert_xpub_prefix(xpub, b'\x04\x9d\x7c\xb2') xpubs += "[%s/49'/0'/0']%s\n" % (master_fpr, ypub) # native Segwit xpub = bip32.get_xpub_from_path('m/84h/0h/0h') zpub = convert_xpub_prefix(xpub, b'\x04\xb2\x47\x46') xpubs += "[%s/84'/0'/0']%s\n" % (master_fpr, zpub) # Multisig nested Segwit xpub = bip32.get_xpub_from_path('m/48h/0h/0h/1h') Ypub = convert_xpub_prefix(xpub, b'\x02\x95\xb4\x3f') xpubs += "[%s/48'/0'/0'/1']%s\n" % (master_fpr, Ypub) # Multisig native Segwit xpub = bip32.get_xpub_from_path('m/48h/0h/0h/2h') Zpub = convert_xpub_prefix(xpub, b'\x02\xaa\x7e\xd3') xpubs += "[%s/48'/0'/0'/2']%s\n" % (master_fpr, Zpub) else: # Testnet nested Segwit xpub = bip32.get_xpub_from_path('m/49h/1h/0h') upub = convert_xpub_prefix(xpub, b'\x04\x4a\x52\x62') xpubs += "[%s/49'/1'/0']%s\n" % (master_fpr, upub) # Testnet native Segwit xpub = bip32.get_xpub_from_path('m/84h/1h/0h') vpub = convert_xpub_prefix(xpub, b'\x04\x5f\x1c\xf6') xpubs += "[%s/84'/1'/0']%s\n" % (master_fpr, vpub) # Testnet multisig nested Segwit xpub = bip32.get_xpub_from_path('m/48h/1h/0h/1h') Upub = convert_xpub_prefix(xpub, b'\x02\x42\x89\xef') xpubs += "[%s/48'/1'/0'/1']%s\n" % (master_fpr, Upub) # Testnet multisig native Segwit xpub = bip32.get_xpub_from_path('m/48h/1h/0h/2h') Vpub = convert_xpub_prefix(xpub, b'\x02\x57\x54\x83') xpubs += "[%s/48'/1'/0'/2']%s\n" % (master_fpr, Vpub) keys, failed = Key.parse_xpubs(xpubs) if len(failed) > 0: # TODO: This should never occur, but just in case, we must make sure to catch it properly so it doesn't crash the app no matter what. raise Exception("Failed to parse these xpubs:\n" + "\n".join(failed)) else: self.add_keys(keys)
def SignFundingTx(self, request, context): """BOLT #3 - Funding Transaction Sign the funding transaction TODO this must be done after we know the remote signature on the commitment tx - need an API call that provides this to the signer. """ node_id = request.node_id.data node = self.nodes.get(node_id) logger.debug("SignFundingTx node:%s" % node_id) xpub = node.bip32_key.get_xpriv_from_path('m/0/0') onchain_bip32_key = BIP32.from_xpriv(xpub) tx = request.tx tx = Transaction.import_raw(tx.raw_tx_bytes, self.network) reply = remotesigner_pb2.SignFundingTxReply() witnesses = remotesigner_pb2.Witness() local_prv = None local_key = None logger.debug(request) logger.debug(request.tx.raw_tx_bytes.hex()) for i, input_desc in enumerate(request.tx.input_descs): key_index = input_desc.key_loc.key_index spend_type = input_desc.spend_type amount = input_desc.prev_output.value_sat channel_nonce = input_desc.close_info.channel_nonce.data commitment_point = input_desc.close_info.commitment_point.data channel = node.channels.get(channel_nonce) if key_index != 0: local_prv = onchain_bip32_key.get_privkey_from_path("m/%d" % key_index) local_key = Key(import_key=local_prv, is_private=True) elif channel: if commitment_point: local_priv_key = derive_priv_key( commitment_point, payment_basepoint, channel.basepoints.keys.payment_basepoint_secret) local_key = Key(import_key=local_priv_key, is_private=True) else: local_key = Key(import_key=channel.basepoints.keys. payment_basepoint_secret, is_private=True) else: witnesses.signature.data = b'' witnesses.pubkey.data = b'' reply.witnesses.extend([witnesses]) continue spend_type = spend_type_to_string(spend_type) tx.inputs[i].script_type = spend_type logger.debug("funding signature: %s" % spend_type) tx.inputs[i].witness_type = "segwit" tx.inputs[i].sigs_required = 1 tx.inputs[i].value = amount if spend_type == 'p2pkh': tx.witness_type = 'legacy' else: tx.witness_type = 'segwit' tx.sign(keys=[local_key], tid=i) witnesses.signature.data = tx.inputs[i].witnesses[0] witnesses.pubkey.data = tx.inputs[i].witnesses[1] logger.debug("funding signature: %s" % tx.inputs[i].signatures[0].as_der_encoded().hex()) logger.debug("funding public key %s" % tx.inputs[i].as_dict()['public_keys']) reply.witnesses.extend([witnesses]) logger.debug(reply) return reply