electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 3a64ec0f2ed9c845b5c01daa07fafcbfa26c3cb7
parent 11377de3986124bbd9cc451ae204e6c180b6fb40
Author: ThomasV <thomasv@electrum.org>
Date:   Mon, 16 Jan 2017 09:48:38 +0100

Initial segwit support (testnet only)

Diffstat:
Mgui/qt/main_window.py | 4++--
Mlib/base_wizard.py | 45+++++++++++++++++++++++++--------------------
Mlib/bitcoin.py | 25+++++++++++++++++--------
Mlib/commands.py | 1+
Mlib/keystore.py | 25+++++++++++++------------
Mlib/mnemonic.py | 4+++-
Mlib/network.py | 2+-
Mlib/synchronizer.py | 2+-
Mlib/transaction.py | 285++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mlib/version.py | 10++++++++++
Mlib/wallet.py | 134+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
11 files changed, 340 insertions(+), 197 deletions(-)

diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -1343,7 +1343,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): return False, _("Payment request has expired") status, msg = self.network.broadcast(tx) if pr and status is True: - pr.set_paid(tx.hash()) + pr.set_paid(tx.txid()) self.invoices.save() self.payment_request = None refund_address = self.wallet.get_receiving_addresses()[0] @@ -1361,7 +1361,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): status, msg = result if status: if tx_desc is not None and tx.is_complete(): - self.wallet.set_label(tx.hash(), tx_desc) + self.wallet.set_label(tx.txid(), tx_desc) parent.show_message(_('Payment sent.') + '\n' + msg) self.invoice_list.update() self.do_clear() diff --git a/lib/base_wizard.py b/lib/base_wizard.py @@ -26,7 +26,7 @@ import os import bitcoin import keystore -from wallet import Wallet, Imported_Wallet, Standard_Wallet, Multisig_Wallet, WalletStorage, wallet_types +from wallet import Wallet, Imported_Wallet, Standard_Wallet, Segwit_Wallet, Multisig_Wallet, WalletStorage, wallet_types from i18n import _ from plugins import run_hook @@ -41,6 +41,7 @@ class BaseWizard(object): self.plugin = None self.keystores = [] self.is_kivy = config.get('gui') == 'kivy' + self.seed_type = None def run(self, *args): action = args[0] @@ -271,25 +272,24 @@ class BaseWizard(object): self.restore_seed_dialog(run_next=self.on_restore_seed, test=test) def on_restore_seed(self, seed, is_bip39, is_ext): - if is_bip39: + self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed) + if self.seed_type == 'bip39': f = lambda passphrase: self.on_restore_bip39(seed, passphrase) self.passphrase_dialog(run_next=f) if is_ext else f('') - else: - seed_type = bitcoin.seed_type(seed) - if seed_type == 'standard': - f = lambda passphrase: self.run('create_keystore', seed, passphrase) - self.passphrase_dialog(run_next=f) if is_ext else f('') - elif seed_type == 'old': - self.run('create_keystore', seed, '') - elif seed_type == '2fa': - if self.is_kivy: - self.show_error('2FA seeds are not supported in this version') - self.run('restore_from_seed') - else: - self.load_2fa() - self.run('on_restore_seed', seed, is_ext) + elif self.seed_type in ['standard', 'segwit']: + f = lambda passphrase: self.run('create_keystore', seed, passphrase) + self.passphrase_dialog(run_next=f) if is_ext else f('') + elif self.seed_type == 'old': + self.run('create_keystore', seed, '') + elif self.seed_type == '2fa': + if self.is_kivy: + self.show_error('2FA seeds are not supported in this version') + self.run('restore_from_seed') else: - raise BaseException('Unknown seed type', seed_type) + self.load_2fa() + self.run('on_restore_seed', seed, is_ext) + else: + raise BaseException('Unknown seed type', seed_type) def on_restore_bip39(self, seed, passphrase): f = lambda x: self.run('on_bip44', seed, passphrase, int(x)) @@ -337,8 +337,12 @@ class BaseWizard(object): if k.may_have_password(): k.update_password(None, password) if self.wallet_type == 'standard': + self.storage.put('seed_type', self.seed_type) self.storage.put('keystore', k.dump()) - self.wallet = Standard_Wallet(self.storage) + if self.seed_type == 'segwit': + self.wallet = Segwit_Wallet(self.storage) + else: + self.wallet = Standard_Wallet(self.storage) self.run('create_addresses') elif self.wallet_type == 'multisig': for i, k in enumerate(self.keystores): @@ -358,8 +362,9 @@ class BaseWizard(object): self.on_keystore(k) def create_seed(self): - from electrum.mnemonic import Mnemonic - seed = Mnemonic('en').make_seed() + import mnemonic + self.seed_type = 'segwit' if bitcoin.TESTNET and self.config.get('segwit') else 'standard' + seed = mnemonic.Mnemonic('en').make_seed(self.seed_type) self.opt_bip39 = False f = lambda x: self.request_passphrase(seed, x) self.show_seed_dialog(run_next=f, seed_text=seed) diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -40,17 +40,19 @@ import aes TESTNET = False ADDRTYPE_P2PKH = 0 ADDRTYPE_P2SH = 5 +ADDRTYPE_P2WPKH = 6 XPRV_HEADER = "0488ade4" XPUB_HEADER = "0488b21e" HEADERS_URL = "https://headers.electrum.org/blockchain_headers" def set_testnet(): - global ADDRTYPE_P2PKH, ADDRTYPE_P2SH + global ADDRTYPE_P2PKH, ADDRTYPE_P2SH, ADDRTYPE_P2WPKH global XPRV_HEADER, XPUB_HEADER global TESTNET, HEADERS_URL TESTNET = True ADDRTYPE_P2PKH = 111 ADDRTYPE_P2SH = 196 + ADDRTYPE_P2WPKH = 3 XPRV_HEADER = "04358394" XPUB_HEADER = "043587cf" HEADERS_URL = "https://headers.electrum.org/testnet_headers" @@ -174,7 +176,6 @@ def Hash(x): if type(x) is unicode: x=x.encode('utf-8') return sha256(sha256(x)) - hash_encode = lambda x: x[::-1].encode('hex') hash_decode = lambda x: x.decode('hex')[::-1] hmac_sha_512 = lambda x,y: hmac.new(x, y, hashlib.sha512).digest() @@ -207,6 +208,8 @@ def seed_type(x): return 'old' elif is_new_seed(x): return 'standard' + elif TESTNET and is_new_seed(x, version.SEED_PREFIX_SW): + return 'segwit' elif is_new_seed(x, version.SEED_PREFIX_2FA): return '2fa' return '' @@ -247,11 +250,12 @@ def hash_160(public_key): md.update(sha256(public_key)) return md.digest() -def hash_160_to_bc_address(h160, addrtype): - vh160 = chr(addrtype) + h160 - h = Hash(vh160) - addr = vh160 + h[0:4] - return base_encode(addr, base=58) +def hash_160_to_bc_address(h160, addrtype, witness_program_version=1): + s = chr(addrtype) + if addrtype == ADDRTYPE_P2WPKH: + s += chr(witness_program_version) + chr(0) + s += h160 + return base_encode(s+Hash(s)[0:4], base=58) def bc_address_to_hash_160(addr): bytes = base_decode(addr, 25, base=58) @@ -263,9 +267,14 @@ def hash160_to_p2pkh(h160): def hash160_to_p2sh(h160): return hash_160_to_bc_address(h160, ADDRTYPE_P2SH) -def public_key_to_bc_address(public_key): +def public_key_to_p2pkh(public_key): return hash160_to_p2pkh(hash_160(public_key)) +def public_key_to_p2wpkh(public_key): + return hash160_to_bc_address(hash_160(public_key), ADDRTYPE_P2WPKH) + + + __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' assert len(__b58chars) == 58 diff --git a/lib/commands.py b/lib/commands.py @@ -747,6 +747,7 @@ def get_parser(): group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet") + group.add_argument("--segwit", action="store_true", dest="segwit", default=False, help="The Wizard will create Segwit seed phrases (Testnet only).") # create main parser parser = argparse.ArgumentParser( parents=[parent_parser], diff --git a/lib/keystore.py b/lib/keystore.py @@ -30,7 +30,7 @@ from unicodedata import normalize from version import * import bitcoin from bitcoin import pw_encode, pw_decode, bip32_root, bip32_private_derivation, bip32_public_derivation, bip32_private_key, deserialize_xprv, deserialize_xpub -from bitcoin import public_key_from_private_key, public_key_to_bc_address +from bitcoin import public_key_from_private_key, public_key_to_p2pkh from bitcoin import * from bitcoin import is_old_seed, is_new_seed, is_seed @@ -166,7 +166,7 @@ class Imported_KeyStore(Software_KeyStore): # fixme: this assumes p2pkh _, addr = xpubkey_to_address(x_pubkey) for pubkey in self.keypairs.keys(): - if public_key_to_bc_address(pubkey.decode('hex')) == addr: + if public_key_to_p2pkh(pubkey.decode('hex')) == addr: return pubkey def update_password(self, old_password, new_password): @@ -398,7 +398,7 @@ class Old_KeyStore(Deterministic_KeyStore): def get_address(self, for_change, n): pubkey = self.get_pubkey(for_change, n) - address = public_key_to_bc_address(pubkey.decode('hex')) + address = public_key_to_p2pkh(pubkey.decode('hex')) return address @classmethod @@ -563,6 +563,11 @@ def parse_xpubkey(x_pubkey): return BIP32_KeyStore.parse_xpubkey(x_pubkey) def xpubkey_to_address(x_pubkey): + if x_pubkey[0:2] == 'fd': + addrtype = ord(x_pubkey[2:4].decode('hex')) + hash160 = x_pubkey[4:].decode('hex') + address = bitcoin.hash_160_to_bc_address(hash160, addrtype) + return x_pubkey, address if x_pubkey[0:2] in ['02','03','04']: pubkey = x_pubkey elif x_pubkey[0:2] == 'ff': @@ -571,15 +576,10 @@ def xpubkey_to_address(x_pubkey): elif x_pubkey[0:2] == 'fe': mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey) pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1]) - elif x_pubkey[0:2] == 'fd': - addrtype = ord(x_pubkey[2:4].decode('hex')) - hash160 = x_pubkey[4:].decode('hex') - pubkey = None - address = hash_160_to_bc_address(hash160, addrtype) else: - raise BaseException("Cannnot parse pubkey") + raise BaseException("Cannot parse pubkey") if pubkey: - address = public_key_to_bc_address(pubkey.decode('hex')) + address = public_key_to_p2pkh(pubkey.decode('hex')) return pubkey, address @@ -660,10 +660,11 @@ def bip44_derivation(account_id): return "m/44'/0'/%d'"% int(account_id) def from_seed(seed, passphrase): - if is_old_seed(seed): + t = seed_type(seed) + if t == 'old': keystore = Old_KeyStore({}) keystore.add_seed(seed) - elif is_new_seed(seed): + elif t in ['standard', 'segwit']: keystore = BIP32_KeyStore({}) keystore.add_seed(seed) keystore.passphrase = passphrase diff --git a/lib/mnemonic.py b/lib/mnemonic.py @@ -159,7 +159,9 @@ class Mnemonic(object): i = self.mnemonic_decode(seed) return i % custom_entropy == 0 - def make_seed(self, num_bits=128, prefix=version.SEED_PREFIX, custom_entropy=1): + def make_seed(self, seed_type='standard', num_bits=128, custom_entropy=1): + import version + prefix = version.seed_prefix(seed_type) # increase num_bits in order to obtain a uniform distibution for the last word bpw = math.log(len(self.wordlist), 2) num_bits = int(math.ceil(num_bits/bpw)) * bpw diff --git a/lib/network.py b/lib/network.py @@ -847,7 +847,7 @@ class Network(util.DaemonThread): return r.get('result') def broadcast(self, tx, timeout=30): - tx_hash = tx.hash() + tx_hash = tx.txid() try: out = self.synchronous_get(('blockchain.transaction.broadcast', [str(tx)]), timeout) except BaseException as e: diff --git a/lib/synchronizer.py b/lib/synchronizer.py @@ -136,7 +136,7 @@ class Synchronizer(ThreadJob): if not params: return tx_hash, tx_height = params - assert tx_hash == hash_encode(Hash(result.decode('hex'))) + #assert tx_hash == hash_encode(Hash(result.decode('hex'))) tx = Transaction(result) try: tx.deserialize() diff --git a/lib/transaction.py b/lib/transaction.py @@ -303,15 +303,26 @@ def parse_scriptSig(d, bytes): print_error("cannot find address in input script", bytes.encode('hex')) return - # payto_pubkey match = [ opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): - sig = decoded[0][1].encode('hex') - d['address'] = "(pubkey)" - d['signatures'] = [sig] - d['num_sig'] = 1 - d['x_pubkeys'] = ["(pubkey)"] - d['pubkeys'] = ["(pubkey)"] + item = decoded[0][1] + if item[0] == chr(0): + redeemScript = item.encode('hex') + d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(redeemScript.decode('hex'))) + d['type'] = 'p2wpkh-p2sh' + d['redeemScript'] = redeemScript + d['x_pubkeys'] = ["(witness)"] + d['pubkeys'] = ["(witness)"] + d['signatures'] = ['(witness)'] + d['num_sig'] = 1 + else: + # payto_pubkey + d['type'] = 'p2pk' + d['address'] = "(pubkey)" + d['signatures'] = [item.encode('hex')] + d['num_sig'] = 1 + d['x_pubkeys'] = ["(pubkey)"] + d['pubkeys'] = ["(pubkey)"] return # non-generated TxIn transactions push a signature @@ -329,6 +340,7 @@ def parse_scriptSig(d, bytes): traceback.print_exc(file=sys.stdout) print_error("cannot find address in input script", bytes.encode('hex')) return + d['type'] = 'p2pkh' d['signatures'] = signatures d['x_pubkeys'] = [x_pubkey] d['num_sig'] = 1 @@ -353,8 +365,9 @@ def parse_scriptSig(d, bytes): return x_pubkeys = map(lambda x: x[1].encode('hex'), dec2[1:-2]) pubkeys = [xpubkey_to_address(x)[0] for x in x_pubkeys] - redeemScript = Transaction.multisig_script(pubkeys, m) + redeemScript = multisig_script(pubkeys, m) # write result in d + d['type'] = 'p2sh' d['num_sig'] = m d['signatures'] = parse_sig(x_sig) d['x_pubkeys'] = x_pubkeys @@ -412,6 +425,10 @@ def parse_input(vds): parse_scriptSig(d, scriptSig) return d +def parse_witness(vds): + n = vds.read_compact_size() + for i in range(n): + x = vds.read_bytes(vds.read_compact_size()) def parse_output(vds, i): d = {} @@ -430,16 +447,54 @@ def deserialize(raw): start = vds.read_cursor d['version'] = vds.read_int32() n_vin = vds.read_compact_size() + is_segwit = (n_vin == 0) + if is_segwit: + marker = vds.read_bytes(1) + assert marker == chr(1) + n_vin = vds.read_compact_size() d['inputs'] = list(parse_input(vds) for i in xrange(n_vin)) n_vout = vds.read_compact_size() d['outputs'] = list(parse_output(vds,i) for i in xrange(n_vout)) + if is_segwit: + d['witness'] = list(parse_witness(vds) for i in xrange(n_vin)) d['lockTime'] = vds.read_uint32() return d +# pay & redeem scripts + def push_script(x): return op_push(len(x)/2) + x +def get_scriptPubKey(addr): + addrtype, hash_160 = bc_address_to_hash_160(addr) + if addrtype == bitcoin.ADDRTYPE_P2PKH: + script = '76a9' # op_dup, op_hash_160 + script += push_script(hash_160.encode('hex')) + script += '88ac' # op_equalverify, op_checksig + elif addrtype == bitcoin.ADDRTYPE_P2SH: + script = 'a9' # op_hash_160 + script += push_script(hash_160.encode('hex')) + script += '87' # op_equal + else: + raise BaseException('unknown address type') + return script + +def segwit_script(pubkey): + pkh = hash_160(pubkey.decode('hex')).encode('hex') + return '00' + push_script(pkh) + +def multisig_script(public_keys, m): + n = len(public_keys) + assert n <= 15 + assert m <= n + op_m = format(opcodes.OP_1 + m - 1, 'x') + op_n = format(opcodes.OP_1 + n - 1, 'x') + keylist = [op_push(len(k)/2) + k for k in public_keys] + return op_m + ''.join(keylist) + op_n + 'ae' + + + class Transaction: @@ -485,7 +540,7 @@ class Transaction: for sig in sigs2: if sig in sigs1: continue - for_sig = Hash(self.tx_for_sig(i).decode('hex')) + pre_hash = Hash(self.serialize_preimage(i).decode('hex')) # der to string order = ecdsa.ecdsa.generator_secp256k1.order() r, s = ecdsa.util.sigdecode_der(sig.decode('hex'), order) @@ -493,10 +548,10 @@ class Transaction: pubkeys = txin.get('pubkeys') compressed = True for recid in range(4): - public_key = MyVerifyingKey.from_signature(sig_string, recid, for_sig, curve = SECP256k1) + public_key = MyVerifyingKey.from_signature(sig_string, recid, pre_hash, curve = SECP256k1) pubkey = point_to_ser(public_key.pubkey.point, compressed).encode('hex') if pubkey in pubkeys: - public_key.verify_digest(sig_string, for_sig, sigdecode = ecdsa.util.sigdecode_string) + public_key.verify_digest(sig_string, pre_hash, sigdecode = ecdsa.util.sigdecode_string) j = pubkeys.index(pubkey) print_error("adding sig", i, j, pubkey, sig) self._inputs[i]['signatures'][j] = sig @@ -526,91 +581,71 @@ class Transaction: return self @classmethod - def multisig_script(klass, public_keys, m): - n = len(public_keys) - assert n <= 15 - assert m <= n - op_m = format(opcodes.OP_1 + m - 1, 'x') - op_n = format(opcodes.OP_1 + n - 1, 'x') - keylist = [op_push(len(k)/2) + k for k in public_keys] - return op_m + ''.join(keylist) + op_n + 'ae' - - @classmethod def pay_script(self, output_type, addr): if output_type == TYPE_SCRIPT: return addr.encode('hex') elif output_type == TYPE_ADDRESS: - addrtype, hash_160 = bc_address_to_hash_160(addr) - if addrtype == bitcoin.ADDRTYPE_P2PKH: - script = '76a9' # op_dup, op_hash_160 - script += push_script(hash_160.encode('hex')) - script += '88ac' # op_equalverify, op_checksig - elif addrtype == bitcoin.ADDRTYPE_P2SH: - script = 'a9' # op_hash_160 - script += push_script(hash_160.encode('hex')) - script += '87' # op_equal - else: - raise + return get_scriptPubKey(addr) else: - raise + raise BaseException('Unknown output type') return script @classmethod - def input_script(self, txin, i, for_sig): - # for_sig: - # -1 : do not sign, estimate length - # i>=0 : serialized tx for signing input i - # None : add all known signatures - - p2sh = txin.get('redeemScript') is not None - num_sig = txin['num_sig'] if p2sh else 1 - address = txin['address'] - + def get_siglist(self, txin, estimate_size=False): + # if we have enough signatures, we use the actual pubkeys + # otherwise, use extended pubkeys (with bip32 derivation) + num_sig = txin.get('num_sig', 1) x_signatures = txin['signatures'] signatures = filter(None, x_signatures) is_complete = len(signatures) == num_sig + if estimate_size: + # we assume that signature will be 0x48 bytes long + pubkeys = txin['pubkeys'] + sig_list = [ "00" * 0x48 ] * num_sig + elif is_complete: + pubkeys = txin['pubkeys'] + sig_list = [(sig + '01') for sig in signatures] + else: + pubkeys = txin['x_pubkeys'] + sig_list = [(sig + '01') if sig else NO_SIGNATURE for sig in x_signatures] + return pubkeys, sig_list - if for_sig in [-1, None]: - # if we have enough signatures, we use the actual pubkeys - # use extended pubkeys (with bip32 derivation) - if for_sig == -1: - # we assume that signature will be 0x48 bytes long - pubkeys = txin['pubkeys'] - sig_list = [ "00" * 0x48 ] * num_sig - elif is_complete: - pubkeys = txin['pubkeys'] - sig_list = ((sig + '01') for sig in signatures) - else: - pubkeys = txin['x_pubkeys'] - sig_list = ((sig + '01') if sig else NO_SIGNATURE for sig in x_signatures) - script = ''.join(push_script(x) for x in sig_list) - if not pubkeys: - pass - elif not p2sh: - x_pubkey = pubkeys[0] - if x_pubkey is None: - addrtype, h160 = bc_address_to_hash_160(txin['address']) - x_pubkey = 'fd' + (chr(addrtype) + h160).encode('hex') - script += push_script(x_pubkey) - else: - script = '00' + script # put op_0 in front of script - redeem_script = self.multisig_script(pubkeys, num_sig) - script += push_script(redeem_script) + @classmethod + def serialize_witness(self, txin): + pubkeys, sig_list = self.get_siglist(txin) + n = len(pubkeys) + len(sig_list) + return var_int(n) + ''.join(push_script(x) for x in sig_list) + ''.join(push_script(x) for x in pubkeys) - elif for_sig==i: - script = txin['redeemScript'] if p2sh else self.pay_script(TYPE_ADDRESS, address) - else: - script = '' + @classmethod + def is_segwit_input(self, txin): + return txin['type'] in ['p2wpkh-p2sh'] + @classmethod + def input_script(self, txin, estimate_size=False): + redeem_script = txin.get('redeemScript') + if self.is_segwit_input(txin): + return push_script(redeem_script) + pubkeys, sig_list = self.get_siglist(txin, estimate_size) + script = ''.join(push_script(x) for x in sig_list) + if not pubkeys: + pass + elif redeem_script: + # put op_0 before script + script = '00' + script + script += push_script(redeem_script) + else: + script += push_script(pubkeys[0]) return script @classmethod - def serialize_input(self, txin, i, for_sig): + def serialize_outpoint(self, txin): + return txin['prevout_hash'].decode('hex')[::-1].encode('hex') + int_to_hex(txin['prevout_n'], 4) + + @classmethod + def serialize_input(self, txin, script): # Prev hash and index - s = txin['prevout_hash'].decode('hex')[::-1].encode('hex') - s += int_to_hex(txin['prevout_n'], 4) + s = self.serialize_outpoint(txin) # Script length, script, sequence - script = self.input_script(txin, i, for_sig) s += var_int(len(script)/2) s += script s += int_to_hex(txin.get('sequence', 0xffffffff), 4) @@ -625,30 +660,71 @@ class Transaction: self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n'])) self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1]))) - def serialize(self, for_sig=None): - inputs = self.inputs() - outputs = self.outputs() - s = int_to_hex(1, 4) # version - s += var_int(len(inputs)) # number of inputs - for i, txin in enumerate(inputs): - s += self.serialize_input(txin, i, for_sig) - s += var_int(len(outputs)) # number of outputs - for output in outputs: - output_type, addr, amount = output - s += int_to_hex(amount, 8) # amount - script = self.pay_script(output_type, addr) - s += var_int(len(script)/2) # script length - s += script # script - s += int_to_hex(self.locktime, 4) # locktime - if for_sig is not None and for_sig != -1: - s += int_to_hex(1, 4) # hash type + def serialize_output(self, output): + output_type, addr, amount = output + s = int_to_hex(amount, 8) + script = self.pay_script(output_type, addr) + s += var_int(len(script)/2) + s += script return s - def tx_for_sig(self,i): - return self.serialize(for_sig = i) + def serialize_preimage(self, i): + nVersion = int_to_hex(1, 4) + nHashType = int_to_hex(1, 4) + nLocktime = int_to_hex(self.locktime, 4) + inputs = self.inputs() + outputs = self.outputs() + txin = inputs[i] + if self.is_segwit_input(txin): + hashPrevouts = Hash(''.join(self.serialize_outpoint(txin) for txin in inputs).decode('hex')).encode('hex') + hashSequence = Hash(''.join(int_to_hex(txin.get('sequence', 0xffffffff), 4) for txin in inputs).decode('hex')).encode('hex') + hashOutputs = Hash(''.join(self.serialize_output(o) for o in outputs).decode('hex')).encode('hex') + outpoint = self.serialize_outpoint(txin) + pubkey = txin['pubkeys'][0] + pkh = bitcoin.hash_160(pubkey.decode('hex')).encode('hex') + redeemScript = '00' + push_script(pkh) + scriptCode = push_script('76a9' + push_script(pkh) + '88ac') + script_hash = bitcoin.hash_160(redeemScript.decode('hex')).encode('hex') + scriptPubKey = 'a9' + push_script(script_hash) + '87' + amount = int_to_hex(txin['value'], 8) + nSequence = int_to_hex(txin.get('sequence', 0xffffffff), 4) + preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType + else: + txin_script = lambda txin: txin.get('redeemScript') or get_scriptPubKey(txin['address']) + txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, txin_script(txin) if i==k else '') for k, txin in enumerate(inputs)) + txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) + preimage = nVersion + txins + txouts + nLocktime + nHashType + return preimage + + def is_segwit(self): + return any(self.is_segwit_input(x) for x in self.inputs()) + + def serialize(self, estimate_size=False, witness=True): + nVersion = int_to_hex(1, 4) + nLocktime = int_to_hex(self.locktime, 4) + inputs = self.inputs() + outputs = self.outputs() + txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs) + txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) + if witness and self.is_segwit(): + marker = '00' + flag = '01' + witness = ''.join(self.serialize_witness(x) for x in inputs) + return nVersion + marker + flag + txins + txouts + witness + nLocktime + else: + return nVersion + txins + txouts + nLocktime def hash(self): - return Hash(self.raw.decode('hex'))[::-1].encode('hex') + print "warning: deprecated tx.hash()" + return self.txid() + + def txid(self): + ser = self.serialize(witness=False) + return Hash(ser.decode('hex'))[::-1].encode('hex') + + def wtxid(self): + ser = self.serialize(witness=True) + return Hash(ser.decode('hex'))[::-1].encode('hex') def add_inputs(self, inputs): self._inputs.extend(inputs) @@ -662,7 +738,7 @@ class Transaction: return sum(x['value'] for x in self.inputs()) def output_value(self): - return sum( val for tp,addr,val in self.outputs()) + return sum(val for tp, addr, val in self.outputs()) def get_fee(self): return self.input_value() - self.output_value() @@ -673,12 +749,13 @@ class Transaction: @profiler def estimated_size(self): '''Return an estimated tx size in bytes.''' - return len(self.serialize(-1)) / 2 if not self.is_complete() or self.raw is None else len(self.raw) / 2 # ASCII hex string + return len(self.serialize(True)) / 2 if not self.is_complete() or self.raw is None else len(self.raw) / 2 # ASCII hex string @classmethod def estimated_input_size(self, txin): '''Return an estimated of serialized input size in bytes.''' - return len(self.serialize_input(txin, -1, -1)) / 2 + script = self.input_script(txin, True) + return len(self.serialize_input(txin, script)) / 2 def signature_count(self): r = 0 @@ -723,13 +800,13 @@ class Transaction: txin['pubkeys'][ii] = pubkey self._inputs[i] = txin # add signature - for_sig = Hash(self.tx_for_sig(i).decode('hex')) + pre_hash = Hash(self.serialize_preimage(i).decode('hex')) pkey = regenerate_key(sec) secexp = pkey.secret - private_key = bitcoin.MySigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) + private_key = bitcoin.MySigningKey.from_secret_exponent(secexp, curve = SECP256k1) public_key = private_key.get_verifying_key() - sig = private_key.sign_digest_deterministic( for_sig, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der ) - assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der) + sig = private_key.sign_digest_deterministic(pre_hash, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der) + assert public_key.verify_digest(sig, pre_hash, sigdecode = ecdsa.util.sigdecode_der) txin['signatures'][ii] = sig.encode('hex') self._inputs[i] = txin print_error("is_complete", self.is_complete()) diff --git a/lib/version.py b/lib/version.py @@ -3,4 +3,14 @@ PROTOCOL_VERSION = '0.10' # protocol version requested # The hash of the mnemonic seed must begin with this SEED_PREFIX = '01' # Electrum standard wallet +SEED_PREFIX_SW = '02' # Electrum segwit wallet SEED_PREFIX_2FA = '101' # extended seed for two-factor authentication + + +def seed_prefix(seed_type): + if seed_type == 'standard': + return SEED_PREFIX + elif seed_type == 'segwit': + return SEED_PREFIX_SW + elif seed_type == '2Fa': + return SEED_PREFIX_2FA diff --git a/lib/wallet.py b/lib/wallet.py @@ -52,6 +52,7 @@ from version import * from keystore import load_keystore, Hardware_KeyStore from storage import multisig_type +import transaction from transaction import Transaction from plugins import run_hook import bitcoin @@ -436,7 +437,7 @@ class Abstract_Wallet(PrintError): label = '' height = conf = timestamp = None if tx.is_complete(): - tx_hash = tx.hash() + tx_hash = tx.txid() if tx_hash in self.transactions.keys(): label = self.get_label(tx_hash) height, conf, timestamp = self.get_tx_height(tx_hash) @@ -879,7 +880,7 @@ class Abstract_Wallet(PrintError): pubkey = public_key_from_private_key(privkey) address = address_from_private_key(privkey) u = network.synchronous_get(('blockchain.address.listunspent', [address])) - pay_script = Transaction.pay_script(TYPE_ADDRESS, address) + pay_script = transaction.get_scriptPubKey(address) for item in u: if len(inputs) >= imax: break @@ -1381,45 +1382,9 @@ class Imported_Wallet(Abstract_Wallet): txin['x_pubkeys'] = [ xpubkey ] txin['pubkeys'] = [ xpubkey ] txin['signatures'] = [None] + txin['type'] = 'unknown' -class P2PKH_Wallet(Abstract_Wallet): - - def pubkeys_to_address(self, pubkey): - return public_key_to_bc_address(pubkey.decode('hex')) - - def load_keystore(self): - self.keystore = load_keystore(self.storage, 'keystore') - - def get_pubkey(self, c, i): - pubkey_list = self.change_pubkeys if c else self.receiving_pubkeys - return pubkey_list[i] - - def get_public_keys(self, address): - return [self.get_public_key(address)] - - def add_input_sig_info(self, txin, address): - if not self.keystore.can_import(): - txin['derivation'] = derivation = self.get_address_index(address) - x_pubkey = self.keystore.get_xpubkey(*derivation) - pubkey = self.get_pubkey(*derivation) - else: - pubkey = self.get_public_key(address) - assert pubkey is not None - x_pubkey = pubkey - txin['x_pubkeys'] = [x_pubkey] - txin['pubkeys'] = [pubkey] - txin['signatures'] = [None] - txin['redeemPubkey'] = pubkey - txin['num_sig'] = 1 - - def sign_message(self, address, message, password): - index = self.get_address_index(address) - return self.keystore.sign_message(index, message, password) - - def decrypt_message(self, pubkey, message, password): - index = self.get_pubkey_index(pubkey) - return self.keystore.decrypt_message(index, message, password) class Deterministic_Wallet(Abstract_Wallet): @@ -1545,8 +1510,53 @@ class Deterministic_Wallet(Abstract_Wallet): -class Standard_Wallet(Deterministic_Wallet, P2PKH_Wallet): - wallet_type = 'standard' +class Simple_Wallet(Abstract_Wallet): + + """ Wallet with a single pubkey per address """ + + def load_keystore(self): + self.keystore = load_keystore(self.storage, 'keystore') + + def get_pubkey(self, c, i): + pubkey_list = self.change_pubkeys if c else self.receiving_pubkeys + return pubkey_list[i] + + def get_public_keys(self, address): + return [self.get_public_key(address)] + + def add_input_sig_info(self, txin, address): + if not self.keystore.can_import(): + txin['derivation'] = derivation = self.get_address_index(address) + x_pubkey = self.keystore.get_xpubkey(*derivation) + pubkey = self.get_pubkey(*derivation) + else: + pubkey = self.get_public_key(address) + assert pubkey is not None + x_pubkey = pubkey + txin['x_pubkeys'] = [x_pubkey] + txin['pubkeys'] = [pubkey] + txin['signatures'] = [None] + + addrtype, hash160 = bc_address_to_hash_160(address) + if addrtype == bitcoin.ADDRTYPE_P2SH: + txin['redeemScript'] = self.pubkeys_to_redeem_script(pubkey) + txin['type'] = 'p2wpkh-p2sh' + else: + txin['redeemPubkey'] = pubkey + txin['type'] = 'p2pkh' + + txin['num_sig'] = 1 + + def sign_message(self, address, message, password): + index = self.get_address_index(address) + return self.keystore.sign_message(index, message, password) + + def decrypt_message(self, pubkey, message, password): + index = self.get_pubkey_index(pubkey) + return self.keystore.decrypt_message(index, message, password) + + +class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet): def __init__(self, storage): Deterministic_Wallet.__init__(self, storage) @@ -1607,7 +1617,35 @@ class Standard_Wallet(Deterministic_Wallet, P2PKH_Wallet): return addr -class Multisig_Wallet(Deterministic_Wallet): +class P2SH: + + def pubkeys_to_redeem_script(self, pubkeys): + raise NotImplementedError() + + def pubkeys_to_address(self, pubkeys): + redeem_script = self.pubkeys_to_redeem_script(pubkeys) + return bitcoin.hash160_to_p2sh(hash_160(redeem_script.decode('hex'))) + + +class P2PKH: + + def pubkeys_to_address(self, pubkey): + return bitcoin.public_key_to_p2pkh(pubkey.decode('hex')) + + +class Standard_Wallet(Simple_Deterministic_Wallet, P2PKH): + wallet_type = 'standard' + + +class Segwit_Wallet(Simple_Deterministic_Wallet, P2SH): + wallet_type = 'segwit' + + def pubkeys_to_redeem_script(self, pubkey): + return transaction.segwit_script(pubkey) + + + +class Multisig_Wallet(Deterministic_Wallet, P2SH): # generic m of n gap_limit = 20 @@ -1622,12 +1660,10 @@ class Multisig_Wallet(Deterministic_Wallet): def redeem_script(self, c, i): pubkeys = self.get_pubkeys(c, i) - return Transaction.multisig_script(sorted(pubkeys), self.m) + return transaction.multisig_script(sorted(pubkeys), self.m) - def pubkeys_to_address(self, pubkeys): - redeem_script = Transaction.multisig_script(sorted(pubkeys), self.m) - address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), bitcoin.ADDRTYPE_P2SH) - return address + def pubkeys_to_redeem_script(self, pubkeys): + return transaction.multisig_script(sorted(pubkeys), self.m) def new_pubkeys(self, c, i): return [k.derive_pubkey(c, i) for k in self.get_keystores()] @@ -1683,6 +1719,7 @@ class Multisig_Wallet(Deterministic_Wallet): x_pubkeys = [k.get_xpubkey(*derivation) for k in self.get_keystores()] # sort pubkeys and x_pubkeys, using the order of pubkeys pubkeys, x_pubkeys = zip(*sorted(zip(pubkeys, x_pubkeys))) + txin['type'] = 'p2sh' txin['pubkeys'] = list(pubkeys) txin['x_pubkeys'] = list(x_pubkeys) txin['signatures'] = [None] * len(pubkeys) @@ -1699,7 +1736,8 @@ wallet_constructors = { 'standard': Standard_Wallet, 'old': Standard_Wallet, 'xpub': Standard_Wallet, - 'imported': Imported_Wallet + 'imported': Imported_Wallet, + 'segwit': Segwit_Wallet } def register_constructor(wallet_type, constructor):