def entropy_from_mnemonic(cls, seed): m = Mnemonic(cls.MNEMONIC_LANG) seed = seed.lower() if not m.check(seed): raise WalletError("Invalid mnemonic seed.") ent = m.to_entropy(seed) if not cls._verify_entropy(ent): raise WalletError("Seed entropy is too low.") return bytes(ent)
def test(words): mnemo = Mnemonic("english") if mnemo.check(words): print("valid=1") else: print("valid=0")
def new_device(): app.specter.check() err = None device_type = "" device_name = "" xpubs = "" strength = 128 mnemonic = generate_mnemonic(strength=strength) if request.method == 'POST': action = request.form['action'] device_type = request.form['device_type'] device_name = request.form['device_name'] if action == "newcolddevice": if not device_name: err = "Device name must not be empty" elif device_name in app.specter.device_manager.devices_names: err = "Device with this name already exists" xpubs = request.form['xpubs'] if not xpubs: err = "xpubs name must not be empty" keys, failed = Key.parse_xpubs(xpubs) if len(failed) > 0: err = "Failed to parse these xpubs:\n" + "\n".join(failed) if err is None: device = app.specter.device_manager.add_device( name=device_name, device_type=device_type, keys=keys) return redirect("/devices/%s/" % device.alias) elif action == "newhotdevice": if not device_name: err = "Device name must not be empty" elif device_name in app.specter.device_manager.devices_names: err = "Device with this name already exists" if len(request.form['mnemonic'].split(' ')) not in [ 12, 15, 18, 21, 24 ]: err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words." mnemo = Mnemonic('english') if not mnemo.check(request.form['mnemonic']): err = "Invalid mnemonic entered." if err is None: mnemonic = request.form['mnemonic'] passphrase = request.form['passphrase'] device = app.specter.device_manager.add_device( name=device_name, device_type=device_type, keys=[]) device.setup_device(mnemonic, passphrase, app.specter.wallet_manager, app.specter.chain != 'main') return redirect("/devices/%s/" % device.alias) elif action == 'generatemnemonic': strength = int(request.form['strength']) mnemonic = generate_mnemonic(strength=strength) return render_template("device/new_device.jinja", device_type=device_type, device_name=device_name, xpubs=xpubs, mnemonic=mnemonic, strength=strength, error=err, specter=app.specter, rand=rand)
def load_device_by_mnemonic(self, mnemonic, pin, passphrase_protection, label, language, skip_checksum=False): m = Mnemonic('english') if not skip_checksum and not m.check(mnemonic): raise Exception("Invalid mnemonic checksum") # Convert mnemonic to UTF8 NKFD mnemonic = Mnemonic.normalize_string(mnemonic) # Convert mnemonic to ASCII stream mnemonic = normalize_nfc(mnemonic) if self.features.initialized: raise Exception( "Device is initialized already. Call wipe_device() and try again." ) resp = self.call( proto.LoadDevice(mnemonic=mnemonic, pin=pin, passphrase_protection=passphrase_protection, language=language, label=label, skip_checksum=skip_checksum)) self.init_device() return resp
def load_device_by_mnemonic(self, mnemonic, pin, passphrase_protection, label, language, skip_checksum=False): m = Mnemonic("english") if not skip_checksum and not m.check(mnemonic): raise Exception("Invalid mnemonic checksum") # Convert mnemonic to UTF8 NKFD mnemonic = Mnemonic.normalize_string(mnemonic) # Convert mnemonic to ASCII stream mnemonic = unicode(str(bytearray(mnemonic, "utf-8")), "utf-8") if self.features.initialized: raise Exception("Device is initialized already. Call wipe_device() and try again.") resp = self.call( proto.LoadDevice( mnemonic=mnemonic, pin=pin, passphrase_protection=passphrase_protection, language=language, label=label, skip_checksum=skip_checksum, ) ) self.init_device() return resp
def restore_from_seed(self): logger.debug('In restore_from_seed') from mnemonic import Mnemonic MNEMONIC = Mnemonic(language="english") info1 = ( "Please enter your BIP39 seed phrase in order to restore your wallet." ) layout = [[sg.Text("Enter Seed")], [sg.Text(info1)], [sg.InputText(key='seed')], [ sg.Checkbox('Extends this seed with custom words', key='use_passphrase') ], [sg.Button('Back'), sg.Button('Next')]] window = sg.Window("Satochip-Bridge: Enter seed", layout, icon=self.satochip_icon) while True: event, values = window.read() if event == 'Next': if not MNEMONIC.check( values['seed']): # check that seed is valid self.client.request( 'show_error', "Invalid BIP39 seed! Please type again!") else: break else: # event=='Back' break window.close() del window # logger.debug("Event:"+str(type(event))+str(event)) # logger.debug("Values:"+str(type(values))+str(values)) return (event, values)
def load_device_by_mnemonic(self, mnemonic, pin, passphrase_protection, label, language='english', skip_checksum=False, expand=False): # Convert mnemonic to UTF8 NKFD mnemonic = Mnemonic.normalize_string(mnemonic) # Convert mnemonic to ASCII stream mnemonic = mnemonic.encode('utf-8') m = Mnemonic('english') if expand: mnemonic = m.expand(mnemonic) if not skip_checksum and not m.check(mnemonic): raise ValueError("Invalid mnemonic checksum") if self.features.initialized: raise RuntimeError("Device is initialized already. Call wipe_device() and try again.") resp = self.call(proto.LoadDevice(mnemonic=mnemonic, pin=pin, passphrase_protection=passphrase_protection, language=language, label=label, skip_checksum=skip_checksum)) self.init_device() return resp
def recover_phrase(): m = Mnemonic('english') four = [w for w in m.wordlist if len(w) == 4] six = [w for w in m.wordlist if len(w) == 6] for i in product(four, six): mnemonic = phrase + ' '.join(i) if m.check(mnemonic) and m.to_seed(mnemonic).hex().startswith(seed): return mnemonic
def _check_list(self, language, vectors): mnemo = Mnemonic(language) for v in vectors: code = mnemo.to_mnemonic(unhexlify(v[0])) seed = hexlify(Mnemonic.to_seed(code, passphrase = 'TREZOR')) self.assertIs(mnemo.check(v[1]), True) self.assertEqual(v[1], code) self.assertEqual(v[2], seed)
def _check_list(self, language, vectors): mnemo = Mnemonic(language) for v in vectors: code = mnemo.to_mnemonic(unhexlify(v[0])) seed = hexlify(Mnemonic.to_seed(code, passphrase='TREZOR')) self.assertIs(mnemo.check(v[1]), True) self.assertEqual(v[1], code) self.assertEqual(v[2], seed)
def restore(ctx): """ Restore a wallet from a mnemonic \b If you accidently deleted your wallet file or the file became corrupted, use this command to restore your wallet. You must have your 12 word phrase (mnemonic) that was displayed when you created your wallet. """ # Stop daemon if it's running. d = None try: d = get_daemonizer() except OSError as e: pass if d: try: d.stop() except exceptions.DaemonizerError as e: click.echo("ERROR: Couldn't stop daemon: %s" % e) ctx.exit(code=4) # Check to see if the current wallet path exists if os.path.exists(ctx.obj['wallet_path']): if click.confirm("Wallet file already exists and may have a balance. Do you want to delete it?"): os.remove(ctx.obj['wallet_path']) else: click.echo("Not continuing.") ctx.exit(code=4) # Ask for mnemonic mnemonic = click.prompt("Please enter the wallet's 12 word mnemonic") # Sanity check the mnemonic m = Mnemonic(language='english') if not m.check(mnemonic): click.echo("ERROR: Invalid mnemonic.") ctx.exit(code=5) if click.confirm("Did the wallet have a passphrase?"): passphrase = get_passphrase() else: passphrase = '' # Try creating the wallet click.echo("\nRestoring...") wallet = Two1Wallet.import_from_mnemonic( data_provider=ctx.obj['data_provider'], mnemonic=mnemonic, passphrase=passphrase) wallet.to_file(ctx.obj['wallet_path']) if Two1Wallet.check_wallet_file(ctx.obj['wallet_path']): click.echo("Wallet successfully restored.") else: click.echo("Wallet not restored.") ctx.exit(code=6)
class SeedDialog(QtGui.QDialog): def __init__(self, persoData, parent=None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalization01seed.Ui_Dialog() self.ui.setupUi(self) self.ui.seed.setEnabled(False) self.ui.RestoreWalletButton.toggled.connect(self.restoreWalletToggled) self.ui.NextButton.clicked.connect(self.processNext) self.ui.CancelButton.clicked.connect(self.processCancel) if MNEMONIC: self.mnemonic = Mnemonic('english') self.ui.mnemonicNotAvailableLabel.hide() def restoreWalletToggled(self, toggled): self.ui.seed.setEnabled(toggled) def processNext(self): self.persoData['seed'] = None if self.ui.RestoreWalletButton.isChecked(): # Check if it's an hexa string seedText = str(self.ui.seed.text()) if len(seedText) == 0: QMessageBox.warning(self, "Error", "Please enter a seed", "OK") return if seedText[-1] == 'X': seedText = seedText[0:-1] try: self.persoData['seed'] = seedText.decode('hex') except: pass if self.persoData['seed'] == None: if not MNEMONIC: QMessageBox.warning( self, "Error", "Mnemonic API not available. Please install https://github.com/trezor/python-mnemonic", "OK") return if not self.mnemonic.check(seedText): QMessageBox.warning(self, "Error", "Invalid mnemonic", "OK") return self.persoData['seed'] = Mnemonic.to_seed(seedText) else: if (len(self.persoData['seed']) < 32) or (len( self.persoData['seed']) > 64): QMessageBox.warning(self, "Error", "Invalid seed length", "OK") return dialog = SecurityDialog(self.persoData, self) self.hide() dialog.exec_() def processCancel(self): self.reject() self.persoData['main'].reject()
def _check_list(self, language, vectors): mnemo = Mnemonic(language) for v in vectors: code = mnemo.to_mnemonic(bytes.fromhex(v[0])) seed = Mnemonic.to_seed(code, passphrase="TREZOR") xprv = Mnemonic.to_hd_master_key(seed) self.assertIs(mnemo.check(v[1]), True) self.assertEqual(v[1], code) self.assertEqual(v[2], seed.hex()) self.assertEqual(v[3], xprv)
def check_bip39_case(vectors, language="english"): mnemo = Mnemonic(language) for v in vectors: code = mnemo.to_mnemonic(binascii.unhexlify(v[0])) seed = binascii.hexlify(Mnemonic.to_seed(code, passphrase=v[4])) if sys.version >= '3': seed = seed.decode('utf8') print('checking this phrase: ' + v[1]) assert mnemo.check(v[1]) assert v[1] == code assert v[2] == seed
def _check_list(self, language, vectors): mnemo = Mnemonic(language) for v in vectors: code = mnemo.to_mnemonic(unhexlify(v[0])) seed = hexlify(Mnemonic.to_seed(code, passphrase="TREZOR")) xprv = Mnemonic.to_hd_master_key(unhexlify(seed)) seed = seed.decode("utf8") self.assertIs(mnemo.check(v[1]), True) self.assertEqual(v[1], code) self.assertEqual(v[2], seed) self.assertEqual(v[3], xprv)
def parse_mnemonic_to_master_key(string): mnemo = Mnemonic("english") words = string.split() mnemonic = " ".join(words) if len(words) not in {12, 24}: print("Error: Mnemonic phrase must be either 12 or 24 words long. Exiting.") sys.exit(1) if mnemo.check(mnemonic) != True: print("Error: The mnemonic phrase is invalid. Exiting.") sys.exit(1) seed = mnemo.to_seed(mnemonic) return mnemo.to_hd_master_key(seed, network in {"testnet", "regtest"})
def _check_list(self, language, vectors): mnemo = Mnemonic(language) for v in vectors: code = mnemo.to_mnemonic(unhexlify(v[0])) seed = hexlify(Mnemonic.to_seed(code, passphrase="TREZOR")) xprv = Mnemonic.to_hd_master_key(unhexlify(seed)) if sys.version >= "3": seed = seed.decode("utf8") self.assertIs(mnemo.check(v[1]), True) self.assertEqual(v[1], code) self.assertEqual(v[2], seed) self.assertEqual(v[3], xprv)
def unlock(self, passphrase: str = "") -> None: if self.root_priv is not None: return mnemonic = Mnemonic(self.language) # TODO skip wallet words words = self.shards.wallet_words() if mnemonic.check(words): seed = Mnemonic.to_seed(words, passphrase=passphrase) self.root_priv = bip32_master_key(seed) else: raise HermitError("Wallet words failed checksum.")
class SeedDialog(QtGui.QDialog): def __init__(self, persoData, parent = None): QDialog.__init__(self, parent) self.persoData = persoData self.ui = ui.personalization01seed.Ui_Dialog() self.ui.setupUi(self) self.ui.seed.setEnabled(False) self.ui.RestoreWalletButton.toggled.connect(self.restoreWalletToggled) self.ui.NextButton.clicked.connect(self.processNext) self.ui.CancelButton.clicked.connect(self.processCancel) if MNEMONIC: self.mnemonic = Mnemonic('english') self.ui.mnemonicNotAvailableLabel.hide() def restoreWalletToggled(self, toggled): self.ui.seed.setEnabled(toggled) def processNext(self): self.persoData['seed'] = None if self.ui.RestoreWalletButton.isChecked(): # Check if it's an hexa string seedText = str(self.ui.seed.text()) if len(seedText) == 0: QMessageBox.warning(self, "Error", "Please enter a seed", "OK") return if seedText[-1] == 'X': seedText = seedText[0:-1] try: self.persoData['seed'] = seedText.decode('hex') except: pass if self.persoData['seed'] == None: if not MNEMONIC: QMessageBox.warning(self, "Error", "Mnemonic API not available. Please install https://github.com/trezor/python-mnemonic", "OK") return if not self.mnemonic.check(seedText): QMessageBox.warning(self, "Error", "Invalid mnemonic", "OK") return self.persoData['seed'] = Mnemonic.to_seed(seedText) else: if (len(self.persoData['seed']) < 32) or (len(self.persoData['seed']) > 64): QMessageBox.warning(self, "Error", "Invalid seed length", "OK") return dialog = SecurityDialog(self.persoData, self) self.hide() dialog.exec_() def processCancel(self): self.reject() self.persoData['main'].reject()
def restore_wallet(PIN): if (len(PIN) == 0): PIN = choose_PIN_code() retry = True while retry: userWords = [] print_screen_title("RESTORE AN EXISTING WALLET (step 2/3)") print "Please type in your 24 words backup phrase." print "Warning: BIP39 is " + term.bold("case-sensitive!") + "\n" for i in range(1, 25): validWord = False while not validWord: typedWord = raw_input("Word #" + str(i) + "? ") typedWord = typedWord.lower() if (len(typedWord) > 0): if (typedWord in english_wordlist): userWords.append(typedWord) validWord = True else: print "This word is not in the official BIP39 english wordlist." else: print "Type a word a please." bip39Seed = " ".join(userWords) mnemo = Mnemonic('english') if mnemo.check(bip39Seed) == False: print "" print "An " + term.bold("error occurred") + "." print "Your BIP39 seed is invalid !\n" question = "Try again to type the seed?" retry = yes_or_no_question(question) else: retry = False print "\nWe're almost done!" print "Here is your BIP39 seed:\n" print_wordlist(bip39Seed) print "\nPlease check that it is correct." question = "Continue and restore your wallet from this seed?" choice = yes_or_no_question(question) if choice == True: start_flashing(PIN, bip39Seed) else: restore_wallet(PIN)
def restore_wallet(PIN): if len(PIN) == 0: PIN = choose_PIN_code() retry = True while retry: userWords = [] print_screen_title("RESTORE AN EXISTING WALLET (step 2/3)") print "Please type in your 24 words backup phrase." print "Warning: BIP39 is " + term.bold("case-sensitive!") + "\n" for i in range(1, 25): validWord = False while not validWord: typedWord = raw_input("Word #" + str(i) + "? ") typedWord = typedWord.lower() if len(typedWord) > 0: if typedWord in english_wordlist: userWords.append(typedWord) validWord = True else: print "This word is not in the official BIP39 english wordlist." else: print "Type a word a please." bip39Seed = " ".join(userWords) mnemo = Mnemonic("english") if mnemo.check(bip39Seed) == False: print "" print "An " + term.bold("error occurred") + "." print "Your BIP39 seed is invalid !\n" question = "Try again to type the seed?" retry = yes_or_no_question(question) else: retry = False print "\nWe're almost done!" print "Here is your BIP39 seed:\n" print_wordlist(bip39Seed) print "\nPlease check that it is correct." question = "Continue and restore your wallet from this seed?" choice = yes_or_no_question(question) if choice == True: start_flashing(PIN, bip39Seed) else: restore_wallet(PIN)
def new_device_mnemonic(device_type): err = None strength = 128 mnemonic = generate_mnemonic(strength=strength) existing_device = None if request.method == "POST": if len(request.form["mnemonic"].split(" ")) not in [ 12, 15, 18, 21, 24 ]: err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words." mnemo = Mnemonic("english") if not mnemo.check(request.form["mnemonic"]): err = "Invalid mnemonic entered." range_start = int(request.form["range_start"]) range_end = int(request.form["range_end"]) if range_start > range_end: err = "Invalid address range selected." mnemonic = request.form["mnemonic"] passphrase = request.form["passphrase"] file_password = request.form["file_password"] existing_device = request.form.get("existing_device", None) if existing_device: existing_device = app.specter.device_manager.get_by_alias( existing_device) if not err: return render_template( "device/new_device/new_device_keys.jinja", device_class=get_device_class(device_type), mnemonic=mnemonic, passphrase=passphrase, file_password=file_password, range_start=range_start, range_end=range_end, existing_device=existing_device, error=err, specter=app.specter, rand=rand, ) return render_template( "device/new_device/new_device_mnemonic.jinja", device_type=device_type, strength=strength, mnemonic=mnemonic, existing_device=existing_device, error=err, specter=app.specter, rand=rand, )
def page5_import_account_is_complete(self): words = self.edit_seed.toPlainText() if not words: self.edit_seed.setStyleSheet('background-color: white;') return False validator = Mnemonic('english') if validator.check(words): self.edit_seed.setStyleSheet('background-color: #c4df9b;') self._mnemonic = words return True else: self.edit_seed.setStyleSheet('background-color: #fff79a;') return False
def verify_koinify_words(words): """ This function checks to make sure there are multiple words and that the first word is in the english wordlist. Both of these errors would crash the Mnemonic library, so they should be checked before using it. The Mnemonic library checks for other errors. """ if ' ' in words: wordchecker = Mnemonic('english') firstword = words.split(' ')[0] if firstword in wordchecker.wordlist: check = wordchecker.check(words) return check else: return False else: return False
def load_device_by_mnemonic( client, mnemonic, pin, passphrase_protection, label, language="english", skip_checksum=False, expand=False, ): # Convert mnemonic to UTF8 NKFD mnemonic = Mnemonic.normalize_string(mnemonic) # Convert mnemonic to ASCII stream mnemonic = mnemonic.encode() m = Mnemonic("english") if expand: mnemonic = m.expand(mnemonic) if not skip_checksum and not m.check(mnemonic): raise ValueError("Invalid mnemonic checksum") if client.features.initialized: raise RuntimeError( "Device is initialized already. Call device.wipe() and try again." ) resp = client.call( proto.LoadDevice( mnemonic=mnemonic, pin=pin, passphrase_protection=passphrase_protection, language=language, label=label, skip_checksum=skip_checksum, ) ) client.init_device() return resp
def main() -> None: parser = argparse.ArgumentParser(description='Encode/Decode mnemonics') parser.add_argument( '-i --input', dest='typein', choices=['generate', 'hex', 'entropy', 'mnemonic', 'stamp'], default='generate', help="generate mnemonic or input type at stdin") parser.add_argument( '-o --output', dest='typeout', choices=['entropy', 'hex', 'mnemonic', 'stamp', 'seed', 'key'], default='mnemonic', help="type of output to print to stdout") parser.add_argument('-l --language', dest='lang', choices=[ f.split('.')[0] for f in os.listdir(Mnemonic._get_directory()) ], default='english') parser.add_argument('-s --strength', dest='strength', choices=[128, 160, 192, 224, 256], default=128) parser.add_argument('-p --passphrase', dest='passphrase', type=str, default='') parser.add_argument('-t --testnet', dest='testnet', type=bool, default=False) args = parser.parse_args() m = Mnemonic(args.lang) # input types if args.typein == 'generate': mnemonic = m.generate(args.strength) elif args.typein == 'hex': num = int(sys.stdin.readline().strip(), 16) mnemonic = m.from_int(num) elif args.typein == 'entropy': entropy = sys.stdin.buffer.read() mnemonic = m.to_mnemonic(entropy) elif args.typein == 'mnemonic': mnemonic = sys.stdin.readline().strip() if not m.check(mnemonic): raise ValueError(mnemonic) elif args.typein == 'stamp': stamp = sys.stdin.readline().strip() mnemonic = m.from_stamp(stamp) # output types if args.typeout == 'entropy': sys.stdout.buffer.write(m.to_entropy(mnemonic)) if args.typeout == 'hex': print(hex(m.to_int(mnemonic))) elif args.typeout == 'mnemonic': print(mnemonic) elif args.typeout == 'stamp': print(m.to_stamp(mnemonic)) elif args.typeout == 'seed': print(m.to_seed(mnemonic, args.passphrase)) elif args.typeout == 'key': print( m.to_hd_master_key(m.to_seed(mnemonic, args.passphrase), args.testnet))
def test_failed_checksum(self): code = ( "bless cloud wheel regular tiny venue bird web grief security dignity zoo" ) mnemo = Mnemonic("english") self.assertFalse(mnemo.check(code))
class HDWallet(BaseWallet): """ Hierarchical Deterministic Wallet based in BIP32 (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) """ def __init__(self, *, words: Optional[Any] = None, language: str = 'english', passphrase: bytes = b'', gap_limit: int = 20, word_count: int = 24, directory: str = './', pubsub: Optional[Any] = None, reactor: Optional[Any] = None, initial_key_generation: Optional[Any] = None) -> None: """ :param words: words to generate the seed. It's a string with the words separated by a single space. If None we generate new words when starting the wallet :type words: string :param language: language of the words :type language: string :param passphrase: one more security level to generate the seed :type passphrase: bytes :param gap_limit: maximum of unused addresses in sequence (default value based in https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#address-gap-limit) :type gap_limit: int :param word_count: quantity of words that are gonna generate the seed Possible choices are [12, 15, 18, 21, 24] :type word_count: int :param initial_key_generation: number of keys that will be generated in the initialization If not set we make it equal to gap_limit :type initial_key_generation: int :raises ValueError: Raised on invalid word_count """ super().__init__(directory=directory, pubsub=pubsub, reactor=reactor) # Dict[string(base58), BIP32Key] self.keys: Dict[str, Any] = {} # Last index that the address was shared # We use this index to know which address should be shared with the user # This index together with last_generated_index show us if the gap limit was achieved self.last_shared_index = 0 # Last index that the address was generated self.last_generated_index = 0 # Maximum gap between indexes of last generated address and last used address self.gap_limit = gap_limit # XXX Should we save this data in the object? self.language = language self.words = words self.passphrase = passphrase self.mnemonic = None # Used in admin frontend to know which wallet is being used self.type = self.WalletType.HD # Validating word count if word_count not in WORD_COUNT_CHOICES: raise ValueError( 'Word count ({}) is not one of the options {}.'.format( word_count, WORD_COUNT_CHOICES)) self.word_count = word_count # Number of keys that will be generated in the initialization self.initial_key_generation = initial_key_generation or gap_limit def _manually_initialize(self): """ Create words (if is None) and start seed and master node Then we generate the first addresses, so we can check if we already have transactions """ self.mnemonic = Mnemonic(self.language) if not self.words: # Initialized but still locked return # Validate words first self.validate_words() assert isinstance(self.passphrase, bytes), 'Passphrase must be in bytes' # Master seed seed = self.mnemonic.to_seed(self.words, self.passphrase.decode('utf-8')) # Master node from pycoin.networks.registry import network_for_netcode _register_pycoin_networks() network = network_for_netcode('htr') key = network.keys.bip32_seed(seed) # Until account key should be hardened # Chain path = 44'/0'/0'/0 # 44' (hardened) -> BIP44 # 280' (hardened) -> Coin type (280 = hathor) # 0' (hardened) -> Account # 0 -> Chain self.chain_key = key.subkey_for_path('44H/280H/0H/0') for key in self.chain_key.children(self.initial_key_generation, 0, False): self._key_generated(key, key.child_index()) def get_private_key(self, address58: str) -> 'EllipticCurvePrivateKey': """ We get the private key bytes and generate the cryptography object :param address58: address in base58 :type address58: string :return: Private key object. :rtype: :py:class:`cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` """ return self.keys[address58] def generate_new_key(self, index): """ Generate a new key in the tree at defined index We add this new key to self.keys and set last_generated_index :param index: index to generate the key :type index: int """ new_key = self.chain_key.subkey(index) self._key_generated(new_key, index) def _key_generated(self, key, index): """ Add generated key to self.keys and set last_generated_index :param key: generated key of hd wallet :type key: pycoin.key.Key.Key :param index: index to generate the key :type index: int """ self.keys[self.get_address(key)] = key self.last_generated_index = index def get_address(self, new_key): return new_key.address() def get_key_at_index(self, index): """ Return the key generated by the index in the parameter :param index: index to return the key :type index: int """ return self.chain_key.subkey(index) def tokens_received(self, address58: str) -> None: """ Method called when the wallet receive new tokens If the gap limit is not yet achieved we generate more keys :param address58: address that received the token in base58 :type address58: string """ received_key = self.keys[address58] # If the gap now is less than the limit, we generate the new keys until the limit # Because we might be in sync phase, so we need those keys pre generated diff = self.last_generated_index - received_key.child_index() if (self.gap_limit - diff) > 0: for _ in range(self.gap_limit - diff): self.generate_new_key(self.last_generated_index + 1) # Last shared index should be at least the index after the received one self.last_shared_index = max(self.last_shared_index, received_key.child_index() + 1) def get_unused_address(self, mark_as_used: bool = True) -> str: """ Return an address that is not used yet :param mark_as_used: if True we consider that this address is already used :type mark_as_used: bool :return: unused address in base58 :rtype: string """ if self.last_shared_index != self.last_generated_index: # Only in case we are not yet in the gap limit if mark_as_used: self.last_shared_index += 1 else: if mark_as_used: self.publish_update(HathorEvents.WALLET_GAP_LIMIT, limit=self.gap_limit) key = self.get_key_at_index(self.last_shared_index) return self.get_address(key) def is_locked(self) -> bool: """ Return if wallet is currently locked The wallet is locked if self.words is None :return: if wallet is locked :rtype: bool """ return self.words is None def lock(self): """ Lock the wallet Set all parameters to default values """ self.words = None self.keys = {} self.passphrase = b'' self.language = '' self.unspent_txs = {} self.spent_txs = [] self.balance = 0 self.last_shared_index = 0 self.last_generated_index = 0 def unlock(self, tx_storage, words=None, passphrase=b'', language='english'): """ Unlock the wallet Set all parameters to initialize the wallet and load the txs :param tx_storage: storage from where I should load the txs :type tx_storage: :py:class:`hathor.transaction.storage.transaction_storage.TransactionStorage` :param words: words to generate the seed. It's a string with the words separated by a single space. If None we generate new words when starting the wallet :type words: string :param language: language of the words :type language: string :param passphrase: one more security level to generate the seed :type passphrase: bytes :return: hd wallet words. Generated in this method or passed as parameter :rtype: string """ self.language = language if not words: # Decide to choose words automatically # Can be a different language than self.mnemonic m = Mnemonic(self.language) # We can't pass the word_count to generate method, only the strength # Multiplying by 10.67 gives the result we expect words = m.generate(strength=int(self.word_count * 10.67)) self.words = words self.passphrase = passphrase self._manually_initialize() self.load_txs(tx_storage) return words def load_txs(self, tx_storage): """ Load all saved txs to fill the wallet txs :param tx_storage: storage from where I should load the txs :type tx_storage: :py:class:`hathor.transaction.storage.transaction_storage.TransactionStorage` """ for tx in tx_storage._topological_sort(): self.on_new_tx(tx) def validate_words(self): """ Validate if set of words is valid If words is None or is not valid we raise error :raises InvalidWords: when the words are invalid """ if not self.words or not self.mnemonic.check(self.words): raise InvalidWords def get_input_aux_data(self, data_to_sign: bytes, private_key: 'Key') -> Tuple[bytes, bytes]: """ Sign the data to be used in input and get public key compressed in bytes :param data_to_sign: Data to be signed :type data_to_sign: bytes :param private_key: private key to sign data :type private_key: pycoin.key.Key.Key :return: public key compressed in bytes and signature :rtype: tuple[bytes, bytes] """ prehashed_msg = hashlib.sha256( hashlib.sha256(data_to_sign).digest()).digest() signature = private_key.sign(prehashed_msg) return private_key.sec(), signature
def test_failed_checksum(self): code = ('bless cloud wheel regular tiny venue bird web grief security ' 'dignity zoo') mnemo = Mnemonic('english') self.assertFalse(mnemo.check(code))
class PollyAudit(): """ Auditing tests and utilities for Polly. """ def __init__(self, wordfile = 'wordlist.txt'): # Read create the mnemonic wordlist object self.mnemonic = Mnemonic("english") # Set up a default reference wallet self.wallet = Wallet.from_master_secret(bytes(0)) # Set up a Polly communication pipe self.polly = PollyCom() # Default print padding self.PAD = "{:35}" # # Tests # def test_set_seed(self, wordlist): """ Sets the wallet seed for Polly and the reference wallet. Note: Subsequent tests will use the seed set by this routine. wordlist - a space separated string of 18 mnemonic words from the Polly wordlist. Note: the checksum must be correct (part of the 18th word) - see BIP0039. gen_wordlist can be used to generate a wordlist including the proper checksum. """ assert len(wordlist.split(" ")) == 18, "expecting 18 words" assert self.mnemonic.check(wordlist) == True, "invalid word list" print (self.PAD.format("Set seed"), end='') # Set polly self.polly.send_set_master_seed(wordlist) print (self.__outok()) # Set the reference wallet seed = self.mnemonic.to_seed(wordlist) self.wallet = Wallet.from_master_secret(seed) def test_key(self, keytype, account = 0, chain = 0, address = 0): """ Performs a public key retrieval test, comparing Polly's key against the reference wallet. keytype - Type of key to retrieve, valid values are KEY_MASTER, KEY_ACCOUNT, KEY_CHAIN, or KEY_ADDRESS. account - Account to use for type KEY_ACCOUNT, KEY_CHAIN, KEY_ADDRESS. chain - Chain to use for type KEY_CHAIN, KEY_ADDRESS. address - Index (0 - 0x7FFFFFFF) to use for type KEY_ADDRESS. """ assert address < 0x80000000, "hardened address keys are not supported" if keytype == PollyCom.KEY_MASTER: print(self.PAD.format("Get master key"), end='') refkey = self.wallet check_chaincode = False elif keytype == PollyCom.KEY_ACCOUNT: print(self.PAD.format("Get account key m/" + str(account) + "h"), end='') refkey = self.wallet.subkey(account, is_hardened = True) check_chaincode = True elif keytype == PollyCom.KEY_CHAIN: print(self.PAD.format("Get chain key m/" + str(account) + "h/" + str(chain)), end='') refkey = self.wallet.subkey(account, is_hardened = True).subkey(chain) check_chaincode = True else: # keytype == PollyCom.KEY_ADDRESS print(self.PAD.format("Get address key m/" + str(account) + "h/" + str(chain) + "/" + str(address)), end='') refkey = self.wallet.subkey(account, is_hardened = True).subkey(chain).subkey(address) check_chaincode = False # Get keypair from Polly (pubx, puby, chaincode) = self.polly.send_get_public_key(keytype, account, chain, address) print (self.__outok()) # Check against reference wallet addr = encoding.public_pair_to_hash160_sec((pubx, puby)) addr_check = encoding.public_pair_to_hash160_sec(refkey.public_pair) assert addr == addr_check, "public key mismatch\nexpected: " + self.hexstr(addr_check) + "\nactual: " + self.hexstr(addr) if check_chaincode == True : assert refkey.chain_code == chaincode, "chain code mismatch\nexpected: " + self.hexstr(refkey.chain_code) + "\nactual: " + self.hexstr(chaincode) def test_sign(self, keynums_satoshi, out_addr, out_satoshi, change_keynum, change_satoshi, prevtx_keynums, prevtx_outputs, prevtx_inputs): """ Performs a tx signing test, comparing Polly's signed tx against the reference wallet. Basic tx signing parameters: keynums_satoshi - list of tuples (keynum, satoshis) with key indices and their unspent value to use as tx inputs. Funding above out_satoshi + change_satoshi will be fees. out_addr - output address in bitcoin address format. out_satoshi - output amount in satoshis. change_keynum - change key index in the wallet, use None for no change. change_satoshi - change amount in satoshis, use 0 for no change. Supporting (previous) txs will be created to fund keynums and are controlled by these parameters: prevtx_keynums - keynums will show up as outputs of previous txs. A number randomly picked from this list controls how many keynums are chosen to include per prev tx. prevtx_outputs - in addition to previous tx outputs funding keynums, other outputs may be present. A number randomly picked from this list controls how many ignored outputs are injected per keynum. prevtx_inputs - previous txs need inputs too. A number randomly picked from this list controls how many inputs are chosen per previous tx. """ total_in_satoshi = sum(satoshi for _, satoshi in keynums_satoshi) fee_satoshi = total_in_satoshi - out_satoshi - change_satoshi chain0 = self.wallet.subkey(0, is_hardened = True).subkey(0) chain1 = self.wallet.subkey(0, is_hardened = True).subkey(1) assert total_in_satoshi >= out_satoshi + change_satoshi assert len(keynums_satoshi) <= 32 # # Step 1: send the inputs and outputs to use in the signed tx # # Create the (key num, compressed public key) tuple, input keys assume an m/0h/0/keynum path for now. keys = [(keynum, encoding.public_pair_to_sec(chain0.subkey(keynum).public_pair)) for (keynum, _) in keynums_satoshi] # Convert base58 address to raw hex address out_addr_160 = encoding.bitcoin_address_to_hash160_sec(out_addr) print() print("Sign tx parameters:", "") for i, (keynum, satoshi) in enumerate(keynums_satoshi): print("{:<10}{:16.8f} btc < key {}".format (" inputs" if 0 == i else "", satoshi / 100000000, keynum)) print("{:<10}{:16.8f} btc > {}".format (" output", out_satoshi / 100000000, self.hexstr(out_addr_160))) print("{:<10}{:16.8f} btc > key {}".format (" change", change_satoshi / 100000000, change_keynum)) print("{:<10}{:16.8f} btc".format (" fee", fee_satoshi / 100000000)) print("{:<10}{:16.8f} btc".format (" total", total_in_satoshi / 100000000)) print() print(self.PAD.format("Send tx parameters"), end='') # ---> send to Polly self.polly.send_sign_tx(keys, out_addr_160, out_satoshi, change_keynum, change_satoshi) print(self.__outok()) # # Step 2: send previous txs to fund the inputs # print() cur = 0 prevtx_info = [] while cur < len(keynums_satoshi) : prevtx_outputs_satoshi = [] # Calculate how many keynums will be associated with this prev tx end = min(cur + random.choice(prevtx_keynums), len(keynums_satoshi)) # Create the prev tx output list for keynum, satoshi in keynums_satoshi[cur:end] : # Inject a random number of outputs not associated with tx input keynums for _ in range(0, random.choice(prevtx_outputs)) : prevtx_outputs_satoshi.append((random.randint(0, 0x7FFFFFFF), random.randint(0, 2099999997690000))) # Add the outputs funding the tx input keynums prevtx_outputs_satoshi.append((keynum, satoshi)) # Create output script addr = chain0.subkey(keynum, as_private = True).bitcoin_address() script = standard_tx_out_script(addr) # Capture some info we'll use later to verify the signed tx prevtx_info.append((keynum, satoshi, script, 0, # This is the hash and will be replaced later len(prevtx_outputs_satoshi) - 1)) # Index of the valid output print("{:30}{}".format("Make prev tx for keys", " ".join(str(keynum) for (keynum, _, _, _, _) in prevtx_info[cur:]))) # Create the prev tx prevtx = self.create_prev_tx(win = Wallet.from_master_secret(bytes(0)), # create a dummy wallet in_keynum = list(range(0, random.choice(prevtx_inputs))), sources_per_input = 1, wout = chain0, out_keynum_satoshi = prevtx_outputs_satoshi, fees_satoshi = random.randint(100, 1000)) # We have built the prev tx, calculate its hash (and reverse the bytes) prevtx_hash = encoding.double_sha256(prevtx)[::-1] # Update the hashes now that we have a full prev tx for i, (keynum, satoshi, script, _, outidx) in enumerate(prevtx_info[cur:]) : prevtx_info[i + cur] = (keynum, satoshi, script, prevtx_hash, outidx) # Create the index table that matches a keynum index with an ouput index in this prev tx idx_table = [(keynum_idx + cur, outidx) for keynum_idx, (_, _, _, _, outidx) in enumerate(prevtx_info[cur:])] print(self.PAD.format("Send prev tx "), end='') # ---> send to Polly self.polly.send_prev_tx(idx_table, prevtx) print(self.__outok()) cur = end # # Step 3: generate a signed tx with the reference wallet and compare against Polly's # spendables = [] wifs = [] # Make sure that the inputs add up correctly, and prep the input_sources for reference wallet signing for (keynum, satoshi, script, prevtx_hash, outidx) in prevtx_info: spendables.append(Spendable(satoshi, script, prevtx_hash, outidx)) wifs.append(chain0.subkey(keynum, as_private = True).wif()) change_addr = chain1.subkey(change_keynum).bitcoin_address() payables = [(out_addr, out_satoshi), (change_addr, change_satoshi)] print() print(self.PAD.format("Make reference signature")) signed_tx = create_signed_tx(spendables, payables, wifs, fee_satoshi) signed_tx = self.get_tx_bytes(signed_tx) print(self.PAD.format("Get signed tx"), end='', flush = True) # <--- get the signed tx from Polly polly_signed_tx = self.polly.send_get_signed_tx() #print(self.txstr(polly_signed_tx)) #print(self.txstr(signed_tx)) print(self.__outok()) # Compare reference wallet signed tx with polly's assert signed_tx == polly_signed_tx, "test_sign: signature mismatch\nExpected:\n" + self.hexstr(signed_tx) + "\n\nActual:\n" + self.hexstr(polly_signed_tx) def test_ref_bip32(self): """ Performs a test of the reference wallet's BIP32 key generation capability. """ # BIP32 test vectors, see https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Test_Vectors # Vector 1 m = Wallet.from_master_secret(bytes.fromhex("000102030405060708090a0b0c0d0e0f")) assert m.wallet_key() == "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" assert m.wallet_key(as_private=True) == "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" assert m.bitcoin_address() == "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma" assert m.wif() == "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW" m0h = m.subkey(is_hardened=True) assert m0h.wallet_key() == "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw" assert m0h.wallet_key(as_private=True) == "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7" m0h1 = m0h.subkey(i=1) assert m0h1.wallet_key() == "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ" assert m0h1.wallet_key(as_private=True) == "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" m0h1_1_2h = m0h1.subkey(i=2, is_hardened=True) assert m0h1_1_2h.wallet_key() == "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5" assert m0h1_1_2h.wallet_key(as_private=True) == "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM" m0h1_1_2h_2 = m0h1_1_2h.subkey(i=2) assert m0h1_1_2h_2.wallet_key() == "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV" assert m0h1_1_2h_2.wallet_key(as_private=True) == "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" m0h1_1_2h_2_1000000000 = m0h1_1_2h_2.subkey(i=1000000000) assert m0h1_1_2h_2_1000000000.wallet_key() == "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" assert m0h1_1_2h_2_1000000000.wallet_key(as_private=True) == "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" # Vector 2 m = Wallet.from_master_secret(bytes.fromhex("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542")) assert m.wallet_key() == "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" assert m.wallet_key(as_private=True) == "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" m0 = m.subkey() assert m0.wallet_key() == "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH" assert m0.wallet_key(as_private=True) == "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt" m0_2147483647p = m0.subkey(i=2147483647, is_hardened=True) assert m0_2147483647p.wallet_key() == "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a" assert m0_2147483647p.wallet_key(as_private=True) == "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9" m0_2147483647p_1 = m0_2147483647p.subkey(i=1) assert m0_2147483647p_1.wallet_key() == "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon" assert m0_2147483647p_1.wallet_key(as_private=True) == "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" m0_2147483647p_1_2147483646p = m0_2147483647p_1.subkey(i=2147483646, is_hardened=True) assert m0_2147483647p_1_2147483646p.wallet_key() == "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" assert m0_2147483647p_1_2147483646p.wallet_key(as_private=True) == "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc" m0_2147483647p_1_2147483646p_2 = m0_2147483647p_1_2147483646p.subkey(i=2) assert m0_2147483647p_1_2147483646p_2.wallet_key() == "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt" assert m0_2147483647p_1_2147483646p_2.wallet_key(as_private=True) == "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" def test_rfc6979(self): """ Performs a test of the reference wallet's RFC6979 signatures against test vectors. """ # Test vectors for RFC 6979 ECDSA (secp256k1, SHA-256). # Thanks to the Haskoin developer for these fully formed vectors. # (private key hex, private key WIF, message, r || r as hex, sig as DER) test_vectors = [ ( 0x0000000000000000000000000000000000000000000000000000000000000001, "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "Everything should be made as simple as possible, but not simpler.", "33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c96f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262", "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262" ), ( 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140, "L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFLaHLuZ9", "Equations are more important to me, because politics is for the present, but an equation is something for eternity.", "54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5", "3044022054c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed022007082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5" ), ( 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140, "L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFLaHLuZ9", "Not only is the Universe stranger than we think, it is stranger than we can think.", "ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd06fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283", "3045022100ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd002206fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283" ), ( 0x0000000000000000000000000000000000000000000000000000000000000001, "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "How wonderful that we have met with a paradox. Now we have some hope of making progress.", "c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d375afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3", "3045022100c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d3022075afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3" ), ( 0x69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64, "KzmcSTRmg8Gtoq8jbBCwsrvgiTKRrewQXniAHHTf7hsten8MZmBB", "Computer science is no more about computers than astronomy is about telescopes.", "7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6", "304402207186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d02200de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6" ), ( 0x00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637, "KwDiBf89QgGbjEhKnhXJwe1E2mCa8asowBrSKuCaBV6EsPYEAFZ8", "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough", "fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda4870e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37", "3045022100fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda48702200e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37" ), ( 0x000000000000000000000000000000000000000000056916d0f9b31dc9b637f3, "KwDiBf89QgGbjEhKnhXJuH7LrciVrZiib5S9h4knkymNojPUVsWN", "The question of whether computers can think is like the question of whether submarines can swim.", "cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf906ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef", "3045022100cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf9022006ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef" ) ] for (secret_exponent, _, message, _, expected_sig) in test_vectors: h = hashlib.sha256(message.encode('utf-8')).digest() val = intbytes.from_bytes(h) # This will use deterministic values of k based on 'val' r, s = ecdsa.sign(secp256k1.generator_secp256k1, secret_exponent, val) # Ensure that 's' is even to prevent attacks - see https://bitcointalk.org/index.php?topic=285142.msg3295518#msg3295518 if s > (secp256k1.generator_secp256k1.order() / 2): s = secp256k1.generator_secp256k1.order() - s sig = der.sigencode_der(r, s) assert sig == bytes.fromhex(expected_sig), "ECDSA signature using RFC 6979 failed\nExpected: " + expected_sig + "\nActual: " + self.hexstr(sig) def test_txhash(self): """ Performs a test of the reference wallet's tx hashing against a known blockchain tx. """ # 's' and 'expected' are from: # https://blockchain.info/rawtx/a8196acaf3938b988f9816ae3e9da1df5a04afff0b5b460e4c1dc4a08dd52109?format=hex s = ("0100000002bca066b9cfe1eb81e667f219a442acdc5c5e2e470610659a314" "74dfb5e29c552000000008c493046022100b2857170045d5e59112e0d5200" "4a8f65d18945e52d42c2eb12f7d3c2314600b802210098bdab40dfe38b5d4" "fe02e1fa3057ada3e0a982a5c7979eabff86395e2a911e8014104a8075344" "0c651f7191f46085411679545486f1dc6bf34cdaba453966c5fe7cc34f3dd" "c15ae321974f426807faa34b3fc10034e129222067ec053c409a6ac1f30ff" "ffffffe047c65f9e560d799580f6a965c12a059ca1e82cebc3e220659ba29" "ee31c8d0a010000008b48304502206b7eaa2dec17b53022b57a55b48ac245" "6f1c22d87b0170aa969de04146b80bbc022100d6700f6eb9bde89c35b0545" "588c06dcfed95e0502941d79786b5ea24eafc2cfe01410435d1d08c6f5296" "0d056e60c3b5c858e5299c1a688395b589dbde6b58861b20fdd7ee58832b3" "528845973765038cafc1c81280dc635ee202ce06aa4a373db012fffffffff" "0200f2052a010000001976a914315bfd9ee07d6779e44b8e07229650f039f" "0942788aced931600000000001976a91484004861f9a742fc83ad4ab83c42" "e709b512df1888ac00000000") expected = "a8196acaf3938b988f9816ae3e9da1df5a04afff0b5b460e4c1dc4a08dd52109" expected = bytes.fromhex(expected) actual = encoding.double_sha256(bytes.fromhex(s)) # Reverse the bytes to flow lsb -> msb actual = actual[::-1] assert actual == expected, "tx hash calculation mismatch\n" + "Expected: " + self.hexstr(expected) + "\nActual: " + self.hexstr(actual) # # Utilities # def fw_update(self, fwfile): """ Updates device firmware. fwfile - Path to and name of the firmware file to use. """ self.polly.send_fw_download(fwfile) def create_prev_tx(self, win, in_keynum, sources_per_input, wout, out_keynum_satoshi, fees_satoshi): """ Creates and returns a supporting 'previous' tx of 100KB or less win - wallet to use for input addresses in_keynum - key nums from win sources_per_input - how many sources are used to fund each input wout - wallet to use for output addresses out_keynum_satoshi - list of key nums from wout and satoshis to spend in tuples of (num, satoshis) Returns a bytes object containing the previous tx. """ # Calculate the total output payables = [] total_spent = 0 for (out_key_id, out_satoshi) in out_keynum_satoshi: address = wout.subkey(out_key_id).bitcoin_address() payables.append((address, out_satoshi)) total_spent += out_satoshi # Split the total to spend across all of the inputs spendables = [] total_value = 0 satoshi_per_input = int(total_spent + fees_satoshi) / len(in_keynum) for keynum in in_keynum: # Grab the address for the current key num addr = win.subkey(keynum, as_private = True).bitcoin_address(); # Generate fake sources for funding the input coin spendables.extend(self.fake_sources_for_address(addr, sources_per_input, satoshi_per_input)) total_value += satoshi_per_input # Calculate the fee tx_fee = total_value - total_spent assert tx_fee >= 0, "fee < 0: " + str(tx_fee) # Create and 'sign' the transaction unsigned_tx = create_tx(spendables, payables, tx_fee) signed_tx = self.__sign_fake(unsigned_tx) return self.get_tx_bytes(signed_tx) def fake_sources_for_address(self, addr, num_sources, total_satoshi): """ Returns a fake list of funding sources for a bitcoin address. Note: total_satoshi will be split evenly by num_sources addr - bitcoin address to fund num_sources - number of sources to fund it with total_satoshi - total satoshis to fund 'addr' with Returns a list of Spendable objects """ spendables = [] satoshi_left = total_satoshi satoshi_per_tx = satoshi_left / num_sources satoshi_per_tx = int(satoshi_per_tx) # Create the output script for the input to fund script = standard_tx_out_script(addr) while satoshi_left > 0: if satoshi_left < satoshi_per_tx: satoshi_per_tx = satoshi_left # Create a random hash value rand_hash = bytes([random.randint(0, 0xFF) for _ in range(0, 32)]) # Create a random output index # This field is 32 bits, but typically transactions dont have that many, limit to 0xFF rand_output_index = random.randint(0, 0xFF) # Append the new fake source spend = Spendable(satoshi_per_tx, script, rand_hash, rand_output_index) spendables.append(spend) satoshi_left -= satoshi_per_tx assert satoshi_left == 0, "incorrect funding" return spendables def get_tx_bytes(self, tx): """ Takes a Tx object and returns a bytes object containing the tx bytes. """ s = io.BytesIO() tx.stream(s) return s.getvalue() def gen_wordlist(self, seed): """ Generates a polly mnemonic wordlist from a seed, including the checksum. seed - a string of 24 hex bytes (for a strength of 192 bits) Returns a space separated string of 18 words from the wordlist. """ assert len(seed) == 24, "incorrect seed length, expecting 24 bytes" return self.mnemonic.to_mnemonic(seed) def hexstr(self, data): """ Takes a bytes object and returns a packed hex string. """ # Hexlify the bytes object and strip off the leading b' and trailing ' return str(binascii.hexlify(data))[2:-1] def txstr(self, tx): """ Takes a tx bytes object and prints out its details field by field. """ def hexy(tag, data): print ("{0:<20s} : {1}".format(tag, self.hexstr(data))) print("\n[tx details]\n") s = 0 hexy("version", tx[s:s + 4]) s += 4 in_count = ord(tx[s:s + 1]) hexy("in count", tx[s:s + 1]) s += 1 for _ in range(0, in_count) : print(" -------------------") hexy(" prev out hash", tx[s:s + 32]) s += 32 hexy(" prev out index", tx[s:s + 4]) s += 4 scriptlen = ord(tx[s:s + 1]) hexy(" scriptlen", tx[s:s + 1]) s += 1 hexy(" script", tx[s:s + scriptlen]) s += scriptlen hexy(" sequence", tx[s:s + 4]) s += 4 print() out_count = ord(tx[s:s + 1]) hexy("out count", tx[s:s + 1]) s += 1 for _ in range(0, out_count) : print(" -------------------") hexy(" value", tx[s:s + 8]) s += 8 scriptlen = ord(tx[s:s + 1]) hexy(" pk scriptlen", tx[s:s + 1]) s += 1 hexy(" pk script", tx[s:s + scriptlen]) s += scriptlen print() hexy("lock time", tx[s:s + 4]) s += 4 # # Private # def __outok(self): """ Creates a standard successful completion string for Polly operations """ return "ok (" + self.polly.get_cmd_time() + ")" def __sign_fake(self, tx): """ Sign a transaction using a fake randomly generated signature. """ # Create a fake ecdsa signature from 0x68 - 0x6b bytes rand_script = bytes([random.randint(0,0xFF) for _ in range(0, random.randint(0x68, 0x6b))]) tx.check_unspents() for idx, tx_in in enumerate(tx.txs_in): if tx.unspents[idx]: tx_in.script = rand_script return tx
def to_binary_seed(seed_phrase, passphrase='', language='english'): """Derive a 64-byte binary seed from a mnemonic seed phrase. Return a pair (binary_seed, seed_phrase_type), where seed_phrase_type is one of the following strings: - 'BIP-0039' - 'BIP-0039 and Electrum standard' - 'BIP-0039 and Electrum segwit' - 'BIP-0039 and Electrum 2FA' - 'Old (pre 2.0) Electrum' - 'Electrum standard' - 'Electrum segwit' - 'Electrum 2FA' - 'UNKNOWN' If seed_phrase is a BIP39-compliant phrase for the specfied language (this condition covers the first four cases listed above), generate the binary seed as recommended by BIP39. Otherwise, if seed_phrase is an Electrum seed phrase (this condition covers the next four cases listed above), generate the binary seed by using Electrum's algorithm. Otherwise (this is the last case listed above), generate the binary seed in a non-standard way. Keyword arguments: passphrase -- an optional extension of the seed phrase (default: '') language -- the language for a BIP-0039 seed phrase (default: 'english') """ import hashlib import hmac from pbkdf2 import PBKDF2 from mnemonic import Mnemonic from .electrum_mnemonic import normalize_text, \ is_new_electrum_seed_phrase, \ ELECTRUM_SEED_PREFIX_SW, \ ELECTRUM_SEED_PREFIX_2FA, \ electrum_seed_type seed_phrase = normalize_text(seed_phrase) passphrase = normalize_text(passphrase) m = Mnemonic(language) if m.check(seed_phrase): seed_phrase_type = 'BIP-0039' if is_new_electrum_seed_phrase(seed_phrase): seed_phrase_type += ' and Electrum standard' elif is_new_electrum_seed_phrase(seed_phrase, ELECTRUM_SEED_PREFIX_SW): seed_phrase_type += ' and Electrum segwit' elif is_new_electrum_seed_phrase(seed_phrase, ELECTRUM_SEED_PREFIX_2FA): seed_phrase_type += ' and Electrum 2FA' # salt for BIP-0039 seed salt = 'mnemonic' + passphrase else: seed_phrase_type = electrum_seed_type(seed_phrase) salt = ('electrum' if seed_phrase_type != 'UNKNOWN' else 'non-standard') + passphrase PBKDF2_ROUNDS = 2048 binary_seed = PBKDF2(seed_phrase, salt, iterations=PBKDF2_ROUNDS, macmodule=hmac, digestmodule=hashlib.sha512).read(64) return (binary_seed, seed_phrase_type)
def new_device(): err = None strength = 128 mnemonic = generate_mnemonic(strength=strength) if request.method == "POST": if request.form.get("existing_device"): device = app.specter.device_manager.get_by_alias( request.form.get("existing_device")) device_type = device.device_type else: device_type = request.form.get("devices") device_name = request.form.get("device_name", "") if not device_name: err = "Device name must not be empty" elif device_name in app.specter.device_manager.devices_names: err = "Device with this name already exists" xpubs_rows_count = int(request.form["xpubs_rows_count"]) + 1 if device_type != "bitcoincore": keys = [] for i in range(0, xpubs_rows_count): purpose = request.form.get( "xpubs-table-row-{}-purpose".format(i), "Custom") xpub = request.form.get( "xpubs-table-row-{}-xpub-hidden".format(i), "-") if xpub != "-": try: keys.append(Key.parse_xpub(xpub, purpose=purpose)) except: err = "Failed to parse these xpubs:\n" + "\n".join( xpub) break if not keys and not err: err = "xpubs name must not be empty" if err is None: if request.form.get("existing_device"): device.add_keys(keys) flash("{} keys were added successfully".format(len(keys))) return redirect( url_for("devices_endpoint.device", device_alias=device.alias)) device = app.specter.device_manager.add_device( name=device_name, device_type=device_type, keys=keys) flash("{} was added successfully!".format(device_name)) return redirect( url_for("devices_endpoint.device", device_alias=device.alias) + "?newdevice=true") else: flash(err, "error") else: if len(request.form["mnemonic"].split(" ")) not in [ 12, 15, 18, 21, 24 ]: err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words." mnemo = Mnemonic("english") if not mnemo.check(request.form["mnemonic"]): err = "Invalid mnemonic entered." range_start = int(request.form["range_start"]) range_end = int(request.form["range_end"]) if range_start > range_end: err = "Invalid address range selected." mnemonic = request.form["mnemonic"] paths = [] keys_purposes = [] for i in range(0, xpubs_rows_count): purpose = request.form.get( "xpubs-table-row-{}-purpose".format(i), "Custom") path = request.form.get( "xpubs-table-row-{}-derivation-hidden".format(i), "") if path != "": paths.append(path) keys_purposes.append(purpose) if not paths: err = "No paths were specified, please provide at lease one." if err is None: passphrase = request.form["passphrase"] file_password = request.form["file_password"] if request.form.get("existing_device"): device.add_hot_wallet_keys( mnemonic, passphrase, paths, file_password, app.specter.wallet_manager, is_testnet(app.specter.chain), keys_range=[range_start, range_end], keys_purposes=keys_purposes, ) flash("{} keys were added successfully".format(len(paths))) return redirect( url_for("devices_endpoint.device", device_alias=device.alias)) device = app.specter.device_manager.add_device( name=device_name, device_type=device_type, keys=[]) device.setup_device(file_password, app.specter.wallet_manager) device.add_hot_wallet_keys( mnemonic, passphrase, paths, file_password, app.specter.wallet_manager, is_testnet(app.specter.chain), keys_range=[range_start, range_end], keys_purposes=keys_purposes, ) flash("{} was added successfully!".format(device_name)) return redirect( url_for("devices_endpoint.device", device_alias=device.alias) + "?newdevice=true") else: flash(err, "error") return render_template( "device/new_device.jinja", mnemonic=mnemonic, strength=strength, specter=app.specter, rand=rand, )
# for R in range(1,7): # for G in range(1,7): # for B in range(1,7): # for W in range(1,7): # for C in ['H', 'T']: # if i>=2048: break # print(R, G, B, W, C, ' ', wordlist[i], sep='') # i+=1 L = [] for i in range(100000): R, G, B, W, C = (randint(0, 5), randint(0, 5), randint(0, 5), randint(0, 5), randint(1, 2)) n = C * (R * (6**3) + G * (6**2) + B * (6**1) + W) if n < 2048: L += [n] W = [wordlist[i] for i in L] p, f = [0, 0] for i in range(len(W)): if i < 12: continue x = i - 12 seed = " ".join(W[x:i]) if nemo.check(seed): p += 1 else: f += 1 # print(test, seed) print(f / p)
def new_device_manual(): err = None device_type = "" device_name = "" xpubs = "" strength = 128 mnemonic = generate_mnemonic(strength=strength) if request.method == "POST": action = request.form["action"] device_type = request.form["device_type"] device_name = request.form["device_name"] if action == "newcolddevice": if not device_name: err = "Device name must not be empty" elif device_name in app.specter.device_manager.devices_names: err = "Device with this name already exists" xpubs = request.form["xpubs"] if not xpubs: err = "xpubs name must not be empty" keys, failed = Key.parse_xpubs(xpubs) if len(failed) > 0: err = "Failed to parse these xpubs:\n" + "\n".join(failed) if err is None: device = app.specter.device_manager.add_device( name=device_name, device_type=device_type, keys=keys) return redirect( url_for("devices_endpoint.device", device_alias=device.alias)) elif action == "newhotdevice": if not device_name: err = "Device name must not be empty" elif device_name in app.specter.device_manager.devices_names: err = "Device with this name already exists" if len(request.form["mnemonic"].split(" ")) not in [ 12, 15, 18, 21, 24 ]: err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words." mnemo = Mnemonic("english") if not mnemo.check(request.form["mnemonic"]): err = "Invalid mnemonic entered." range_start = int(request.form["range_start"]) range_end = int(request.form["range_end"]) if range_start > range_end: err = "Invalid address range selected." if err is None: mnemonic = request.form["mnemonic"] paths = [ l.strip() for l in request.form["derivation_paths"].split("\n") if len(l) > 0 ] passphrase = request.form["passphrase"] file_password = request.form["file_password"] device = app.specter.device_manager.add_device( name=device_name, device_type=device_type, keys=[]) device.setup_device(file_password, app.specter.wallet_manager) device.add_hot_wallet_keys( mnemonic, passphrase, paths, file_password, app.specter.wallet_manager, is_testnet(app.specter.chain), keys_range=[range_start, range_end], ) return redirect( url_for("devices_endpoint.device", device_alias=device.alias)) elif action == "generatemnemonic": strength = int(request.form["strength"]) mnemonic = generate_mnemonic(strength=strength) return render_template( "device/new_device_manual.jinja", device_type=device_type, device_name=device_name, xpubs=xpubs, mnemonic=mnemonic, strength=strength, error=err, specter=app.specter, rand=rand, )
def device(device_alias): err = None try: device = app.specter.device_manager.get_by_alias(device_alias) except: return render_template("base.jinja", error="Device not found", specter=app.specter, rand=rand) if not device: return redirect(url_for("index")) wallets = device.wallets(app.specter.wallet_manager) if request.method == "POST": action = request.form["action"] if action == "forget": if len(wallets) != 0: err = "Device could not be removed since it is used in wallets: {}.\nYou must delete those wallets before you can remove this device.".format( [wallet.name for wallet in wallets]) else: app.specter.device_manager.remove_device( device, app.specter.wallet_manager, bitcoin_datadir=app.specter.bitcoin_datadir, chain=app.specter.chain, ) return redirect("") elif action == "delete_key": key = request.form["key"] device.remove_key(Key.from_json({"original": key})) elif action == "rename": device_name = request.form["newtitle"] if not device_name: flash("Device name must not be empty", "error") elif device_name == device.name: pass elif device_name in app.specter.device_manager.devices_names: flash("Device already exists", "error") else: device.rename(device_name) elif action == "add_keys": strength = 128 mnemonic = generate_mnemonic(strength=strength) return render_template( "device/new_device_manual.jinja", mnemonic=mnemonic, strength=strength, existing_device=device, device_alias=device_alias, specter=app.specter, rand=rand, ) elif action == "morekeys": if device.hot_wallet: if len(request.form["mnemonic"].split(" ")) not in [ 12, 15, 18, 21, 24 ]: err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words." mnemo = Mnemonic("english") if not mnemo.check(request.form["mnemonic"]): err = "Invalid mnemonic entered." range_start = int(request.form["range_start"]) range_end = int(request.form["range_end"]) if range_start > range_end: err = "Invalid address range selected." if err is None: mnemonic = request.form["mnemonic"] paths = [ l.strip() for l in request.form["derivation_paths"].split("\n") if len(l) > 0 ] passphrase = request.form["passphrase"] file_password = request.form["file_password"] device.add_hot_wallet_keys( mnemonic, passphrase, paths, file_password, app.specter.wallet_manager, is_testnet(app.specter.chain), keys_range=[range_start, range_end], ) else: # refactor to fn xpubs = request.form["xpubs"] keys, failed = Key.parse_xpubs(xpubs) err = None if len(failed) > 0: err = "Failed to parse these xpubs:\n" + "\n".join(failed) return render_template( "device/new_device_manual.jinja", device=device, device_alias=device_alias, xpubs=xpubs, error=err, specter=app.specter, rand=rand, ) if err is None: device.add_keys(keys) elif action == "settype": device_type = request.form["device_type"] device.set_type(device_type) device = copy.deepcopy(device) device.keys.sort(key=lambda k: k.metadata["chain"] + k.metadata["purpose"], reverse=True) return render_template( "device/device.jinja", device=device, device_alias=device_alias, purposes=purposes, wallets=wallets, error=err, specter=app.specter, rand=rand, )
class PollyAudit(): """ Auditing tests and utilities for Polly. """ def __init__(self, wordfile='wordlist.txt'): # Read create the mnemonic wordlist object self.mnemonic = Mnemonic("english") # Set up a default reference wallet self.wallet = Wallet.from_master_secret(bytes(0)) # Set up a Polly communication pipe self.polly = PollyCom() # Default print padding self.PAD = "{:35}" # # Tests # def test_set_seed(self, wordlist): """ Sets the wallet seed for Polly and the reference wallet. Note: Subsequent tests will use the seed set by this routine. wordlist - a space separated string of 18 mnemonic words from the Polly wordlist. Note: the checksum must be correct (part of the 18th word) - see BIP0039. gen_wordlist can be used to generate a wordlist including the proper checksum. """ assert len(wordlist.split(" ")) == 18, "expecting 18 words" assert self.mnemonic.check(wordlist) == True, "invalid word list" print(self.PAD.format("Set seed"), end='') # Set polly self.polly.send_set_master_seed(wordlist) print(self.__outok()) # Set the reference wallet seed = self.mnemonic.to_seed(wordlist) self.wallet = Wallet.from_master_secret(seed) def test_key(self, keytype, account=0, chain=0, address=0): """ Performs a public key retrieval test, comparing Polly's key against the reference wallet. keytype - Type of key to retrieve, valid values are KEY_MASTER, KEY_ACCOUNT, KEY_CHAIN, or KEY_ADDRESS. account - Account to use for type KEY_ACCOUNT, KEY_CHAIN, KEY_ADDRESS. chain - Chain to use for type KEY_CHAIN, KEY_ADDRESS. address - Index (0 - 0x7FFFFFFF) to use for type KEY_ADDRESS. """ assert address < 0x80000000, "hardened address keys are not supported" if keytype == PollyCom.KEY_MASTER: print(self.PAD.format("Get master key"), end='') refkey = self.wallet check_chaincode = False elif keytype == PollyCom.KEY_ACCOUNT: print(self.PAD.format("Get account key m/" + str(account) + "h"), end='') refkey = self.wallet.subkey(account, is_hardened=True) check_chaincode = True elif keytype == PollyCom.KEY_CHAIN: print(self.PAD.format("Get chain key m/" + str(account) + "h/" + str(chain)), end='') refkey = self.wallet.subkey(account, is_hardened=True).subkey(chain) check_chaincode = True else: # keytype == PollyCom.KEY_ADDRESS print(self.PAD.format("Get address key m/" + str(account) + "h/" + str(chain) + "/" + str(address)), end='') refkey = self.wallet.subkey( account, is_hardened=True).subkey(chain).subkey(address) check_chaincode = False # Get keypair from Polly (pubx, puby, chaincode) = self.polly.send_get_public_key(keytype, account, chain, address) print(self.__outok()) # Check against reference wallet addr = encoding.public_pair_to_hash160_sec((pubx, puby)) addr_check = encoding.public_pair_to_hash160_sec(refkey.public_pair) assert addr == addr_check, "public key mismatch\nexpected: " + self.hexstr( addr_check) + "\nactual: " + self.hexstr(addr) if check_chaincode == True: assert refkey.chain_code == chaincode, "chain code mismatch\nexpected: " + self.hexstr( refkey.chain_code) + "\nactual: " + self.hexstr(chaincode) def test_sign(self, keynums_satoshi, out_addr, out_satoshi, change_keynum, change_satoshi, prevtx_keynums, prevtx_outputs, prevtx_inputs): """ Performs a tx signing test, comparing Polly's signed tx against the reference wallet. Basic tx signing parameters: keynums_satoshi - list of tuples (keynum, satoshis) with key indices and their unspent value to use as tx inputs. Funding above out_satoshi + change_satoshi will be fees. out_addr - output address in bitcoin address format. out_satoshi - output amount in satoshis. change_keynum - change key index in the wallet, use None for no change. change_satoshi - change amount in satoshis, use 0 for no change. Supporting (previous) txs will be created to fund keynums and are controlled by these parameters: prevtx_keynums - keynums will show up as outputs of previous txs. A number randomly picked from this list controls how many keynums are chosen to include per prev tx. prevtx_outputs - in addition to previous tx outputs funding keynums, other outputs may be present. A number randomly picked from this list controls how many ignored outputs are injected per keynum. prevtx_inputs - previous txs need inputs too. A number randomly picked from this list controls how many inputs are chosen per previous tx. """ total_in_satoshi = sum(satoshi for _, satoshi in keynums_satoshi) fee_satoshi = total_in_satoshi - out_satoshi - change_satoshi chain0 = self.wallet.subkey(0, is_hardened=True).subkey(0) chain1 = self.wallet.subkey(0, is_hardened=True).subkey(1) assert total_in_satoshi >= out_satoshi + change_satoshi assert len(keynums_satoshi) <= 32 # # Step 1: send the inputs and outputs to use in the signed tx # # Create the (key num, compressed public key) tuple, input keys assume an m/0h/0/keynum path for now. keys = [ (keynum, encoding.public_pair_to_sec(chain0.subkey(keynum).public_pair)) for (keynum, _) in keynums_satoshi ] # Convert base58 address to raw hex address out_addr_160 = encoding.bitcoin_address_to_hash160_sec(out_addr) print() print("Sign tx parameters:", "") for i, (keynum, satoshi) in enumerate(keynums_satoshi): print("{:<10}{:16.8f} btc < key {}".format( " inputs" if 0 == i else "", satoshi / 100000000, keynum)) print("{:<10}{:16.8f} btc > {}".format(" output", out_satoshi / 100000000, self.hexstr(out_addr_160))) print("{:<10}{:16.8f} btc > key {}".format(" change", change_satoshi / 100000000, change_keynum)) print("{:<10}{:16.8f} btc".format(" fee", fee_satoshi / 100000000)) print("{:<10}{:16.8f} btc".format(" total", total_in_satoshi / 100000000)) print() print(self.PAD.format("Send tx parameters"), end='') # ---> send to Polly self.polly.send_sign_tx(keys, out_addr_160, out_satoshi, change_keynum, change_satoshi) print(self.__outok()) # # Step 2: send previous txs to fund the inputs # print() cur = 0 prevtx_info = [] while cur < len(keynums_satoshi): prevtx_outputs_satoshi = [] # Calculate how many keynums will be associated with this prev tx end = min(cur + random.choice(prevtx_keynums), len(keynums_satoshi)) # Create the prev tx output list for keynum, satoshi in keynums_satoshi[cur:end]: # Inject a random number of outputs not associated with tx input keynums for _ in range(0, random.choice(prevtx_outputs)): prevtx_outputs_satoshi.append( (random.randint(0, 0x7FFFFFFF), random.randint(0, 2099999997690000))) # Add the outputs funding the tx input keynums prevtx_outputs_satoshi.append((keynum, satoshi)) # Create output script addr = chain0.subkey(keynum, as_private=True).bitcoin_address() script = standard_tx_out_script(addr) # Capture some info we'll use later to verify the signed tx prevtx_info.append(( keynum, satoshi, script, 0, # This is the hash and will be replaced later len(prevtx_outputs_satoshi) - 1)) # Index of the valid output print("{:30}{}".format( "Make prev tx for keys", " ".join( str(keynum) for (keynum, _, _, _, _) in prevtx_info[cur:]))) # Create the prev tx prevtx = self.create_prev_tx( win=Wallet.from_master_secret( bytes(0)), # create a dummy wallet in_keynum=list(range(0, random.choice(prevtx_inputs))), sources_per_input=1, wout=chain0, out_keynum_satoshi=prevtx_outputs_satoshi, fees_satoshi=random.randint(100, 1000)) # We have built the prev tx, calculate its hash (and reverse the bytes) prevtx_hash = encoding.double_sha256(prevtx)[::-1] # Update the hashes now that we have a full prev tx for i, (keynum, satoshi, script, _, outidx) in enumerate(prevtx_info[cur:]): prevtx_info[i + cur] = (keynum, satoshi, script, prevtx_hash, outidx) # Create the index table that matches a keynum index with an ouput index in this prev tx idx_table = [ (keynum_idx + cur, outidx) for keynum_idx, (_, _, _, _, outidx) in enumerate(prevtx_info[cur:]) ] print(self.PAD.format("Send prev tx "), end='') # ---> send to Polly self.polly.send_prev_tx(idx_table, prevtx) print(self.__outok()) cur = end # # Step 3: generate a signed tx with the reference wallet and compare against Polly's # spendables = [] wifs = [] # Make sure that the inputs add up correctly, and prep the input_sources for reference wallet signing for (keynum, satoshi, script, prevtx_hash, outidx) in prevtx_info: spendables.append(Spendable(satoshi, script, prevtx_hash, outidx)) wifs.append(chain0.subkey(keynum, as_private=True).wif()) change_addr = chain1.subkey(change_keynum).bitcoin_address() payables = [(out_addr, out_satoshi), (change_addr, change_satoshi)] print() print(self.PAD.format("Make reference signature")) signed_tx = create_signed_tx(spendables, payables, wifs, fee_satoshi) signed_tx = self.get_tx_bytes(signed_tx) print(self.PAD.format("Get signed tx"), end='', flush=True) # <--- get the signed tx from Polly polly_signed_tx = self.polly.send_get_signed_tx() #print(self.txstr(polly_signed_tx)) #print(self.txstr(signed_tx)) print(self.__outok()) # Compare reference wallet signed tx with polly's assert signed_tx == polly_signed_tx, "test_sign: signature mismatch\nExpected:\n" + self.hexstr( signed_tx) + "\n\nActual:\n" + self.hexstr(polly_signed_tx) def test_ref_bip32(self): """ Performs a test of the reference wallet's BIP32 key generation capability. """ # BIP32 test vectors, see https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Test_Vectors # Vector 1 m = Wallet.from_master_secret( bytes.fromhex("000102030405060708090a0b0c0d0e0f")) assert m.wallet_key( ) == "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" assert m.wallet_key( as_private=True ) == "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" assert m.bitcoin_address() == "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma" assert m.wif( ) == "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW" m0h = m.subkey(is_hardened=True) assert m0h.wallet_key( ) == "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw" assert m0h.wallet_key( as_private=True ) == "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7" m0h1 = m0h.subkey(i=1) assert m0h1.wallet_key( ) == "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ" assert m0h1.wallet_key( as_private=True ) == "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" m0h1_1_2h = m0h1.subkey(i=2, is_hardened=True) assert m0h1_1_2h.wallet_key( ) == "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5" assert m0h1_1_2h.wallet_key( as_private=True ) == "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM" m0h1_1_2h_2 = m0h1_1_2h.subkey(i=2) assert m0h1_1_2h_2.wallet_key( ) == "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV" assert m0h1_1_2h_2.wallet_key( as_private=True ) == "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" m0h1_1_2h_2_1000000000 = m0h1_1_2h_2.subkey(i=1000000000) assert m0h1_1_2h_2_1000000000.wallet_key( ) == "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" assert m0h1_1_2h_2_1000000000.wallet_key( as_private=True ) == "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" # Vector 2 m = Wallet.from_master_secret( bytes.fromhex( "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" )) assert m.wallet_key( ) == "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" assert m.wallet_key( as_private=True ) == "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" m0 = m.subkey() assert m0.wallet_key( ) == "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH" assert m0.wallet_key( as_private=True ) == "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt" m0_2147483647p = m0.subkey(i=2147483647, is_hardened=True) assert m0_2147483647p.wallet_key( ) == "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a" assert m0_2147483647p.wallet_key( as_private=True ) == "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9" m0_2147483647p_1 = m0_2147483647p.subkey(i=1) assert m0_2147483647p_1.wallet_key( ) == "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon" assert m0_2147483647p_1.wallet_key( as_private=True ) == "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" m0_2147483647p_1_2147483646p = m0_2147483647p_1.subkey( i=2147483646, is_hardened=True) assert m0_2147483647p_1_2147483646p.wallet_key( ) == "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" assert m0_2147483647p_1_2147483646p.wallet_key( as_private=True ) == "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc" m0_2147483647p_1_2147483646p_2 = m0_2147483647p_1_2147483646p.subkey( i=2) assert m0_2147483647p_1_2147483646p_2.wallet_key( ) == "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt" assert m0_2147483647p_1_2147483646p_2.wallet_key( as_private=True ) == "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" def test_rfc6979(self): """ Performs a test of the reference wallet's RFC6979 signatures against test vectors. """ # Test vectors for RFC 6979 ECDSA (secp256k1, SHA-256). # Thanks to the Haskoin developer for these fully formed vectors. # (private key hex, private key WIF, message, r || r as hex, sig as DER) test_vectors = [ (0x0000000000000000000000000000000000000000000000000000000000000001, "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "Everything should be made as simple as possible, but not simpler.", "33a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c96f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262", "3044022033a69cd2065432a30f3d1ce4eb0d59b8ab58c74f27c41a7fdb5696ad4e6108c902206f807982866f785d3f6418d24163ddae117b7db4d5fdf0071de069fa54342262" ), (0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140, "L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFLaHLuZ9", "Equations are more important to me, because politics is for the present, but an equation is something for eternity.", "54c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed07082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5", "3044022054c4a33c6423d689378f160a7ff8b61330444abb58fb470f96ea16d99d4a2fed022007082304410efa6b2943111b6a4e0aaa7b7db55a07e9861d1fb3cb1f421044a5" ), (0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140, "L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFLaHLuZ9", "Not only is the Universe stranger than we think, it is stranger than we can think.", "ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd06fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283", "3045022100ff466a9f1b7b273e2f4c3ffe032eb2e814121ed18ef84665d0f515360dab3dd002206fc95f5132e5ecfdc8e5e6e616cc77151455d46ed48f5589b7db7771a332b283" ), (0x0000000000000000000000000000000000000000000000000000000000000001, "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn", "How wonderful that we have met with a paradox. Now we have some hope of making progress.", "c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d375afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3", "3045022100c0dafec8251f1d5010289d210232220b03202cba34ec11fec58b3e93a85b91d3022075afdc06b7d6322a590955bf264e7aaa155847f614d80078a90292fe205064d3" ), (0x69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64, "KzmcSTRmg8Gtoq8jbBCwsrvgiTKRrewQXniAHHTf7hsten8MZmBB", "Computer science is no more about computers than astronomy is about telescopes.", "7186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d0de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6", "304402207186363571d65e084e7f02b0b77c3ec44fb1b257dee26274c38c928986fea45d02200de0b38e06807e46bda1f1e293f4f6323e854c86d58abdd00c46c16441085df6" ), (0x00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637, "KwDiBf89QgGbjEhKnhXJwe1E2mCa8asowBrSKuCaBV6EsPYEAFZ8", "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough", "fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda4870e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37", "3045022100fbfe5076a15860ba8ed00e75e9bd22e05d230f02a936b653eb55b61c99dda48702200e68880ebb0050fe4312b1b1eb0899e1b82da89baa5b895f612619edf34cbd37" ), (0x000000000000000000000000000000000000000000056916d0f9b31dc9b637f3, "KwDiBf89QgGbjEhKnhXJuH7LrciVrZiib5S9h4knkymNojPUVsWN", "The question of whether computers can think is like the question of whether submarines can swim.", "cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf906ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef", "3045022100cde1302d83f8dd835d89aef803c74a119f561fbaef3eb9129e45f30de86abbf9022006ce643f5049ee1f27890467b77a6a8e11ec4661cc38cd8badf90115fbd03cef" ) ] for (secret_exponent, _, message, _, expected_sig) in test_vectors: h = hashlib.sha256(message.encode('utf-8')).digest() val = intbytes.from_bytes(h) # This will use deterministic values of k based on 'val' r, s = ecdsa.sign(secp256k1.generator_secp256k1, secret_exponent, val) # Ensure that 's' is even to prevent attacks - see https://bitcointalk.org/index.php?topic=285142.msg3295518#msg3295518 if s > (secp256k1.generator_secp256k1.order() / 2): s = secp256k1.generator_secp256k1.order() - s sig = der.sigencode_der(r, s) assert sig == bytes.fromhex( expected_sig ), "ECDSA signature using RFC 6979 failed\nExpected: " + expected_sig + "\nActual: " + self.hexstr( sig) def test_txhash(self): """ Performs a test of the reference wallet's tx hashing against a known blockchain tx. """ # 's' and 'expected' are from: # https://blockchain.info/rawtx/a8196acaf3938b988f9816ae3e9da1df5a04afff0b5b460e4c1dc4a08dd52109?format=hex s = ("0100000002bca066b9cfe1eb81e667f219a442acdc5c5e2e470610659a314" "74dfb5e29c552000000008c493046022100b2857170045d5e59112e0d5200" "4a8f65d18945e52d42c2eb12f7d3c2314600b802210098bdab40dfe38b5d4" "fe02e1fa3057ada3e0a982a5c7979eabff86395e2a911e8014104a8075344" "0c651f7191f46085411679545486f1dc6bf34cdaba453966c5fe7cc34f3dd" "c15ae321974f426807faa34b3fc10034e129222067ec053c409a6ac1f30ff" "ffffffe047c65f9e560d799580f6a965c12a059ca1e82cebc3e220659ba29" "ee31c8d0a010000008b48304502206b7eaa2dec17b53022b57a55b48ac245" "6f1c22d87b0170aa969de04146b80bbc022100d6700f6eb9bde89c35b0545" "588c06dcfed95e0502941d79786b5ea24eafc2cfe01410435d1d08c6f5296" "0d056e60c3b5c858e5299c1a688395b589dbde6b58861b20fdd7ee58832b3" "528845973765038cafc1c81280dc635ee202ce06aa4a373db012fffffffff" "0200f2052a010000001976a914315bfd9ee07d6779e44b8e07229650f039f" "0942788aced931600000000001976a91484004861f9a742fc83ad4ab83c42" "e709b512df1888ac00000000") expected = "a8196acaf3938b988f9816ae3e9da1df5a04afff0b5b460e4c1dc4a08dd52109" expected = bytes.fromhex(expected) actual = encoding.double_sha256(bytes.fromhex(s)) # Reverse the bytes to flow lsb -> msb actual = actual[::-1] assert actual == expected, "tx hash calculation mismatch\n" + "Expected: " + self.hexstr( expected) + "\nActual: " + self.hexstr(actual) # # Utilities # def fw_update(self, fwfile): """ Updates device firmware. fwfile - Path to and name of the firmware file to use. """ self.polly.send_fw_download(fwfile) def create_prev_tx(self, win, in_keynum, sources_per_input, wout, out_keynum_satoshi, fees_satoshi): """ Creates and returns a supporting 'previous' tx of 100KB or less win - wallet to use for input addresses in_keynum - key nums from win sources_per_input - how many sources are used to fund each input wout - wallet to use for output addresses out_keynum_satoshi - list of key nums from wout and satoshis to spend in tuples of (num, satoshis) Returns a bytes object containing the previous tx. """ # Calculate the total output payables = [] total_spent = 0 for (out_key_id, out_satoshi) in out_keynum_satoshi: address = wout.subkey(out_key_id).bitcoin_address() payables.append((address, out_satoshi)) total_spent += out_satoshi # Split the total to spend across all of the inputs spendables = [] total_value = 0 satoshi_per_input = int(total_spent + fees_satoshi) / len(in_keynum) for keynum in in_keynum: # Grab the address for the current key num addr = win.subkey(keynum, as_private=True).bitcoin_address() # Generate fake sources for funding the input coin spendables.extend( self.fake_sources_for_address(addr, sources_per_input, satoshi_per_input)) total_value += satoshi_per_input # Calculate the fee tx_fee = total_value - total_spent assert tx_fee >= 0, "fee < 0: " + str(tx_fee) # Create and 'sign' the transaction unsigned_tx = create_tx(spendables, payables, tx_fee) signed_tx = self.__sign_fake(unsigned_tx) return self.get_tx_bytes(signed_tx) def fake_sources_for_address(self, addr, num_sources, total_satoshi): """ Returns a fake list of funding sources for a bitcoin address. Note: total_satoshi will be split evenly by num_sources addr - bitcoin address to fund num_sources - number of sources to fund it with total_satoshi - total satoshis to fund 'addr' with Returns a list of Spendable objects """ spendables = [] satoshi_left = total_satoshi satoshi_per_tx = satoshi_left / num_sources satoshi_per_tx = int(satoshi_per_tx) # Create the output script for the input to fund script = standard_tx_out_script(addr) while satoshi_left > 0: if satoshi_left < satoshi_per_tx: satoshi_per_tx = satoshi_left # Create a random hash value rand_hash = bytes([random.randint(0, 0xFF) for _ in range(0, 32)]) # Create a random output index # This field is 32 bits, but typically transactions dont have that many, limit to 0xFF rand_output_index = random.randint(0, 0xFF) # Append the new fake source spend = Spendable(satoshi_per_tx, script, rand_hash, rand_output_index) spendables.append(spend) satoshi_left -= satoshi_per_tx assert satoshi_left == 0, "incorrect funding" return spendables def get_tx_bytes(self, tx): """ Takes a Tx object and returns a bytes object containing the tx bytes. """ s = io.BytesIO() tx.stream(s) return s.getvalue() def gen_wordlist(self, seed): """ Generates a polly mnemonic wordlist from a seed, including the checksum. seed - a string of 24 hex bytes (for a strength of 192 bits) Returns a space separated string of 18 words from the wordlist. """ assert len(seed) == 24, "incorrect seed length, expecting 24 bytes" return self.mnemonic.to_mnemonic(seed) def hexstr(self, data): """ Takes a bytes object and returns a packed hex string. """ # Hexlify the bytes object and strip off the leading b' and trailing ' return str(binascii.hexlify(data))[2:-1] def txstr(self, tx): """ Takes a tx bytes object and prints out its details field by field. """ def hexy(tag, data): print("{0:<20s} : {1}".format(tag, self.hexstr(data))) print("\n[tx details]\n") s = 0 hexy("version", tx[s:s + 4]) s += 4 in_count = ord(tx[s:s + 1]) hexy("in count", tx[s:s + 1]) s += 1 for _ in range(0, in_count): print(" -------------------") hexy(" prev out hash", tx[s:s + 32]) s += 32 hexy(" prev out index", tx[s:s + 4]) s += 4 scriptlen = ord(tx[s:s + 1]) hexy(" scriptlen", tx[s:s + 1]) s += 1 hexy(" script", tx[s:s + scriptlen]) s += scriptlen hexy(" sequence", tx[s:s + 4]) s += 4 print() out_count = ord(tx[s:s + 1]) hexy("out count", tx[s:s + 1]) s += 1 for _ in range(0, out_count): print(" -------------------") hexy(" value", tx[s:s + 8]) s += 8 scriptlen = ord(tx[s:s + 1]) hexy(" pk scriptlen", tx[s:s + 1]) s += 1 hexy(" pk script", tx[s:s + scriptlen]) s += scriptlen print() hexy("lock time", tx[s:s + 4]) s += 4 # # Private # def __outok(self): """ Creates a standard successful completion string for Polly operations """ return "ok (" + self.polly.get_cmd_time() + ")" def __sign_fake(self, tx): """ Sign a transaction using a fake randomly generated signature. """ # Create a fake ecdsa signature from 0x68 - 0x6b bytes rand_script = bytes([ random.randint(0, 0xFF) for _ in range(0, random.randint(0x68, 0x6b)) ]) tx.check_unspents() for idx, tx_in in enumerate(tx.txs_in): if tx.unspents[idx]: tx_in.script = rand_script return tx
w = " ".join(words) print("Seed words loaded:".format(tGRN, tNRM)) #Keep track of invalid words (not in BIP39 list): invalids = [] while len(words) // 3 != len(words) / 3: words = words + ["-"] for i, wd in enumerate(words): if wd not in biplist: print("{}{:>3}) {}{}".format(tYLW, i + 1, wd, tNRM)) invalids = invalids + [i] else: print("{:>3}) {}".format(i + 1, wd)) #Check the seed phrase: if m.check(w): print("{}Valid Seed Words (checksum pass){}".format(tGRN, tNRM)) seed = m.to_seed(w) print("Seed: {}".format(seed.hex())) print("Master Key (null password): {}".format(m.to_hd_master_key(seed))) else: print("{}Invalid Seed Words (checksum fail){}".format(tRED, tNRM)) #Currently, this script can only find one unknown word: if len(invalids) > 1: exit( "Too many unknowns! Please ensure only 1 seed word is missing or invalid." ) #Get user input for which word to switch out: try:
xpub = acct.hwif(as_private=False) xprv = acct.hwif(as_private=True) path += '0/0' print(f"\n{xprv}\n{xpub}\nwif @ {path}: {wif}\naddr @ {path}: {addr}") def main(seed): print("Entropy:", f'"{entropy.hex()}"') print("Seed:", f'"{seed.hex()}"') print(mnemonic) print_keys(seed, ACCT44, None) print_keys(seed, ACCT49, HW49) print_keys(seed, ACCT84, HW84) if __name__ == "__main__": if len(argv) < 2: print(f"Usage:\t{argv[0]}", "<mnemonic>") else: mnemo = Mnemonic("english") mnemonic = " ".join(argv[1:]).strip().split(" ") shuffle(mnemonic) i = j = 0 # animal cousin brother raise federal moment direct lemon silent curious sense scale for p in permutations(mnemonic, len(mnemonic)): i += 1 j += 1 if mnemo.check(" ".join(p)) else 0 if not i % 10_000: print(f"Passes: {i:,}, Hits: {j:,}, Expected {int(i/16):,}") # sleep(1/100_000_000)