commit d9f2edf6b0c8d8e1b5219bfa5cfeac903b6a2561
parent 68873d92f9973b3d6befe9d2b69f67be71217501
Author: ThomasV <thomasv@electrum.org>
Date: Fri, 1 Sep 2017 14:15:54 +0200
support native segwit transactions
Diffstat:
6 files changed, 120 insertions(+), 61 deletions(-)
diff --git a/lib/base_wizard.py b/lib/base_wizard.py
@@ -357,7 +357,7 @@ class BaseWizard(object):
def create_seed(self):
from . import mnemonic
- self.seed_type = 'segwit' if bitcoin.TESTNET and self.config.get('segwit') else 'standard'
+ self.seed_type = 'segwit' if 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)
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
@@ -251,7 +251,7 @@ def seed_type(x):
return 'old'
elif is_new_seed(x):
return 'standard'
- elif TESTNET and is_new_seed(x, version.SEED_PREFIX_SW):
+ elif is_new_seed(x, version.SEED_PREFIX_SW):
return 'segwit'
elif is_new_seed(x, version.SEED_PREFIX_2FA):
return '2fa'
@@ -307,16 +307,21 @@ def b58_address_to_hash160(addr):
def hash160_to_p2pkh(h160):
return hash160_to_b58_address(h160, ADDRTYPE_P2PKH)
-
def hash160_to_p2sh(h160):
return hash160_to_b58_address(h160, ADDRTYPE_P2SH)
-
def public_key_to_p2pkh(public_key):
return hash160_to_p2pkh(hash_160(public_key))
-def hash160_to_segwit_addr(h160):
- return segwit_addr.encode(SEGWIT_HRP, 0, h160)
+def hash_to_segwit_addr(h):
+ return segwit_addr.encode(SEGWIT_HRP, 0, h)
+
+def public_key_to_p2wpkh(public_key):
+ return hash_to_segwit_addr(hash_160(public_key))
+
+def script_to_p2wsh(script):
+ return hash_to_segwit_addr(sha256(bfh(script)))
+
def address_to_script(addr):
if is_segwit_address(addr):
@@ -838,7 +843,7 @@ def deserialize_xkey(xkey, prv):
c = xkey[13:13+32]
header = XPRV_HEADER if prv else XPUB_HEADER
xtype = int('0x' + bh2u(xkey[0:4]), 16) - header
- if xtype not in ([0, 1] if TESTNET else [0]):
+ if xtype not in [0, 1]:
raise BaseException('Invalid header')
n = 33 if prv else 32
K_or_k = xkey[13+n:]
diff --git a/lib/commands.py b/lib/commands.py
@@ -163,7 +163,8 @@ class Commands:
def make_seed(self, nbits=132, entropy=1, language=None):
"""Create a seed"""
from .mnemonic import Mnemonic
- s = Mnemonic(language).make_seed('standard', nbits, custom_entropy=entropy)
+ t = 'segwit' if self.config.get('segwit') else 'standard'
+ s = Mnemonic(language).make_seed(t, nbits, custom_entropy=entropy)
return s
@command('')
diff --git a/lib/keystore.py b/lib/keystore.py
@@ -701,6 +701,8 @@ def from_seed(seed, passphrase):
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
xtype = 0 if t == 'standard' else 1
keystore.add_xprv_from_seed(bip32_seed, xtype, "m/")
+ else:
+ raise BaseException(t)
return keystore
def from_private_key_list(text):
diff --git a/lib/transaction.py b/lib/transaction.py
@@ -116,6 +116,19 @@ class BCDataStream(object):
def write_int64(self, val): return self._write_num('<q', val)
def write_uint64(self, val): return self._write_num('<Q', val)
+ def read_push_size(self):
+ size = self.input[self.read_cursor]
+ self.read_cursor += 1
+ if size < 0x4c:
+ return size
+ if size == 0x4c:
+ nsize = self.read_bytes(1)[0]
+ elif size == 0x4d:
+ nsize = self._read_num('<H')
+ elif size == 0x4e:
+ nsize = self._read_num('<I')
+ return nsize
+
def read_compact_size(self):
size = self.input[self.read_cursor]
self.read_cursor += 1
@@ -309,9 +322,6 @@ def parse_scriptSig(d, _bytes):
d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(item))
d['type'] = 'p2wpkh-p2sh'
d['redeemScript'] = redeemScript
- d['x_pubkeys'] = ["(witness)"]
- d['pubkeys'] = ["(witness)"]
- d['signatures'] = ['(witness)']
d['num_sig'] = 1
else:
# payto_pubkey
@@ -350,7 +360,19 @@ def parse_scriptSig(d, _bytes):
print_error("cannot find address in input script", bh2u(_bytes))
return
x_sig = [bh2u(x[1]) for x in decoded[1:-1]]
- dec2 = [ x for x in script_GetOp(decoded[-1][1]) ]
+ m, n, x_pubkeys, pubkeys, redeemScript = parse_redeemScript(decoded[-1][1])
+ # write result in d
+ d['type'] = 'p2sh'
+ d['num_sig'] = m
+ d['signatures'] = parse_sig(x_sig)
+ d['x_pubkeys'] = x_pubkeys
+ d['pubkeys'] = pubkeys
+ d['redeemScript'] = redeemScript
+ d['address'] = hash160_to_p2sh(hash_160(bfh(redeemScript)))
+
+
+def parse_redeemScript(s):
+ dec2 = [ x for x in script_GetOp(s) ]
m = dec2[0][0] - opcodes.OP_1 + 1
n = dec2[-2][0] - opcodes.OP_1 + 1
op_m = opcodes.OP_1 + m - 1
@@ -362,15 +384,7 @@ def parse_scriptSig(d, _bytes):
x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]
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
- d['pubkeys'] = pubkeys
- d['redeemScript'] = redeemScript
- d['address'] = hash160_to_p2sh(hash_160(bfh(redeemScript)))
-
+ return m, n, x_pubkeys, pubkeys, redeemScript
def get_address_from_output_script(_bytes):
decoded = [x for x in script_GetOp(_bytes)]
@@ -395,7 +409,7 @@ def get_address_from_output_script(_bytes):
# segwit address
match = [ opcodes.OP_0, opcodes.OP_PUSHDATA4 ]
if match_decoded(decoded, match):
- return TYPE_ADDRESS, hash160_to_segwit_addr(decoded[1][1])
+ return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1])
return TYPE_SCRIPT, bh2u(_bytes)
@@ -406,7 +420,6 @@ def parse_input(vds):
prevout_n = vds.read_uint32()
scriptSig = vds.read_bytes(vds.read_compact_size())
sequence = vds.read_uint32()
- d['scriptSig'] = bh2u(scriptSig)
d['prevout_hash'] = prevout_hash
d['prevout_n'] = prevout_n
d['sequence'] = sequence
@@ -420,12 +433,30 @@ def parse_input(vds):
d['type'] = 'unknown'
d['num_sig'] = 0
if scriptSig:
- parse_scriptSig(d, scriptSig)
+ if len(scriptSig) == 8:
+ d['value'] = struct.unpack_from('<Q', scriptSig, 0)[0]
+ d['scriptSig'] = ''
+ else:
+ d['scriptSig'] = bh2u(scriptSig)
+ parse_scriptSig(d, scriptSig)
return d
-def parse_witness(vds):
+
+def parse_witness(vds, txin):
n = vds.read_compact_size()
- return list(vds.read_bytes(vds.read_compact_size()) for i in range(n))
+ w = list(bh2u(vds.read_bytes(vds.read_push_size())) for i in range(n))
+ if n > 2:
+ txin['num_sig'] = n - 2
+ txin['signatures'] = parse_sig(w[1:-1])
+ m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))
+ txin['x_pubkeys'] = x_pubkeys
+ txin['pubkeys'] = pubkeys
+ txin['witnessScript'] = witnessScript
+ else:
+ txin['num_sig'] = 1
+ txin['pubkeys'] = [ w[-1] ]
+ txin['signatures'] = parse_sig([w[:-1]])
+
def parse_output(vds, i):
d = {}
@@ -451,19 +482,24 @@ def deserialize(raw):
n_vin = vds.read_compact_size()
d['inputs'] = [parse_input(vds) for i in range(n_vin)]
n_vout = vds.read_compact_size()
- d['outputs'] = [parse_output(vds,i) for i in range(n_vout)]
+ d['outputs'] = [parse_output(vds, i) for i in range(n_vout)]
if is_segwit:
- d['witness'] = [parse_witness(vds) for i in range(n_vin)]
+ for i in range(n_vin):
+ txin = d['inputs'][i]
+ parse_witness(vds, txin)
+ if not txin.get('scriptSig'):
+ if txin['num_sig'] == 1:
+ txin['type'] = 'p2wpkh'
+ txin['address'] = bitcoin.public_key_to_p2wpkh(bfh(txin['pubkeys'][0]))
+ else:
+ txin['type'] = 'p2wsh'
+ txin['address'] = bitcoin.script_to_p2wsh(txin['witnessScript'])
d['lockTime'] = vds.read_uint32()
return d
# pay & redeem scripts
-
-
-
-
def segwit_script(pubkey):
pubkey = safe_parse_pubkey(pubkey)
pkh = bh2u(hash_160(bfh(pubkey)))
@@ -536,12 +572,7 @@ class Transaction:
for i, txin in enumerate(self.inputs()):
pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
sigs1 = txin.get('signatures')
- if d.get('witness') is None:
- sigs2 = d['inputs'][i].get('signatures')
- else:
- # signatures are in the witnesses. But the last item is
- # the pubkey or the multisig script, so skip that.
- sigs2 = d['witness'][i][:-1]
+ sigs2 = d['inputs'][i].get('signatures')
for sig in sigs2:
if sig in sigs1:
continue
@@ -622,12 +653,18 @@ class Transaction:
@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)
+ if txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']:
+ n = 2
+ return var_int(n) + push_script(sig_list[0]) + push_script(pubkeys[0])
+ elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']:
+ n = len(sig_list) + 2
+ # fixme: witness script must be decided by wallet
+ witness_script = multisig_script(pubkeys, txin['num_sig'])
+ return var_int(n) + '00' + ''.join(push_script(x) for x in sig_list) + push_script(witness_script)
@classmethod
def is_segwit_input(self, txin):
- return txin['type'] in ['p2wpkh-p2sh']
+ return txin['type'] in ['p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh']
@classmethod
def input_script(self, txin, estimate_size=False):
@@ -645,7 +682,10 @@ class Transaction:
script += push_script(redeem_script)
elif _type == 'p2pkh':
script += push_script(pubkeys[0])
- elif _type == 'p2wpkh-p2sh':
+ elif _type in ['p2wpkh', 'p2wsh']:
+ # if it is not complete we store the value
+ return '' if self.is_txin_complete(txin) or estimate_size else int_to_hex(txin['value'], 8)
+ elif _type in ['p2wpkh-p2sh', 'p2wsh-p2sh']:
redeem_script = txin.get('redeemScript') or segwit_script(pubkeys[0])
return push_script(redeem_script)
elif _type == 'address':
@@ -655,14 +695,21 @@ class Transaction:
return script
@classmethod
+ def is_txin_complete(self, txin):
+ num_sig = txin.get('num_sig', 1)
+ x_signatures = txin['signatures']
+ signatures = list(filter(None, x_signatures))
+ return len(signatures) == num_sig
+
+ @classmethod
def get_preimage_script(self, txin):
# only for non-segwit
if txin['type'] == 'p2pkh':
return bitcoin.address_to_script(txin['address'])
- elif txin['type'] == 'p2sh':
+ elif txin['type'] in ['p2sh', 'p2wsh']:
pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
return multisig_script(pubkeys, txin['num_sig'])
- elif txin['type'] == 'p2wpkh-p2sh':
+ elif txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']:
pubkey = txin['pubkeys'][0]
pkh = bh2u(bitcoin.hash_160(bfh(pubkey)))
return '76a9' + push_script(pkh) + '88ac'
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -1545,7 +1545,7 @@ class Simple_Wallet(Abstract_Wallet):
def load_keystore(self):
self.keystore = load_keystore(self.storage, 'keystore')
self.is_segwit = self.keystore.is_segwit()
- self.txin_type = 'p2wpkh-p2sh' if self.is_segwit else 'p2pkh'
+ self.txin_type = 'p2wpkh' if self.is_segwit else 'p2pkh'
def get_pubkey(self, c, i):
return self.derive_pubkeys(c, i)
@@ -1635,15 +1635,6 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet):
return addr
-class P2SH:
-
- def pubkeys_to_redeem_script(self, pubkeys):
- raise NotImplementedError()
-
- def pubkeys_to_address(self, pubkey):
- redeem_script = self.pubkeys_to_redeem_script(pubkey)
- return bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))
-
class Standard_Wallet(Simple_Deterministic_Wallet):
wallet_type = 'standard'
@@ -1653,19 +1644,20 @@ class Standard_Wallet(Simple_Deterministic_Wallet):
return transaction.segwit_script(pubkey)
def pubkeys_to_address(self, pubkey):
- if not self.is_segwit:
+ if self.txin_type == 'p2pkh':
return bitcoin.public_key_to_p2pkh(bfh(pubkey))
- elif bitcoin.TESTNET:
+ elif self.txin_type == 'p2wpkh':
+ return bitcoin.hash_to_segwit_addr(hash_160(bfh(pubkey)))
+ elif self.txin_type == 'p2wpkh-p2sh':
redeem_script = self.pubkeys_to_redeem_script(pubkey)
return bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))
else:
raise NotImplementedError()
-class Multisig_Wallet(Deterministic_Wallet, P2SH):
+class Multisig_Wallet(Deterministic_Wallet):
# generic m of n
gap_limit = 20
- txin_type = 'p2sh'
def __init__(self, storage):
self.wallet_type = storage.get('wallet_type')
@@ -1675,9 +1667,19 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH):
def get_pubkeys(self, c, i):
return self.derive_pubkeys(c, i)
- def redeem_script(self, c, i):
- pubkeys = self.get_pubkeys(c, i)
- return transaction.multisig_script(sorted(pubkeys), self.m)
+ def pubkeys_to_address(self, pubkey):
+ if self.txin_type == 'p2sh':
+ redeem_script = self.pubkeys_to_redeem_script(pubkey)
+ return bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))
+ elif self.txin_type == 'p2wsh':
+ witness_script = self.pubkeys_to_redeem_script(pubkey)
+ return bitcoin.script_to_p2wsh(witness_script)
+ else:
+ raise NotImplementedError()
+
+ #def redeem_script(self, c, i):
+ # pubkeys = self.get_pubkeys(c, i)
+ # return transaction.multisig_script(sorted(pubkeys), self.m)
def pubkeys_to_redeem_script(self, pubkeys):
return transaction.multisig_script(sorted(pubkeys), self.m)
@@ -1691,6 +1693,8 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH):
name = 'x%d/'%(i+1)
self.keystores[name] = load_keystore(self.storage, name)
self.keystore = self.keystores['x1/']
+ self.is_segwit = self.keystore.is_segwit()
+ self.txin_type = 'p2wsh' if self.is_segwit else 'p2sh'
def save_keystore(self):
for name, k in self.keystores.items():