예제 #1
1
    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)
예제 #2
0
def test(words):

    mnemo = Mnemonic("english")
    if mnemo.check(words):
        print("valid=1")
    else:
        print("valid=0")
예제 #3
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)
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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)
예제 #7
0
    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
예제 #8
0
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
예제 #9
0
 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)
예제 #10
0
 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)
예제 #11
0
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)
예제 #12
0
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()
예제 #13
0
 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
예제 #15
0
 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)
예제 #16
0
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"})
예제 #17
0
 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)
예제 #18
0
    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.")
예제 #19
0
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()
예제 #20
0
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)
예제 #21
0
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)
예제 #22
0
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,
    )
예제 #23
0
    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
예제 #24
0
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 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
예제 #26
0
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
예제 #27
0
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))
예제 #28
0
 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))
예제 #29
0
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
예제 #30
0
 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))
예제 #31
0
파일: audit.py 프로젝트: ngburke/pollyaudit
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)
예제 #33
0
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,
    )
예제 #34
0
# 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)
예제 #35
0
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,
    )
예제 #36
0
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,
    )
예제 #37
0
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
예제 #38
0
    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:
예제 #39
0
    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)