1
0

generic m of n multisig

This commit is contained in:
ThomasV
2015-06-26 14:29:26 +02:00
parent 547886d6f1
commit 56b3c98332
7 changed files with 169 additions and 139 deletions

View File

@@ -1,7 +1,7 @@
from version import ELECTRUM_VERSION
from util import format_satoshis, print_msg, print_json, print_error, set_verbosity
from wallet import WalletSynchronizer, WalletStorage
from wallet import Wallet, Wallet_2of2, Wallet_2of3, Imported_Wallet
from wallet import Wallet, Imported_Wallet
from network import Network, DEFAULT_SERVERS, DEFAULT_PORTS, pick_random_server
from interface import Interface
from simple_config import SimpleConfig, get_config, set_config

View File

@@ -366,15 +366,17 @@ class BIP32_Account(Account):
class BIP32_Account_2of2(BIP32_Account):
class Multisig_Account(BIP32_Account):
def __init__(self, v):
BIP32_Account.__init__(self, v)
self.xpub2 = v['xpub2']
self.m = v.get('m', 2)
Account.__init__(self, v)
self.xpub_list = v['xpubs']
def dump(self):
d = BIP32_Account.dump(self)
d['xpub2'] = self.xpub2
d = Account.dump(self)
d['xpubs'] = self.xpub_list
d['m'] = self.m
return d
def get_pubkeys(self, for_change, n):
@@ -385,10 +387,10 @@ class BIP32_Account_2of2(BIP32_Account):
def redeem_script(self, for_change, n):
pubkeys = self.get_pubkeys(for_change, n)
return Transaction.multisig_script(sorted(pubkeys), 2)
return Transaction.multisig_script(sorted(pubkeys), self.m)
def pubkeys_to_address(self, pubkeys):
redeem_script = Transaction.multisig_script(sorted(pubkeys), 2)
redeem_script = Transaction.multisig_script(sorted(pubkeys), self.m)
address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
return address
@@ -396,25 +398,9 @@ class BIP32_Account_2of2(BIP32_Account):
return self.pubkeys_to_address(self.get_pubkeys(for_change, n))
def get_master_pubkeys(self):
return [self.xpub, self.xpub2]
return self.xpub_list
def get_type(self):
return _('Multisig 2 of 2')
return _('Multisig %d of %d'%(self.m, len(self.xpub_list)))
class BIP32_Account_2of3(BIP32_Account_2of2):
def __init__(self, v):
BIP32_Account_2of2.__init__(self, v)
self.xpub3 = v['xpub3']
def dump(self):
d = BIP32_Account_2of2.dump(self)
d['xpub3'] = self.xpub3
return d
def get_master_pubkeys(self):
return [self.xpub, self.xpub2, self.xpub3]
def get_type(self):
return _('Multisig 2 of 3')

View File

@@ -368,32 +368,29 @@ def parse_scriptSig(d, bytes):
d['address'] = address
return
# p2sh transaction, 2 of n
# p2sh transaction, m of n
match = [ opcodes.OP_0 ] + [ opcodes.OP_PUSHDATA4 ] * (len(decoded) - 1)
if not match_decoded(decoded, match):
print_error("cannot find address in input script", bytes.encode('hex'))
return
x_sig = [x[1].encode('hex') for x in decoded[1:-1]]
d['signatures'] = parse_sig(x_sig)
d['num_sig'] = 2
dec2 = [ x for x in script_GetOp(decoded[-1][1]) ]
match_2of2 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2, opcodes.OP_CHECKMULTISIG ]
match_2of3 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_3, opcodes.OP_CHECKMULTISIG ]
if match_decoded(dec2, match_2of2):
x_pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex') ]
elif match_decoded(dec2, match_2of3):
x_pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ]
else:
m = dec2[0][0] - opcodes.OP_1 + 1
n = dec2[-2][0] - opcodes.OP_1 + 1
op_m = opcodes.OP_1 + m - 1
op_n = opcodes.OP_1 + n - 1
match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ]
if not match_decoded(dec2, match_multisig):
print_error("cannot find address in input script", bytes.encode('hex'))
return
d['x_pubkeys'] = x_pubkeys
x_pubkeys = map(lambda x: x[1].encode('hex'), dec2[1:-2])
pubkeys = [parse_xpub(x)[0] for x in x_pubkeys] # xpub, addr = parse_xpub()
redeemScript = Transaction.multisig_script(pubkeys, m)
# write result in d
d['num_sig'] = m
d['signatures'] = parse_sig(x_sig)
d['x_pubkeys'] = x_pubkeys
d['pubkeys'] = pubkeys
redeemScript = Transaction.multisig_script(pubkeys,2)
d['redeemScript'] = redeemScript
d['address'] = hash_160_to_bc_address(hash_160(redeemScript.decode('hex')), 5)
@@ -535,31 +532,14 @@ class Transaction:
return self
@classmethod
def multisig_script(klass, public_keys, num=None):
def multisig_script(klass, public_keys, m):
n = len(public_keys)
if num is None: num = n
assert num <= n and n in [2,3] , 'Only "2 of 2", and "2 of 3" transactions are supported'
if num==2:
s = '52'
elif num == 3:
s = '53'
else:
raise
for k in public_keys:
s += op_push(len(k)/2) + k
if n==2:
s += '52'
elif n==3:
s += '53'
else:
raise
s += 'ae'
return s
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):
@@ -617,7 +597,7 @@ class Transaction:
script += push_script(x_pubkey)
else:
script = '00' + script # put op_0 in front of script
redeem_script = self.multisig_script(pubkeys,2)
redeem_script = self.multisig_script(pubkeys, num_sig)
script += push_script(redeem_script)
elif for_sig==i:

View File

@@ -240,7 +240,6 @@ class Abstract_Wallet(object):
def load_accounts(self):
self.accounts = {}
d = self.storage.get('accounts', {})
for k, v in d.items():
if self.wallet_type == 'old' and k in [0, '0']:
@@ -248,10 +247,6 @@ class Abstract_Wallet(object):
self.accounts['0'] = OldAccount(v)
elif v.get('imported'):
self.accounts[k] = ImportedAccount(v)
elif v.get('xpub3'):
self.accounts[k] = BIP32_Account_2of3(v)
elif v.get('xpub2'):
self.accounts[k] = BIP32_Account_2of2(v)
elif v.get('xpub'):
self.accounts[k] = BIP32_Account(v)
elif v.get('pending'):
@@ -942,7 +937,7 @@ class Abstract_Wallet(object):
if redeemScript:
txin['redeemScript'] = redeemScript
txin['num_sig'] = 2
txin['num_sig'] = account.m
else:
txin['redeemPubkey'] = account.get_pubkey(*sequence)
txin['num_sig'] = 1
@@ -1732,55 +1727,45 @@ class NewWallet(BIP32_Wallet, Mnemonic):
self.add_account('0', account)
class Wallet_2of2(BIP32_Wallet, Mnemonic):
# Wallet with multisig addresses.
class Multisig_Wallet(BIP32_Wallet, Mnemonic):
# generic m of n
root_name = "x1/"
root_derivation = "m/"
wallet_type = '2of2'
def __init__(self, storage):
BIP32_Wallet.__init__(self, storage)
self.wallet_type = storage.get('wallet_type')
m = re.match('(\d+)of(\d+)', self.wallet_type)
self.m = int(m.group(1))
self.n = int(m.group(2))
def load_accounts(self):
self.accounts = {}
d = self.storage.get('accounts', {})
v = d.get('0')
if v:
if v.get('xpub3'):
v['xpubs'] = [v['xpub'], v['xpub2'], v['xpub3']]
elif v.get('xpub2'):
v['xpubs'] = [v['xpub'], v['xpub2']]
self.accounts = {'0': Multisig_Account(v)}
def create_main_account(self, password):
xpub1 = self.master_public_keys.get("x1/")
xpub2 = self.master_public_keys.get("x2/")
account = BIP32_Account_2of2({'xpub':xpub1, 'xpub2':xpub2})
account = Multisig_Account({'xpubs': self.master_public_keys.values(), 'm': self.m})
self.add_account('0', account)
def get_master_public_keys(self):
return self.master_public_keys
def get_action(self):
xpub1 = self.master_public_keys.get("x1/")
xpub2 = self.master_public_keys.get("x2/")
if xpub1 is None:
return 'create_seed'
if xpub2 is None:
return 'add_cosigner'
for i in range(self.n):
if self.master_public_keys.get("x%d/"%(i+1)) is None:
return 'create_seed' if i == 0 else 'add_cosigners'
if not self.accounts:
return 'create_accounts'
class Wallet_2of3(Wallet_2of2):
# multisig 2 of 3
wallet_type = '2of3'
def create_main_account(self, password):
xpub1 = self.master_public_keys.get("x1/")
xpub2 = self.master_public_keys.get("x2/")
xpub3 = self.master_public_keys.get("x3/")
account = BIP32_Account_2of3({'xpub':xpub1, 'xpub2':xpub2, 'xpub3':xpub3})
self.add_account('0', account)
def get_action(self):
xpub1 = self.master_public_keys.get("x1/")
xpub2 = self.master_public_keys.get("x2/")
xpub3 = self.master_public_keys.get("x3/")
if xpub1 is None:
return 'create_seed'
if xpub2 is None or xpub3 is None:
return 'add_two_cosigners'
if not self.accounts:
return 'create_accounts'
class OldWallet(Deterministic_Wallet):
wallet_type = 'old'
@@ -1859,8 +1844,8 @@ wallet_types = [
('standard', 'xpub', ("BIP32 Import"), BIP32_Simple_Wallet),
('standard', 'standard', ("Standard wallet"), NewWallet),
('standard', 'imported', ("Imported wallet"), Imported_Wallet),
('multisig', '2of2', ("Multisig wallet (2 of 2)"), Wallet_2of2),
('multisig', '2of3', ("Multisig wallet (2 of 3)"), Wallet_2of3)
('multisig', '2of2', ("Multisig wallet (2 of 2)"), Multisig_Wallet),
('multisig', '2of3', ("Multisig wallet (2 of 3)"), Multisig_Wallet)
]
# former WalletFactory
@@ -1898,7 +1883,10 @@ class Wallet(object):
WalletClass = c
break
else:
raise BaseException('unknown wallet type', wallet_type)
if re.match('(\d+)of(\d+)', wallet_type):
WalletClass = Multisig_Wallet
else:
raise BaseException('unknown wallet type', wallet_type)
else:
if seed_version == OLD_SEED_VERSION:
WalletClass = OldWallet
@@ -2012,10 +2000,7 @@ class Wallet(object):
@classmethod
def from_multisig(klass, key_list, password, storage):
if len(key_list) == 2:
self = Wallet_2of2(storage)
elif len(key_list) == 3:
self = Wallet_2of3(storage)
self = Multisig_Wallet(storage)
key_list = sorted(key_list, key = lambda x: klass.is_xpub(x))
for i, text in enumerate(key_list):
assert klass.is_seed(text) or klass.is_xprv(text) or klass.is_xpub(text)