qml wizard: enforce homogeneous master keys in multisig
- {xpub, Ypub, Zpub} categories cannot be mixed
- old mpk must not be used in multisig
This commit is contained in:
@@ -289,7 +289,8 @@ class BIP32Node(NamedTuple):
|
|||||||
return hash_160(self.eckey.get_public_key_bytes(compressed=True))[0:4]
|
return hash_160(self.eckey.get_public_key_bytes(compressed=True))[0:4]
|
||||||
|
|
||||||
|
|
||||||
def xpub_type(x):
|
def xpub_type(x: str):
|
||||||
|
assert x is not None
|
||||||
return BIP32Node.from_xkey(x).xtype
|
return BIP32Node.from_xkey(x).xtype
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ Wizard {
|
|||||||
walletwizard.path = wiz.path
|
walletwizard.path = wiz.path
|
||||||
walletwizard.walletCreated()
|
walletwizard.walletCreated()
|
||||||
}
|
}
|
||||||
|
function onCreateError(error) {
|
||||||
|
var dialog = app.messageDialog.createObject(app, { text: error })
|
||||||
|
dialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,14 @@ WizardComponent {
|
|||||||
|
|
||||||
if (cosigner) {
|
if (cosigner) {
|
||||||
applyMasterKey(key)
|
applyMasterKey(key)
|
||||||
if (wiz.hasDuplicateKeys(wizard_data)) {
|
if (wiz.hasDuplicateMasterKeys(wizard_data)) {
|
||||||
validationtext.text = qsTr('Error: duplicate master public key')
|
validationtext.text = qsTr('Error: duplicate master public key')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if (wiz.hasHeterogeneousMasterKeys(wizard_data)) {
|
||||||
|
validationtext.text = qsTr('Error: master public key types do not match')
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid = true
|
return valid = true
|
||||||
|
|||||||
@@ -65,9 +65,12 @@ WizardComponent {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
apply()
|
apply()
|
||||||
if (wiz.hasDuplicateKeys(wizard_data)) {
|
if (wiz.hasDuplicateMasterKeys(wizard_data)) {
|
||||||
validationtext.text = qsTr('Error: duplicate master public key')
|
validationtext.text = qsTr('Error: duplicate master public key')
|
||||||
return
|
return
|
||||||
|
} else if (wiz.hasHeterogeneousMasterKeys(wizard_data)) {
|
||||||
|
validationtext.text = qsTr('Error: master public key types do not match')
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
valid = true
|
valid = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,22 +113,30 @@ class QEBitcoin(QObject):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
k = keystore.from_master_key(key)
|
k = keystore.from_master_key(key)
|
||||||
if isinstance(k, keystore.Xpub): # has xpub # TODO are these checks useful?
|
if wallet_type == 'standard':
|
||||||
t1 = xpub_type(k.xpub)
|
if isinstance(k, keystore.Xpub): # has bip32 xpub
|
||||||
if wallet_type == 'standard':
|
t1 = xpub_type(k.xpub)
|
||||||
if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']:
|
if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']: # disallow Ypub/Zpub
|
||||||
self.validationMessage = '%s: %s' % (_('Wrong key type'), t1)
|
self.validationMessage = '%s: %s' % (_('Wrong key type'), t1)
|
||||||
return False
|
return False
|
||||||
return True
|
elif isinstance(k, keystore.Old_KeyStore):
|
||||||
elif wallet_type == 'multisig':
|
pass
|
||||||
if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']:
|
|
||||||
self.validationMessage = '%s: %s' % (_('Wrong key type'), t1)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
self.validationMessage = '%s: %s' % (_('Unsupported wallet type'), wallet_type)
|
self._logger.error(f"unexpected keystore type: {type(keystore)}")
|
||||||
self.logger.error(f'Unsupported wallet type: {wallet_type}')
|
|
||||||
return False
|
return False
|
||||||
|
elif wallet_type == 'multisig':
|
||||||
|
if not isinstance(k, keystore.Xpub): # old mpk?
|
||||||
|
self.validationMessage = '%s: %s' % (_('Wrong key type'), "not bip32")
|
||||||
|
return False
|
||||||
|
t1 = xpub_type(k.xpub)
|
||||||
|
if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']: # disallow ypub/zpub
|
||||||
|
self.validationMessage = '%s: %s' % (_('Wrong key type'), t1)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.validationMessage = '%s: %s' % (_('Unsupported wallet type'), wallet_type)
|
||||||
|
self._logger.error(f'Unsupported wallet type: {wallet_type}')
|
||||||
|
return False
|
||||||
|
# looks okay
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@pyqtSlot(str, result=bool)
|
@pyqtSlot(str, result=bool)
|
||||||
|
|||||||
@@ -83,10 +83,16 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
|||||||
return self._daemon.singlePasswordEnabled
|
return self._daemon.singlePasswordEnabled
|
||||||
|
|
||||||
@pyqtSlot('QJSValue', result=bool)
|
@pyqtSlot('QJSValue', result=bool)
|
||||||
def hasDuplicateKeys(self, js_data):
|
def hasDuplicateMasterKeys(self, js_data):
|
||||||
self._logger.info('Checking for duplicate keys')
|
self._logger.info('Checking for duplicate masterkeys')
|
||||||
data = js_data.toVariant()
|
data = js_data.toVariant()
|
||||||
return self.has_duplicate_keys(data)
|
return self.has_duplicate_masterkeys(data)
|
||||||
|
|
||||||
|
@pyqtSlot('QJSValue', result=bool)
|
||||||
|
def hasHeterogeneousMasterKeys(self, js_data):
|
||||||
|
self._logger.info('Checking for heterogeneous masterkeys')
|
||||||
|
data = js_data.toVariant()
|
||||||
|
return self.has_heterogeneous_masterkeys(data)
|
||||||
|
|
||||||
@pyqtSlot('QJSValue', bool, str)
|
@pyqtSlot('QJSValue', bool, str)
|
||||||
def createStorage(self, js_data, single_password_enabled, single_password):
|
def createStorage(self, js_data, single_password_enabled, single_password):
|
||||||
|
|||||||
@@ -305,18 +305,38 @@ class NewWalletWizard(AbstractWizard):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def has_duplicate_keys(self, wizard_data):
|
def has_duplicate_masterkeys(self, wizard_data) -> bool:
|
||||||
|
"""Multisig wallets need distinct master keys. If True, need to prevent wallet-creation."""
|
||||||
xpubs = []
|
xpubs = []
|
||||||
xpubs.append(self.keystore_from_data(wizard_data).get_master_public_key())
|
xpubs.append(self.keystore_from_data(wizard_data).get_master_public_key())
|
||||||
for cosigner in wizard_data['multisig_cosigner_data']:
|
for cosigner in wizard_data['multisig_cosigner_data']:
|
||||||
data = wizard_data['multisig_cosigner_data'][cosigner]
|
data = wizard_data['multisig_cosigner_data'][cosigner]
|
||||||
xpubs.append(self.keystore_from_data(data).get_master_public_key())
|
xpubs.append(self.keystore_from_data(data).get_master_public_key())
|
||||||
|
assert xpubs
|
||||||
|
return len(xpubs) != len(set(xpubs))
|
||||||
|
|
||||||
while len(xpubs):
|
def has_heterogeneous_masterkeys(self, wizard_data) -> bool:
|
||||||
xpub = xpubs.pop()
|
"""Multisig wallets need homogeneous master keys.
|
||||||
if xpub in xpubs:
|
All master keys need to be bip32, and e.g. Ypub cannot be mixed with Zpub.
|
||||||
|
If True, need to prevent wallet-creation.
|
||||||
|
"""
|
||||||
|
xpubs = []
|
||||||
|
xpubs.append(self.keystore_from_data(wizard_data).get_master_public_key())
|
||||||
|
for cosigner in wizard_data['multisig_cosigner_data']:
|
||||||
|
data = wizard_data['multisig_cosigner_data'][cosigner]
|
||||||
|
xpubs.append(self.keystore_from_data(data).get_master_public_key())
|
||||||
|
assert xpubs
|
||||||
|
try:
|
||||||
|
k_xpub_type = xpub_type(xpubs[0])
|
||||||
|
except Exception:
|
||||||
|
return True # maybe old_mpk?
|
||||||
|
for xpub in xpubs:
|
||||||
|
try:
|
||||||
|
my_xpub_type = xpub_type(xpub)
|
||||||
|
except Exception:
|
||||||
|
return True # maybe old_mpk?
|
||||||
|
if my_xpub_type != k_xpub_type:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def keystore_from_data(self, data):
|
def keystore_from_data(self, data):
|
||||||
@@ -422,10 +442,17 @@ class NewWalletWizard(AbstractWizard):
|
|||||||
db.put('x3/', data['x3/'])
|
db.put('x3/', data['x3/'])
|
||||||
db.put('use_trustedcoin', True)
|
db.put('use_trustedcoin', True)
|
||||||
elif data['wallet_type'] == 'multisig':
|
elif data['wallet_type'] == 'multisig':
|
||||||
|
if not isinstance(k, keystore.Xpub):
|
||||||
|
raise Exception(f"unexpected keystore(main) type={type(k)} in multisig. not bip32.")
|
||||||
|
k_xpub_type = xpub_type(k.xpub)
|
||||||
db.put('wallet_type', '%dof%d' % (data['multisig_signatures'],data['multisig_participants']))
|
db.put('wallet_type', '%dof%d' % (data['multisig_signatures'],data['multisig_participants']))
|
||||||
db.put('x1/', k.dump())
|
db.put('x1/', k.dump())
|
||||||
for cosigner in data['multisig_cosigner_data']:
|
for cosigner in data['multisig_cosigner_data']:
|
||||||
cosigner_keystore = self.keystore_from_data(data['multisig_cosigner_data'][cosigner])
|
cosigner_keystore = self.keystore_from_data(data['multisig_cosigner_data'][cosigner])
|
||||||
|
if not isinstance(cosigner_keystore, keystore.Xpub):
|
||||||
|
raise Exception(f"unexpected keystore(cosigner) type={type(cosigner_keystore)} in multisig. not bip32.")
|
||||||
|
if k_xpub_type != xpub_type(cosigner_keystore.xpub):
|
||||||
|
raise Exception("multisig wallet needs to have homogeneous xpub types")
|
||||||
if data['encrypt'] and cosigner_keystore.may_have_password():
|
if data['encrypt'] and cosigner_keystore.may_have_password():
|
||||||
cosigner_keystore.update_password(None, data['password'])
|
cosigner_keystore.update_password(None, data['password'])
|
||||||
db.put(f'x{cosigner}/', cosigner_keystore.dump())
|
db.put(f'x{cosigner}/', cosigner_keystore.dump())
|
||||||
|
|||||||
Reference in New Issue
Block a user