From fc3ec7dda05d304778792e40a02d1ec217273b07 Mon Sep 17 00:00:00 2001 From: atticnotion Date: Thu, 16 Dec 2021 22:03:45 +0100 Subject: [PATCH 01/47] Separate many Dlgs from qtdialogs.py --- .gitignore | 2 + ArmoryQt.py | 11 +- armoryengine/ArmoryUtils.py | 1 + armorymodels.py | 4 +- dynamicImport.py | 69 +- jasvet.py | 683 ++++ pytest/testPyBtcWalletRecovery.py | 3 +- qtdialogs/.pylintrc | 568 +++ qtdialogs/ArmoryDialog.py | 64 + qtdialogs/DlgAddressBook.py | 5 +- qtdialogs/DlgBrowserWarn.py | 8 +- qtdialogs/DlgChangePassphrase.py | 140 + qtdialogs/DlgConfirmSend.py | 15 +- qtdialogs/DlgCorruptWallet.py | 313 ++ qtdialogs/DlgDispTxInfo.py | 4 +- qtdialogs/DlgEULA.py | 3 +- qtdialogs/DlgExportTxHistory.py | 344 ++ qtdialogs/DlgHelpAbout.py | 57 + qtdialogs/DlgInflatedQR.py | 39 + qtdialogs/DlgIntroMessage.py | 5 +- qtdialogs/DlgKeypoolSettings.py | 5 +- qtdialogs/DlgMigrateWallet.py | 4 +- qtdialogs/DlgNewAddress.py | 6 +- qtdialogs/DlgOfflineTx.py | 6 +- qtdialogs/DlgPasswd3.py | 62 + qtdialogs/DlgProgress.py | 4 +- qtdialogs/DlgQRCodeDisplay.py | 2 +- qtdialogs/DlgReplaceWallet.py | 97 + qtdialogs/DlgRequestPayment.py | 4 +- qtdialogs/DlgRestoreFragged.py | 582 +++ qtdialogs/DlgRestoreSingle.py | 399 ++ qtdialogs/DlgRestoreWOData.py | 266 ++ qtdialogs/DlgSendBitcoins.py | 4 +- qtdialogs/DlgSetComment.py | 4 +- qtdialogs/DlgSettings.py | 788 ++++ qtdialogs/DlgShowKeyList.py | 4 +- qtdialogs/DlgUniversalRestoreSelect.py | 106 + qtdialogs/DlgUnlockWallet.py | 5 +- qtdialogs/DlgWalletDetails.py | 9 +- qtdialogs/DlgWalletSelect.py | 5 +- qtdialogs/DlgWltRecoverWallet.py | 229 ++ qtdialogs/MsgBoxCustom.py | 100 + qtdialogs/MsgBoxWithDNAA.py | 103 + qtdialogs/QRCodeWidget.py | 102 + qtdialogs/qtdefines.py | 339 +- qtdialogs/qtdialogs.py | 4734 ++++-------------------- ui/AddressTypeSelectDialog.py | 4 +- ui/CoinControlUI.py | 3 +- ui/FeeSelectUI.py | 3 +- ui/MultiSigDialogs.py | 1515 ++++---- ui/TxFramesOffline.py | 5 +- ui/Wizards.py | 189 +- ui/toolsDialogs.py | 19 +- 53 files changed, 6837 insertions(+), 5208 deletions(-) create mode 100644 jasvet.py create mode 100644 qtdialogs/.pylintrc create mode 100644 qtdialogs/ArmoryDialog.py create mode 100644 qtdialogs/DlgChangePassphrase.py create mode 100644 qtdialogs/DlgCorruptWallet.py create mode 100644 qtdialogs/DlgExportTxHistory.py create mode 100644 qtdialogs/DlgHelpAbout.py create mode 100644 qtdialogs/DlgInflatedQR.py create mode 100644 qtdialogs/DlgPasswd3.py create mode 100644 qtdialogs/DlgReplaceWallet.py create mode 100644 qtdialogs/DlgRestoreFragged.py create mode 100644 qtdialogs/DlgRestoreSingle.py create mode 100644 qtdialogs/DlgRestoreWOData.py create mode 100644 qtdialogs/DlgSettings.py create mode 100644 qtdialogs/DlgUniversalRestoreSelect.py create mode 100644 qtdialogs/DlgWltRecoverWallet.py create mode 100644 qtdialogs/MsgBoxCustom.py create mode 100644 qtdialogs/MsgBoxWithDNAA.py create mode 100644 qtdialogs/QRCodeWidget.py diff --git a/.gitignore b/.gitignore index 4ef4bf443..2b9187bd2 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.bak *.swp *.pyc *~ @@ -44,6 +45,7 @@ stamp-h1 .dirstamp 1 .vs +client_cookie Makefile ArmoryDB diff --git a/ArmoryQt.py b/ArmoryQt.py index 99503d30e..f5ba980c9 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -55,7 +55,7 @@ ARMORY_DB_DIR, coin2str, DEFAULT_DATE_FORMAT, \ unixTimeToFormatStr, binary_to_hex, BTC_HOME_DIR, secondsToHumanTime, \ LEVELDB_BLKDATA, LOGRAWDATA, LOGPPRINT, hex_to_binary, \ - getRandomHexits_NotSecure, coin2strNZS + getRandomHexits_NotSecure, coin2strNZS, DEFAULT_ADDR_TYPE from armoryengine.Block import PyBlock from armoryengine.Decorators import RemoveRepeatingExtensions @@ -80,9 +80,10 @@ makeLayoutFrame, HORIZONTAL, QRichLabel, relaxedSizeStr, STYLE_SUNKEN, \ makeHorizFrame, DASHBTNS, STYLE_NONE, UserModeStr, makeVertFrame, \ restoreTableView, determineWalletType, WLTTYPES, tightSizeStr, \ - QLabelButton, MsgBoxWithDNAA, MSGBOX, saveTableView + QLabelButton, MSGBOX, saveTableView from qtdialogs.qtdialogs import URLHandler, ArmorySplashScreen, LoadingDisp +from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.DlgMigrateWallet import DlgMigrateWallet from qtdialogs.DlgSendBitcoins import DlgSendBitcoins from qtdialogs.DlgAddressBook import DlgAddressBook, createAddrBookButton @@ -96,6 +97,12 @@ from qtdialogs.DlgUnlockWallet import DlgUnlockWallet from qtdialogs.DlgDispTxInfo import DlgDispTxInfo from qtdialogs.DlgSetComment import DlgSetComment +from qtdialogs.DlgSettings import DlgSettings +from qtdialogs.DlgExportTxHistory import DlgExportTxHistory +from qtdialogs.DlgWltRecoverWallet import DlgWltRecoverWallet +from qtdialogs.DlgHelpAbout import DlgHelpAbout +from qtdialogs.MsgBoxCustom import MsgBoxCustom +from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA from ui.QtExecuteSignal import QtExecuteSignal diff --git a/armoryengine/ArmoryUtils.py b/armoryengine/ArmoryUtils.py index 204bcef68..70d5bc119 100755 --- a/armoryengine/ArmoryUtils.py +++ b/armoryengine/ArmoryUtils.py @@ -316,6 +316,7 @@ class SignerException(Exception): pass BTC_HOME_DIR = '' ARMORY_HOME_DIR = '' ARMORY_DB_DIR = '' +DEFAULT_ADDR_TYPE= '' SUBDIR = 'testnet3' if USE_TESTNET else '' + 'regtest' if USE_REGTEST else '' #settingsObject = SettingsFile(CLI_OPTIONS.settingsPath) diff --git a/armorymodels.py b/armorymodels.py index 1443cbff5..dd6a39dd5 100755 --- a/armorymodels.py +++ b/armorymodels.py @@ -34,9 +34,11 @@ from armoryengine.CppBridge import TheBridge from armorycolors import Colors -from qtdialogs.qtdefines import ArmoryDialog, determineWalletType, WLTTYPES, \ +from qtdialogs.qtdefines import determineWalletType, WLTTYPES, \ GETFONT, CHANGE_ADDR_DESCR_STRING +from qtdialogs.ArmoryDialog import ArmoryDialog + WLTVIEWCOLS = enum('Visible', 'ID', 'Name', 'Secure', 'Bal') LEDGERCOLS = enum('NumConf', 'UnixTime', 'DateStr', 'TxDir', 'WltName', 'Comment', \ 'Amount', 'isOther', 'WltID', 'TxHash', 'isCoinbase', 'toSelf', \ diff --git a/dynamicImport.py b/dynamicImport.py index 3ce79440c..338048ada 100644 --- a/dynamicImport.py +++ b/dynamicImport.py @@ -11,7 +11,7 @@ import sys from armoryengine.ArmoryUtils import * from zipfile import ZipFile -from CppBlockUtils import SecureBinaryData, CryptoECDSA +#from CppBlockUtils import SecureBinaryData, CryptoECDSA PY_EXTENSION = '.py' ZIP_EXTENSION = '.zip' SIG_EXTENSION = '.sig' @@ -34,34 +34,34 @@ # signature.txt (signature of the inner zip file) def verifyZipSignature(outerZipFilePath): result = MODULE_ZIP_STATUS.Invalid - try: - dataToSign = None - signature = None - outerZipFile = ZipFile(outerZipFilePath) - # look for a zip file in the name list. - # There should only be 2 files in this zip: - # The inner zip file and the sig file - if len(outerZipFile.namelist()) == 3: - dataToSign = sha256(sha256(outerZipFile.read(INNER_ZIP_FILENAME)) + - sha256(outerZipFile.read(PROPERTIES_FILENAME))) - signature = outerZipFile.read(SIGNATURE_FILENAME) - - if dataToSign and signature: - """ - Signature file contains multiple lines, of the form "key=value\n" - The last line is the hex-encoded signature, which is over the - source code + everything in the sig file up to the last line. - The key-value lines may contain properties such as signature - validity times/expiration, contact info of author, etc. - """ - dataToSignSBD = SecureBinaryData(dataToSign) - sigSBD = SecureBinaryData(hex_to_binary(signature.strip())) - publicKeySBD = SecureBinaryData(hex_to_binary(ARMORY_INFO_SIGN_PUBLICKEY)) - result = MODULE_ZIP_STATUS.Valid if CryptoECDSA().VerifyData(dataToSignSBD, sigSBD, publicKeySBD) else \ - MODULE_ZIP_STATUS.Unsigned - except: +# try: +# dataToSign = None +# signature = None +# outerZipFile = ZipFile(outerZipFilePath) +# # look for a zip file in the name list. +# # There should only be 2 files in this zip: +# # The inner zip file and the sig file +# if len(outerZipFile.namelist()) == 3: +# dataToSign = sha256(sha256(outerZipFile.read(INNER_ZIP_FILENAME)) + +# sha256(outerZipFile.read(PROPERTIES_FILENAME))) +# signature = outerZipFile.read(SIGNATURE_FILENAME) +# +# if dataToSign and signature: +# """ +# Signature file contains multiple lines, of the form "key=value\n" +# The last line is the hex-encoded signature, which is over the +# source code + everything in the sig file up to the last line. +# The key-value lines may contain properties such as signature +# validity times/expiration, contact info of author, etc. +# """ +# dataToSignSBD = SecureBinaryData(dataToSign) +# sigSBD = SecureBinaryData(hex_to_binary(signature.strip())) +# publicKeySBD = SecureBinaryData(hex_to_binary(ARMORY_INFO_SIGN_PUBLICKEY)) +# result = MODULE_ZIP_STATUS.Valid if CryptoECDSA().VerifyData(dataToSignSBD, sigSBD, publicKeySBD) else \ +# MODULE_ZIP_STATUS.Unsigned +# except: # if anything goes wrong an invalid zip file indicator will get returned - pass +# pass return result # TODO - Write this method - currently this just a place holder @@ -74,13 +74,13 @@ def signZipFile(zipFilePath, propertiesDictionary=None): # else if ti's a dictionary save it to a file and use that. # Read the contents of the Zip File and the properties file - zipFileData = None - propertiesFileData = None - dataToSign = sha256(sha256(zipFileData) + sha256(propertiesFileData)) - dataToSignSBD = SecureBinaryData(dataToSign) +# zipFileData = None +# propertiesFileData = None +# dataToSign = sha256(sha256(zipFileData) + sha256(propertiesFileData)) +# dataToSignSBD = SecureBinaryData(dataToSign) # get the privKeySBD - privKeySBD = None - signature = CryptoECDSA().SignData(dataToSignSBD, privKeySBD, ENABLE_DETSIGN) +# privKeySBD = None +# signature = CryptoECDSA().SignData(dataToSignSBD, privKeySBD, ENABLE_DETSIGN) # Write the Signature to signature.txt # rename the source Zip file to inner.zip # Create a new Zip File with the original name of the source zip file @@ -216,4 +216,3 @@ def dynamicImportNoZip(inDir, moduleName, injectLocals=None): exit(1) return modTemp - \ No newline at end of file diff --git a/jasvet.py b/jasvet.py new file mode 100644 index 000000000..bedb7e00e --- /dev/null +++ b/jasvet.py @@ -0,0 +1,683 @@ +#!/usr/bin/env python + +# jackjack's signing/verifying tool +# verifies base64 signatures from Bitcoin +# signs message in three formats: +# - Bitcoin base64 (compatible with Bitcoin) +# - ASCII armored, Clearsign +# - ASCII armored, Base64 +# +# Licence: Public domain or CC0 + +from __future__ import print_function +import base64 +import hashlib +import random +import time + +#import CppBlockUtils +from armoryengine.ArmoryUtils import getVersionString, BTCARMORY_VERSION, \ + ChecksumError, ADDRBYTE + + +FTVerbose=False + +version='0.1.0' + +_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F +_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +_b = 0x0000000000000000000000000000000000000000000000000000000000000007 +_a = 0x0000000000000000000000000000000000000000000000000000000000000000 +_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 +_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 + +BEGIN_MARKER = '-----BEGIN ' +END_MARKER = '-----END ' +DASHX5 = '-----' +RN = '\r\n' +RNRN = '\r\n\r\n' +CLEARSIGN_MSG_TYPE_MARKER = 'BITCOIN SIGNED MESSAGE' +BITCOIN_SIG_TYPE_MARKER = 'BITCOIN SIGNATURE' +BASE64_MSG_TYPE_MARKER = 'BITCOIN MESSAGE' +BITCOIN_ARMORY_COMMENT = '' +class UnknownSigBlockType(Exception): pass + +def randomk(): + # Using Crypto++ CSPRNG instead of python's + #sbdRandK = CppBlockUtils.SecureBinaryData().GenerateRandom(32) + sbdRandK = 0 + hexRandK = sbdRandK.toBinStr().encode('hex_codec') + return int(hexRandK, 16) + + +# Common constants/functions for Bitcoin +def hash_160_to_bc_address(h160, addrtype=0): + vh160 = chr(addrtype) + h160 + h = Hash(vh160) + addr = vh160 + h[0:4] + return b58encode(addr) + +def bc_address_to_hash_160(addr): + hash160 = b58decode(addr, 25) + return hash160[1:21] + +def Hash(data): + return hashlib.sha256(hashlib.sha256(data).digest()).digest() + +def sha256(data): + return hashlib.sha256(data).digest() + +def sha1(data): + return hashlib.sha1(data).digest() + +__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +__b58base = len(__b58chars) + +def b58encode(v): + long_value = 0 + for (i, c) in enumerate(v[::-1]): + long_value += (256**i) * ord(c) + + result = '' + while long_value >= __b58base: + div, mod = divmod(long_value, __b58base) + result = __b58chars[mod] + result + long_value = div + result = __b58chars[long_value] + result + + nPad = 0 + for c in v: + if c == '\0': nPad += 1 + else: break + + return (__b58chars[0]*nPad) + result + +def b58decode(v, length): + long_value = 0 + for (i, c) in enumerate(v[::-1]): + long_value += __b58chars.find(c) * (__b58base**i) + + result = '' + while long_value >= 256: + div, mod = divmod(long_value, 256) + result = chr(mod) + result + long_value = div + result = chr(long_value) + result + + nPad = 0 + for c in v: + if c == __b58chars[0]: nPad += 1 + else: break + + result = chr(0)*nPad + result + if length is not None and len(result) != length: + return None + + return result + +def ASecretToSecret(key): + vch = DecodeBase58Check(key) + if vch and vch[0] == chr(128): + return vch[1:] + else: + return False + +def DecodeBase58Check(psz): + vchRet = b58decode(psz, None) + key = vchRet[0:-4] + csum = vchRet[-4:] + hashValue = Hash(key) + cs32 = hashValue[0:4] + if cs32 != csum: + return None + else: + return key + + +def regenerate_key(sec): + b = ASecretToSecret(sec) + if not b: + return False + b = b[0:32] + secret = int('0x' + b.encode('hex'), 16) + return EC_KEY(secret) + +def GetPubKey(pkey, compressed=False): + return i2o_ECPublicKey(pkey, compressed) + +def GetPrivKey(pkey, compressed=False): + return i2d_ECPrivateKey(pkey, compressed) + +def GetSecret(pkey): + return ('%064x' % pkey.secret).decode('hex') + + +def i2d_ECPrivateKey(pkey, compressed=False):#, crypted=True): + part3='a081a53081a2020101302c06072a8648ce3d0101022100' # for uncompressed keys + if compressed: + if True:#not crypted: ## Bitcoin accepts both part3's for crypted wallets... + part3='a08185308182020101302c06072a8648ce3d0101022100' # for compressed keys + key = '3081d30201010420' + \ + '%064x' % pkey.secret + \ + part3 + \ + '%064x' % _p + \ + '3006040100040107042102' + \ + '%064x' % _Gx + \ + '022100' + \ + '%064x' % _r + \ + '020101a124032200' + else: + key = '308201130201010420' + \ + '%064x' % pkey.secret + \ + part3 + \ + '%064x' % _p + \ + '3006040100040107044104' + \ + '%064x' % _Gx + \ + '%064x' % _Gy + \ + '022100' + \ + '%064x' % _r + \ + '020101a144034200' + + return key.decode('hex') + i2o_ECPublicKey(pkey, compressed) + +def i2o_ECPublicKey(pkey, compressed=False): + if compressed: + if pkey.pubkey.point.y() & 1: + key = '03' + '%064x' % pkey.pubkey.point.x() + else: + key = '02' + '%064x' % pkey.pubkey.point.x() + else: + key = '04' + \ + '%064x' % pkey.pubkey.point.x() + \ + '%064x' % pkey.pubkey.point.y() + + return key.decode('hex') + +def hash_160(public_key): + md = hashlib.new('ripemd160') + md.update(hashlib.sha256(public_key).digest()) + return md.digest() + +def public_key_to_bc_address(public_key, v=ADDRBYTE): + h160 = hash_160(public_key) + if isinstance(v, str): + v = ord(v) + return hash_160_to_bc_address(h160, v) + +def inverse_mod( a, m ): + if a < 0 or m <= a: a = a % m + c, d = a, m + uc, vc, ud, vd = 1, 0, 0, 1 + while c != 0: + q, c, d = divmod( d, c ) + ( c, ) + uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc + assert d == 1 + if ud > 0: return ud + else: return ud + m + +class CurveFp( object ): + def __init__( self, p, a, b ): + self.__p = p + self.__a = a + self.__b = b + + def p( self ): + return self.__p + + def a( self ): + return self.__a + + def b( self ): + return self.__b + + def contains_point( self, x, y ): + return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 + +class Point( object ): + def __init__( self, curve, x, y, order = None ): + self.__curve = curve + self.__x = x + self.__y = y + self.__order = order + if self.__curve: assert self.__curve.contains_point( x, y ) + if order: assert self * order == INFINITY + + def __add__( self, other ): + if other == INFINITY: return self + if self == INFINITY: return other + assert self.__curve == other.__curve + if self.__x == other.__x: + if ( self.__y + other.__y ) % self.__curve.p() == 0: + return INFINITY + else: + return self.double() + + p = self.__curve.p() + l = ( ( other.__y - self.__y ) * \ + inverse_mod( other.__x - self.__x, p ) ) % p + x3 = ( l * l - self.__x - other.__x ) % p + y3 = ( l * ( self.__x - x3 ) - self.__y ) % p + return Point( self.__curve, x3, y3 ) + + def __mul__( self, other ): + def leftmost_bit( x ): + assert x > 0 + result = 1 + while result <= x: result = 2 * result + return result / 2 + + e = other + if self.__order: e = e % self.__order + if e == 0: return INFINITY + if self == INFINITY: return INFINITY + assert e > 0 + e3 = 3 * e + negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) + i = leftmost_bit( e3 ) / 2 + result = self + while i > 1: + result = result.double() + if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self + if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self + i = i / 2 + return result + + def __rmul__( self, other ): + return self * other + + def __str__( self ): + if self == INFINITY: return "infinity" + return "(%d,%d)" % ( self.__x, self.__y ) + + def double( self ): + if self == INFINITY: + return INFINITY + + p = self.__curve.p() + a = self.__curve.a() + l = ( ( 3 * self.__x * self.__x + a ) * \ + inverse_mod( 2 * self.__y, p ) ) % p + x3 = ( l * l - 2 * self.__x ) % p + y3 = ( l * ( self.__x - x3 ) - self.__y ) % p + return Point( self.__curve, x3, y3 ) + + def x( self ): + return self.__x + + def y( self ): + return self.__y + + def curve( self ): + return self.__curve + + def order( self ): + return self.__order + +INFINITY = Point( None, None, None ) + +def str_to_long(b): + res = 0 + pos = 1 + for a in reversed(b): + res += ord(a) * pos + pos *= 256 + return res + +class Public_key( object ): + def __init__( self, generator, point, c ): + self.curve = generator.curve() + self.generator = generator + self.point = point + self.compressed = c + n = generator.order() + if not n: + raise RuntimeError("Generator point must have order.") + if not n * point == INFINITY: + raise RuntimeError("Generator point order is bad.") + if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): + raise RuntimeError("Generator point has x or y out of range.") + + def verify( self, hashValue, signature ): + if isinstance(hashValue, str): + hashValue=str_to_long(hashValue) + G = self.generator + n = G.order() + r = signature.r + s = signature.s + if r < 1 or r > n-1: return False + if s < 1 or s > n-1: return False + c = inverse_mod( s, n ) + u1 = ( hashValue * c ) % n + u2 = ( r * c ) % n + xy = u1 * G + u2 * self.point + v = xy.x() % n + return v == r + + def ser(self): + if self.compressed: + if self.point.y() & 1: + key = '03' + '%064x' % self.point.x() + else: + key = '02' + '%064x' % self.point.x() + else: + key = '04' + \ + '%064x' % self.point.x() + \ + '%064x' % self.point.y() + + return key.decode('hex') + + +class Signature( object ): + def __init__( self, r, s ): + self.r = r + self.s = s + + def ser(self): + return ("%064x%064x"%(self.r,self.s)).decode('hex') + +class Private_key( object ): + def __init__( self, public_key, secret_multiplier ): + self.public_key = public_key + self.secret_multiplier = secret_multiplier + +# def der( self ): +# hex_der_key = '06052b8104000a30740201010420' + \ +# '%064x' % self.secret_multiplier + \ +# 'a00706052b8104000aa14403420004' + \ +# '%064x' % self.public_key.point.x() + \ +# '%064x' % self.public_key.point.y() +# return hex_der_key.decode('hex') + + def sign( self, hashValue, random_k ): + if isinstance(hashValue, str): + hashValue=str_to_long(hashValue) + G = self.public_key.generator + n = G.order() + k = random_k % n + p1 = k * G + r = p1.x() + if r == 0: raise RuntimeError("amazingly unlucky random number r") + s = ( inverse_mod( k, n ) * \ + ( hashValue + ( self.secret_multiplier * r ) % n ) ) % n + if s == 0: raise RuntimeError("amazingly unlucky random number s") + return Signature( r, s ) + +class EC_KEY(object): + def __init__( self, secret, c=False): + curve = CurveFp( _p, _a, _b ) + generator = Point( curve, _Gx, _Gy, _r ) + self.pubkey = Public_key( generator, generator * secret, c ) + self.privkey = Private_key( self.pubkey, secret ) + self.secret = secret + +def decbin(d, l=0, rev=False): + if l==0: + a="%x"%d + if len(a)%2: a='0'+a + else: + a=("%0"+str(2*l)+"x")%d + a=a.decode('hex') + if rev: + a=a[::-1] + return a + +def decvi(d): + if d<0xfd: + return decbin(d) + elif d<0xffff: + return '\xfd'+decbin(d,2,True) + elif d<0xffffffff: + return '\xfe'+decbin(d,4,True) + return '\xff'+decbin(d,8,True) + +def format_msg_to_sign(msg): + return "\x18Bitcoin Signed Message:\n"+decvi(len(msg))+msg + +def sqrt_mod(a, p): + return pow(a, (p+1)/4, p) + + + +curve_secp256k1 = CurveFp (_p, _a, _b) +generator_secp256k1 = g = Point (curve_secp256k1, _Gx, _Gy, _r) +randrange = random.SystemRandom().randrange + +# Signing/verifying + +def verify_message_Bitcoin(signature, message, pureECDSASigning=False, networkVersionNumber=0): + msg=message + if not pureECDSASigning: + msg=Hash(format_msg_to_sign(message)) + + compressed=False + curve = curve_secp256k1 + G = generator_secp256k1 + _a,_b,_p=curve.a(),curve.b(),curve.p() + + order = G.order() + sig = base64.b64decode(signature) + if len(sig) != 65: + raise Exception("vmB","Bad signature") + + hb = ord(sig[0]) + r,s = map(str_to_long,[sig[1:33],sig[33:65]]) + + if hb < 27 or hb >= 35: + raise Exception("vmB","Bad first byte") + if hb >= 31: + compressed = True + hb -= 4 + + recid = hb - 27 + x = (r + (recid/2) * order) % _p + y2 = ( pow(x,3,_p) + _a*x + _b ) % _p + yomy = sqrt_mod(y2, _p) + if (yomy - recid) % 2 == 0: + y=yomy + else: + y=_p - yomy + + R = Point(curve, x, y, order) + e = str_to_long(msg) + minus_e = -e % order + inv_r = inverse_mod(r,order) + Q = inv_r * ( R*s + G*minus_e ) + public_key = Public_key(G, Q, compressed) + addr = public_key_to_bc_address(public_key.ser(), networkVersionNumber) + return addr + +def sign_message(secret, message, pureECDSASigning=False): + if len(secret) == 32: + pkey = EC_KEY(str_to_long(secret)) + compressed = False + elif len(secret) == 33: + pkey = EC_KEY(str_to_long(secret[:-1])) + secret=secret[:-1] + compressed = True + else: + raise Exception("sm","Bad private key size") + + msg=message + if not pureECDSASigning: + msg=Hash(format_msg_to_sign(message)) + + eckey = EC_KEY(str_to_long(secret), compressed) + private_key = eckey.privkey + public_key = eckey.pubkey + addr = public_key_to_bc_address(GetPubKey(eckey,eckey.pubkey.compressed)) + + sig = private_key.sign(msg, randomk()) + if not public_key.verify(msg, sig): + raise Exception("sm","Problem signing message") + return [sig,addr,compressed,public_key] + + +def sign_message_Bitcoin(secret, msg, pureECDSASigning=False): + sig,addr,compressed,public_key=sign_message(secret, msg, pureECDSASigning) + + for i in range(4): + hb=27+i + if compressed: + hb+=4 + sign=base64.b64encode(chr(hb)+sig.ser()) + try: + networkVersionNumber = str_to_long(b58decode(addr, None)) >> (8*24) + if addr == verify_message_Bitcoin(sign, msg, pureECDSASigning, networkVersionNumber): + return {'address':addr, 'b64-signature':sign, 'signature':chr(hb)+sig.ser(), 'message':msg} + except Exception as e: +# print e.args + pass + + raise Exception("smB","Unable to construct recoverable key") + +def FormatText(t, sigctx=False, verbose=False): #sigctx: False=what is displayed, True=what is signed + r='' + te=t.split('\n') + for l in te: + while len(l) and l[len(l)-1] in [' ', '\r', '\t', chr(9)]: + l=l[:-1] + if not len(l) or l[len(l)-1]!='\r': + l+='\r' + if not sigctx: + if len(l) and l[0]=='-': + l='- '+l + r+=l+'\n' + r=r[:-2] + + global FTVerbose + if FTVerbose: + print(' -- Sent: '+t.encode('hex')) + if sigctx: + print(' -- Signed: '+r.encode('hex')) + else: + print(' -- Displayed: '+r.encode('hex')) + + return r + + +def crc24(m): + INIT = 0xB704CE + POLY = 0x1864CFB + crc = INIT + r = '' + for o in m: + o=ord(o) + crc ^= (o << 16) + for i in xrange(8): + crc <<= 1 + if crc & 0x1000000: + crc ^= POLY + for i in range(3): + r += chr( ( crc & (0xff<<(8*i))) >> (8*i) ) + return r + +def chunks(t, n): + return [t[i:i+n] for i in range(0, len(t), n)] + +def ASCIIArmory(block, name, addComment=False): + + r=BEGIN_MARKER+name+DASHX5+RN + if addComment: + r+= BITCOIN_ARMORY_COMMENT + r+=RNRN + r+=RN.join(chunks(base64.b64encode(block), 64))+RN+'=' + r+=base64.b64encode(crc24(block))+RN + + r+=END_MARKER+name+DASHX5 + return r + +def readSigBlock(r): + # Take the name off of the end because the BEGIN markers are confusing + r = FormatText(r, True) + name = r.split(BEGIN_MARKER)[1].split(DASHX5)[0] + if name == BASE64_MSG_TYPE_MARKER: + encoded,crc = r.split(BEGIN_MARKER)[1].split(END_MARKER)[0].split(DASHX5)[1].strip().split('\n=') + crc = crc.strip() + # Always starts with a blank line (\r\n\r\n) chop that off with the comment oand process the rest + encoded = encoded.split(RNRN)[1] + # Combines 64 byte chunks that are separated by \r\n + encoded = ''.join(encoded.split(RN)) + # decode the message. + decoded = base64.b64decode(encoded) + # Check sum of decoded messgae + if base64.b64decode(crc) != crc24(decoded): + raise ChecksumError + # The signature is followed by the message and the whole thing is encoded + # The message always starts at 65 because the signature is 65 bytes. + signature = base64.b64encode(decoded[:65]) + msg = decoded[65:] + elif name == CLEARSIGN_MSG_TYPE_MARKER: + # First get rid of the Clearsign marker and everything before it in case the user + # added extra lines that would confuse the parsing that follows + # The message is preceded by a blank line (\r\n\r\n) chop that off with the comment and process the rest + # For Clearsign the message is unencoded since the message could include the \r\n\r\n we only ignore + # the first and combine the rest. + msg = r.split(BEGIN_MARKER+CLEARSIGN_MSG_TYPE_MARKER+DASHX5)[1] + msg = RNRN.join(msg.split(RNRN)[1:]) + msg = msg.split(RN+DASHX5)[0] + # Only the signature is encoded, use the original r to pull out the encoded signature + encoded = r.split(BEGIN_MARKER)[2].split(DASHX5)[1].split(BITCOIN_SIG_TYPE_MARKER)[0] + encoded, crc = encoded.split('\n=') + encoded = ''.join(encoded.split('\n')) + signature = ''.join(encoded.split('\r')) + crc = crc.strip() + if base64.b64decode(crc) != crc24(base64.b64decode(signature)): + raise ChecksumError + else: + raise UnknownSigBlockType() + return signature, msg + +#============================================== + +def verifySignature(b64sig, msg, signVer='v0', networkVersionNumber=0): + # If version 1, apply RFC2440 formatting rules to the message + if signVer=='v1': + msg = FormatText(msg, True) + return verify_message_Bitcoin(b64sig, msg, networkVersionNumber = networkVersionNumber) + +def ASv0(privkey, msg): + return sign_message_Bitcoin(privkey, msg) + +def ASv1CS(privkey, msg): + sig=ASv0(privkey, FormatText(msg)) + r=BEGIN_MARKER+CLEARSIGN_MSG_TYPE_MARKER+DASHX5+RN+BITCOIN_ARMORY_COMMENT+RN + r+=FormatText(msg)+RN + r+=ASCIIArmory(sig['signature'], BITCOIN_SIG_TYPE_MARKER) + return r + +def ASv1B64(privkey, msg): + sig=ASv0(privkey, FormatText(msg)) + return ASCIIArmory(sig['signature']+sig['message'], BASE64_MSG_TYPE_MARKER, True) + +#============================================== + +# +# Some tests with ugly output +# You can delete the print commands in FormatText() after testing +# + +if __name__=='__main__': + pvk1='\x01'*32 + text0='Hello world!' + text1='Hello world!\n' + text2='Hello world!\n\t' + text3='Hello world!\n-jackjack' + text4='Hello world!\n-jackjack ' + text5='Hello world!' + + FTVerbose=True + + sv0=ASv0(pvk1, text1) + print(sv0) + print(verifySignature(sv0['b64-signature'], sv0['message'], signVer='v0')) + print(ASv1B64(pvk1, text1)) + print() + print(ASv1CS(pvk1, text1)) + print() + print(ASv1CS(pvk1, text2)) + print() + print(ASv1CS(pvk1, text3)) + print() + print(ASv1CS(pvk1, text4)) + print() + print(ASv1CS(pvk1, text5)) \ No newline at end of file diff --git a/pytest/testPyBtcWalletRecovery.py b/pytest/testPyBtcWalletRecovery.py index 31ae7235c..e2277ecc7 100644 --- a/pytest/testPyBtcWalletRecovery.py +++ b/pytest/testPyBtcWalletRecovery.py @@ -2,6 +2,7 @@ sys.path.append('..') from pytest.Tiab import TiabTest + from CppBlockUtils import CryptoECDSA, CryptoAES from armoryengine.PyBtcAddress import PyBtcAddress from armoryengine.ArmoryUtils import * @@ -154,4 +155,4 @@ def testWalletRecovery(self): # Running tests with "python " will NOT work for any Armory tests # You must run tests with "python -m unittest " or run all tests with "python -m unittest discover" # if __name__ == "__main__": -# unittest.main() \ No newline at end of file +# unittest.main() diff --git a/qtdialogs/.pylintrc b/qtdialogs/.pylintrc new file mode 100644 index 000000000..f030c4343 --- /dev/null +++ b/qtdialogs/.pylintrc @@ -0,0 +1,568 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths= + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.9 + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# class is considered mixin if its name matches the mixin-class-rgx option. +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins ignore-mixin- +# members is set to 'yes' +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=no + +# Signatures are removed from the similarity computation +ignore-signatures=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/qtdialogs/ArmoryDialog.py b/qtdialogs/ArmoryDialog.py new file mode 100644 index 000000000..cd2777725 --- /dev/null +++ b/qtdialogs/ArmoryDialog.py @@ -0,0 +1,64 @@ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-17, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## + +from PySide2.QtGui import QIcon +from PySide2.QtWidgets import QDialog +from PySide2.QtCore import Signal, Qt +from qtdialogs.qtdefines import AddToRunningDialogsList, GETFONT + +from armoryengine.ArmoryUtils import USE_TESTNET, USE_REGTEST + +################################################################################ +class ArmoryDialog(QDialog): + #create a signal with a random name that children to this dialog will + #connect to close themselves if the parent is closed first + + closeSignal = Signal() + + def __init__(self, parent=None, main=None): + super(ArmoryDialog, self).__init__(parent) + + self.parent = parent + self.main = main + if self.main != None: + self.signalExecution = self.main.signalExecution + + #connect this dialog to the parent's close signal + if self.parent is not None and hasattr(self.parent, 'closeSignal'): + self.parent.closeSignal.connect(self.reject) + + self.setFont(GETFONT('var')) + self.setWindowFlags(Qt.Window) + + if USE_TESTNET: + self.setWindowTitle(self.tr('Armory - Bitcoin Wallet Management [TESTNET] ' + self.__class__.__name__)) + self.setWindowIcon(QIcon('./img/armory_icon_green_32x32.png')) + elif USE_REGTEST: + self.setWindowTitle(self.tr('Armory - Bitcoin Wallet Management [REGTEST] ' + self.__class__.__name__)) + self.setWindowIcon(QIcon('./img/armory_icon_green_32x32.png')) + else: + self.setWindowTitle(self.tr('Armory - Bitcoin Wallet Management')) + self.setWindowIcon(QIcon('./img/armory_icon_32x32.png')) + + @AddToRunningDialogsList + def exec_(self): + return super(ArmoryDialog, self).exec_() + + def reject(self): + self.closeSignal.emit() + super(ArmoryDialog, self).reject() + + def executeMethod(self, _callable, *args): + self.signalExecution.executeMethod(_callable, *args) + + def callLater(self, delay, _callable, *args): + self.signalExecution.callLater(delay, _callable, *args) diff --git a/qtdialogs/DlgAddressBook.py b/qtdialogs/DlgAddressBook.py index 8b7d830cd..8fbe5859f 100644 --- a/qtdialogs/DlgAddressBook.py +++ b/qtdialogs/DlgAddressBook.py @@ -19,10 +19,11 @@ from armoryengine.MultiSigUtils import isBareLockbox, isP2SHLockbox from qtdialogs.qtdialogs import STRETCH -from qtdialogs.qtdefines import ArmoryDialog, QRichLabel, tightSizeStr, \ +from qtdialogs.qtdefines import QRichLabel, tightSizeStr, \ initialColResize, USERMODE, HLINE, makeHorizFrame, restoreTableView, \ saveTableView from qtdialogs.DlgSetComment import DlgSetComment +from qtdialogs.ArmoryDialog import ArmoryDialog from ui.MultiSigModels import LockboxDisplayModel, LockboxDisplayProxy, \ LOCKBOXCOLS @@ -642,4 +643,4 @@ def showContextMenuRx(self, pos): clipb = QApplication.clipboard() clipb.clear() - clipb.setText(str(s).strip()) + clipb.setText(str(s).strip()) \ No newline at end of file diff --git a/qtdialogs/DlgBrowserWarn.py b/qtdialogs/DlgBrowserWarn.py index 9b7857dd4..0b424be2e 100644 --- a/qtdialogs/DlgBrowserWarn.py +++ b/qtdialogs/DlgBrowserWarn.py @@ -6,7 +6,7 @@ # # ################################################################################ -from qtdialogs.qtdefines import ArmoryDialog +from qtdialogs.ArmoryDialog import ArmoryDialog ################################################################################ class DlgBrowserWarn(ArmoryDialog): @@ -33,6 +33,6 @@ def cancel(self): super(DlgBrowserWarn, self).reject() def accept(self): - import webbrowser - webbrowser.open(self.link) - super(DlgBrowserWarn, self).accept() + import webbrowser + webbrowser.open(self.link) + super(DlgBrowserWarn, self).accept() \ No newline at end of file diff --git a/qtdialogs/DlgChangePassphrase.py b/qtdialogs/DlgChangePassphrase.py new file mode 100644 index 000000000..5c7b5a801 --- /dev/null +++ b/qtdialogs/DlgChangePassphrase.py @@ -0,0 +1,140 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +from PySide2.QtWidgets import QCheckBox, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QMessageBox, QPushButton +from PySide2.QtGui import QIcon +from PySide2.QtCore import Qt, SIGNAL, SLOT + +from armorycolors import htmlColor +from armoryengine.ArmoryUtils import isASCII + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.DlgPasswd3 import DlgPasswd3 +from qtdialogs.DlgSettings import MIN_PASSWD_WIDTH + +################################################################################ +class DlgChangePassphrase(ArmoryDialog): + def __init__(self, parent=None, main=None, noPrevEncrypt=True): + super(DlgChangePassphrase, self).__init__(parent, main) + + + + layout = QGridLayout() + if noPrevEncrypt: + lblDlgDescr = QLabel(self.tr('Please enter an passphrase for wallet encryption.\n\n' + 'A good passphrase consists of at least 8 or more\n' + 'random letters, or 5 or more random words.\n')) + lblDlgDescr.setWordWrap(True) + layout.addWidget(lblDlgDescr, 0, 0, 1, 2) + else: + lblDlgDescr = QLabel(self.tr("Change your wallet encryption passphrase")) + layout.addWidget(lblDlgDescr, 0, 0, 1, 2) + self.edtPasswdOrig = QLineEdit() + self.edtPasswdOrig.setEchoMode(QLineEdit.Password) + self.edtPasswdOrig.setMinimumWidth(MIN_PASSWD_WIDTH(self)) + lblCurrPasswd = QLabel(self.tr('Current Passphrase:')) + layout.addWidget(lblCurrPasswd, 1, 0) + layout.addWidget(self.edtPasswdOrig, 1, 1) + + + + lblPwd1 = QLabel(self.tr("New Passphrase:")) + self.edtPasswd1 = QLineEdit() + self.edtPasswd1.setEchoMode(QLineEdit.Password) + self.edtPasswd1.setMinimumWidth(MIN_PASSWD_WIDTH(self)) + + lblPwd2 = QLabel(self.tr("Again:")) + self.edtPasswd2 = QLineEdit() + self.edtPasswd2.setEchoMode(QLineEdit.Password) + self.edtPasswd2.setMinimumWidth(MIN_PASSWD_WIDTH(self)) + + layout.addWidget(lblPwd1, 2, 0) + layout.addWidget(lblPwd2, 3, 0) + layout.addWidget(self.edtPasswd1, 2, 1) + layout.addWidget(self.edtPasswd2, 3, 1) + + self.lblMatches = QLabel(' ' * 20) + self.lblMatches.setTextFormat(Qt.RichText) + layout.addWidget(self.lblMatches, 4, 1) + + + self.chkDisableCrypt = QCheckBox(self.tr('Disable encryption for this wallet')) + if not noPrevEncrypt: + self.connect(self.chkDisableCrypt, SIGNAL('toggled(bool)'), \ + self.disablePassphraseBoxes) + layout.addWidget(self.chkDisableCrypt, 4, 0) + + + self.btnAccept = QPushButton(self.tr("Accept")) + self.btnCancel = QPushButton(self.tr("Cancel")) + buttonBox = QDialogButtonBox() + buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) + buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) + layout.addWidget(buttonBox, 5, 0, 1, 2) + + if noPrevEncrypt: + self.setWindowTitle(self.tr("Set Encryption Passphrase")) + else: + self.setWindowTitle(self.tr("Change Encryption Passphrase")) + + self.setWindowIcon(QIcon(self.main.iconfile)) + + self.setLayout(layout) + + self.connect(self.edtPasswd1, SIGNAL('textChanged(QString)'), \ + self.checkPassphrase) + self.connect(self.edtPasswd2, SIGNAL('textChanged(QString)'), \ + self.checkPassphrase) + + self.connect(self.btnAccept, SIGNAL("clicked()"), \ + self.checkPassphraseFinal) + + self.connect(self.btnCancel, SIGNAL("clicked()"), \ + self, SLOT('reject()')) + + + def disablePassphraseBoxes(self, noEncrypt=True): + self.edtPasswd1.setEnabled(not noEncrypt) + self.edtPasswd2.setEnabled(not noEncrypt) + + + def checkPassphrase(self): + if self.chkDisableCrypt.isChecked(): + return True + p1 = self.edtPasswd1.text() + p2 = self.edtPasswd2.text() + goodColor = htmlColor('TextGreen') + badColor = htmlColor('TextRed') + if not isASCII(p1) or \ + ot isASCII(p2): + self.lblMatches.setText(self.tr('Passphrase is non-ASCII!' % badColor)) + return False + if not p1 == p2: + self.lblMatches.setText(self.tr('Passphrases do not match!' % badColor)) + return False + if len(p1) < 5: + self.lblMatches.setText(self.tr('Passphrase is too short!' % badColor)) + return False + self.lblMatches.setText(self.tr('Passphrases match!' % goodColor)) + return True + + + def checkPassphraseFinal(self): + if self.chkDisableCrypt.isChecked(): + self.accept() + else: + if self.checkPassphrase(): + dlg = DlgPasswd3(self, self.main) + if dlg.exec_(): + if not str(dlg.edtPasswd3.text()) == str(self.edtPasswd1.text()): + QMessageBox.critical(self, self.tr('Invalid Passphrase'), \ + self.tr('You entered your confirmation passphrase incorrectly!'), QMessageBox.Ok) + else: + self.accept() + else: + self.reject() \ No newline at end of file diff --git a/qtdialogs/DlgConfirmSend.py b/qtdialogs/DlgConfirmSend.py index ca550f6dc..9b3a8edf8 100644 --- a/qtdialogs/DlgConfirmSend.py +++ b/qtdialogs/DlgConfirmSend.py @@ -6,21 +6,22 @@ # # ################################################################################ +from PySide2.QtCore import Qt +from PySide2.QtGui import QPixmap, QFont +from PySide2.QtWidgets import QLabel, QGridLayout, QSpacerItem, QPushButton, \ + QDialogButtonBox, QFrame + from armoryengine.ArmoryUtils import CPP_TXOUT_HAS_ADDRSTR, \ CPP_TXOUT_P2WPKH, CPP_TXOUT_P2WSH, script_to_scrAddr, \ scrAddr_to_hash160, coin2strNZS, coin2str from armoryengine.Transaction import getTxOutScriptType from armorycolors import htmlColor -from PySide2.QtCore import Qt -from PySide2.QtGui import QPixmap, QFont -from PySide2.QtWidgets import QLabel, QGridLayout, QSpacerItem, QPushButton, \ - QDialogButtonBox, QFrame - -from qtdialogs.qtdefines import ArmoryDialog, USERMODE, QRichLabel, \ +from qtdialogs.qtdefines import USERMODE, QRichLabel, \ GETFONT, HLINE, makeLayoutFrame, makeVertFrame, makeHorizFrame, \ VERTICAL, STYLE_RAISED from qtdialogs.DlgDispTxInfo import DlgDispTxInfo +from qtdialogs.ArmoryDialog import ArmoryDialog ############################################################################# def excludeChange(outputPairs, wlt): @@ -226,4 +227,4 @@ def openDlgTxInfo(*args): self.setLayout(layout) self.setMinimumWidth(350) - self.setWindowTitle(self.tr('Confirm Transaction')) + self.setWindowTitle(self.tr('Confirm Transaction')) \ No newline at end of file diff --git a/qtdialogs/DlgCorruptWallet.py b/qtdialogs/DlgCorruptWallet.py new file mode 100644 index 000000000..e36bc1785 --- /dev/null +++ b/qtdialogs/DlgCorruptWallet.py @@ -0,0 +1,313 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +import threading + +from armoryengine.PyBtcWallet import PyBtcWallet +#from armoryengine.PyBtcWalletRecovery import RECOVERMODE + +from qtdialogs.DlgProgress import DlgProgress +from qtdialogs.qtdefines import HLINE, QRichLabel, makeVertFrame +from PySide2.QtWidgets import QDialog, QGridLayout, QLabel, QLayout, QPushButton, QScrollArea, QVBoxLayout +from PySide2.QtCore import Qt, SIGNAL +from armorycolors import htmlColor + +################################################################################# +class DlgCorruptWallet(DlgProgress): + def __init__(self, wallet, status, main=None, parent=None, alreadyFailed=True): + super(DlgProgress, self).__init__(parent, main) + + self.connectDlg() + + self.main = main + self.walletList = [] + self.logDirs = [] + + self.running = 1 + self.status = 1 + self.isFixing = False + self.needToSubmitLogs = False +# self.checkMode = RECOVERMODE.NotSet + + self.lock = threading.Lock() + self.condVar = threading.Condition(self.lock) + + mainLayout = QVBoxLayout() + + self.connect(self, SIGNAL('UCF'), self.UCF) + self.connect(self, SIGNAL('Show'), self.show) + self.connect(self, SIGNAL('Exec'), self.run_lock) + self.connect(self, SIGNAL('SNP'), self.setNewProgress) + self.connect(self, SIGNAL('LFW'), self.LFW) + self.connect(self, SIGNAL('SRD'), self.SRD) + + if alreadyFailed: + titleStr = self.tr('Wallet Consistency Check Failed!') + else: + titleStr = self.tr('Perform Wallet Consistency Check') + + lblDescr = QRichLabel(self.tr( + '%2 ' + '

' + 'Armory software now detects and prevents certain kinds of ' + 'hardware errors that could lead to problems with your wallet. ' + '
').arg(htmlColor('TextWarn'), titleStr)) + + lblDescr.setAlignment(Qt.AlignCenter) + + + if alreadyFailed: + self.lblFirstMsg = QRichLabel(self.tr( + 'Armory has detected that wallet file Wallet "%1" (%2) ' + 'is inconsistent and should be further analyzed to ensure that your ' + 'funds are protected. ' + '

' + 'This error will pop up every time you start ' + 'Armory until the wallet has been analyzed and fixed!').arg(wallet.labelName, wallet.uniqueIDB58, htmlColor('TextWarn'))) + elif isinstance(wallet, PyBtcWallet): + self.lblFirstMsg = QRichLabel(self.tr( + 'Armory will perform a consistency check on Wallet "%1" (%2) ' + 'and determine if any further action is required to keep your funds ' + 'protected. This check is normally performed on startup on all ' + 'your wallets, but you can click below to force another ' + 'check.').arg(wallet.labelName, wallet.uniqueIDB58)) + else: + self.lblFirstMsg = QRichLabel('') + + self.QDS = QDialog() + self.lblStatus = QLabel('') + self.addStatus(wallet, status) + self.QDSlo = QVBoxLayout() + self.QDS.setLayout(self.QDSlo) + + self.QDSlo.addWidget(self.lblFirstMsg) + self.QDSlo.addWidget(self.lblStatus) + + self.lblStatus.setVisible(False) + self.lblFirstMsg.setVisible(True) + + saStatus = QScrollArea() + saStatus.setWidgetResizable(True) + saStatus.setWidget(self.QDS) + saStatus.setMinimumHeight(250) + saStatus.setMinimumWidth(500) + + + layoutButtons = QGridLayout() + layoutButtons.setColumnStretch(0, 1) + layoutButtons.setColumnStretch(4, 1) + self.btnClose = QPushButton(self.tr('Hide')) + self.btnFixWallets = QPushButton(self.tr('Run Analysis and Recovery Tool')) + self.btnFixWallets.setDisabled(True) + self.connect(self.btnFixWallets, SIGNAL('clicked()'), self.doFixWallets) + self.connect(self.btnClose, SIGNAL('clicked()'), self.hide) + layoutButtons.addWidget(self.btnClose, 0, 1, 1, 1) + layoutButtons.addWidget(self.btnFixWallets, 0, 2, 1, 1) + + self.lblDescr2 = QRichLabel('') + self.lblDescr2.setAlignment(Qt.AlignCenter) + + self.lblFixRdy = QRichLabel(self.tr( + 'Your wallets will be ready to fix once the scan is over
' + 'You can hide this window until then
')) + + self.lblFixRdy.setAlignment(Qt.AlignCenter) + + self.frmBottomMsg = makeVertFrame(['Space(5)', + HLINE(), + self.lblDescr2, + self.lblFixRdy, + HLINE()]) + + self.frmBottomMsg.setVisible(False) + + + mainLayout.addWidget(lblDescr) + mainLayout.addWidget(saStatus) + mainLayout.addWidget(self.frmBottomMsg) + mainLayout.addLayout(layoutButtons) + + self.setLayout(mainLayout) + self.layout().setSizeConstraint(QLayout.SetFixedSize) + self.setWindowTitle(self.tr('Wallet Error')) + + def addStatus(self, wallet, status): + if wallet: + strStatus = ''.join(status) + str(self.lblStatus.text()) + self.lblStatus.setText(strStatus) + + self.walletList.append(wallet) + + def show(self): + super(DlgCorruptWallet, self).show() + self.activateWindow() + + def run_lock(self): + self.btnClose.setVisible(False) + self.hide() + super(DlgProgress, self).exec_() + self.walletList = None + + def UpdateCanFix(self, conditions, canFix=False): + self.emit(SIGNAL('UCF'), conditions, canFix) + + def UCF(self, conditions, canFix=False): + self.lblFixRdy.setText('') + if canFix: + self.btnFixWallets.setEnabled(True) + self.btnClose.setText(self.tr('Close')) + self.btnClose.setVisible(False) + self.connect(self.btnClose, SIGNAL('clicked()'), self.reject) + self.hide() + + def doFixWallets(self): + self.lblFixRdy.hide() + self.adjustSize() + + self.lblStatus.setVisible(True) + self.lblFirstMsg.setVisible(False) + self.frmBottomMsg.setVisible(False) + + self.btnClose.setDisabled(True) + self.btnFixWallets.setDisabled(True) + self.isFixing = True + + self.lblStatus.hide() + self.QDSlo.removeWidget(self.lblStatus) + + for wlt in self.walletList: + self.main.removeWalletFromApplication(wlt.uniqueIDB58) + +# FixWalletList(self.walletList, self, Progress=self.UpdateText, async=True) + self.adjustSize() + +# def ProcessWallet(self, mode=RECOVERMODE.Full): + def ProcessWallet(self, mode): + #Serves as the entry point for non processing wallets that arent loaded + #or fully processed. Only takes 1 wallet at a time + + if len(self.walletList) > 0: + wlt = None + wltPath = '' + + if isinstance(self.walletList[0], str): + wltPath = self.walletList[0] + else: + wlt = self.walletList[0] + + self.lblDesc = QLabel('') + self.QDSlo.addWidget(self.lblDesc) + + self.lblFixRdy.hide() + self.adjustSize() + + self.frmBottomMsg.setVisible(False) + self.lblStatus.setVisible(True) + self.lblFirstMsg.setVisible(False) + + self.btnClose.setDisabled(True) + self.btnFixWallets.setDisabled(True) + self.isFixing = True + +# self.checkMode = mode +# ParseWallet(wltPath, wlt, mode, self, +# Progress=self.UpdateText, async=True) + + def UpdateDlg(self, text=None, HBar=None, Title=None): + if text is not None: self.lblDesc.setText(text) + self.adjustSize() + + def accept(self): + self.main.emit(SIGNAL('checkForNegImports')) + super(DlgCorruptWallet, self).accept() + + def reject(self): + if not self.isFixing: + super(DlgProgress, self).reject() + self.main.emit(SIGNAL('checkForNegImports')) + + def sigSetNewProgress(self, status): + self.emit(SIGNAL('SNP'), status) + + def setNewProgress(self, status): + self.lblDesc = QLabel('') + self.QDSlo.addWidget(self.lblDesc) + #self.QDS.adjustSize() + status[0] = 1 + + def setRecoveryDone(self, badWallets, goodWallets, fixedWallets, fixers): + self.emit(SIGNAL('SRD'), badWallets, goodWallets, fixedWallets, fixers) + + def SRD(self, badWallets, goodWallets, fixedWallets, fixerObjs): + self.btnClose.setEnabled(True) + self.btnClose.setVisible(True) + self.btnClose.setText(self.tr('Continue')) + self.btnFixWallets.setVisible(False) + self.btnClose.disconnect(self, SIGNAL('clicked()'), self.hide) + self.btnClose.connect(self, SIGNAL('clicked()'), self.accept) + self.isFixing = False + self.frmBottomMsg.setVisible(True) + + anyNegImports = False + for fixer in fixerObjs: + if len(fixer.negativeImports) > 0: + anyNegImports = True + break + + + if len(badWallets) > 0: + self.lblDescr2.setText(self.tr( + 'Failed to fix wallets!').arg(htmlColor('TextWarn'))) + self.main.statusBar().showMessage('Failed to fix wallets!', 150000) + elif len(goodWallets) == len(fixedWallets) and not anyNegImports: + self.lblDescr2.setText(self.tr( + 'Wallet(s) consistent, nothing to ' + 'fix.', "", len(goodWallets)).arg(htmlColor("TextBlue"))) + self.main.statusBar().showMessage( \ + self.tr("Wallet(s) consistent!", "", len(goodWallets)) % \ + 15000) + elif len(fixedWallets) > 0 or anyNegImports: +# if self.checkMode != RECOVERMODE.Check: +# self.lblDescr2.setText(self.tr( +# ' ' +# 'There may still be issues with your ' +# 'wallet! ' +# '
' +# 'It is important that you send us the recovery logs ' +# 'and an email address so the Armory team can check for ' +# 'further risk to your funds!
').arg(htmlColor('TextWarn'))) +# #self.main.statusBar().showMessage('Wallets fixed!', 15000) +# else: +# self.lblDescr2.setText(self.tr('

\ +# Consistency check failed!

')) + + self.lblDescr2.setText(self.tr('

\ + Consistency check failed!

')) + self.adjustSize() + + + def loadFixedWallets(self, wallets): + self.emit(SIGNAL('LFW'), wallets) + + def LFW(self, wallets): + for wlt in wallets: + newWallet = PyBtcWallet().readWalletFile(wlt) + self.main.addWalletToApplication(newWallet, False) + + self.main.emit(SIGNAL('checkForkedImport')) + + + # Decided that we can just add all the logic to + #def checkForkedSubmitLogs(self): + #forkedImports = [] + #for wlt in self.walletMap: + #if self.walletMap[wlt].hasForkedImports: + #dlgIWR = DlgInconsistentWltReport(self, self.main, self.logDirs) + #if dlgIWR.exec_(): + #return + #return \ No newline at end of file diff --git a/qtdialogs/DlgDispTxInfo.py b/qtdialogs/DlgDispTxInfo.py index 55172ca3c..66f0e4dba 100644 --- a/qtdialogs/DlgDispTxInfo.py +++ b/qtdialogs/DlgDispTxInfo.py @@ -28,10 +28,11 @@ from armoryengine.CppBridge import TheBridge from armorymodels import TxInDispModel, TxOutDispModel, TXINCOLS, TXOUTCOLS -from qtdialogs.qtdefines import ArmoryDialog, STYLE_RAISED, USERMODE, \ +from qtdialogs.qtdefines import STYLE_RAISED, USERMODE, \ QRichLabel, relaxedSizeStr, GETFONT, tightSizeNChar, STYLE_SUNKEN, \ HORIZONTAL, makeHorizFrame, initialColResize, makeLayoutFrame from qtdialogs.qtdialogs import STRETCH +from qtdialogs.ArmoryDialog import ArmoryDialog ################################################################################ class DlgDisplayTxIn(ArmoryDialog): @@ -1020,4 +1021,3 @@ def extractTxInfo(pytx, rcvTime=None): sumTxIn = None return [txHash, txOutToList, sumTxOut, txinFromList, sumTxIn, \ - txTime, txBlk, txIdx, txSize, txWeight] \ No newline at end of file diff --git a/qtdialogs/DlgEULA.py b/qtdialogs/DlgEULA.py index 6db335c02..4521b603e 100644 --- a/qtdialogs/DlgEULA.py +++ b/qtdialogs/DlgEULA.py @@ -10,8 +10,9 @@ from PySide2.QtGui import QIcon from PySide2.QtWidgets import QVBoxLayout, QTextEdit, QCheckBox, QPushButton -from qtdialogs.qtdefines import ArmoryDialog, GETFONT, tightSizeNChar, \ +from qtdialogs.qtdefines import GETFONT, tightSizeNChar, \ makeHorizFrame, makeVertFrame, QRichLabel +from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.qtdialogs import STRETCH ############################################################################# diff --git a/qtdialogs/DlgExportTxHistory.py b/qtdialogs/DlgExportTxHistory.py new file mode 100644 index 000000000..f9b76bf31 --- /dev/null +++ b/qtdialogs/DlgExportTxHistory.py @@ -0,0 +1,344 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +from PySide2.QtCore import SIGNAL +from PySide2.QtWidgets import QComboBox, QLineEdit, QPushButton, QGridLayout, \ + QMessageBox +from armoryengine.BDM import TheBDM +from armoryengine.ArmoryUtils import str2coin, coin2str, unixTimeToFormatStr, \ + IGNOREZC, RightNow, hex_to_binary, hex_switchEndian, FORMAT_SYMBOLS, \ + DEFAULT_DATE_FORMAT +from qtdialogs.qtdefines import QRichLabel, HLINE, STRETCH, WLTTYPES, \ + makeHorizFrame, determineWalletType +from qtdialogs.ArmoryDialog import ArmoryDialog + +from armorymodels import LEDGERCOLS +from armoryengine.Transaction import getFeeForTx + + +####################################################################### +class DlgExportTxHistory(ArmoryDialog): + def __init__(self, parent=None, main=None): + super(DlgExportTxHistory, self).__init__(parent, main) + + self.reversedLBdict = {v:k for k,v in self.main.lockboxIDMap.items()} + + self.cmbWltSelect = QComboBox() + self.cmbWltSelect.clear() + self.cmbWltSelect.addItem(self.tr('My Wallets')) + self.cmbWltSelect.addItem(self.tr('Offline Wallets')) + self.cmbWltSelect.addItem(self.tr('Other Wallets')) + + self.cmbWltSelect.insertSeparator(4) + self.cmbWltSelect.addItem(self.tr('All Wallets')) + self.cmbWltSelect.addItem(self.tr('All Lockboxes')) + self.cmbWltSelect.addItem(self.tr('All Wallets & Lockboxes')) + + self.cmbWltSelect.insertSeparator(8) + for wltID in self.main.walletIDList: + self.cmbWltSelect.addItem(self.main.walletMap[wltID].labelName) + + self.cmbWltSelect.insertSeparator(8 + len(self.main.walletIDList)) + for idx in self.reversedLBdict: + self.cmbWltSelect.addItem(self.main.allLockboxes[idx].shortName) + + + self.cmbSortSelect = QComboBox() + self.cmbSortSelect.clear() + self.cmbSortSelect.addItem(self.tr('Date (newest first)')) + self.cmbSortSelect.addItem(self.tr('Date (oldest first)')) + + + self.cmbFileFormat = QComboBox() + self.cmbFileFormat.clear() + self.cmbFileFormat.addItem(self.tr('Comma-Separated Values (*.csv)')) + + + fmt = self.main.getPreferredDateFormat() + ttipStr = self.tr('Use any of the following symbols:
') + fmtSymbols = [x[0] + ' = ' + x[1] for x in FORMAT_SYMBOLS] + ttipStr += '
'.join(fmtSymbols) + + self.edtDateFormat = QLineEdit() + self.edtDateFormat.setText(fmt) + self.ttipFormatDescr = self.main.createToolTipWidget(ttipStr) + + self.lblDateExample = QRichLabel('', doWrap=False) + self.connect(self.edtDateFormat, SIGNAL('textEdited(QString)'), self.doExampleDate) + self.doExampleDate() + self.btnResetFormat = QPushButton(self.tr("Reset to Default")) + + def doReset(): + self.edtDateFormat.setText(DEFAULT_DATE_FORMAT) + self.doExampleDate() + self.connect(self.btnResetFormat, SIGNAL("clicked()"), doReset) + + + + # Add the usual buttons + self.btnCancel = QPushButton(self.tr("Cancel")) + self.btnAccept = QPushButton(self.tr("Export")) + self.connect(self.btnCancel, SIGNAL("clicked()"), self.reject) + self.connect(self.btnAccept, SIGNAL("clicked()"), self.accept) + btnBox = makeHorizFrame([STRETCH, self.btnCancel, self.btnAccept]) + + + dlgLayout = QGridLayout() + + i = 0 + dlgLayout.addWidget(QRichLabel(self.tr('Export Format:')), i, 0) + dlgLayout.addWidget(self.cmbFileFormat, i, 1) + + i += 1 + dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + + i += 1 + dlgLayout.addWidget(QRichLabel(self.tr('Wallet(s) to export:')), i, 0) + dlgLayout.addWidget(self.cmbWltSelect, i, 1) + + i += 1 + dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + + i += 1 + dlgLayout.addWidget(QRichLabel(self.tr('Sort Table:')), i, 0) + dlgLayout.addWidget(self.cmbSortSelect, i, 1) + i += 1 + dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + + i += 1 + dlgLayout.addWidget(QRichLabel(self.tr('Date Format:')), i, 0) + fmtfrm = makeHorizFrame([self.lblDateExample, STRETCH, self.ttipFormatDescr]) + dlgLayout.addWidget(fmtfrm, i, 1) + + i += 1 + dlgLayout.addWidget(self.btnResetFormat, i, 0) + dlgLayout.addWidget(self.edtDateFormat, i, 1) + + i += 1 + dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + + i += 1 + dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + + i += 1 + dlgLayout.addWidget(btnBox, i, 0, 1, 2) + + self.setLayout(dlgLayout) + + + ############################################################################# + def doExampleDate(self, qstr=None): + fmtstr = str(self.edtDateFormat.text()) + try: + exampleDateStr = unixTimeToFormatStr(1030501970, fmtstr) + self.lblDateExample.setText(self.tr('Example: %s' % exampleDateStr)) + self.isValidFormat = True + except: + self.lblDateExample.setText(self.tr('Example: [[invalid date format]]')) + self.isValidFormat = False + + ############################################################################# + def accept(self, *args): + if self.createFile_CSV(): + super(DlgExportTxHistory, self).accept(*args) + + + ############################################################################# + def createFile_CSV(self): + if not self.isValidFormat: + QMessageBox.warning(self, self.tr('Invalid date format'), \ + self.tr('Cannot create CSV without a valid format for transaction ' + 'dates and times'), QMessageBox.Ok) + return False + + COL = LEDGERCOLS + + # This was pretty much copied from the createCombinedLedger method... + # I rarely do this, but modularizing this piece is a non-trivial + wltIDList = [] + typelist = [[wid, determineWalletType(self.main.walletMap[wid], self.main)[0]] \ + for wid in self.main.walletIDList] + currIdx = self.cmbWltSelect.currentIndex() + if currIdx >= 8: + idx = currIdx - 8 + if idx < len(self.main.walletIDList): + #picked a single wallet + wltIDList = [self.main.walletIDList[idx]] + else: + #picked a single lockbox + idx -= len(self.main.walletIDList) +1 + wltIDList = [self.reversedLBdict[idx]] + else: + listOffline = [t[0] for t in filter(lambda x: x[1] == WLTTYPES.Offline, typelist)] + listWatching = [t[0] for t in filter(lambda x: x[1] == WLTTYPES.WatchOnly, typelist)] + listCrypt = [t[0] for t in filter(lambda x: x[1] == WLTTYPES.Crypt, typelist)] + listPlain = [t[0] for t in filter(lambda x: x[1] == WLTTYPES.Plain, typelist)] + lockboxIDList = [t for t in self.main.lockboxIDMap] + + if currIdx == 0: + wltIDList = listOffline + listCrypt + listPlain + elif currIdx == 1: + wltIDList = listOffline + elif currIdx == 2: + wltIDList = listWatching + elif currIdx == 4: + wltIDList = self.main.walletIDList + elif currIdx == 5: + wltIDList = lockboxIDList + elif currIdx == 6: + wltIDList = self.main.walletIDList + lockboxIDList + else: + pass + + order = "ascending" + sortTxt = str(self.cmbSortSelect.currentText()) + if 'newest' in sortTxt: + order = "descending" + + totalFunds, spendFunds, unconfFunds = 0, 0, 0 + wltBalances = {} + for wltID in wltIDList: + if wltID in self.main.walletMap: + wlt = self.main.walletMap[wltID] + + totalFunds += wlt.getBalance('Total') + spendFunds += wlt.getBalance('Spendable') + unconfFunds += wlt.getBalance('Unconfirmed') + if order == "ascending": + wltBalances[wltID] = 0 # will be accumulated + else: + wltBalances[wltID] = wlt.getBalance('Total') + + else: + #lockbox + cppwlt = self.main.cppLockboxWltMap[wltID] + totalFunds += cppwlt.getFullBalance() + spendFunds += cppwlt.getSpendableBalance(TheBDM.getTopBlockHeight(), IGNOREZC) + unconfFunds += cppwlt.getUnconfirmedBalance(TheBDM.getTopBlockHeight(), IGNOREZC) + if order == "ascending": + wltBalances[wltID] = 0 # will be accumulated + else: + wltBalances[wltID] = cppwlt.getFullBalance() + + if order == "ascending": + allBalances = 0 + else: + allBalances = totalFunds + + + #prepare csv file + wltSelectStr = str(self.cmbWltSelect.currentText()).replace(' ', '_') + timestampStr = unixTimeToFormatStr(RightNow(), '%Y%m%d_%H%M') + filenamePrefix = 'ArmoryTxHistory_%s_%s' % (wltSelectStr, timestampStr) + fmtstr = str(self.cmbFileFormat.currentText()) + if 'csv' in fmtstr: + defaultName = filenamePrefix + '.csv' + fullpath = self.main.getFileSave('Save CSV File', \ + ['Comma-Separated Values (*.csv)'], \ + defaultName) + + if len(fullpath) == 0: + return + + f = open(fullpath, 'w') + + f.write(self.tr('Export Date: %s\n' % unixTimeToFormatStr(RightNow()))) + f.write(self.tr('Total Funds: %s\n' % coin2str(totalFunds, maxZeros=0).strip())) + f.write(self.tr('Spendable Funds: %s\n' % coin2str(spendFunds, maxZeros=0).strip())) + f.write(self.tr('Unconfirmed Funds: %s\n' % coin2str(unconfFunds, maxZeros=0).strip())) + f.write('\n') + + f.write(self.tr('Included Wallets:\n')) + for wltID in wltIDList: + if wltID in self.main.walletMap: + wlt = self.main.walletMap[wltID] + f.write('%s,%s\n' % (wltID, wlt.labelName.replace(',', ';'))) + else: + wlt = self.main.allLockboxes[self.main.lockboxIDMap[wltID]] + f.write(self.tr('%s (lockbox),%s\n' % (wltID, wlt.shortName.replace(',', ';')))) + f.write('\n') + + + headerRow = [self.tr('Date'), self.tr('Transaction ID'), + self.tr('Number of Confirmations'), self.tr('Wallet ID'), + self.tr('Wallet Name'), self.tr('Credit'), self.tr('Debit'), + self.tr('Fee (paid by this wallet)'), self.tr('Wallet Balance'), + self.tr('Total Balance'), self.tr('Label')] + + f.write(','.join(str(header) for header in headerRow) + '\n') + + #get history + historyLedger = TheBDM.bdv().getHistoryForWalletSelection(wltIDList, order) + + # Each value in COL.Amount will be exactly how much the wallet balance + # increased or decreased as a result of this transaction. + ledgerTable = self.main.convertLedgerToTable(historyLedger, + showSentToSelfAmt=True) + + # Sort the data chronologically first, compute the running balance for + # each row, then sort it the way that was requested by the user. + for row in ledgerTable: + if row[COL.toSelf] == False: + rawAmt = str2coin(row[COL.Amount]) + else: + #if SentToSelf, balance and total rolling balance should only + #take fee in account + rawAmt, fee_byte = getFeeForTx(hex_to_binary(row[COL.TxHash])) + rawAmt = -1 * rawAmt + + if order == "ascending": + wltBalances[row[COL.WltID]] += rawAmt + allBalances += rawAmt + + row.append(wltBalances[row[COL.WltID]]) + row.append(allBalances) + + if order == "descending": + wltBalances[row[COL.WltID]] -= rawAmt + allBalances -= rawAmt + + + for row in ledgerTable: + vals = [] + + fmtstr = str(self.edtDateFormat.text()) + unixTime = row[COL.UnixTime] + vals.append(unixTimeToFormatStr(unixTime, fmtstr)) + vals.append(hex_switchEndian(row[COL.TxHash])) + vals.append(row[COL.NumConf]) + vals.append(row[COL.WltID]) + if row[COL.WltID] in self.main.walletMap: + vals.append(self.main.walletMap[row[COL.WltID]].labelName.replace(',', ';')) + else: + lbId = self.main.lockboxIDMap[row[COL.WltID]] + vals.append(self.main.allLockboxes[lbId].shortName.replace(',', ';')) + + wltEffect = row[COL.Amount] + txFee, fee_byte = getFeeForTx(hex_to_binary(row[COL.TxHash])) + if float(wltEffect) >= 0: + if row[COL.toSelf] == False: + vals.append(wltEffect.strip()) + vals.append('') + vals.append('') + else: + vals.append(wltEffect.strip() + ' (STS)') + vals.append('') + vals.append(coin2str(txFee).strip()) + else: + vals.append('') + vals.append(wltEffect.strip()[1:]) # remove negative sign + vals.append(coin2str(txFee).strip()) + + vals.append(coin2str(row[-2])) + vals.append(coin2str(row[-1])) + vals.append(row[COL.Comment]) + + f.write('%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,"%s"\n' % tuple(vals)) + + f.close() + return True diff --git a/qtdialogs/DlgHelpAbout.py b/qtdialogs/DlgHelpAbout.py new file mode 100644 index 000000000..5348a12ee --- /dev/null +++ b/qtdialogs/DlgHelpAbout.py @@ -0,0 +1,57 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +from PySide2.QtCore import Qt +from PySide2.QtGui import QPixmap +from PySide2.QtWidgets import QHBoxLayout, QLabel + +#from armoryengine.ArmoryBuild import BTCARMORY_BUILD +from armoryengine.ArmoryUtils import BTCARMORY_VERSION, getVersionString + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.qtdefines import QRichLabel, STRETCH, makeVertFrame + +################################################################################ +class DlgHelpAbout(ArmoryDialog): + def __init__(self, putResultInWidget, defaultWltID=None, parent=None, main=None): + super(DlgHelpAbout, self).__init__(parent, main) + + imgLogo = QLabel() + imgLogo.setPixmap(QPixmap('./img/armory_logo_h56.png')) + imgLogo.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + +# if BTCARMORY_BUILD != None: +# lblHead = QRichLabel(self.tr('Armory Bitcoin Wallet : Version %s-beta-%s' % (getVersionString(BTCARMORY_VERSION), BTCARMORY_BUILD)), doWrap=False) +# else: +# lblHead = QRichLabel(self.tr('Armory Bitcoin Wallet : Version %s-beta' % getVersionString(BTCARMORY_VERSION)), doWrap=False) + lblHead = QRichLabel(self.tr('Armory Bitcoin Wallet : Version %s-beta' % getVersionString(BTCARMORY_VERSION)), doWrap=False) + + lblOldCopyright = QRichLabel(self.tr( u'Copyright © 2011-2015 Armory Technologies, Inc.')) + lblCopyright = QRichLabel(self.tr( u'Copyright © 2016 Goatpig')) + lblOldLicense = QRichLabel(self.tr( u'Licensed to Armory Technologies, Inc. under the ' + '' + 'Affero General Public License, Version 3 (AGPLv3)')) + lblOldLicense.setOpenExternalLinks(True) + lblLicense = QRichLabel(self.tr( u'Licensed to Goatpig under the ' + '' + 'MIT License')) + lblLicense.setOpenExternalLinks(True) + + lblHead.setAlignment(Qt.AlignHCenter) + lblCopyright.setAlignment(Qt.AlignHCenter) + lblOldCopyright.setAlignment(Qt.AlignHCenter) + lblLicense.setAlignment(Qt.AlignHCenter) + lblOldLicense.setAlignment(Qt.AlignHCenter) + + dlgLayout = QHBoxLayout() + dlgLayout.addWidget(makeVertFrame([imgLogo, lblHead, lblCopyright, lblOldCopyright, STRETCH, lblLicense, lblOldLicense])) + self.setLayout(dlgLayout) + + self.setMinimumWidth(450) + + self.setWindowTitle(self.tr('About Armory')) \ No newline at end of file diff --git a/qtdialogs/DlgInflatedQR.py b/qtdialogs/DlgInflatedQR.py new file mode 100644 index 000000000..3835be8ed --- /dev/null +++ b/qtdialogs/DlgInflatedQR.py @@ -0,0 +1,39 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +from PySide2.QtWidgets import QVBoxLayout + +from qtdialogs.qtdefines import QRichLabel +from qtdialogs.ArmoryDialog import ArmoryDialog + +# Create a very simple dialog and execute it +class DlgInflatedQR(ArmoryDialog): + def __init__(self, parent, dataToQR): + super(DlgInflatedQR, self).__init__(parent, parent.main) + + sz = QApplication.desktop().size() + w,h = sz.width(), sz.height() + qrSize = int(min(w,h)*0.8) + qrDisp = QRCodeWidget(dataToQR, prefSize=qrSize) + + def closeDlg(*args): + self.accept() + qrDisp.mouseDoubleClickEvent = closeDlg + self.mouseDoubleClickEvent = closeDlg + + lbl = QRichLabel(self.tr('Double-click or press ESC to close')) + lbl.setAlignment(Qt.AlignTop | Qt.AlignHCenter) + + frmQR = makeHorizFrame(['Stretch', qrDisp, 'Stretch']) + frmFull = makeVertFrame(['Stretch',frmQR, lbl, 'Stretch']) + + layout = QVBoxLayout() + layout.addWidget(frmFull) + + self.setLayout(layout) + self.showFullScreen() diff --git a/qtdialogs/DlgIntroMessage.py b/qtdialogs/DlgIntroMessage.py index 2a359d9b3..dca893fc3 100644 --- a/qtdialogs/DlgIntroMessage.py +++ b/qtdialogs/DlgIntroMessage.py @@ -11,9 +11,10 @@ from PySide2.QtWidgets import QLabel, QSpacerItem, QSizePolicy, QCheckBox, \ QGridLayout, QDialogButtonBox, QPushButton -from qtdialogs.qtdefines import ArmoryDialog, QRichLabel, GETFONT, \ +from qtdialogs.qtdefines import QRichLabel, GETFONT, \ makeLayoutFrame, VERTICAL, HORIZONTAL +from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.qtdialogs import STRETCH ################################################################################ @@ -98,4 +99,4 @@ def importClicked(self): self.accept() def sizeHint(self): - return QSize(750, 500) + return QSize(750, 500) \ No newline at end of file diff --git a/qtdialogs/DlgKeypoolSettings.py b/qtdialogs/DlgKeypoolSettings.py index 1724d2d5b..1500ad622 100644 --- a/qtdialogs/DlgKeypoolSettings.py +++ b/qtdialogs/DlgKeypoolSettings.py @@ -11,8 +11,9 @@ from armorycolors import htmlColor -from qtdialogs.qtdefines import ArmoryDialog, relaxedSizeStr, \ +from qtdialogs.qtdefines import relaxedSizeStr, \ STYLE_SUNKEN, QRichLabel, makeHorizFrame, makeVertFrame +from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.qtdialogs import STRETCH from qtdialogs.DlgProgress import DlgProgress @@ -137,4 +138,4 @@ def completeCompute(self): cred = htmlColor('TextRed') self.lblAddrCompVal.setText('%d' % \ (cred, self.wlt.lastComputedChainIndex)) - self.addressesWereGenerated = True + self.addressesWereGenerated = True \ No newline at end of file diff --git a/qtdialogs/DlgMigrateWallet.py b/qtdialogs/DlgMigrateWallet.py index 20d8744b7..7c31279af 100644 --- a/qtdialogs/DlgMigrateWallet.py +++ b/qtdialogs/DlgMigrateWallet.py @@ -12,11 +12,13 @@ QDialogButtonBox, QGridLayout, QPushButton, QVBoxLayout, \ QSizePolicy, QRadioButton, QButtonGroup, QLayout -from qtdialogs.qtdefines import ArmoryDialog, tightSizeStr, \ +from qtdialogs.qtdefines import tightSizeStr, \ makeHorizFrame from qtdialogs.qtdialogs import MIN_PASSWD_WIDTH, LetterButton, \ STRETCH +from qtdialogs.ArmoryDialog import ArmoryDialog + from armoryengine.CppBridge import TheBridge ################################################################################ diff --git a/qtdialogs/DlgNewAddress.py b/qtdialogs/DlgNewAddress.py index 03fc54fd0..8b86aef8a 100644 --- a/qtdialogs/DlgNewAddress.py +++ b/qtdialogs/DlgNewAddress.py @@ -12,10 +12,12 @@ QLineEdit, QHBoxLayout, QDialogButtonBox, QTextEdit, QMessageBox from armorycolors import Colors -from qtdialogs.qtdefines import ArmoryDialog, determineWalletType, \ - STYLE_RAISED, QRichLabel, tightSizeStr, QRCodeWidget, makeHorizFrame, \ +from qtdialogs.qtdefines import determineWalletType, \ + STYLE_RAISED, QRichLabel, tightSizeStr, makeHorizFrame, \ makeVertFrame, WLTTYPES, tightSizeNChar, STYLE_SUNKEN from qtdialogs.qtdialogs import STRETCH +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.QRCodeWidget import QRCodeWidget from armoryengine.ArmoryUtils import DEFAULT_RECEIVE_TYPE from armoryengine.BDM import TheBDM, BDM_OFFLINE diff --git a/qtdialogs/DlgOfflineTx.py b/qtdialogs/DlgOfflineTx.py index d1d608453..0c731f2d0 100644 --- a/qtdialogs/DlgOfflineTx.py +++ b/qtdialogs/DlgOfflineTx.py @@ -14,11 +14,12 @@ from armoryengine.ArmoryUtils import OS_WINDOWS, LOGINFO, LOGEXCEPT from armoryengine.BDM import TheBDM, BDM_BLOCKCHAIN_READY -from qtdialogs.qtdefines import ArmoryDialog, QRichLabel, HORIZONTAL, \ +from qtdialogs.qtdefines import QRichLabel, HORIZONTAL, \ STYLE_SUNKEN, GETFONT, STYLE_RAISED, VERTICAL, makeLayoutFrame, \ relaxedSizeNChar, determineWalletType, WLTTYPES, makeHorizFrame, \ makeVertFrame, STYLE_PLAIN, HLINE, tightSizeNChar from qtdialogs.qtdialogs import STRETCH +from qtdialogs.ArmoryDialog import ArmoryDialog from ui.TxFramesOffline import SignBroadcastOfflineTxFrame @@ -126,7 +127,7 @@ def setWallet(self, wlt): 'On the offline computer, click "Offline Transactions" on the main ' 'window. Load the transaction, review it, then sign it ' '(the filename now end with *.signed.tx). Click "Continue" ' - 'below when you have the signed transaction on this computer. ' + 'below when you have the signed transaction on this computer. ' '

' 'NOTE: The USB drive only ever holds public transaction ' 'data that will be broadcast to the network. This data may be ' @@ -323,4 +324,3 @@ def __init__(self, parent=None, main=None): dlgLayout.addWidget(doneForm) self.setLayout(dlgLayout) signBroadcastOfflineTxFrame.processUSTX() - diff --git a/qtdialogs/DlgPasswd3.py b/qtdialogs/DlgPasswd3.py new file mode 100644 index 000000000..756bec17f --- /dev/null +++ b/qtdialogs/DlgPasswd3.py @@ -0,0 +1,62 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +from PySide2.QtWidgets import QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QPushButton +from PySide2.QtGui import QPixmap +from PySide2.QtCore import Qt, SIGNAL + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.DlgSettings import MIN_PASSWD_WIDTH +from qtdialogs.qtdefines import QRichLabel + +class DlgPasswd3(ArmoryDialog): + def __init__(self, parent=None, main=None): + super(DlgPasswd3, self).__init__(parent, main) + + + lblWarnImgL = QLabel() + lblWarnImgL.setPixmap(QPixmap('./img/MsgBox_warning48.png')) + lblWarnImgL.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + + lblWarnTxt1 = QRichLabel(\ + self.tr('!!! DO NOT FORGET YOUR PASSPHRASE !!!'), size=4) + lblWarnTxt1.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + lblWarnTxt2 = QRichLabel(self.tr( + 'No one can help you recover you bitcoins if you forget the ' + 'passphrase and don\'t have a paper backup! Your wallet and ' + 'any digital backups are useless if you forget it. ' + '

' + 'A paper backup protects your wallet forever, against ' + 'hard-drive loss and losing your passphrase. It also protects you ' + 'from theft, if the wallet was encrypted and the paper backup ' + 'was not stolen with it. Please make a paper backup and keep it in ' + 'a safe place.' + '

' + 'Please enter your passphrase a third time to indicate that you ' + 'are aware of the risks of losing your passphrase!'), doWrap=True) + + + self.edtPasswd3 = QLineEdit() + self.edtPasswd3.setEchoMode(QLineEdit.Password) + self.edtPasswd3.setMinimumWidth(MIN_PASSWD_WIDTH(self)) + + bbox = QDialogButtonBox() + btnOk = QPushButton(self.tr('Accept')) + btnNo = QPushButton(self.tr('Cancel')) + self.connect(btnOk, SIGNAL("clicked()"), self.accept) + self.connect(btnNo, SIGNAL("clicked()"), self.reject) + bbox.addButton(btnOk, QDialogButtonBox.AcceptRole) + bbox.addButton(btnNo, QDialogButtonBox.RejectRole) + layout = QGridLayout() + layout.addWidget(lblWarnImgL, 0, 0, 4, 1) + layout.addWidget(lblWarnTxt1, 0, 1, 1, 1) + layout.addWidget(lblWarnTxt2, 2, 1, 1, 1) + layout.addWidget(self.edtPasswd3, 5, 1, 1, 1) + layout.addWidget(bbox, 6, 1, 1, 2) + self.setLayout(layout) + self.setWindowTitle(self.tr('WARNING!')) \ No newline at end of file diff --git a/qtdialogs/DlgProgress.py b/qtdialogs/DlgProgress.py index 1bea740ba..8fe80758f 100644 --- a/qtdialogs/DlgProgress.py +++ b/qtdialogs/DlgProgress.py @@ -13,8 +13,8 @@ from PySide2.QtWidgets import QPushButton, QVBoxLayout, QLabel, \ QProgressBar, QGridLayout -from qtdialogs.qtdefines import ArmoryDialog from qtdialogs.qtdialogs import STRETCH +from qtdialogs.ArmoryDialog import ArmoryDialog from armoryengine.ArmoryUtils import PyBackgroundThread from armoryengine.BDM import BDMPhase_Completed @@ -189,4 +189,4 @@ def setup(self, parent=None): self.hide() self.status = 1 - self.callbackId = self.main.registerProgressCallback(self) + self.callbackId = self.main.registerProgressCallback(self) \ No newline at end of file diff --git a/qtdialogs/DlgQRCodeDisplay.py b/qtdialogs/DlgQRCodeDisplay.py index 83c3c26e1..ac32849d5 100644 --- a/qtdialogs/DlgQRCodeDisplay.py +++ b/qtdialogs/DlgQRCodeDisplay.py @@ -6,7 +6,7 @@ # # ################################################################################ -from qtdialogs.qtdefines import ArmoryDialog +from qtdialogs.ArmoryDialog import ArmoryDialog ################################################################################ class DlgQRCodeDisplay(ArmoryDialog): diff --git a/qtdialogs/DlgReplaceWallet.py b/qtdialogs/DlgReplaceWallet.py new file mode 100644 index 000000000..b3cc38843 --- /dev/null +++ b/qtdialogs/DlgReplaceWallet.py @@ -0,0 +1,97 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +import os + +from PySide2.QtCore import SIGNAL +from PySide2.QtWidgets import QGridLayout, QLabel, QPushButton + +from armoryengine.ArmoryUtils import LOGEXCEPT, RightNowStr +from armoryengine.PyBtcWalletRecovery import RECOVERMODE + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.DlgProgress import DlgProgress + +################################################################################ +class DlgReplaceWallet(ArmoryDialog): + + ############################################################################# + def __init__(self, WalletID, parent, main): + super(DlgReplaceWallet, self).__init__(parent, main) + + lblDesc = QLabel(self.tr( + 'You already have this wallet loaded!
' + 'You can choose to:
' + '- Cancel wallet restore operation
' + '- Set new password and fix any errors
' + '- Overwrite old wallet (delete comments & labels)
')) + + self.WalletID = WalletID + self.main = main + self.Meta = None + self.output = 0 + + self.wltPath = main.walletMap[WalletID].walletPath + + self.btnAbort = QPushButton(self.tr('Cancel')) + self.btnReplace = QPushButton(self.tr('Overwrite')) + self.btnSaveMeta = QPushButton(self.tr('Merge')) + + self.connect(self.btnAbort, SIGNAL('clicked()'), self.reject) + self.connect(self.btnReplace, SIGNAL('clicked()'), self.Replace) + self.connect(self.btnSaveMeta, SIGNAL('clicked()'), self.SaveMeta) + + layoutDlg = QGridLayout() + + layoutDlg.addWidget(lblDesc, 0, 0, 4, 4) + layoutDlg.addWidget(self.btnAbort, 4, 0, 1, 1) + layoutDlg.addWidget(self.btnSaveMeta, 4, 1, 1, 1) + layoutDlg.addWidget(self.btnReplace, 4, 2, 1, 1) + + self.setLayout(layoutDlg) + self.setWindowTitle('Wallet already exists') + + ######### + def Replace(self): + self.main.removeWalletFromApplication(self.WalletID) + + datestr = RightNowStr('%Y-%m-%d-%H%M') + homedir = os.path.dirname(self.wltPath) + + oldpath = os.path.join(homedir, self.WalletID, datestr) + try: + if not os.path.exists(oldpath): + os.makedirs(oldpath) + except: + LOGEXCEPT('Cannot create new folder in dataDir! Missing credentials?') + self.reject() + return + + oldname = os.path.basename(self.wltPath) + self.newname = os.path.join(oldpath, '%s_old.wallet' % (oldname[0:-7])) + + os.rename(self.wltPath, self.newname) + + backup = '%s_backup.wallet' % (self.wltPath[0:-7]) + if os.path.exists(backup): + os.remove(backup) + + self.output =1 + self.accept() + + ######### + def SaveMeta(self): + from armoryengine.PyBtcWalletRecovery import PyBtcWalletRecovery + + metaProgress = DlgProgress(self, self.main, Title=self.tr('Ripping Meta Data')) + getMeta = PyBtcWalletRecovery() + self.Meta = metaProgress.exec_(getMeta.ProcessWallet, + WalletPath=self.wltPath, + Mode=RECOVERMODE.Meta, + Progress=metaProgress.UpdateText) + self.Replace() \ No newline at end of file diff --git a/qtdialogs/DlgRequestPayment.py b/qtdialogs/DlgRequestPayment.py index d27518d41..c3f3ae2a0 100644 --- a/qtdialogs/DlgRequestPayment.py +++ b/qtdialogs/DlgRequestPayment.py @@ -6,7 +6,7 @@ # # ################################################################################ -from qtdialogs.qtdefines import ArmoryDialog +from qtdialogs.ArmoryDialog import ArmoryDialog ################################################################################ class DlgRequestPayment(ArmoryDialog): @@ -425,4 +425,4 @@ def clickCopyAll(self): qmd = QMimeData() qmd.setHtml(self.dispText) clipb.setMimeData(qmd) - self.lblWarn.setText(self.tr('Copied!')) + self.lblWarn.setText(self.tr('Copied!')) \ No newline at end of file diff --git a/qtdialogs/DlgRestoreFragged.py b/qtdialogs/DlgRestoreFragged.py new file mode 100644 index 000000000..c858f9d0c --- /dev/null +++ b/qtdialogs/DlgRestoreFragged.py @@ -0,0 +1,582 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +from PySide2.QtWidgets import QCheckBox, QFrame, QGridLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QScrollArea, QTabWidget, QVBoxLayout +from PySide2.QtGui import QPixmap +from PySide2.QtCore import QSize, Qt, SIGNAL + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, STYLE_SUNKEN, makeHorizFrame, makeVertFrame, relaxedSizeNChar +#from CppBlockUtils import SecureBinaryData + +################################################################################ +class DlgRestoreFragged(ArmoryDialog): + def __init__(self, parent, main, thisIsATest=False, expectWltID=None): + super(DlgRestoreFragged, self).__init__(parent, main) + + self.thisIsATest = thisIsATest + self.testWltID = expectWltID + headerStr = '' + if thisIsATest: + headerStr = self.tr('Testing a ' + 'Fragmented Backup') + else: + headerStr = self.tr('Restore Wallet from Fragments') + + descr = self.trUtf8( + '%s

' + 'Use this form to enter all the fragments to be restored. Fragments ' + 'can be stored on a mix of paper printouts, and saved files. ' + u'If any of the fragments require a SecurePrint\u200b\u2122 code, ' + 'you will only have to enter it once, since that code is the same for ' + 'all fragments of any given wallet.' % headerStr) + + if self.thisIsATest: + descr += self.tr('

' + 'For testing purposes, you may enter more fragments than needed ' + 'and Armory will test all subsets of the entered fragments to verify ' + 'that each one still recovers the wallet successfully.') + + lblDescr = QRichLabel(descr) + + frmDescr = makeHorizFrame([lblDescr], STYLE_RAISED) + + # HLINE + + self.scrollFragInput = QScrollArea() + self.scrollFragInput.setWidgetResizable(True) + self.scrollFragInput.setMinimumHeight(150) + + lblFragList = QRichLabel(self.tr('Input Fragments Below:'), doWrap=False, bold=True) + self.btnAddFrag = QPushButton(self.tr('+Frag')) + self.btnRmFrag = QPushButton(self.tr('-Frag')) + self.btnRmFrag.setVisible(False) + self.connect(self.btnAddFrag, SIGNAL(CLICKED), self.addFragment) + self.connect(self.btnRmFrag, SIGNAL(CLICKED), self.removeFragment) + self.chkEncrypt = QCheckBox(self.tr('Encrypt Restored Wallet')) + self.chkEncrypt.setChecked(True) + frmAddRm = makeHorizFrame([self.chkEncrypt, STRETCH, self.btnRmFrag, self.btnAddFrag]) + + self.fragDataMap = {} + self.tableSize = 2 + self.wltType = UNKNOWN + self.fragIDPrefix = UNKNOWN + + doItText = self.tr('Test Backup') if thisIsATest else self.tr('Restore from Fragments') + + btnExit = QPushButton(self.tr('Cancel')) + self.btnRestore = QPushButton(doItText) + self.connect(btnExit, SIGNAL(CLICKED), self.reject) + self.connect(self.btnRestore, SIGNAL(CLICKED), self.processFrags) + frmBtns = makeHorizFrame([btnExit, STRETCH, self.btnRestore]) + + self.lblRightFrm = QRichLabel('', hAlign=Qt.AlignHCenter) + self.lblSecureStr = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code:'), \ + hAlign=Qt.AlignHCenter, + doWrap=False, + color='TextWarn') + self.displaySecureString = QLineEdit() + self.imgPie = QRichLabel('', hAlign=Qt.AlignHCenter) + self.imgPie.setMinimumWidth(96) + self.imgPie.setMinimumHeight(96) + self.lblReqd = QRichLabel('', hAlign=Qt.AlignHCenter) + self.lblWltID = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) + self.lblFragID = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) + self.lblSecureStr.setVisible(False) + self.displaySecureString.setVisible(False) + self.displaySecureString.setMaximumWidth(relaxedSizeNChar(self.displaySecureString, 16)[0]) + # The Secure String is now edited in DlgEnterOneFrag, It is only displayed here + self.displaySecureString.setEnabled(False) + frmSecPair = makeVertFrame([self.lblSecureStr, self.displaySecureString]) + frmSecCtr = makeHorizFrame([STRETCH, frmSecPair, STRETCH]) + + frmWltInfo = makeVertFrame([STRETCH, + self.lblRightFrm, + self.imgPie, + self.lblReqd, + self.lblWltID, + self.lblFragID, + HLINE(), + frmSecCtr, + 'Strut(200)', + STRETCH], STYLE_SUNKEN) + + + fragmentsLayout = QGridLayout() + fragmentsLayout.addWidget(frmDescr, 0, 0, 1, 2) + fragmentsLayout.addWidget(frmAddRm, 1, 0, 1, 1) + fragmentsLayout.addWidget(self.scrollFragInput, 2, 0, 1, 1) + fragmentsLayout.addWidget(frmWltInfo, 1, 1, 2, 1) + setLayoutStretchCols(fragmentsLayout, 1, 0) + + walletRestoreTabs = QTabWidget() + fragmentsFrame = QFrame() + fragmentsFrame.setLayout(fragmentsLayout) + walletRestoreTabs.addTab(fragmentsFrame, self.tr("Fragments")) + self.advancedOptionsTab = AdvancedOptionsFrame(parent, main) + walletRestoreTabs.addTab(self.advancedOptionsTab, self.tr("Advanced Options")) + + self.chkEncrypt.setChecked(not thisIsATest) + self.chkEncrypt.setVisible(not thisIsATest) + self.advancedOptionsTab.setEnabled(not thisIsATest) + if not thisIsATest: + self.connect(self.chkEncrypt, SIGNAL(CLICKED), self.onEncryptCheckboxChange) + + layout = QVBoxLayout() + layout.addWidget(walletRestoreTabs) + layout.addWidget(frmBtns) + self.setLayout(layout) + self.setMinimumWidth(650) + self.setMinimumHeight(500) + self.sizeHint = lambda: QSize(800, 650) + self.setWindowTitle(self.tr('Restore wallet from fragments')) + + self.makeFragInputTable() + self.checkRestoreParams() + + ############################################################################# + # Hide advanced options whenver the restored wallet is unencrypted + def onEncryptCheckboxChange(self): + self.advancedOptionsTab.setEnabled(self.chkEncrypt.isChecked()) + + def makeFragInputTable(self, addCount=0): + + self.tableSize += addCount + newLayout = QGridLayout() + newFrame = QFrame() + self.fragsDone = [] + newLayout.addWidget(HLINE(), 0, 0, 1, 5) + for i in range(self.tableSize): + btnEnter = QPushButton(self.tr('Type Data')) + btnLoad = QPushButton(self.tr('Load File')) + btnClear = QPushButton(self.tr('Clear')) + lblFragID = QRichLabel('', doWrap=False) + lblSecure = QLabel('') + if i in self.fragDataMap: + M, fnum, wltID, doMask, fid = ReadFragIDLineBin(self.fragDataMap[i][0]) + self.fragsDone.append(fnum) + lblFragID.setText('' + fid + '') + if doMask: + lblFragID.setText('' + fid + '', color='TextWarn') + + + self.connect(btnEnter, SIGNAL(CLICKED), \ + functools.partial(self.dataEnter, fnum=i)) + self.connect(btnLoad, SIGNAL(CLICKED), \ + functools.partial(self.dataLoad, fnum=i)) + self.connect(btnClear, SIGNAL(CLICKED), \ + functools.partial(self.dataClear, fnum=i)) + + + newLayout.addWidget(btnEnter, 2 * i + 1, 0) + newLayout.addWidget(btnLoad, 2 * i + 1, 1) + newLayout.addWidget(btnClear, 2 * i + 1, 2) + newLayout.addWidget(lblFragID, 2 * i + 1, 3) + newLayout.addWidget(lblSecure, 2 * i + 1, 4) + newLayout.addWidget(HLINE(), 2 * i + 2, 0, 1, 5) + + btnFrame = QFrame() + btnFrame.setLayout(newLayout) + + frmFinal = makeVertFrame([btnFrame, STRETCH], STYLE_SUNKEN) + self.scrollFragInput.setWidget(frmFinal) + + self.btnAddFrag.setVisible(self.tableSize < 12) + self.btnRmFrag.setVisible(self.tableSize > 2) + + + ############################################################################# + def addFragment(self): + self.makeFragInputTable(1) + + ############################################################################# + def removeFragment(self): + self.makeFragInputTable(-1) + toRemove = [] + for key, val in self.fragDataMap.iteritems(): + if key >= self.tableSize: + toRemove.append(key) + + # Have to do this in a separate loop, cause you can't remove items + # from a map while you are iterating over them + for key in toRemove: + self.dataClear(key) + + + ############################################################################# + def dataEnter(self, fnum): + dlg = DlgEnterOneFrag(self, self.main, self.fragsDone, self.wltType, self.displaySecureString.text()) + if dlg.exec_(): + LOGINFO('Good data from enter_one_frag exec! %d', fnum) + self.displaySecureString.setText(dlg.editSecurePrint.text()) + self.addFragToTable(fnum, dlg.fragData) + self.makeFragInputTable() + + + ############################################################################# + def dataLoad(self, fnum): + LOGINFO('Loading data for entry, %d', fnum) + toLoad = str(self.main.getFileLoad('Load Fragment File', \ + ['Wallet Fragments (*.frag)'])) + + if len(toLoad) == 0: + return + + if not os.path.exists(toLoad): + LOGERROR('File just chosen does not exist! %s', toLoad) + QMessageBox.critical(self, self.tr('File Does Not Exist'), self.tr( + 'The file you select somehow does not exist...? ' + '

%s

Try a different file' % toLoad), \ + QMessageBox.Ok) + + fragMap = {} + with open(toLoad, 'r') as fin: + allData = [line.strip() for line in fin.readlines()] + fragMap = {} + for line in allData: + if line[:2].lower() in ['id', 'x1', 'x2', 'x3', 'x4', \ + 'y1', 'y2', 'y3', 'y4', \ + 'f1', 'f2', 'f3', 'f4']: + fragMap[line[:2].lower()] = line[3:].strip().replace(' ', '') + + + cList, nList = [], [] + if len(fragMap) == 9: + cList, nList = ['x', 'y'], ['1', '2', '3', '4'] + elif len(fragMap) == 5: + cList, nList = ['f'], ['1', '2', '3', '4'] + elif len(fragMap) == 3: + cList, nList = ['f'], ['1', '2'] + else: + LOGERROR('Unexpected number of lines in the frag file, %d', len(fragMap)) + return + + fragData = [] + fragData.append(hex_to_binary(fragMap['id'])) + for c in cList: + for n in nList: + mapKey = c + n + rawBin, err = readSixteenEasyBytes(fragMap[c + n]) + if err == 'Error_2+': + QMessageBox.critical(self, self.tr('Fragment Error'), self.tr( + 'There was an unfixable error in the fragment file: ' + '

File: %s
Line: %s
' % (toLoad, mapKey)), \ + QMessageBox.Ok) + return +# fragData.append(SecureBinaryData(rawBin)) + rawBin = None + + self.addFragToTable(fnum, fragData) + self.makeFragInputTable() + + + ############################################################################# + def dataClear(self, fnum): + if not fnum in self.fragDataMap: + return + + for i in range(1, 3): + self.fragDataMap[fnum][i].destroy() + del self.fragDataMap[fnum] + self.makeFragInputTable() + self.checkRestoreParams() + + + ############################################################################# + def checkRestoreParams(self): + showRightFrm = False + self.btnRestore.setEnabled(False) + self.lblRightFrm.setText(self.tr( + 'Start entering fragments into the table to left...')) + for row, data in self.fragDataMap.iteritems(): + showRightFrm = True + M, fnum, setIDBin, doMask, idBase58 = ReadFragIDLineBin(data[0]) + self.lblRightFrm.setText(self.tr('Wallet Being Restored:')) + self.imgPie.setPixmap(QPixmap('./img/frag%df.png' % M).scaled(96,96)) + self.lblReqd.setText(self.tr('Frags Needed: %s' % M)) + self.lblFragID.setText(self.tr('Fragments: %s' % idBase58.split('-')[0])) + self.btnRestore.setEnabled(len(self.fragDataMap) >= M) + break + + anyMask = False + for row, data in self.fragDataMap.iteritems(): + M, fnum, wltIDBin, doMask, idBase58 = ReadFragIDLineBin(data[0]) + if doMask: + anyMask = True + break + # If all of the rows with a Mask have been removed clear the securePrintCode + if not anyMask: + self.displaySecureString.setText('') + self.lblSecureStr.setVisible(anyMask) + self.displaySecureString.setVisible(anyMask) + + if not showRightFrm: + self.fragIDPrefix = UNKNOWN + self.wltType = UNKNOWN + + self.imgPie.setVisible(showRightFrm) + self.lblReqd.setVisible(showRightFrm) + self.lblWltID.setVisible(showRightFrm) + self.lblFragID.setVisible(showRightFrm) + + + ############################################################################# + def addFragToTable(self, tableIndex, fragData): + + if len(fragData) == 9: + currType = '0' + elif len(fragData) == 5: + currType = BACKUP_TYPE_135A + elif len(fragData) == 3: + currType = BACKUP_TYPE_135C + else: + LOGERROR('How\'d we get fragData of size: %d', len(fragData)) + return + + if self.wltType == UNKNOWN: + self.wltType = currType + elif not self.wltType == currType: + QMessageBox.critical(self, self.tr('Mixed fragment types'), self.tr( + 'You entered a fragment for a different wallet type. Please check ' + 'that all fragments are for the same wallet, of the same version, ' + 'and require the same number of fragments.'), QMessageBox.Ok) + LOGERROR('Mixing frag types! How did that happen?') + return + + + M, fnum, wltIDBin, doMask, idBase58 = ReadFragIDLineBin(fragData[0]) + # If we don't know the Secure String Yet we have to get it + if doMask and len(str(self.displaySecureString.text()).strip()) == 0: + dlg = DlgEnterSecurePrintCode(self, self.main) + if dlg.exec_(): + self.displaySecureString.setText(dlg.editSecurePrint.text()) + else: + return + + if self.fragIDPrefix == UNKNOWN: + self.fragIDPrefix = idBase58.split('-')[0] + elif not self.fragIDPrefix == idBase58.split('-')[0]: + QMessageBox.critical(self, self.tr('Multiple Wallets'), self.tr( + 'The fragment you just entered is actually for a different wallet ' + 'than the previous fragments you entered. Please double-check that ' + 'all the fragments you are entering belong to the same wallet and ' + 'have the "number of needed fragments" (M-value, in M-of-N).'), \ + QMessageBox.Ok) + LOGERROR('Mixing fragments of different wallets! %s', idBase58) + return + + + if not self.verifyNonDuplicateFrag(fnum): + QMessageBox.critical(self, self.tr('Duplicate Fragment'), self.tr( + 'You just input fragment #%s, but that fragment has already been ' + 'entered!' % fnum), QMessageBox.Ok) + return + + + +# if currType == '0': +# X = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 5)])) +# Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(5, 9)])) +# elif currType == BACKUP_TYPE_135A: +# X = SecureBinaryData(int_to_binary(fnum + 1, widthBytes=64, endOut=BIGENDIAN)) +# Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 5)])) +# elif currType == BACKUP_TYPE_135C: +# X = SecureBinaryData(int_to_binary(fnum + 1, widthBytes=32, endOut=BIGENDIAN)) +# Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 3)])) + + self.fragDataMap[tableIndex] = [fragData[0][:], X.copy(), Y.copy()] + + X.destroy() + Y.destroy() + self.checkRestoreParams() + + ############################################################################# + def verifyNonDuplicateFrag(self, fnum): + for row, data in self.fragDataMap.iteritems(): + rowFrag = ReadFragIDLineBin(data[0])[1] + if fnum == rowFrag: + return False + + return True + + + + ############################################################################# + def processFrags(self): + if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: + QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ + self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) + return + if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfBytes() == -1: + QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ + self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) + return + SECPRINT = HardcodedKeyMaskParams() + pwd, ekey = '', '' + if self.displaySecureString.isVisible(): + pwd = str(self.displaySecureString.text()).strip() + maskKey = SECPRINT['FUNC_KDF'](pwd) + + fragMtrx, M = [], -1 + for row, trip in self.fragDataMap.iteritems(): + M, fnum, wltID, doMask, fid = ReadFragIDLineBin(trip[0]) + X, Y = trip[1], trip[2] + if doMask: + LOGINFO('Row %d needs unmasking' % row) + Y = SECPRINT['FUNC_UNMASK'](Y, ekey=maskKey) + else: + LOGINFO('Row %d is already unencrypted' % row) + fragMtrx.append([X.toBinStr(), Y.toBinStr()]) + + typeToBytes = {'0': 64, BACKUP_TYPE_135A: 64, BACKUP_TYPE_135C: 32} + nBytes = typeToBytes[self.wltType] + + + if self.thisIsATest and len(fragMtrx) > M: + self.testFragSubsets(fragMtrx, M) + return + + + SECRET = ReconstructSecret(fragMtrx, M, nBytes) + for i in range(len(fragMtrx)): + fragMtrx[i] = [] + + LOGINFO('Final length of frag mtrx: %d', len(fragMtrx)) + LOGINFO('Final length of secret: %d', len(SECRET)) + + priv, chain = '', '' +# if len(SECRET) == 64: +# priv = SecureBinaryData(SECRET[:32 ]) +# chain = SecureBinaryData(SECRET[ 32:]) +# elif len(SECRET) == 32: +# priv = SecureBinaryData(SECRET) +# chain = DeriveChaincodeFromRootKey(priv) + + + # If we got here, the data is valid, let's create the wallet and accept the dlg + # Now we should have a fully-plaintext rootkey and chaincode + root = PyBtcAddress().createFromPlainKeyData(priv) + root.chaincode = chain + + first = root.extendAddressChain() + newWltID = binary_to_base58((ADDRBYTE + first.getAddr160()[:5])[::-1]) + + # If this is a test, then bail + if self.thisIsATest: + verifyRecoveryTestID(self, newWltID, self.testWltID) + return + + dlgOwnWlt = None + if newWltID in self.main.walletMap: + dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) + + if (dlgOwnWlt.exec_()): + if dlgOwnWlt.output == 0: + return + else: + self.reject() + return + + reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), self.tr( + 'The data you entered corresponds to a wallet with the ' + 'ID:
{%s}
Does this ID ' + 'match the "Wallet Unique ID" printed on your paper backup? ' + 'If not, click "No" and reenter key and chain-code data ' + 'again.' % newWltID), QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.No: + return + + + passwd = [] + if self.chkEncrypt.isChecked(): + dlgPasswd = DlgChangePassphrase(self, self.main) +# if dlgPasswd.exec_(): +# passwd = SecureBinaryData(str(dlgPasswd.edtPasswd1.text())) +# else: +# QMessageBox.critical(self, self.tr('Cannot Encrypt'), self.tr( +# 'You requested your restored wallet be encrypted, but no ' +# 'valid passphrase was supplied. Aborting wallet ' +# 'recovery.'), QMessageBox.Ok) +# return + + shortl = '' + longl = '' + nPool = 1000 + + if dlgOwnWlt is not None: + if dlgOwnWlt.Meta is not None: + shortl = ' - %s' % (dlgOwnWlt.Meta['shortLabel']) + longl = dlgOwnWlt.Meta['longLabel'] + nPool = max(nPool, dlgOwnWlt.Meta['naddress']) + + if passwd: + self.newWallet = PyBtcWallet().createNewWallet(\ + plainRootKey=priv, \ + chaincode=chain, \ + shortLabel='Restored - ' + newWltID + shortl, \ + longLabel=longl, \ + withEncrypt=True, \ + securePassphrase=passwd, \ + kdfTargSec=self.advancedOptionsTab.getKdfSec(), \ + kdfMaxMem=self.advancedOptionsTab.getKdfBytes(), + isActuallyNew=False, \ + doRegisterWithBDM=False) + else: + self.newWallet = PyBtcWallet().createNewWallet(\ + plainRootKey=priv, \ + chaincode=chain, \ + shortLabel='Restored - ' + newWltID +shortl, \ + longLabel=longl, \ + withEncrypt=False, \ + isActuallyNew=False, \ + doRegisterWithBDM=False) + + + # Will pop up a little "please wait..." window while filling addr pool + fillAddrPoolProgress = DlgProgress(self, self.parent, HBar=1, + Title=self.tr("Computing New Addresses")) + fillAddrPoolProgress.exec_(self.newWallet.fillAddressPool, nPool) + + if dlgOwnWlt is not None: + if dlgOwnWlt.Meta is not None: + from armoryengine.PyBtcWallet import WLT_UPDATE_ADD + for n_cmt in range(0, dlgOwnWlt.Meta['ncomments']): + entrylist = [] + entrylist = list(dlgOwnWlt.Meta[n_cmt]) + self.newWallet.walletFileSafeUpdate([[WLT_UPDATE_ADD, entrylist[2], entrylist[1], entrylist[0]]]) + + self.newWallet = PyBtcWallet().readWalletFile(self.newWallet.walletPath) + self.accept() + + ############################################################################# + def testFragSubsets(self, fragMtrx, M): + # If the user entered multiple fragments + fragMap = {} + for x, y in fragMtrx: + fragMap[binary_to_int(x, BIGENDIAN) - 1] = [x, y] + typeToBytes = {'0': 64, BACKUP_TYPE_135A: 64, BACKUP_TYPE_135C: 32} + + isRandom, results = testReconstructSecrets(fragMap, M, 100) + def privAndChainFromRow(secret): + priv, chain = None, None +# if len(secret) == 64: +# priv = SecureBinaryData(secret[:32 ]) +# chain = SecureBinaryData(secret[ 32:]) +# return (priv, chain) +# elif len(secret) == 32: +# priv = SecureBinaryData(secret) +# chain = DeriveChaincodeFromRootKey(priv) +# return (priv, chain) +# else: +# LOGERROR('Root secret is %s bytes ?!' % len(secret)) +# raise KeyDataError + + results = [(row[0], privAndChainFromRow(row[1])) for row in results] + subsAndIDs = [(row[0], calcWalletIDFromRoot(*row[1])) for row in results] + + DlgShowTestResults(self, isRandom, subsAndIDs, \ diff --git a/qtdialogs/DlgRestoreSingle.py b/qtdialogs/DlgRestoreSingle.py new file mode 100644 index 000000000..2a9c1d6dc --- /dev/null +++ b/qtdialogs/DlgRestoreSingle.py @@ -0,0 +1,399 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +from PySide2.QtWidgets import QButtonGroup, QCheckBox, QDialogButtonBox, QFrame, QGridLayout, QLabel, QLayout, QLineEdit, QMessageBox, QPushButton, QRadioButton, QTabWidget, QVBoxLayout +from PySide2.QtCore import SIGNAL + +from armoryengine import ClientProto_pb2 +from armoryengine.ArmoryUtils import LOGERROR, UINT32_MAX, UINT8_MAX +from armoryengine.BDM import TheBDM +from armoryengine.PyBtcWallet import PyBtcWallet + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.DlgChangePassphrase import DlgChangePassphrase +from qtdialogs.DlgReplaceWallet import DlgReplaceWallet +from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, makeHorizFrame, makeVertFrame +from qtdialogs.qtdialogs import MaskedInputLineEdit, verifyRecoveryTestID + +from ui.WalletFrames import AdvancedOptionsFrame + +################################################################################ +class DlgRestoreSingle(ArmoryDialog): + ############################################################################# + def __init__(self, parent, main, thisIsATest=False, expectWltID=None): + super(DlgRestoreSingle, self).__init__(parent, main) + + self.newWltID = None + self.callbackId = None + self.thisIsATest = thisIsATest + self.testWltID = expectWltID + headerStr = '' + if thisIsATest: + lblDescr = QRichLabel(self.tr( + 'Test a Paper Backup ' + '

' + 'Use this window to test a single-sheet paper backup. If your ' + 'backup includes imported keys, those will not be covered by this test.')) + else: + lblDescr = QRichLabel(self.tr( + 'Restore a Wallet from Paper Backup ' + '

' + 'Use this window to restore a single-sheet paper backup. ' + 'If your backup includes extra pages with ' + 'imported keys, please restore the base wallet first, then ' + 'double-click the restored wallet and select "Import Private ' + 'Keys" from the right-hand menu.')) + + + lblType = QRichLabel(self.tr('Backup Type:'), doWrap=False) + + self.version135Button = QRadioButton(self.tr('Version 1.35 (4 lines)'), self) + self.version135aButton = QRadioButton(self.tr('Version 1.35a (4 lines Unencrypted)'), self) + self.version135aSPButton = QRadioButton(self.trUtf8(u'Version 1.35a (4 lines + SecurePrint\u200b\u2122)'), self) + self.version135cButton = QRadioButton(self.tr('Version 1.35c (2 lines Unencrypted)'), self) + self.version135cSPButton = QRadioButton(self.trUtf8(u'Version 1.35c (2 lines + SecurePrint\u200b\u2122)'), self) + self.backupTypeButtonGroup = QButtonGroup(self) + self.backupTypeButtonGroup.addButton(self.version135Button) + self.backupTypeButtonGroup.addButton(self.version135aButton) + self.backupTypeButtonGroup.addButton(self.version135aSPButton) + self.backupTypeButtonGroup.addButton(self.version135cButton) + self.backupTypeButtonGroup.addButton(self.version135cSPButton) + self.version135cButton.setChecked(True) + self.connect(self.backupTypeButtonGroup, SIGNAL('buttonClicked(int)'), self.changeType) + + layoutRadio = QVBoxLayout() + layoutRadio.addWidget(self.version135Button) + layoutRadio.addWidget(self.version135aButton) + layoutRadio.addWidget(self.version135aSPButton) + layoutRadio.addWidget(self.version135cButton) + layoutRadio.addWidget(self.version135cSPButton) + layoutRadio.setSpacing(0) + + radioButtonFrame = QFrame() + radioButtonFrame.setLayout(layoutRadio) + + frmBackupType = makeVertFrame([lblType, radioButtonFrame]) + + self.lblSP = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code:'), doWrap=False) + self.editSecurePrint = QLineEdit() + self.prfxList = [QLabel(self.tr('Root Key:')), QLabel(''), QLabel(self.tr('Chaincode:')), QLabel('')] + + inpMask = '
%s' % errorMsg \ + ), QMessageBox.Ok) + + self.reject() + return + + result, extra = self.processCallbackPayload(payload) + if result == False: + TheBDM.unregisterCustomPrompt(self.callbackId) + + reply = ClientProto_pb2.RestoreReply() + reply.result = result + + if extra != None: + reply.extra = bytes(extra, 'utf-8') + +# TheBridge.callbackFollowUp(reply, self.callbackId, callerId) + + ############################################################################# + def processCallbackPayload(self, payload): + msg = ClientProto_pb2.RestorePrompt() + msg.ParseFromString(payload) + + if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Id") or \ + msg.promptType == ClientProto_pb2.RestorePromptType.Value("ChecksumError"): + #check the id generated by this backup + + newWltID = msg.extra + if len(newWltID) > 0: + if self.thisIsATest: + # Stop here if this was just a test + verifyRecoveryTestID(self, newWltID, self.testWltID) + + #return false to caller to end the restore process + return False, None + + # return result of id comparison + dlgOwnWlt = None + if newWltID in self.main.walletMap: + dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) + + if (dlgOwnWlt.exec_()): + #TODO: deal with replacement code + if dlgOwnWlt.output == 0: + return False, None + else: + return False, None + else: + reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), \ + self.tr('The data you entered corresponds to a wallet with a wallet ID: \n\n' + '%s\n\nDoes this ID match the "Wallet Unique ID" ' + 'printed on your paper backup? If not, click "No" and reenter ' + 'key and chain-code data again.' % newWltID), \ + QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.Yes: + #return true to caller to proceed with restore operation + self.newWltID = newWltID + return True, None + + #reconstructed wallet id is invalid if we get this far + lineNumber = -1 + canBeSalvaged = True + if len(msg.checksums) != self.lineCount: + canBeSalvaged = False + + for i in range(0, len(msg.checksums)): + if msg.checksums[i] < 0 or msg.checksums[i] == UINT8_MAX: + lineNumber = i + 1 + break + + if lineNumber == -1 or canBeSalvaged == False: + QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( + 'Encountered an unkonwn error when restoring this backup. Aborting.', \ + QMessageBox.Ok)) + + self.reject() + return False, None + + reply = QMessageBox.critical(self, self.tr('Invalid Data'), self.tr( + 'There is an error in the data you entered that could not be ' + 'fixed automatically. Please double-check that you entered the ' + 'text exactly as it appears on the wallet-backup page.

' + 'The error occured on line #%d.' % lineNumber), \ + QMessageBox.Ok) + LOGERROR('Error in wallet restore field') + self.prfxList[i].setText(\ + '' + str(self.prfxList[i].text()) + '') + + return False, None + + if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Passphrase"): + #return new wallet's private keys password + passwd = [] + if self.chkEncrypt.isChecked(): + dlgPasswd = DlgChangePassphrase(self, self.main) + if dlgPasswd.exec_(): + passwd = str(dlgPasswd.edtPasswd1.text()) + return True, passwd + else: + QMessageBox.critical(self, self.tr('Cannot Encrypt'), \ + self.tr('You requested your restored wallet be encrypted, but no ' + 'valid passphrase was supplied. Aborting wallet recovery.'), \ + QMessageBox.Ok) + self.reject() + return False, None + + if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Control"): + #TODO: need UI to input control passphrase + return True, None + + if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Success"): + if self.newWltID == None or len(self.newWltID) == 0: + LOGERROR("wallet import did not yield an id") + raise Exception("wallet import did not yield an id") + + self.newWallet = PyBtcWallet() + self.newWallet.loadFromBridge(self.newWltID) + self.accept() + + return True, None + + if msg.promptType == ClientProto_pb2.RestorePromptType.Value("FormatError") or \ + sg.promptType == ClientProto_pb2.RestorePromptType.Value("Failure"): + + QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( + 'Encountered an unkonwn error when restoring this backup. Aborting.', \ + QMessageBox.Ok)) + + self.reject() + return False, None + + if msg.promptType == ClientProto_pb2.RestorePromptType.Value("DecryptError"): + #TODO: notify of invalid SP pass + pass + + if msg.promptType == ClientProto_pb2.RestorePromptType.Value("TypeError"): + #TODO: wallet type conveyed by backup is unknown + pass + + else: + #TODO: unknown error + return False, None + + + ############################################################################# + def verifyUserInput(self): + + root = [] + for i in range(2): + root.append(str(self.edtList[i].text())) + + chaincode = [] + if self.isLongForm: + for i in range(2): + chaincode.append(str(self.edtList[i+2].text())) + + self.lineCount = len(root) + len(chaincode) + + spPass = "" + if self.doMask: + #add secureprint passphrase if this backup is encrypted + spPass = str(self.editSecurePrint.text()).strip() + + ''' + verifyBackupString is a method that will trigger multiple callbacks + during the course of its execution. Unlike a password request callback + which only requires to generate a dedicated dialog to retrieve passwords + from users, verifyBackupString set of notifications is complex and comes + with branches. + + A dedicated callbackId is generated for this interaction and passed to + TheBDM callback map along with a py side method to handle the protobuf + packet from the C++ side. + + The C++ method is called with that id. + ''' + def callback(payload, callerId): + self.main.signalExecution.executeMethod(\ + self.processCallback, payload, callerId) + + self.callbackId = TheBDM.registerCustomPrompt(callback) +# TheBridge.restoreWallet(root, chaincode, spPass, self.callbackId) + return + + if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: + QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ + self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) + return + if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfBytes() == -1: + QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ + self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) + return +# if nError > 0: +# pluralStr = 'error' if nError == 1 else 'errors' +# +# msg = self.tr( +# 'Detected errors in the data you entered. ' +# 'Armory attempted to fix the errors but it is not ' +# 'always right. Be sure to verify the "Wallet Unique ID" ' +# 'closely on the next window.') +# +# QMessageBox.question(self, self.tr('Errors Corrected'), msg, \ +# QMessageBox.Ok) \ No newline at end of file diff --git a/qtdialogs/DlgRestoreWOData.py b/qtdialogs/DlgRestoreWOData.py new file mode 100644 index 000000000..f313ac4ed --- /dev/null +++ b/qtdialogs/DlgRestoreWOData.py @@ -0,0 +1,266 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +# Class that will create the watch-only wallet data (root public key & chain +# code) restoration window. +################################################################################ + +import os +import sys + +from PySide2.QtWidgets import QDialogButtonBox, QFrame, QGridLayout, QLabel, QLayout, QMessageBox, QPushButton, QVBoxLayout +from PySide2.QtCore import SIGNAL + +from armoryengine.ArmoryUtils import LOGERROR, binary_to_base58, binary_to_int, easyType16_to_binary, int_to_binary, readSixteenEasyBytes, verifyChecksum +from armoryengine.PyBtcWallet import PYROOTPKCCSIGNMASK, PYROOTPKCCVERMASK, PyBtcWallet +from armoryengine.PyBtcAddress import PyBtcAddress +#from CppBlockUtils import CryptoECDSA, SecureBinaryData + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.DlgProgress import DlgProgress +from qtdialogs.qtdefines import GETFONT, HLINE, QRichLabel, STRETCH, STYLE_RAISED, makeHorizFrame +from qtdialogs.qtdialogs import MaskedInputLineEdit, verifyRecoveryTestID + +class DlgRestoreWOData(ArmoryDialog): + ############################################################################# + def __init__(self, parent, main, thisIsATest=False, expectWltID=None): + super(DlgRestoreWOData, self).__init__(parent, main) + + self.thisIsATest = thisIsATest + self.testWltID = expectWltID + headerStr = '' + lblDescr = '' + + # Write the text at the top of the window. + if thisIsATest: + lblDescr = QRichLabel(self.tr( + 'Test a Watch-Only Wallet Restore ' + '

' + 'Use this window to test the restoration of a watch-only wallet using ' + 'the wallet\'s data. You can either type the data on a root data ' + 'printout or import the data from a file.')) + else: + lblDescr = QRichLabel(self.tr( + 'Restore a Watch-Only Wallet ' + '

' + 'Use this window to restore a watch-only wallet using the wallet\'s ' + 'data. You can either type the data on a root data printout or import ' + 'the data from a file.')) + + # Create the line that will contain the imported ID. + self.rootIDLabel = QRichLabel(self.tr('Watch-Only Root ID:'), doWrap=False) + inpMask = '
'), + QMessageBox.Ok) + LOGERROR('Error in root ID restore field') + LOGERROR('Error Type: %s', errType) + LOGERROR('Error Value: %s', errVal) + return + + # Save the version/key byte and the root ID. For now, ignore the version. + inRootVer = inRootChecked[0] # 1 byte + inRootID = inRootChecked[1:7] # 6 bytes + + # Read in the root data (public key & chain code) and handle any errors. + for i in range(nLine): + hasError = False + try: + rawEntry = str(self.pkccList[i].text()) + rawBin, err = readSixteenEasyBytes(rawEntry.replace(' ', '')) + if err == 'Error_2+': # 2+ bytes are wrong, so we need to stop. + hasError = True + elif err == 'Fixed_1': # 1 byte is wrong, so we may be okay. + nError += 1 + except: + hasError = True + + # If the root ID is busted, stop. + if hasError: + lineNumber = i+1 + reply = QMessageBox.critical(self, self.tr('Invalid Data'), self.tr( + 'There is an error in the root data you entered that could not be ' + 'fixed automatically. Please double-check that you entered the ' + 'text exactly as it appears on the wallet-backup page.

' + 'The error occured on line #%d.' % lineNumber), QMessageBox.Ok) + LOGERROR('Error in root data restore field') + return + + # If we've gotten this far, save the incoming line. + inputLines.append(rawBin) + + # Set up the root ID data. + pkVer = binary_to_int(inRootVer) & PYROOTPKCCVERMASK # Ignored for now. + pkSignByte = ((binary_to_int(inRootVer) & PYROOTPKCCSIGNMASK) >> 7) + 2 + rootPKComBin = int_to_binary(pkSignByte) + ''.join(inputLines[:2]) + rootPubKey = CryptoECDSA().UncompressPoint(SecureBinaryData(rootPKComBin)) + rootChainCode = SecureBinaryData(''.join(inputLines[2:])) + + # Now we should have a fully-plaintext root key and chain code, and can + # get some related data. + root = PyBtcAddress().createFromPublicKeyData(rootPubKey) + root.chaincode = rootChainCode + first = root.extendAddressChain() + newWltID = binary_to_base58(inRootID) + + # Stop here if this was just a test + if self.thisIsATest: + verifyRecoveryTestID(self, newWltID, self.testWltID) + return + + # If we already have the wallet, don't replace it, otherwise proceed. + dlgOwnWlt = None + if newWltID in self.main.walletMap: + QMessageBox.warning(self, self.tr('Wallet Already Exists'), self.tr( + 'The wallet already exists and will not be ' + 'replaced.'), QMessageBox.Ok) + self.reject() + return + else: + # Make sure the user is restoring the wallet they want to restore. + reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), \ + self.tr('The data you entered corresponds to a wallet with a wallet ' + 'ID: \n\n\t%s\n\nDoes this ' + 'ID match the "Wallet Unique ID" you intend to restore? ' + 'If not, click "No" and enter the key and chain-code data ' + 'again.' % binary_to_base58(inRootID)), QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.No: + return + + # Create the wallet. + self.newWallet = PyBtcWallet().createNewWalletFromPKCC(rootPubKey, \ + rootChainCode) + + # Create some more addresses and show a progress bar while restoring. + nPool = 1000 + fillAddrPoolProgress = DlgProgress(self, self.main, HBar=1, + Title=self.tr("Computing New Addresses")) + fillAddrPoolProgress.exec_(self.newWallet.fillAddressPool, nPool) + + self.accept() \ No newline at end of file diff --git a/qtdialogs/DlgSendBitcoins.py b/qtdialogs/DlgSendBitcoins.py index 4c8e9f2da..3f26af4be 100644 --- a/qtdialogs/DlgSendBitcoins.py +++ b/qtdialogs/DlgSendBitcoins.py @@ -9,8 +9,8 @@ from PySide2.QtCore import QSize from PySide2.QtWidgets import QVBoxLayout -from qtdialogs.qtdefines import ArmoryDialog from qtdialogs.DlgOfflineTx import DlgOfflineTxCreated +from qtdialogs.ArmoryDialog import ArmoryDialog from ui.TxFrames import SendBitcoinsFrame @@ -71,4 +71,4 @@ def accept(self, *args): ############################################################################# def reject(self, *args): self.saveGeometrySettings() - super(DlgSendBitcoins, self).reject(*args) + super(DlgSendBitcoins, self).reject(*args) \ No newline at end of file diff --git a/qtdialogs/DlgSetComment.py b/qtdialogs/DlgSetComment.py index 4aaf4e2e3..2f389abed 100644 --- a/qtdialogs/DlgSetComment.py +++ b/qtdialogs/DlgSetComment.py @@ -12,9 +12,9 @@ QDialogButtonBox, QGridLayout from armoryengine.ArmoryUtils import MAX_COMMENT_LENGTH, isASCII -from qtdialogs.qtdefines import ArmoryDialog, relaxedSizeNChar, \ +from qtdialogs.qtdefines import relaxedSizeNChar, \ UnicodeErrorBox - +from qtdialogs.ArmoryDialog import ArmoryDialog ################################################################################ class DlgSetComment(ArmoryDialog): diff --git a/qtdialogs/DlgSettings.py b/qtdialogs/DlgSettings.py new file mode 100644 index 000000000..546700207 --- /dev/null +++ b/qtdialogs/DlgSettings.py @@ -0,0 +1,788 @@ +# -*- coding: UTF-8 -*- +from __future__ import (absolute_import, division, + print_function, unicode_literals) + +############################################################################### +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +############################################################################### + +import time +import os + +from PySide2.QtCore import Qt, QLocale, SIGNAL +from PySide2.QtWidgets import QPushButton, QRadioButton, QLineEdit, \ + QCheckBox, QMessageBox, QGridLayout, QLabel, QTabWidget, QVBoxLayout, \ + QScrollArea, QFrame, QComboBox, QSlider + +from armoryengine.ArmoryUtils import BTC_HOME_DIR, DEFAULT_ADDR_TYPE, \ + OS_MACOSX, OS_WINDOWS, ARMORY_DB_DIR, LANGUAGES, OS_VARIANT, \ + unixTimeToFormatStr, coin2str, str2coin, MIN_FEE_BYTE, \ + MIN_TX_FEE, DEFAULT_FEE_TYPE, FORMAT_SYMBOLS, \ + DEFAULT_DATE_FORMAT, DEFAULT_CHANGE_TYPE, DEFAULT_RECEIVE_TYPE + +from armoryengine.CoinSelection import NBLOCKS_TO_CONFIRM + +from qtdialogs.qtdefines import USERMODE, GETFONT, \ + HLINE, tightSizeStr, tightSizeNChar, STYLE_RAISED, \ + QRichLabel, createDirectorySelectButton, makeHorizFrame, \ + makeVertFrame + +from qtdialogs.ArmoryDialog import ArmoryDialog + +from ui.AddressTypeSelectDialog import AddressLabelFrame + +MIN_PASSWD_WIDTH = lambda obj: tightSizeStr(obj, '*' * 16)[0] +NO_CHANGE = 'NoChange' +STRETCH = 'Stretch' +BACKUP_TYPE_135A = '1.35a' +BACKUP_TYPE_135C = '1.35c' +BACKUP_TYPE_0_TEXT = 'Version 0 (from script, 9 lines)' +BACKUP_TYPE_135a_TEXT = 'Version 1.35a (5 lines Unencrypted)' +BACKUP_TYPE_135a_SP_TEXT = u'Version 1.35a (5 lines + SecurePrint\u200b\u2122)' +BACKUP_TYPE_135c_TEXT = 'Version 1.35c (3 lines Unencrypted)' +BACKUP_TYPE_135c_SP_TEXT = u'Version 1.35c (3 lines + SecurePrint\u200b\u2122)' +MAX_QR_SIZE = 198 +MAX_SATOSHIS = 2100000000000000 + + +############################################################################### +class DlgSettings(ArmoryDialog): + def __init__(self, parent=None, main=None): + super(DlgSettings, self).__init__(parent, main) + + defaultWltID = self.main.walletIDList[0] + self.wlt = self.main.walletMap[defaultWltID] + self.addrType = self.main.getSettingOrSetDefault('Default_ReceiveType', self.wlt.getDefaultAddressType()) + ####################################################################### + # bitcoind-management settings + self.chkManageSatoshi = QCheckBox(self.tr( + 'Let Armory run Bitcoin Core/bitcoind in the background')) + self.edtSatoshiExePath = QLineEdit() + self.edtSatoshiHomePath = QLineEdit() + self.edtArmoryDbdir = QLineEdit() + + self.edtSatoshiExePath.setMinimumWidth( + tightSizeNChar(GETFONT('Fixed', 10), 40)[0]) + self.connect( + self.chkManageSatoshi, SIGNAL('clicked()'), + self.clickChkManage) + self.startChk = self.main.getSettingOrSetDefault( + 'ManageSatoshi', not OS_MACOSX) + if self.startChk: + self.chkManageSatoshi.setChecked(True) + if OS_MACOSX: + self.chkManageSatoshi.setEnabled(False) + lblManageSatoshi = QRichLabel(self.tr( + 'Bitcoin Core/bitcoind management is not available on Mac/OSX') + ) + else: + if self.main.settings.hasSetting('SatoshiExe'): + satexe = self.main.settings.get('SatoshiExe') + + sathome = BTC_HOME_DIR + if self.main.settings.hasSetting('SatoshiDatadir'): + sathome = self.main.settings.get('SatoshiDatadir') + + lblManageSatoshi = QRichLabel( + self.tr('Bitcoin Software Management' + '

' + 'By default, Armory will manage the Bitcoin engine/software in the ' + 'background. You can choose to manage it yourself, or tell Armory ' + 'about non-standard installation configuration.')) + if self.main.settings.hasSetting('SatoshiExe'): + self.edtSatoshiExePath.setText(self.main.settings.get('SatoshiExe')) + self.edtSatoshiExePath.home(False) + if self.main.settings.hasSetting('SatoshiDatadir'): + self.edtSatoshiHomePath.setText(self.main.settings.get('SatoshiDatadir')) + self.edtSatoshiHomePath.home(False) + if self.main.settings.hasSetting('ArmoryDbdir'): + self.edtArmoryDbdir.setText(self.main.settings.get('ArmoryDbdir')) + self.edtArmoryDbdir.home(False) + + + lblDescrExe = QRichLabel(self.tr('Bitcoin Install Dir:')) + lblDefaultExe = QRichLabel(self.tr('Leave blank to have Armory search default ' + 'locations for your OS'), size=2) + + self.btnSetExe = createDirectorySelectButton(self, self.edtSatoshiExePath) + + layoutMgmt = QGridLayout() + layoutMgmt.addWidget(lblManageSatoshi, 0, 0, 1, 3) + layoutMgmt.addWidget(self.chkManageSatoshi, 1, 0, 1, 3) + + layoutMgmt.addWidget(lblDescrExe, 2, 0) + layoutMgmt.addWidget(self.edtSatoshiExePath, 2, 1) + layoutMgmt.addWidget(self.btnSetExe, 2, 2) + layoutMgmt.addWidget(lblDefaultExe, 3, 1, 1, 2) + + frmMgmt = QFrame() + frmMgmt.setLayout(layoutMgmt) + + self.clickChkManage() + ########################################################################## + + lblPathing = QRichLabel(self.tr(' Blockchain and Database Paths' + '

' + 'Optional feature to specify custom paths for blockchain ' + 'data and Armory\'s database.' + )) + + lblDescrHome = QRichLabel(self.tr('Bitcoin Home Dir:')) + lblDefaultHome = QRichLabel(self.tr('Leave blank to use default datadir ' + '(%s)' % BTC_HOME_DIR), size=2) + lblDescrDbdir = QRichLabel(self.tr('Armory Database Dir:')) + lblDefaultDbdir = QRichLabel(self.tr('Leave blank to use default datadir ' + '(%s)' % ARMORY_DB_DIR), size=2) + + self.btnSetHome = createDirectorySelectButton(self, self.edtSatoshiHomePath) + self.btnSetDbdir = createDirectorySelectButton(self, self.edtArmoryDbdir) + + layoutPath = QGridLayout() + layoutPath.addWidget(lblPathing, 0, 0, 1, 3) + + layoutPath.addWidget(lblDescrHome, 1, 0) + layoutPath.addWidget(self.edtSatoshiHomePath, 1, 1) + layoutPath.addWidget(self.btnSetHome, 1, 2) + layoutPath.addWidget(lblDefaultHome, 2, 1, 1, 2) + + layoutPath.addWidget(lblDescrDbdir, 3, 0) + layoutPath.addWidget(self.edtArmoryDbdir, 3, 1) + layoutPath.addWidget(self.btnSetDbdir, 3, 2) + layoutPath.addWidget(lblDefaultDbdir, 4, 1, 1, 2) + + frmPaths = QFrame() + frmPaths.setLayout(layoutPath) + + ########################################################################## + lblDefaultUriTitle = QRichLabel(self.tr('Set Armory as default URL handler')) + lblDefaultURI = QRichLabel(self.tr( + 'Set Armory to be the default when you click on "bitcoin:" ' + 'links in your browser or in emails. ' + 'You can test if your operating system is supported by clicking ' + 'on a "bitcoin:" link right after clicking this button.')) + btnDefaultURI = QPushButton(self.tr('Set Armory as Default')) + frmBtnDefaultURI = makeHorizFrame([btnDefaultURI, 'Stretch']) + + self.chkAskURIAtStartup = QCheckBox(self.tr( + 'Check whether Armory is the default handler at startup')) + askuriDNAA = self.main.getSettingOrSetDefault('DNAA_DefaultApp', False) + self.chkAskURIAtStartup.setChecked(not askuriDNAA) + + def clickRegURI(): + self.main.setupUriRegistration(justDoIt=True) + QMessageBox.information(self, self.tr('Registered'), self.tr( + 'Armory just attempted to register itself to handle "bitcoin:" ' + 'links, but this does not work on all operating systems.'), QMessageBox.Ok) + + self.connect(btnDefaultURI, SIGNAL('clicked()'), clickRegURI) + + ############################################################### + # Minimize on Close + lblMinimizeDescr = QRichLabel(self.tr( + 'Minimize to System Tray ' + '
' + 'You can have Armory automatically minimize itself to your system ' + 'tray on open or close. Armory will stay open but run in the ' + 'background, and you will still receive notifications. Access Armory ' + 'through the icon on your system tray. ' + '

' + 'If you select "Minimize on close", the \'x\' on the top window bar will ' + 'minimize Armory instead of exiting the application. You can always use ' + '"File" -> "Quit Armory" to actually close it.')) + + moo = self.main.getSettingOrSetDefault('MinimizeOnOpen', False) + self.chkMinOnOpen = QCheckBox(self.tr('Minimize to system tray on open')) + if moo: + self.chkMinOnOpen.setChecked(True) + + moc = self.main.getSettingOrSetDefault('MinimizeOrClose', 'DontKnow') + self.chkMinOrClose = QCheckBox(self.tr('Minimize to system tray on close')) + + if moc == 'Minimize': + self.chkMinOrClose.setChecked(True) + + + ############################################################### + # System tray notifications. On OS X, notifications won't work on 10.7. + # OS X's built-in notification system was implemented starting in 10.8. + osxMinorVer = '0' + if OS_MACOSX: + osxMinorVer = OS_VARIANT[0].split(".")[1] + + lblNotify = QRichLabel(self.tr('Enable notifications from the system-tray:')) + self.chkBtcIn = QCheckBox(self.tr('Bitcoins Received')) + self.chkBtcOut = QCheckBox(self.tr('Bitcoins Sent')) + self.chkDiscon = QCheckBox(self.tr('Bitcoin Core/bitcoind disconnected')) + self.chkReconn = QCheckBox(self.tr('Bitcoin Core/bitcoind reconnected')) + + # FYI:If we're not on OS X, the if condition will never be hit. + if (OS_MACOSX) and (int(osxMinorVer) < 7): + lblNotify = QRichLabel(self.tr('Sorry! Notifications are not available ' \ + 'on your version of OS X.')) + self.chkBtcIn.setChecked(False) + self.chkBtcOut.setChecked(False) + self.chkDiscon.setChecked(False) + self.chkReconn.setChecked(False) + self.chkBtcIn.setEnabled(False) + self.chkBtcOut.setEnabled(False) + self.chkDiscon.setEnabled(False) + self.chkReconn.setEnabled(False) + else: + notifyBtcIn = self.main.getSettingOrSetDefault('NotifyBtcIn', True) + notifyBtcOut = self.main.getSettingOrSetDefault('NotifyBtcOut', True) + notifyDiscon = self.main.getSettingOrSetDefault('NotifyDiscon', True) + notifyReconn = self.main.getSettingOrSetDefault('NotifyReconn', True) + self.chkBtcIn.setChecked(notifyBtcIn) + self.chkBtcOut.setChecked(notifyBtcOut) + self.chkDiscon.setChecked(notifyDiscon) + self.chkReconn.setChecked(notifyReconn) + + ############################################################### + # Date format preferences + exampleTimeTuple = (2012, 4, 29, 19, 45, 0, -1, -1, -1) + self.exampleUnixTime = time.mktime(exampleTimeTuple) + exampleStr = unixTimeToFormatStr(self.exampleUnixTime, '%c') + lblDateFmt = QRichLabel(self.tr('Preferred Date Format:
')) + lblDateDescr = QRichLabel(self.tr( + 'You can specify how you would like dates ' + 'to be displayed using percent-codes to ' + 'represent components of the date. The ' + 'mouseover text of the "(?)" icon shows ' + 'the most commonly used codes/symbols. ' + 'The text next to it shows how ' + '"%s" would be shown with the ' + 'specified format.' % exampleStr)) + lblDateFmt.setAlignment(Qt.AlignTop) + fmt = self.main.getPreferredDateFormat() + ttipStr = self.tr('Use any of the following symbols:
') + fmtSymbols = [x[0] + ' = ' + x[1] for x in FORMAT_SYMBOLS] + ttipStr += '
'.join(fmtSymbols) + + fmtSymbols = [x[0] + '~' + x[1] for x in FORMAT_SYMBOLS] + lblStk = QRichLabel('; '.join(fmtSymbols)) + + self.edtDateFormat = QLineEdit() + self.edtDateFormat.setText(fmt) + self.ttipFormatDescr = self.main.createToolTipWidget(ttipStr) + + self.lblDateExample = QRichLabel('', doWrap=False) + self.connect(self.edtDateFormat, SIGNAL('textEdited(QString)'), self.doExampleDate) + self.doExampleDate() + self.btnResetFormat = QPushButton(self.tr("Reset to Default")) + + def doReset(): + self.edtDateFormat.setText(DEFAULT_DATE_FORMAT) + self.doExampleDate() + self.connect(self.btnResetFormat, SIGNAL('clicked()'), doReset) + + # Make a little subframe just for the date format stuff... everything + # fits nicer if I do this... + frmTop = makeHorizFrame([self.lblDateExample, STRETCH, self.ttipFormatDescr]) + frmMid = makeHorizFrame([self.edtDateFormat]) + frmBot = makeHorizFrame([self.btnResetFormat, STRETCH]) + fStack = makeVertFrame([frmTop, frmMid, frmBot, STRETCH]) + lblStk = makeVertFrame([lblDateFmt, lblDateDescr, STRETCH]) + subFrm = makeHorizFrame([lblStk, STRETCH, fStack]) + + + # Save/Cancel Button + self.btnCancel = QPushButton(self.tr("Cancel")) + self.btnAccept = QPushButton(self.tr("Save")) + self.connect(self.btnCancel, SIGNAL('clicked()'), self.reject) + self.connect(self.btnAccept, SIGNAL('clicked()'), self.accept) + + ################################################################ + # User mode selection + self.cmbUsermode = QComboBox() + self.cmbUsermode.clear() + self.cmbUsermode.addItem(self.tr('Standard')) + self.cmbUsermode.addItem(self.tr('Advanced')) + self.cmbUsermode.addItem(self.tr('Expert')) + + self.usermodeInit = self.main.usermode + + if self.main.usermode == USERMODE.Standard: + self.cmbUsermode.setCurrentIndex(0) + elif self.main.usermode == USERMODE.Advanced: + self.cmbUsermode.setCurrentIndex(1) + elif self.main.usermode == USERMODE.Expert: + self.cmbUsermode.setCurrentIndex(2) + + lblUsermode = QRichLabel(self.tr('Armory user mode:')) + self.lblUsermodeDescr = QRichLabel('') + self.setUsermodeDescr() + + self.connect(self.cmbUsermode, SIGNAL('activated(int)'), self.setUsermodeDescr) + + ############################################################### + # Language preferences + self.lblLang = QRichLabel(self.tr('Preferred Language:
')) + self.lblLangDescr = QRichLabel(self.tr( + 'Specify which language you would like Armory to be displayed in.')) + self.cmbLang = QComboBox() + self.cmbLang.clear() + for lang in LANGUAGES: + self.cmbLang.addItem(QLocale(lang).nativeLanguageName() + " (" + lang + ")") + self.cmbLang.setCurrentIndex(LANGUAGES.index(self.main.language)) + self.langInit = self.main.language + + frmLayout = QGridLayout() + + i = 0 + frmLayout.addWidget(HLINE(), i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(frmMgmt, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(HLINE(), i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(frmPaths, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(HLINE(), i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(lblDefaultUriTitle, i, 0) + i += 1 + frmLayout.addWidget(lblDefaultURI, i, 0, 1, 3) + i += 1 + frmLayout.addWidget(frmBtnDefaultURI, i, 0, 1, 3) + i += 1 + frmLayout.addWidget(self.chkAskURIAtStartup, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(HLINE(), i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(subFrm, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(HLINE(), i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(lblMinimizeDescr, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(self.chkMinOnOpen, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(self.chkMinOrClose, i, 0, 1, 3) + + + i += 1 + frmLayout.addWidget(HLINE(), i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(lblNotify, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(self.chkBtcIn, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(self.chkBtcOut, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(self.chkDiscon, i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(self.chkReconn, i, 0, 1, 3) + + + i += 1 + frmLayout.addWidget(HLINE(), i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(lblUsermode, i, 0) + frmLayout.addWidget(QLabel(''), i, 1) + frmLayout.addWidget(self.cmbUsermode, i, 2) + + i += 1 + frmLayout.addWidget(self.lblUsermodeDescr, i, 0, 1, 3) + + + i += 1 + frmLayout.addWidget(HLINE(), i, 0, 1, 3) + + i += 1 + frmLayout.addWidget(self.lblLang, i, 0) + frmLayout.addWidget(QLabel(''), i, 1) + frmLayout.addWidget(self.cmbLang, i, 2) + + i += 1 + frmLayout.addWidget(self.lblLangDescr, i, 0, 1, 3) + + + frmOptions = QFrame() + frmOptions.setLayout(frmLayout) + + self.settingsTab = QTabWidget() + self.settingsTab.addTab(frmOptions, self.tr("General")) + + #FeeChange tab + self.setupExtraTabs() + frmFeeChange = makeVertFrame([\ + self.frmFee, self.frmChange, self.frmAddrType, 'Stretch']) + + self.settingsTab.addTab(frmFeeChange, self.tr("Fee and Address Types")) + + self.scrollOptions = QScrollArea() + self.scrollOptions.setWidget(self.settingsTab) + + + + dlgLayout = QVBoxLayout() + dlgLayout.addWidget(self.scrollOptions) + dlgLayout.addWidget(makeHorizFrame([STRETCH, self.btnCancel, self.btnAccept])) + + self.setLayout(dlgLayout) + + self.setMinimumWidth(650) + self.setWindowTitle(self.tr('Armory Settings')) + + ############################################################################# + def setupExtraTabs(self): + ########## + #fee + + feeByte = self.main.getSettingOrSetDefault('Default_FeeByte', MIN_FEE_BYTE) + txFee = self.main.getSettingOrSetDefault('Default_Fee', MIN_TX_FEE) + adjustFee = self.main.getSettingOrSetDefault('AdjustFee', True) + feeOpt = self.main.getSettingOrSetDefault('FeeOption', DEFAULT_FEE_TYPE) + blocksToConfirm = self.main.getSettingOrSetDefault(\ + "Default_FeeByte_BlocksToConfirm", NBLOCKS_TO_CONFIRM) + + def feeRadio(strArg): + self.radioAutoFee.setChecked(False) + + self.radioFeeByte.setChecked(False) + self.leFeeByte.setEnabled(False) + + self.radioFlatFee.setChecked(False) + self.leFlatFee.setEnabled(False) + + if strArg == 'Auto': + self.radioAutoFee.setChecked(True) + elif strArg == 'FeeByte': + self.radioFeeByte.setChecked(True) + self.leFeeByte.setEnabled(True) + elif strArg == 'FlatFee': + self.radioFlatFee.setChecked(True) + self.leFlatFee.setEnabled(True) + + self.feeOpt = strArg + + def getCallbck(strArg): + def callbck(): + return feeRadio(strArg) + return callbck + + labelFee = QRichLabel(self.tr("Fee
")) + + self.radioAutoFee = QRadioButton(self.tr("Auto fee/byte")) + self.connect(self.radioAutoFee, SIGNAL('clicked()'), getCallbck('Auto')) + self.sliderAutoFee = QSlider(Qt.Horizontal, self) + self.sliderAutoFee.setMinimum(2) + self.sliderAutoFee.setMaximum(6) + self.sliderAutoFee.setValue(blocksToConfirm) + self.lblSlider = QLabel() + + def getLblSliderText(): + blocksToConfirm = str(self.sliderAutoFee.value()) + return self.tr("Blocks to confirm: %s" % blocksToConfirm) + + def setLblSliderText(): + self.lblSlider.setText(getLblSliderText()) + + setLblSliderText() + self.sliderAutoFee.valueChanged.connect(setLblSliderText) + + toolTipAutoFee = self.main.createToolTipWidget(self.tr( + 'Fetch fee/byte from local Bitcoin node. ' + 'Defaults to manual fee/byte on failure.')) + + self.radioFeeByte = QRadioButton(self.tr("Manual fee/byte")) + self.connect(self.radioFeeByte, SIGNAL('clicked()'), getCallbck('FeeByte')) + self.leFeeByte = QLineEdit(str(feeByte)) + toolTipFeeByte = self.main.createToolTipWidget(self.tr('Values in satoshis/byte')) + + self.radioFlatFee = QRadioButton(self.tr("Flat fee")) + self.connect(self.radioFlatFee, SIGNAL('clicked()'), getCallbck('FlatFee')) + self.leFlatFee = QLineEdit(coin2str(txFee, maxZeros=0)) + toolTipFlatFee = self.main.createToolTipWidget(self.tr('Values in BTC')) + + self.checkAdjust = QCheckBox(self.tr("Auto-adjust fee/byte for better privacy")) + self.checkAdjust.setChecked(adjustFee) + feeToolTip = self.main.createToolTipWidget(self.tr( + 'Auto-adjust fee may increase your total fee using the selected fee/byte rate ' + 'as its basis in an attempt to align the amount of digits after the decimal ' + 'point between your spend values and change value.' + '

' + 'The purpose of this obfuscation technique is to make the change output ' + 'less obvious. ' + '

' + 'The auto-adjust fee feature only applies to fee/byte options ' + 'and does not inflate your fee by more that 10% of its original value.')) + + frmFeeLayout = QGridLayout() + frmFeeLayout.addWidget(labelFee, 0, 0, 1, 1) + + frmAutoFee = makeHorizFrame([self.radioAutoFee, self.lblSlider, toolTipAutoFee]) + frmFeeLayout.addWidget(frmAutoFee, 1, 0, 1, 1) + frmFeeLayout.addWidget(self.sliderAutoFee, 2, 0, 1, 2) + + frmFeeByte = makeHorizFrame([self.radioFeeByte, self.leFeeByte, \ + toolTipFeeByte, STRETCH, STRETCH]) + frmFeeLayout.addWidget(frmFeeByte, 3, 0, 1, 1) + + frmFlatFee = makeHorizFrame([self.radioFlatFee, self.leFlatFee, \ + toolTipFlatFee, STRETCH, STRETCH]) + frmFeeLayout.addWidget(frmFlatFee, 4, 0, 1, 1) + + frmCheckAdjust = makeHorizFrame([self.checkAdjust, feeToolTip, STRETCH]) + frmFeeLayout.addWidget(frmCheckAdjust, 5, 0, 1, 2) + + feeRadio(feeOpt) + + self.frmFee = QFrame() + self.frmFee.setFrameStyle(STYLE_RAISED) + self.frmFee.setLayout(frmFeeLayout) + + ######### + #change + + def setChangeType(changeType): + self.changeType = changeType + + self.changeType = self.main.getSettingOrSetDefault('Default_ChangeType', self.wlt.getDefaultAddressType()) + self.changeTypeFrame = AddressLabelFrame(self.main, setChangeType, self.wlt.getAddressTypes(), self.changeType) + + def changeRadio(strArg): + self.radioAutoChange.setChecked(False) + self.radioForce.setChecked(False) + self.changeTypeFrame.getFrame().setEnabled(False) + + if strArg == 'Auto': + self.radioAutoChange.setChecked(True) + self.changeType = 'Auto' + elif strArg == 'Force': + self.radioForce.setChecked(True) + self.changeTypeFrame.getFrame().setEnabled(True) + self.changeType = self.changeTypeFrame.getType() + else: + self.changeTypeFrame.setType(strArg) + self.radioForce.setChecked(True) + self.changeTypeFrame.getFrame().setEnabled(True) + self.changeType = self.changeTypeFrame.getType() + + def changeCallbck(strArg): + def callbck(): + return changeRadio(strArg) + return callbck + + + labelChange = QRichLabel(self.tr("Change Address Type
")) + + self.radioAutoChange = QRadioButton(self.tr("Auto change")) + self.connect(self.radioAutoChange, SIGNAL('clicked()'), changeCallbck('Auto')) + toolTipAutoChange = self.main.createToolTipWidget(self.tr( + "Change address type will match the address type of recipient " + "addresses.
" + + "Favors P2SH when recipients are heterogenous.
" + + "Will create nested SegWit change if inputs are SegWit and " + "recipient are P2SH.

" + + "Pre 0.96 Armory cannot spend from P2SH address types" + )) + + self.radioForce = QRadioButton(self.tr("Force a script type:")) + self.connect(self.radioForce, SIGNAL('clicked()'), changeCallbck('Force')) + + changeRadio(self.changeType) + + frmChangeLayout = QGridLayout() + frmChangeLayout.addWidget(labelChange, 0, 0, 1, 1) + + frmAutoChange = makeHorizFrame([self.radioAutoChange, \ + toolTipAutoChange, STRETCH]) + frmChangeLayout.addWidget(frmAutoChange, 1, 0, 1, 1) + + frmForce = makeHorizFrame([self.radioForce, self.changeTypeFrame.getFrame()]) + frmChangeLayout.addWidget(frmForce, 2, 0, 1, 1) + + self.frmChange = QFrame() + self.frmChange.setFrameStyle(STYLE_RAISED) + self.frmChange.setLayout(frmChangeLayout) + + ######### + #receive addr type + + labelAddrType = QRichLabel(self.tr("Preferred Receive Address Type")) + + def setAddrType(addrType): + self.addrType = addrType + + self.addrType = self.main.getSettingOrSetDefault('Default_ReceiveType', self.wlt.getDefaultAddressType()) + self.addrTypeFrame = AddressLabelFrame(self.main, setAddrType, self.wlt.getAddressTypes(), self.addrType) + self.addrTypeFrame.setType(self.addrType) + + frmAddrLayout = QGridLayout() + frmAddrLayout.addWidget(labelAddrType, 0, 0, 1, 1) + + frmAddrTypeSelect = makeHorizFrame([self.addrTypeFrame.getFrame()]) + + frmAddrLayout.addWidget(frmAddrTypeSelect, 2, 0, 1, 1) + + self.frmAddrType = QFrame() + self.frmAddrType.setFrameStyle(STYLE_RAISED) + self.frmAddrType.setLayout(frmAddrLayout) + + ############################################################################# + def accept(self, *args): + + if self.chkManageSatoshi.isChecked(): + # Check valid path is supplied for bitcoin installation + pathExe = str(self.edtSatoshiExePath.text()).strip() + if len(pathExe) > 0: + if not os.path.exists(pathExe): + exeName = 'bitcoin-qt.exe' if OS_WINDOWS else 'bitcoin-qt' + QMessageBox.warning(self, self.tr('Invalid Path'),self.tr( + 'The path you specified for the Bitcoin software installation ' + 'does not exist. Please select the directory that contains %s ' + 'or leave it blank to have Armory search the default location ' + 'for your operating system' % exeName), QMessageBox.Ok) + return + if os.path.isfile(pathExe): + pathExe = os.path.dirname(pathExe) + self.main.writeSetting('SatoshiExe', pathExe) + else: + self.main.settings.delete('SatoshiExe') + + # Check path is supplied for bitcoind home directory + pathHome = str(self.edtSatoshiHomePath.text()).strip() + if len(pathHome) > 0: + if not os.path.exists(pathHome): + QMessageBox.warning(self, self.tr('Invalid Path'), self.tr( + 'The path you specified for the Bitcoin software home directory ' + 'does not exist. Only specify this directory if you use a ' + 'non-standard "-datadir=" option when running Bitcoin Core or ' + 'bitcoind. If you leave this field blank, the following ' + 'path will be used:

%s' % BTC_HOME_DIR), QMessageBox.Ok) + return + self.main.writeSetting('SatoshiDatadir', pathHome) + else: + self.main.settings.delete('SatoshiDatadir') + + # Check path is supplied for armory db directory + pathDbdir = str(self.edtArmoryDbdir.text()).strip() + if len(pathDbdir) > 0: + if not os.path.exists(pathDbdir): + QMessageBox.warning(self, self.tr('Invalid Path'), self.tr( + 'The path you specified for Armory\'s database directory ' + 'does not exist. Only specify this directory if you want ' + 'Armory to save its local database to a custom path. ' + 'If you leave this field blank, the following ' + 'path will be used:

%s' % ARMORY_DB_DIR), QMessageBox.Ok) + return + self.main.writeSetting('ArmoryDbdir', pathDbdir) + else: + self.main.settings.delete('ArmoryDbdir') + + + self.main.writeSetting('ManageSatoshi', self.chkManageSatoshi.isChecked()) + + # Reset the DNAA flag as needed + askuriDNAA = self.chkAskURIAtStartup.isChecked() + self.main.writeSetting('DNAA_DefaultApp', not askuriDNAA) + + if not self.main.setPreferredDateFormat(str(self.edtDateFormat.text())): + return + + if not self.usermodeInit == self.cmbUsermode.currentIndex(): + self.main.setUserMode(self.cmbUsermode.currentIndex()) + + if not self.langInit == self.cmbLang.currentText()[-3:-1]: + self.main.setLang(LANGUAGES[self.cmbLang.currentIndex()]) + + if self.chkMinOrClose.isChecked(): + self.main.writeSetting('MinimizeOrClose', 'Minimize') + else: + self.main.writeSetting('MinimizeOrClose', 'Close') + + self.main.writeSetting('MinimizeOnOpen', self.chkMinOnOpen.isChecked()) + + # self.main.writeSetting('LedgDisplayFee', self.chkInclFee.isChecked()) + self.main.writeSetting('NotifyBtcIn', self.chkBtcIn.isChecked()) + self.main.writeSetting('NotifyBtcOut', self.chkBtcOut.isChecked()) + self.main.writeSetting('NotifyDiscon', self.chkDiscon.isChecked()) + self.main.writeSetting('NotifyReconn', self.chkReconn.isChecked()) + + + #fee + self.main.writeSetting('FeeOption', self.feeOpt) + self.main.writeSetting('Default_FeeByte', str(self.leFeeByte.text())) + self.main.writeSetting('Default_Fee', str2coin(str(self.leFlatFee.text()))) + self.main.writeSetting('AdjustFee', self.checkAdjust.isChecked()) + self.main.writeSetting('Default_FeeByte_BlocksToConfirm', + self.sliderAutoFee.value()) + + #change + self.main.writeSetting('Default_ChangeType', self.changeType) + + #addr type + self.main.writeSetting('Default_ReceiveType', self.addrType) + DEFAULT_ADDR_TYPE = self.addrType + + try: + self.main.createCombinedLedger() + except: + pass + super(DlgSettings, self).accept(*args) + + + ############################################################################# + def setUsermodeDescr(self): + strDescr = '' + modeIdx = self.cmbUsermode.currentIndex() + if modeIdx == USERMODE.Standard: + strDescr += \ + self.tr('"Standard" is for users that only need the core set of features ' + 'to send and receive bitcoins. This includes maintaining multiple ' + 'wallets, wallet encryption, and the ability to make backups ' + 'of your wallets.') + elif modeIdx == USERMODE.Advanced: + strDescr += \ + self.tr('"Advanced" mode provides ' + 'extra Armory features such as private key ' + 'importing & sweeping, message signing, and the offline wallet ' + 'interface. But, with advanced features come advanced risks...') + elif modeIdx == USERMODE.Expert: + strDescr += \ + self.tr('"Expert" mode is similar to "Advanced" but includes ' + 'access to lower-level info about transactions, scripts, keys ' + 'and network protocol. Most extra functionality is geared ' + 'towards Bitcoin software developers.') + self.lblUsermodeDescr.setText(strDescr) + + + ############################################################################# + def doExampleDate(self, qstr=None): + fmtstr = str(self.edtDateFormat.text()) + try: + self.lblDateExample.setText(self.tr('Sample: ') + unixTimeToFormatStr(self.exampleUnixTime, fmtstr)) + self.isValidFormat = True + except: + self.lblDateExample.setText(self.tr('Sample: [[invalid date format]]')) + self.isValidFormat = False + + ############################################################################# + def clickChkManage(self): + self.edtSatoshiExePath.setEnabled(self.chkManageSatoshi.isChecked()) + self.btnSetExe.setEnabled(self.chkManageSatoshi.isChecked()) \ No newline at end of file diff --git a/qtdialogs/DlgShowKeyList.py b/qtdialogs/DlgShowKeyList.py index 14d61cff0..0381789ed 100644 --- a/qtdialogs/DlgShowKeyList.py +++ b/qtdialogs/DlgShowKeyList.py @@ -6,7 +6,7 @@ # # ################################################################################ -from qtdialogs.qtdefines import ArmoryDialog +from qtdialogs.ArmoryDialog import ArmoryDialog ################################################################################ class DlgShowKeyList(ArmoryDialog): @@ -326,4 +326,4 @@ def accept(self): def reject(self): self.cleanup() - super(DlgShowKeyList, self).reject() + super(DlgShowKeyList, self).reject() \ No newline at end of file diff --git a/qtdialogs/DlgUniversalRestoreSelect.py b/qtdialogs/DlgUniversalRestoreSelect.py new file mode 100644 index 000000000..f0f126c45 --- /dev/null +++ b/qtdialogs/DlgUniversalRestoreSelect.py @@ -0,0 +1,106 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.qtdefines import HLINE, QRichLabel +from armoryengine.ArmoryUtils import LOGINFO +from PySide2.QtWidgets import QCheckBox, QDialogButtonBox, QPushButton, QVBoxLayout +from PySide2.QtCore import SIGNAL + +################################################################################ +class DlgUniversalRestoreSelect(ArmoryDialog): + + ############################################################################# + def __init__(self, parent, main): + super(DlgUniversalRestoreSelect, self).__init__(parent, main) + + + lblDescrTitle = QRichLabel(self.tr('Restore Wallet from Backup')) + lblDescr = QRichLabel(self.tr('You can restore any kind of backup ever created by Armory using ' + 'one of the options below. If you have a list of private keys ' + 'you should open the target wallet and select "Import/Sweep ' + 'Private Keys."')) + + self.rdoSingle = QRadioButton(self.tr('Single-Sheet Backup (printed)')) + self.rdoFragged = QRadioButton(self.tr('Fragmented Backup (incl. mix of paper and files)')) + self.rdoDigital = QRadioButton(self.tr('Import digital backup or watching-only wallet')) + self.rdoWOData = QRadioButton(self.tr('Import watching-only wallet data')) + self.chkTest = QCheckBox(self.tr('This is a test recovery to make sure my backup works')) + btngrp = QButtonGroup(self) + btngrp.addButton(self.rdoSingle) + btngrp.addButton(self.rdoFragged) + btngrp.addButton(self.rdoDigital) + btngrp.addButton(self.rdoWOData) + btngrp.setExclusive(True) + + self.rdoSingle.setChecked(True) + self.connect(self.rdoSingle, SIGNAL("clicked()"), self.clickedRadio) + self.connect(self.rdoFragged, SIGNAL("clicked()"), self.clickedRadio) + self.connect(self.rdoDigital, SIGNAL("clicked()"), self.clickedRadio) + self.connect(self.rdoWOData, SIGNAL("clicked()"), self.clickedRadio) + + self.btnOkay = QPushButton(self.tr('Continue')) + self.btnCancel = QPushButton(self.tr('Cancel')) + buttonBox = QDialogButtonBox() + buttonBox.addButton(self.btnOkay, QDialogButtonBox.AcceptRole) + buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) + self.connect(self.btnOkay, SIGNAL("clicked()"), self.clickedOkay) + self.connect(self.btnCancel, SIGNAL("clicked()"), self.reject) + + + layout = QVBoxLayout() + layout.addWidget(lblDescrTitle) + layout.addWidget(lblDescr) + layout.addWidget(HLINE()) + layout.addWidget(self.rdoSingle) + layout.addWidget(self.rdoFragged) + layout.addWidget(self.rdoDigital) + layout.addWidget(self.rdoWOData) + layout.addWidget(HLINE()) + layout.addWidget(self.chkTest) + layout.addWidget(buttonBox) + self.setLayout(layout) + self.setMinimumWidth(450) + + def clickedRadio(self): + if self.rdoDigital.isChecked(): + self.chkTest.setChecked(False) + self.chkTest.setEnabled(False) + else: + self.chkTest.setEnabled(True) + + def clickedOkay(self): + # ## Test backup option + + doTest = self.chkTest.isChecked() + + if self.rdoSingle.isChecked(): + self.accept() + dlg = DlgRestoreSingle(self.parent, self.main, doTest) + if dlg.exec_(): + self.main.addWalletToApplication(dlg.newWallet) + LOGINFO('Wallet Restore Complete!') + + elif self.rdoFragged.isChecked(): + self.accept() + dlg = DlgRestoreFragged(self.parent, self.main, doTest) + if dlg.exec_(): + self.main.addWalletToApplication(dlg.newWallet) + LOGINFO('Wallet Restore Complete!') + elif self.rdoDigital.isChecked(): + self.main.execGetImportWltName() + self.accept() + elif self.rdoWOData.isChecked(): + # Attempt to restore the root public key & chain code for a wallet. + # When done, ask for a wallet rescan. + self.accept() + dlg = DlgRestoreWOData(self.parent, self.main, doTest) + if dlg.exec_(): + LOGINFO('Watching-Only Wallet Restore Complete! Will ask for a' \ + 'rescan.') + self.main.addWalletToApplication(dlg.newWallet) \ No newline at end of file diff --git a/qtdialogs/DlgUnlockWallet.py b/qtdialogs/DlgUnlockWallet.py index 3a44605fb..b97270b44 100644 --- a/qtdialogs/DlgUnlockWallet.py +++ b/qtdialogs/DlgUnlockWallet.py @@ -12,7 +12,8 @@ QPushButton, QLabel, QLineEdit, QDialogButtonBox, QButtonGroup, \ QRadioButton, QSizePolicy, QLayout -from qtdialogs.qtdefines import ArmoryDialog, makeHorizFrame +from qtdialogs.qtdefines import makeHorizFrame +from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.qtdialogs import STRETCH, MIN_PASSWD_WIDTH, LetterButton ################################################################################ @@ -308,4 +309,4 @@ def reject(self): self.edtPasswd.setText('') if self.parent != None: self.parent.cleanupPrompt(self.promptId) - super(ArmoryDialog, self).reject() + super(ArmoryDialog, self).reject() \ No newline at end of file diff --git a/qtdialogs/DlgWalletDetails.py b/qtdialogs/DlgWalletDetails.py index 91068b889..733eb16f5 100644 --- a/qtdialogs/DlgWalletDetails.py +++ b/qtdialogs/DlgWalletDetails.py @@ -16,11 +16,14 @@ from armorycolors import htmlColor from ui.TreeViewGUI import AddressTreeModel -from qtdialogs.qtdefines import ArmoryDialog, USERMODE, determineWalletType, \ +from qtdialogs.qtdefines import USERMODE, determineWalletType, \ relaxedSizeNChar, relaxedSizeStr, QLabelButton, STYLE_SUNKEN, STYLE_NONE, \ - QRichLabel, makeHorizFrame, MsgBoxWithDNAA, restoreTableView, WLTTYPES, \ + QRichLabel, makeHorizFrame, restoreTableView, WLTTYPES, \ WLTFIELDS, tightSizeStr, saveTableView +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA + from qtdialogs.qtdialogs import STRETCH, LoadingDisp from qtdialogs.DlgNewAddress import \ DlgNewAddressDisp, ShowRecvCoinsWarningIfNecessary @@ -995,4 +998,4 @@ def __init__(self, wltID, parent=None, main=None): self.connect(bbox, SIGNAL('rejected()'), self.reject) layout.addWidget(bbox, 4, 0) self.setLayout(layout) - self.setWindowTitle(self.tr('Set Wallet Owner')) + self.setWindowTitle(self.tr('Set Wallet Owner')) \ No newline at end of file diff --git a/qtdialogs/DlgWalletSelect.py b/qtdialogs/DlgWalletSelect.py index 6a09007b9..81b6e11b2 100644 --- a/qtdialogs/DlgWalletSelect.py +++ b/qtdialogs/DlgWalletSelect.py @@ -9,7 +9,8 @@ from PySide2.QtWidgets import QMessageBox, QPushButton, QDialogButtonBox, \ QVBoxLayout -from qtdialogs.qtdefines import ArmoryDialog, HORIZONTAL +from qtdialogs.qtdefines import HORIZONTAL +from qtdialogs.ArmoryDialog import ArmoryDialog from ui.WalletFrames import SelectWalletFrame ################################################################################ @@ -58,4 +59,4 @@ def __init__(self, parent=None, main=None, title='Select Wallet:', descr='', def selectWallet(self, wlt, isDoubleClick=False): self.selectedID = wlt.uniqueIDB58 if isDoubleClick: - self.accept() + self.accept() \ No newline at end of file diff --git a/qtdialogs/DlgWltRecoverWallet.py b/qtdialogs/DlgWltRecoverWallet.py new file mode 100644 index 000000000..25c269a4b --- /dev/null +++ b/qtdialogs/DlgWltRecoverWallet.py @@ -0,0 +1,229 @@ +################################################################################ +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +################################################################################ + +import os + +from PySide2.QtCore import SIGNAL +from PySide2.QtWidgets import QFileDialog, QFrame, QGridLayout, QHBoxLayout, QLabel, QLayout, QLineEdit, QPushButton, QRadioButton + +from armorycolors import htmlColor +from armoryengine.ArmoryUtils import ARMORY_HOME_DIR, LOGINFO, OS_MACOSX +#from armoryengine.PyBtcWalletRecovery import RECOVERMODE +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.DlgCorruptWallet import DlgCorruptWallet +from qtdialogs.DlgWalletSelect import DlgWalletSelect +from qtdialogs.qtdefines import GETFONT, QRichLabel, STYLE_SUNKEN, makeHorizFrame, tightSizeNChar + +############################################################################### +class DlgWltRecoverWallet(ArmoryDialog): + def __init__(self, parent=None, main=None): + super(DlgWltRecoverWallet, self).__init__(parent, main) + + self.edtWalletPath = QLineEdit() + self.edtWalletPath.setFont(GETFONT('Fixed', 9)) + edtW,edtH = tightSizeNChar(self.edtWalletPath, 50) + self.edtWalletPath.setMinimumWidth(edtW) + self.btnWalletPath = QPushButton(self.tr('Browse File System')) + + self.connect(self.btnWalletPath, SIGNAL('clicked()'), self.selectFile) + + lblDesc = QRichLabel(self.tr( + 'Wallet Recovery Tool: ' + '
' + 'This tool will recover data from damaged or inconsistent ' + 'wallets. Specify a wallet file and Armory will analyze the ' + 'wallet and fix any errors with it. ' + '

' + 'If any problems are found with the specified ' + 'wallet, Armory will provide explanation and instructions to ' + 'transition to a new wallet.' % htmlColor('TextWarn'))) + lblDesc.setScaledContents(True) + + lblWalletPath = QRichLabel(self.tr('Wallet Path:')) + + self.selectedWltID = None + + def doWltSelect(): + dlg = DlgWalletSelect(self, self.main, self.tr('Select Wallet...'), '') + if dlg.exec_(): + self.selectedWltID = dlg.selectedID + wlt = self.parent.walletMap[dlg.selectedID] + self.edtWalletPath.setText(wlt.walletPath) + + self.btnWltSelect = QPushButton(self.tr("Select Loaded Wallet")) + self.connect(self.btnWltSelect, SIGNAL("clicked()"), doWltSelect) + + layoutMgmt = QGridLayout() + wltSltQF = QFrame() + wltSltQF.setFrameStyle(STYLE_SUNKEN) + + layoutWltSelect = QGridLayout() + layoutWltSelect.addWidget(lblWalletPath, 0,0, 1, 1) + layoutWltSelect.addWidget(self.edtWalletPath, 0,1, 1, 3) + layoutWltSelect.addWidget(self.btnWltSelect, 1,0, 1, 2) + layoutWltSelect.addWidget(self.btnWalletPath, 1,2, 1, 2) + layoutWltSelect.setColumnStretch(0, 0) + layoutWltSelect.setColumnStretch(1, 1) + layoutWltSelect.setColumnStretch(2, 1) + layoutWltSelect.setColumnStretch(3, 0) + + wltSltQF.setLayout(layoutWltSelect) + + layoutMgmt.addWidget(makeHorizFrame([lblDesc], STYLE_SUNKEN), 0,0, 2,4) + layoutMgmt.addWidget(wltSltQF, 2, 0, 3, 4) + + self.rdbtnStripped = QRadioButton('', parent=self) + self.connect(self.rdbtnStripped, SIGNAL('event()'), self.rdClicked) + lblStripped = QLabel(self.tr('Stripped Recovery
Only attempts to \ + recover the wallet\'s rootkey and chaincode')) + layout_StrippedH = QGridLayout() + layout_StrippedH.addWidget(self.rdbtnStripped, 0, 0, 1, 1) + layout_StrippedH.addWidget(lblStripped, 0, 1, 2, 19) + + self.rdbtnBare = QRadioButton('') + lblBare = QLabel(self.tr('Bare Recovery
Attempts to recover all private key related data')) + layout_BareH = QGridLayout() + layout_BareH.addWidget(self.rdbtnBare, 0, 0, 1, 1) + layout_BareH.addWidget(lblBare, 0, 1, 2, 19) + + self.rdbtnFull = QRadioButton('') + self.rdbtnFull.setChecked(True) + lblFull = QLabel(self.tr('Full Recovery
Attempts to recover as much data as possible')) + layout_FullH = QGridLayout() + layout_FullH.addWidget(self.rdbtnFull, 0, 0, 1, 1) + layout_FullH.addWidget(lblFull, 0, 1, 2, 19) + + self.rdbtnCheck = QRadioButton('') + lblCheck = QLabel(self.tr('Consistency Check
Checks wallet consistency. Works with both full and watch only
wallets.' + ' Unlocking of encrypted wallets is not mandatory')) + layout_CheckH = QGridLayout() + layout_CheckH.addWidget(self.rdbtnCheck, 0, 0, 1, 1) + layout_CheckH.addWidget(lblCheck, 0, 1, 3, 19) + + + layoutMode = QGridLayout() + layoutMode.addLayout(layout_StrippedH, 0, 0, 2, 4) + layoutMode.addLayout(layout_BareH, 2, 0, 2, 4) + layoutMode.addLayout(layout_FullH, 4, 0, 2, 4) + layoutMode.addLayout(layout_CheckH, 6, 0, 3, 4) + + + #self.rdnGroup = QButtonGroup() + #self.rdnGroup.addButton(self.rdbtnStripped) + #self.rdnGroup.addButton(self.rdbtnBare) + #self.rdnGroup.addButton(self.rdbtnFull) + #self.rdnGroup.addButton(self.rdbtnCheck) + + + layoutMgmt.addLayout(layoutMode, 5, 0, 9, 4) + """ + wltModeQF = QFrame() + wltModeQF.setFrameStyle(STYLE_SUNKEN) + wltModeQF.setLayout(layoutMode) + + layoutMgmt.addWidget(wltModeQF, 5, 0, 9, 4) + wltModeQF.setVisible(False) + + + btnShowAllOpts = QLabelButton(self.tr("All Recovery Options>>>")) + frmBtn = makeHorizFrame(['Stretch', btnShowAllOpts, 'Stretch'], STYLE_SUNKEN) + layoutMgmt.addWidget(frmBtn, 5, 0, 9, 4) + + def expandOpts(): + wltModeQF.setVisible(True) + btnShowAllOpts.setVisible(False) + self.connect(btnShowAllOpts, SIGNAL('clicked()'), expandOpts) + + if not self.main.usermode==USERMODE.Expert: + frmBtn.setVisible(False) + """ + + self.btnRecover = QPushButton(self.tr('Recover')) + self.btnCancel = QPushButton(self.tr('Cancel')) + layout_btnH = QHBoxLayout() + layout_btnH.addWidget(self.btnRecover, 1) + layout_btnH.addWidget(self.btnCancel, 1) + + def updateBtn(qstr): + if os.path.exists(str(qstr).strip()): + self.btnRecover.setEnabled(True) + self.btnRecover.setToolTip('') + else: + self.btnRecover.setEnabled(False) + self.btnRecover.setToolTip(self.tr('The entered path does not exist')) + + updateBtn('') + self.connect(self.edtWalletPath, SIGNAL('textChanged(QString)'), updateBtn) + + + layoutMgmt.addLayout(layout_btnH, 14, 1, 1, 2) + + self.connect(self.btnRecover, SIGNAL('clicked()'), self.accept) + self.connect(self.btnCancel , SIGNAL('clicked()'), self.reject) + + self.setLayout(layoutMgmt) + self.layout().setSizeConstraint(QLayout.SetFixedSize) + self.setWindowTitle(self.tr('Wallet Recovery Tool')) + self.setMinimumWidth(550) + + def rdClicked(self): + # TODO: Why does this do nohting? Was it a stub that was forgotten? + LOGINFO("clicked") + + def promptWalletRecovery(self): + """ + Prompts the user with a window asking for wallet path and recovery mode. + Proceeds to Recover the wallet. Prompt for password if the wallet is locked + """ +# if self.exec_(): +# path = str(self.edtWalletPath.text()) +# mode = RECOVERMODE.Bare +# if self.rdbtnStripped.isChecked(): +# mode = RECOVERMODE.Stripped +# elif self.rdbtnFull.isChecked(): +# mode = RECOVERMODE.Full +# elif self.rdbtnCheck.isChecked(): +# mode = RECOVERMODE.Check +# +# if mode==RECOVERMODE.Full and self.selectedWltID: +# # Funnel all standard, full recovery operations through the +# # inconsistent-wallet-dialog. +# wlt = self.main.walletMap[self.selectedWltID] +# dlgRecoveryUI = DlgCorruptWallet(wlt, [], self.main, self, False) +# dlgRecoveryUI.exec_(dlgRecoveryUI.doFixWallets()) +# else: +# # This is goatpig's original behavior - preserved for any +# # non-loaded wallets or non-full recovery operations. +# if self.selectedWltID: +# wlt = self.main.walletMap[self.selectedWltID] +# else: +# wlt = path +# +# dlgRecoveryUI = DlgCorruptWallet(wlt, [], self.main, self, False) +# dlgRecoveryUI.exec_(dlgRecoveryUI.ProcessWallet(mode)) +# else: +# return False + return False + + def selectFile(self): + # Had to reimplement the path selection here, because the way this was + # implemented doesn't let me access self.main.getFileLoad + ftypes = self.tr('Wallet files (*.wallet);; All files (*)') + if not OS_MACOSX: + pathSelect = str(QFileDialog.getOpenFileName(self, \ + self.tr('Recover Wallet'), \ + ARMORY_HOME_DIR, \ + ftypes)) + else: + pathSelect = str(QFileDialog.getOpenFileName(self, \ + self.tr('Recover Wallet'), \ + ARMORY_HOME_DIR, \ + ftypes, \ + options=QFileDialog.DontUseNativeDialog)) + + self.edtWalletPath.setText(pathSelect) \ No newline at end of file diff --git a/qtdialogs/MsgBoxCustom.py b/qtdialogs/MsgBoxCustom.py new file mode 100644 index 000000000..c2760206a --- /dev/null +++ b/qtdialogs/MsgBoxCustom.py @@ -0,0 +1,100 @@ +############################################################################### +# # +# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +############################################################################### + +from PySide2.QtCore import Qt, SIGNAL +from PySide2.QtWidgets import QLabel, QDialogButtonBox, QPushButton, \ + QSpacerItem, QGridLayout, QSizePolicy +from PySide2.QtGui import QPixmap + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.qtdefines import MSGBOX, tightSizeNChar + +################################################################################ +# The optionalMsg argument is not word wrapped so the caller is responsible for limiting +# the length of the longest line in the optionalMsg +def MsgBoxCustom(wtype, title, msg, wCancel=False, yesStr=None, noStr=None, + optionalMsg=None): + """ + Creates a message box with custom button text and icon + """ + + class dlgWarn(ArmoryDialog): + def __init__(self, dtype, dtitle, wmsg, withCancel=False, yesStr=None, noStr=None): + super(dlgWarn, self).__init__(None) + + msgIcon = QLabel() + fpix = '' + if dtype==MSGBOX.Good: + fpix = './img/MsgBox_good48.png' + if dtype==MSGBOX.Info: + fpix = './img/MsgBox_info48.png' + if dtype==MSGBOX.Question: + fpix = './img/MsgBox_question64.png' + if dtype==MSGBOX.Warning: + fpix = './img/MsgBox_warning48.png' + if dtype==MSGBOX.Critical: + fpix = './img/MsgBox_critical64.png' + if dtype==MSGBOX.Error: + fpix = './img/MsgBox_error64.png' + + + if len(fpix)>0: + msgIcon.setPixmap(QPixmap(fpix)) + msgIcon.setAlignment(Qt.AlignHCenter | Qt.AlignTop) + + lblMsg = QLabel(msg) + lblMsg.setTextFormat(Qt.RichText) + lblMsg.setWordWrap(True) + lblMsg.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + lblMsg.setOpenExternalLinks(True) + w,h = tightSizeNChar(lblMsg, 70) + lblMsg.setMinimumSize( w, 3.2*h ) + buttonbox = QDialogButtonBox() + + if dtype==MSGBOX.Question: + if not yesStr: yesStr = self.tr('&Yes') + if not noStr: noStr = self.tr('&No') + btnYes = QPushButton(yesStr) + btnNo = QPushButton(noStr) + self.connect(btnYes, SIGNAL('clicked()'), self.accept) + self.connect(btnNo, SIGNAL('clicked()'), self.reject) + buttonbox.addButton(btnYes,QDialogButtonBox.AcceptRole) + buttonbox.addButton(btnNo, QDialogButtonBox.RejectRole) + else: + cancelStr = self.tr('&Cancel') if (noStr is not None or withCancel) else '' + yesStr = self.tr('&OK') if (yesStr is None) else yesStr + btnOk = QPushButton(yesStr) + btnCancel = QPushButton(cancelStr) + self.connect(btnOk, SIGNAL('clicked()'), self.accept) + self.connect(btnCancel, SIGNAL('clicked()'), self.reject) + buttonbox.addButton(btnOk, QDialogButtonBox.AcceptRole) + if cancelStr: + buttonbox.addButton(btnCancel, QDialogButtonBox.RejectRole) + + spacer = QSpacerItem(20, 10, QSizePolicy.Fixed, QSizePolicy.Expanding) + + layout = QGridLayout() + layout.addItem( spacer, 0,0, 1,2) + layout.addWidget(msgIcon, 1,0, 1,1) + layout.addWidget(lblMsg, 1,1, 1,1) + if optionalMsg: + optionalTextLabel = QLabel(optionalMsg) + optionalTextLabel.setTextFormat(Qt.RichText) + optionalTextLabel.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + w,h = tightSizeNChar(optionalTextLabel, 70) + optionalTextLabel.setMinimumSize( w, 3.2*h ) + layout.addWidget(optionalTextLabel, 2,0,1,2) + layout.addWidget(buttonbox, 3,0, 1,2) + layout.setSpacing(20) + self.setLayout(layout) + self.setWindowTitle(dtitle) + + dlg = dlgWarn(wtype, title, msg, wCancel, yesStr, noStr) + result = dlg.exec_() + + return result \ No newline at end of file diff --git a/qtdialogs/MsgBoxWithDNAA.py b/qtdialogs/MsgBoxWithDNAA.py new file mode 100644 index 000000000..11f5cd3b5 --- /dev/null +++ b/qtdialogs/MsgBoxWithDNAA.py @@ -0,0 +1,103 @@ +#! /usr/bin/python +# -*- coding: UTF-8 -*- +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-17, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## + +from PySide2.QtCore import Qt +from PySide2.QtWidgets import QLabel, QMessageBox, QPushButton, QDialogButtonBox, \ + QCheckBox, QSpacerItem, QSizePolicy, QGridLayout +from PySide2.QtGui import QPixmap + +from qtdialogs.qtdefines import MSGBOX, tightSizeNChar +from qtdialogs.ArmoryDialog import ArmoryDialog + +def MsgBoxWithDNAA(parent, main, wtype, title, msg, dnaaMsg, wCancel=False, \ + yesStr='Yes', noStr='No', dnaaStartChk=False): + """ + Creates a warning/question/critical dialog, but with a "Do not ask again" + checkbox. Will return a pair (response, DNAA-is-checked) + """ + + class dlgWarn(ArmoryDialog): + def __init__(self, parent, main, dtype, dtitle, wmsg, dmsg=None, withCancel=False): + super(dlgWarn, self).__init__(parent, main) + + msgIcon = QLabel() + fpix = '' + if dtype==MSGBOX.Info: + fpix = './img/MsgBox_info48.png' + if not dmsg: dmsg = self.tr('Do not show this message again') + if dtype==MSGBOX.Question: + fpix = './img/MsgBox_question64.png' + if not dmsg: dmsg = self.tr('Do not ask again') + if dtype==MSGBOX.Warning: + fpix = './img/MsgBox_warning48.png' + if not dmsg: dmsg = self.tr('Do not show this warning again') + if dtype==MSGBOX.Critical: + fpix = './img/MsgBox_critical64.png' + if not dmsg: dmsg = None # should always show crits + if dtype==MSGBOX.Error: + fpix = './img/MsgBox_error64.png' + if not dmsg: dmsg = None # should always show errors + + + if len(fpix)>0: + msgIcon.setPixmap(QPixmap(fpix)) + msgIcon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + + self.chkDnaa = QCheckBox(dmsg) + self.chkDnaa.setChecked(dnaaStartChk) + lblMsg = QLabel(msg) + lblMsg.setTextFormat(Qt.RichText) + lblMsg.setWordWrap(True) + lblMsg.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + w,h = tightSizeNChar(lblMsg, 50) + lblMsg.setMinimumSize( w, 3.2*h ) + lblMsg.setOpenExternalLinks(True) + + buttonbox = QDialogButtonBox() + + if dtype==MSGBOX.Question: + btnYes = QPushButton(yesStr) + btnNo = QPushButton(noStr) + btnYes.clicked.connect(self.accept) + btnNo.clicked.connect(self.reject) + buttonbox.addButton(btnYes,QDialogButtonBox.AcceptRole) + buttonbox.addButton(btnNo, QDialogButtonBox.RejectRole) + else: + btnOk = QPushButton('Ok') + btnOk.clicked.connect(self.accept) + buttonbox.addButton(btnOk, QDialogButtonBox.AcceptRole) + if withCancel: + btnCancel = QPushButton('Cancel') + btnCancel.clicked.connect(self.reject) + buttonbox.addButton(btnCancel, QDialogButtonBox.RejectRole) + + + spacer = QSpacerItem(20, 10, QSizePolicy.Fixed, QSizePolicy.Expanding) + + + layout = QGridLayout() + layout.addItem( spacer, 0,0, 1,2) + layout.addWidget(msgIcon, 1,0, 1,1) + layout.addWidget(lblMsg, 1,1, 1,1) + layout.addWidget(self.chkDnaa, 2,0, 1,2) + layout.addWidget(buttonbox, 3,0, 1,2) + layout.setSpacing(20) + self.setLayout(layout) + self.setWindowTitle(dtitle) + + + dlg = dlgWarn(parent, main, wtype, title, msg, dnaaMsg, wCancel) + result = dlg.exec_() + + return (result, dlg.chkDnaa.isChecked()) \ No newline at end of file diff --git a/qtdialogs/QRCodeWidget.py b/qtdialogs/QRCodeWidget.py new file mode 100644 index 000000000..6966124f6 --- /dev/null +++ b/qtdialogs/QRCodeWidget.py @@ -0,0 +1,102 @@ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-17, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## + +from PySide2.QtCore import QSize +from PySide2.QtGui import QPainter, QColor +from PySide2.QtWidgets import QWidget + +from ui.QrCodeMatrix import CreateQRMatrix + +from armoryengine.ArmoryUtils import LOGERROR +from qtdialogs.DlgInflatedQR import DlgInflatedQR + +class QRCodeWidget(QWidget): + + def __init__(self, asciiToEncode='', prefSize=160, errLevel='L', parent=None): + super(QRCodeWidget, self).__init__() + + self.parent = parent + self.qrmtrx = None + self.setAsciiData(asciiToEncode, prefSize, errLevel, repaint=False) + + + def setAsciiData(self, newAscii, prefSize=160, errLevel='L', repaint=True): + if len(newAscii)==0: + self.qrmtrx = [[0]] + self.modCt = 1 + self.pxScale= 1 + return + + self.theData = newAscii + self.qrmtrx, self.modCt = CreateQRMatrix(self.theData, errLevel) + self.setPreferredSize(prefSize) + + + def getModuleCount1D(self): + return self.modCt + + + def setPreferredSize(self, px, policy='Approx'): + self.pxScale,rem = divmod(int(px), int(self.modCt)) + + if policy.lower().startswith('approx'): + if rem>self.modCt/2.0: + self.pxScale += 1 + elif policy.lower().startswith('atleast'): + if rem>0: + self.pxScale += 1 + elif policy.lower().startswith('max'): + pass + else: + LOGERROR('Bad size policy in set qr size') + return self.pxScale*self.modCt + + return + + + def getSize(self): + return self.pxScale*self.modCt + + + def sizeHint(self): + sz1d = self.pxScale*self.modCt + return QSize(sz1d, sz1d) + + + def paintEvent(self, e): + qp = QPainter() + qp.begin(self) + self.drawWidget(qp) + qp.end() + + + + def drawWidget(self, qp): + # In case this is not a white background, draw the white boxes + qp.setPen(QColor(255,255,255)) + qp.setBrush(QColor(255,255,255)) + for r in range(self.modCt): + for c in range(self.modCt): + if not self.qrmtrx[r][c]: + qp.drawRect(*[a*self.pxScale for a in [r,c,1,1]]) + + # Draw the black tiles + qp.setPen(QColor(0,0,0)) + qp.setBrush(QColor(0,0,0)) + for r in range(self.modCt): + for c in range(self.modCt): + if self.qrmtrx[r][c]: + qp.drawRect(*[a*self.pxScale for a in [r,c,1,1]]) + + + def mouseDoubleClickEvent(self, *args): + DlgInflatedQR(self.parent, self.theData).exec_() diff --git a/qtdialogs/qtdefines.py b/qtdialogs/qtdefines.py index 85b9c0a85..2b62cd3a9 100755 --- a/qtdialogs/qtdefines.py +++ b/qtdialogs/qtdefines.py @@ -16,7 +16,7 @@ from tempfile import mkstemp from PySide2.QtCore import Qt, QAbstractTableModel, QModelIndex, \ - QSortFilterProxyModel, QEvent, Signal, QSize + QSortFilterProxyModel, QEvent, Signal, SIGNAL, QSize from PySide2.QtGui import QFont, QIcon, QFontMetricsF, QPixmap, \ QPainter, QColor from PySide2.QtWidgets import QWidget, QDialog, QFrame, QLabel, \ @@ -57,6 +57,8 @@ CHANGE_ADDR_DESCR_STRING = '[[ Change received ]]' # Keep track of dialogs and wizard that are executing +STRETCH = 'Stretch' + runningDialogsList = [] def AddToRunningDialogsList(func): @@ -413,176 +415,6 @@ def leaveEvent(self, ev): ssStr = "QLabel { background-color : %s }" % htmlColor('LBtnNormalBG') self.setStyleSheet(ssStr) -################################################################################ -# The optionalMsg argument is not word wrapped so the caller is responsible for limiting -# the length of the longest line in the optionalMsg -def MsgBoxCustom(wtype, title, msg, wCancel=False, yesStr=None, noStr=None, - optionalMsg=None): - """ - Creates a message box with custom button text and icon - """ - - class dlgWarn(ArmoryDialog): - def __init__(self, dtype, dtitle, wmsg, withCancel=False, yesStr=None, noStr=None): - super(dlgWarn, self).__init__(None) - - msgIcon = QLabel() - fpix = '' - if dtype==MSGBOX.Good: - fpix = './img/MsgBox_good48.png' - if dtype==MSGBOX.Info: - fpix = './img/MsgBox_info48.png' - if dtype==MSGBOX.Question: - fpix = './img/MsgBox_question64.png' - if dtype==MSGBOX.Warning: - fpix = './img/MsgBox_warning48.png' - if dtype==MSGBOX.Critical: - fpix = './img/MsgBox_critical64.png' - if dtype==MSGBOX.Error: - fpix = './img/MsgBox_error64.png' - - - if len(fpix)>0: - msgIcon.setPixmap(QPixmap(fpix)) - msgIcon.setAlignment(Qt.AlignHCenter | Qt.AlignTop) - - lblMsg = QLabel(msg) - lblMsg.setTextFormat(Qt.RichText) - lblMsg.setWordWrap(True) - lblMsg.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - lblMsg.setOpenExternalLinks(True) - w,h = tightSizeNChar(lblMsg, 70) - lblMsg.setMinimumSize( w, 3.2*h ) - buttonbox = QDialogButtonBox() - - if dtype==MSGBOX.Question: - if not yesStr: yesStr = self.tr('&Yes') - if not noStr: noStr = self.tr('&No') - btnYes = QPushButton(yesStr) - btnNo = QPushButton(noStr) - self.connect(btnYes, SIGNAL('clicked()'), self.accept) - self.connect(btnNo, SIGNAL('clicked()'), self.reject) - buttonbox.addButton(btnYes,QDialogButtonBox.AcceptRole) - buttonbox.addButton(btnNo, QDialogButtonBox.RejectRole) - else: - cancelStr = self.tr('&Cancel') if (noStr is not None or withCancel) else '' - yesStr = self.tr('&OK') if (yesStr is None) else yesStr - btnOk = QPushButton(yesStr) - btnCancel = QPushButton(cancelStr) - self.connect(btnOk, SIGNAL('clicked()'), self.accept) - self.connect(btnCancel, SIGNAL('clicked()'), self.reject) - buttonbox.addButton(btnOk, QDialogButtonBox.AcceptRole) - if cancelStr: - buttonbox.addButton(btnCancel, QDialogButtonBox.RejectRole) - - spacer = QSpacerItem(20, 10, QSizePolicy.Fixed, QSizePolicy.Expanding) - - layout = QGridLayout() - layout.addItem( spacer, 0,0, 1,2) - layout.addWidget(msgIcon, 1,0, 1,1) - layout.addWidget(lblMsg, 1,1, 1,1) - if optionalMsg: - optionalTextLabel = QLabel(optionalMsg) - optionalTextLabel.setTextFormat(Qt.RichText) - optionalTextLabel.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - w,h = tightSizeNChar(optionalTextLabel, 70) - optionalTextLabel.setMinimumSize( w, 3.2*h ) - layout.addWidget(optionalTextLabel, 2,0,1,2) - layout.addWidget(buttonbox, 3,0, 1,2) - layout.setSpacing(20) - self.setLayout(layout) - self.setWindowTitle(dtitle) - - dlg = dlgWarn(wtype, title, msg, wCancel, yesStr, noStr) - result = dlg.exec_() - - return result - - -################################################################################ -def MsgBoxWithDNAA(parent, main, wtype, title, msg, dnaaMsg, wCancel=False, \ - yesStr='Yes', noStr='No', dnaaStartChk=False): - """ - Creates a warning/question/critical dialog, but with a "Do not ask again" - checkbox. Will return a pair (response, DNAA-is-checked) - """ - - class dlgWarn(ArmoryDialog): - def __init__(self, parent, main, dtype, dtitle, wmsg, dmsg=None, withCancel=False): - super(dlgWarn, self).__init__(parent, main) - - msgIcon = QLabel() - fpix = '' - if dtype==MSGBOX.Info: - fpix = './img/MsgBox_info48.png' - if not dmsg: dmsg = self.tr('Do not show this message again') - if dtype==MSGBOX.Question: - fpix = './img/MsgBox_question64.png' - if not dmsg: dmsg = self.tr('Do not ask again') - if dtype==MSGBOX.Warning: - fpix = './img/MsgBox_warning48.png' - if not dmsg: dmsg = self.tr('Do not show this warning again') - if dtype==MSGBOX.Critical: - fpix = './img/MsgBox_critical64.png' - if not dmsg: dmsg = None # should always show crits - if dtype==MSGBOX.Error: - fpix = './img/MsgBox_error64.png' - if not dmsg: dmsg = None # should always show errors - - - if len(fpix)>0: - msgIcon.setPixmap(QPixmap(fpix)) - msgIcon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - - self.chkDnaa = QCheckBox(dmsg) - self.chkDnaa.setChecked(dnaaStartChk) - lblMsg = QLabel(msg) - lblMsg.setTextFormat(Qt.RichText) - lblMsg.setWordWrap(True) - lblMsg.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - w,h = tightSizeNChar(lblMsg, 50) - lblMsg.setMinimumSize( w, 3.2*h ) - lblMsg.setOpenExternalLinks(True) - - buttonbox = QDialogButtonBox() - - if dtype==MSGBOX.Question: - btnYes = QPushButton(yesStr) - btnNo = QPushButton(noStr) - btnYes.clicked.connect(self.accept) - btnNo.clicked.connect(self.reject) - buttonbox.addButton(btnYes,QDialogButtonBox.AcceptRole) - buttonbox.addButton(btnNo, QDialogButtonBox.RejectRole) - else: - btnOk = QPushButton('Ok') - btnOk.clicked.connect(self.accept) - buttonbox.addButton(btnOk, QDialogButtonBox.AcceptRole) - if withCancel: - btnCancel = QPushButton('Cancel') - btnCancel.clicked.connect(self.reject) - buttonbox.addButton(btnCancel, QDialogButtonBox.RejectRole) - - - spacer = QSpacerItem(20, 10, QSizePolicy.Fixed, QSizePolicy.Expanding) - - - layout = QGridLayout() - layout.addItem( spacer, 0,0, 1,2) - layout.addWidget(msgIcon, 1,0, 1,1) - layout.addWidget(lblMsg, 1,1, 1,1) - layout.addWidget(self.chkDnaa, 2,0, 1,2) - layout.addWidget(buttonbox, 3,0, 1,2) - layout.setSpacing(20) - self.setLayout(layout) - self.setWindowTitle(dtitle) - - - dlg = dlgWarn(parent, main, wtype, title, msg, dnaaMsg, wCancel) - result = dlg.exec_() - - return (result, dlg.chkDnaa.isChecked()) - - def makeLayoutFrame(dirStr, widgetList, style=QFrame.NoFrame, condenseMargins=False): frm = QFrame() frm.setFrameStyle(style) @@ -715,169 +547,6 @@ def __init__(self, parent, main): self.isComplete = None -################################################################################ -class ArmoryDialog(QDialog): - #create a signal with a random name that children to this dialog will - #connect to close themselves if the parent is closed first - - closeSignal = Signal() - - def __init__(self, parent=None, main=None): - super(ArmoryDialog, self).__init__(parent) - - self.parent = parent - self.main = main - if self.main != None: - self.signalExecution = self.main.signalExecution - - #connect this dialog to the parent's close signal - if self.parent is not None and hasattr(self.parent, 'closeSignal'): - self.parent.closeSignal.connect(self.reject) - - self.setFont(GETFONT('var')) - self.setWindowFlags(Qt.Window) - - if USE_TESTNET: - self.setWindowTitle(self.tr('Armory - Bitcoin Wallet Management [TESTNET] ' + self.__class__.__name__)) - self.setWindowIcon(QIcon('./img/armory_icon_green_32x32.png')) - elif USE_REGTEST: - self.setWindowTitle(self.tr('Armory - Bitcoin Wallet Management [REGTEST] ' + self.__class__.__name__)) - self.setWindowIcon(QIcon('./img/armory_icon_green_32x32.png')) - else: - self.setWindowTitle(self.tr('Armory - Bitcoin Wallet Management')) - self.setWindowIcon(QIcon('./img/armory_icon_32x32.png')) - - @AddToRunningDialogsList - def exec_(self): - return super(ArmoryDialog, self).exec_() - - def reject(self): - self.closeSignal.emit() - super(ArmoryDialog, self).reject() - - def executeMethod(self, _callable, *args): - self.signalExecution.executeMethod(_callable, *args) - - def callLater(self, delay, _callable, *args): - self.signalExecution.callLater(delay, _callable, *args) - - -################################################################################ -class QRCodeWidget(QWidget): - - def __init__(self, asciiToEncode='', prefSize=160, errLevel='L', parent=None): - super(QRCodeWidget, self).__init__() - - self.parent = parent - self.qrmtrx = None - self.setAsciiData(asciiToEncode, prefSize, errLevel, repaint=False) - - - def setAsciiData(self, newAscii, prefSize=160, errLevel='L', repaint=True): - if len(newAscii)==0: - self.qrmtrx = [[0]] - self.modCt = 1 - self.pxScale= 1 - return - - self.theData = newAscii - self.qrmtrx, self.modCt = CreateQRMatrix(self.theData, errLevel) - self.setPreferredSize(prefSize) - - - - - def getModuleCount1D(self): - return self.modCt - - - def setPreferredSize(self, px, policy='Approx'): - self.pxScale,rem = divmod(int(px), int(self.modCt)) - - if policy.lower().startswith('approx'): - if rem>self.modCt/2.0: - self.pxScale += 1 - elif policy.lower().startswith('atleast'): - if rem>0: - self.pxScale += 1 - elif policy.lower().startswith('max'): - pass - else: - LOGERROR('Bad size policy in set qr size') - return self.pxScale*self.modCt - - return - - - def getSize(self): - return self.pxScale*self.modCt - - - def sizeHint(self): - sz1d = self.pxScale*self.modCt - return QSize(sz1d, sz1d) - - - def paintEvent(self, e): - qp = QPainter() - qp.begin(self) - self.drawWidget(qp) - qp.end() - - - - def drawWidget(self, qp): - # In case this is not a white background, draw the white boxes - qp.setPen(QColor(255,255,255)) - qp.setBrush(QColor(255,255,255)) - for r in range(self.modCt): - for c in range(self.modCt): - if not self.qrmtrx[r][c]: - qp.drawRect(*[a*self.pxScale for a in [r,c,1,1]]) - - # Draw the black tiles - qp.setPen(QColor(0,0,0)) - qp.setBrush(QColor(0,0,0)) - for r in range(self.modCt): - for c in range(self.modCt): - if self.qrmtrx[r][c]: - qp.drawRect(*[a*self.pxScale for a in [r,c,1,1]]) - - - def mouseDoubleClickEvent(self, *args): - DlgInflatedQR(self.parent, self.theData).exec_() - - -# Create a very simple dialog and execute it -class DlgInflatedQR(ArmoryDialog): - def __init__(self, parent, dataToQR): - super(DlgInflatedQR, self).__init__(parent, parent.main) - - sz = QApplication.desktop().size() - w,h = sz.width(), sz.height() - qrSize = int(min(w,h)*0.8) - qrDisp = QRCodeWidget(dataToQR, prefSize=qrSize) - - def closeDlg(*args): - self.accept() - qrDisp.mouseDoubleClickEvent = closeDlg - self.mouseDoubleClickEvent = closeDlg - - lbl = QRichLabel(self.tr('Double-click or press ESC to close')) - lbl.setAlignment(Qt.AlignTop | Qt.AlignHCenter) - - frmQR = makeHorizFrame(['Stretch', qrDisp, 'Stretch']) - frmFull = makeVertFrame(['Stretch',frmQR, lbl, 'Stretch']) - - layout = QVBoxLayout() - layout.addWidget(frmFull) - - self.setLayout(layout) - self.showFullScreen() - - - - # Pure-python BMP creator taken from: @@ -1025,4 +694,4 @@ def createDirectorySelectButton(parent, targetWidget, title="Select Directory"): fn = lambda: selectDirectoryForQLineEdit(parent, targetWidget, title) parent.connect(btn, SIGNAL('clicked()'), fn) - return btn + return btn \ No newline at end of file diff --git a/qtdialogs/qtdialogs.py b/qtdialogs/qtdialogs.py index 6c640d97d..c4ec5fdd1 100755 --- a/qtdialogs/qtdialogs.py +++ b/qtdialogs/qtdialogs.py @@ -36,9 +36,11 @@ from armoryengine.Block import PyBlockHeader from armoryengine import ClientProto_pb2 -from qtdialogs.qtdefines import ArmoryDialog, USERMODE, GETFONT, \ +from qtdialogs.qtdefines import USERMODE, GETFONT, \ tightSizeStr, determineWalletType, WLTTYPES +from qtdialogs.ArmoryDialog import ArmoryDialog + NO_CHANGE = 'NoChange' MIN_PASSWD_WIDTH = lambda obj: tightSizeStr(obj, '*' * 16)[0] STRETCH = 'Stretch' @@ -549,181 +551,6 @@ def getImportWltPath(self): self.accept() - - -################################################################################ -class DlgChangePassphrase(ArmoryDialog): - def __init__(self, parent=None, main=None, noPrevEncrypt=True): - super(DlgChangePassphrase, self).__init__(parent, main) - - - - layout = QGridLayout() - if noPrevEncrypt: - lblDlgDescr = QLabel(self.tr('Please enter an passphrase for wallet encryption.\n\n' - 'A good passphrase consists of at least 8 or more\n' - 'random letters, or 5 or more random words.\n')) - lblDlgDescr.setWordWrap(True) - layout.addWidget(lblDlgDescr, 0, 0, 1, 2) - else: - lblDlgDescr = QLabel(self.tr("Change your wallet encryption passphrase")) - layout.addWidget(lblDlgDescr, 0, 0, 1, 2) - self.edtPasswdOrig = QLineEdit() - self.edtPasswdOrig.setEchoMode(QLineEdit.Password) - self.edtPasswdOrig.setMinimumWidth(MIN_PASSWD_WIDTH(self)) - lblCurrPasswd = QLabel(self.tr('Current Passphrase:')) - layout.addWidget(lblCurrPasswd, 1, 0) - layout.addWidget(self.edtPasswdOrig, 1, 1) - - - - lblPwd1 = QLabel(self.tr("New Passphrase:")) - self.edtPasswd1 = QLineEdit() - self.edtPasswd1.setEchoMode(QLineEdit.Password) - self.edtPasswd1.setMinimumWidth(MIN_PASSWD_WIDTH(self)) - - lblPwd2 = QLabel(self.tr("Again:")) - self.edtPasswd2 = QLineEdit() - self.edtPasswd2.setEchoMode(QLineEdit.Password) - self.edtPasswd2.setMinimumWidth(MIN_PASSWD_WIDTH(self)) - - layout.addWidget(lblPwd1, 2, 0) - layout.addWidget(lblPwd2, 3, 0) - layout.addWidget(self.edtPasswd1, 2, 1) - layout.addWidget(self.edtPasswd2, 3, 1) - - self.lblMatches = QLabel(' ' * 20) - self.lblMatches.setTextFormat(Qt.RichText) - layout.addWidget(self.lblMatches, 4, 1) - - - self.chkDisableCrypt = QCheckBox(self.tr('Disable encryption for this wallet')) - if not noPrevEncrypt: - self.connect(self.chkDisableCrypt, SIGNAL('toggled(bool)'), \ - self.disablePassphraseBoxes) - layout.addWidget(self.chkDisableCrypt, 4, 0) - - - self.btnAccept = QPushButton(self.tr("Accept")) - self.btnCancel = QPushButton(self.tr("Cancel")) - buttonBox = QDialogButtonBox() - buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) - buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) - layout.addWidget(buttonBox, 5, 0, 1, 2) - - if noPrevEncrypt: - self.setWindowTitle(self.tr("Set Encryption Passphrase")) - else: - self.setWindowTitle(self.tr("Change Encryption Passphrase")) - - self.setWindowIcon(QIcon(self.main.iconfile)) - - self.setLayout(layout) - - self.connect(self.edtPasswd1, SIGNAL('textChanged(QString)'), \ - self.checkPassphrase) - self.connect(self.edtPasswd2, SIGNAL('textChanged(QString)'), \ - self.checkPassphrase) - - self.connect(self.btnAccept, SIGNAL(CLICKED), \ - self.checkPassphraseFinal) - - self.connect(self.btnCancel, SIGNAL(CLICKED), \ - self, SLOT('reject()')) - - - def disablePassphraseBoxes(self, noEncrypt=True): - self.edtPasswd1.setEnabled(not noEncrypt) - self.edtPasswd2.setEnabled(not noEncrypt) - - - def checkPassphrase(self): - if self.chkDisableCrypt.isChecked(): - return True - p1 = self.edtPasswd1.text() - p2 = self.edtPasswd2.text() - goodColor = htmlColor('TextGreen') - badColor = htmlColor('TextRed') - if not isASCII(p1) or \ - not isASCII(p2): - self.lblMatches.setText(self.tr('Passphrase is non-ASCII!' % badColor)) - return False - if not p1 == p2: - self.lblMatches.setText(self.tr('Passphrases do not match!' % badColor)) - return False - if len(p1) < 5: - self.lblMatches.setText(self.tr('Passphrase is too short!' % badColor)) - return False - self.lblMatches.setText(self.tr('Passphrases match!' % goodColor)) - return True - - - def checkPassphraseFinal(self): - if self.chkDisableCrypt.isChecked(): - self.accept() - else: - if self.checkPassphrase(): - dlg = DlgPasswd3(self, self.main) - if dlg.exec_(): - if not str(dlg.edtPasswd3.text()) == str(self.edtPasswd1.text()): - QMessageBox.critical(self, self.tr('Invalid Passphrase'), \ - self.tr('You entered your confirmation passphrase incorrectly!'), QMessageBox.Ok) - else: - self.accept() - else: - self.reject() - - - -class DlgPasswd3(ArmoryDialog): - def __init__(self, parent=None, main=None): - super(DlgPasswd3, self).__init__(parent, main) - - - lblWarnImgL = QLabel() - lblWarnImgL.setPixmap(QPixmap('./img/MsgBox_warning48.png')) - lblWarnImgL.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - - lblWarnTxt1 = QRichLabel(\ - self.tr('!!! DO NOT FORGET YOUR PASSPHRASE !!!'), size=4) - lblWarnTxt1.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - lblWarnTxt2 = QRichLabel(self.tr( - 'No one can help you recover you bitcoins if you forget the ' - 'passphrase and don\'t have a paper backup! Your wallet and ' - 'any digital backups are useless if you forget it. ' - '

' - 'A paper backup protects your wallet forever, against ' - 'hard-drive loss and losing your passphrase. It also protects you ' - 'from theft, if the wallet was encrypted and the paper backup ' - 'was not stolen with it. Please make a paper backup and keep it in ' - 'a safe place.' - '

' - 'Please enter your passphrase a third time to indicate that you ' - 'are aware of the risks of losing your passphrase!'), doWrap=True) - - - self.edtPasswd3 = QLineEdit() - self.edtPasswd3.setEchoMode(QLineEdit.Password) - self.edtPasswd3.setMinimumWidth(MIN_PASSWD_WIDTH(self)) - - bbox = QDialogButtonBox() - btnOk = QPushButton(self.tr('Accept')) - btnNo = QPushButton(self.tr('Cancel')) - self.connect(btnOk, SIGNAL(CLICKED), self.accept) - self.connect(btnNo, SIGNAL(CLICKED), self.reject) - bbox.addButton(btnOk, QDialogButtonBox.AcceptRole) - bbox.addButton(btnNo, QDialogButtonBox.RejectRole) - layout = QGridLayout() - layout.addWidget(lblWarnImgL, 0, 0, 4, 1) - layout.addWidget(lblWarnTxt1, 0, 1, 1, 1) - layout.addWidget(lblWarnTxt2, 2, 1, 1, 1) - layout.addWidget(self.edtPasswd3, 5, 1, 1, 1) - layout.addWidget(bbox, 6, 1, 1, 2) - self.setLayout(layout) - self.setWindowTitle(self.tr('WARNING!')) - - - ################################################################################ class DlgChangeLabels(ArmoryDialog): def __init__(self, currName='', currDescr='', parent=None, main=None): @@ -3927,3398 +3754,1023 @@ def eccClear(self): self.txtPtPtC_x.setText('') self.txtPtPtC_y.setText('') -################################################################################ -class DlgHelpAbout(ArmoryDialog): - def __init__(self, putResultInWidget, defaultWltID=None, parent=None, main=None): - super(DlgHelpAbout, self).__init__(parent, main) - - imgLogo = QLabel() - imgLogo.setPixmap(QPixmap('./img/armory_logo_h56.png')) - imgLogo.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - - if BTCARMORY_BUILD != None: - lblHead = QRichLabel(self.tr('Armory Bitcoin Wallet : Version %s-beta-%s' % (getVersionString(BTCARMORY_VERSION), BTCARMORY_BUILD)), doWrap=False) - else: - lblHead = QRichLabel(self.tr('Armory Bitcoin Wallet : Version %s-beta' % getVersionString(BTCARMORY_VERSION)), doWrap=False) - - lblOldCopyright = QRichLabel(self.tr( u'Copyright © 2011-2015 Armory Technologies, Inc.')) - lblCopyright = QRichLabel(self.tr( u'Copyright © 2016 Goatpig')) - lblOldLicense = QRichLabel(self.tr( u'Licensed to Armory Technologies, Inc. under the ' - '
' - 'Affero General Public License, Version 3 (AGPLv3)')) - lblOldLicense.setOpenExternalLinks(True) - lblLicense = QRichLabel(self.tr( u'Licensed to Goatpig under the ' - '' - 'MIT License')) - lblLicense.setOpenExternalLinks(True) - - lblHead.setAlignment(Qt.AlignHCenter) - lblCopyright.setAlignment(Qt.AlignHCenter) - lblOldCopyright.setAlignment(Qt.AlignHCenter) - lblLicense.setAlignment(Qt.AlignHCenter) - lblOldLicense.setAlignment(Qt.AlignHCenter) - - dlgLayout = QHBoxLayout() - dlgLayout.addWidget(makeVertFrame([imgLogo, lblHead, lblCopyright, lblOldCopyright, STRETCH, lblLicense, lblOldLicense])) - self.setLayout(dlgLayout) - - self.setMinimumWidth(450) - - self.setWindowTitle(self.tr('About Armory')) - ################################################################################ -class DlgSettings(ArmoryDialog): - def __init__(self, parent=None, main=None): - super(DlgSettings, self).__init__(parent, main) +# STUB STUB STUB STUB STUB +class ArmoryPref(object): + """ + Create a class that will handle arbitrary preferences for Armory. This + means that I can just create maps/lists of preferences, and auto-include + them in the preferences dialog, and know how to set/get them. This will + be subclassed for each unique/custom preference type that is needed. + """ + def __init__(self, prefName, dispStr, setType, defaultVal, validRange, descr, ttip, usermodes=None): + self.preference = prefName + self.displayStr = dispStr + self.preferType = setType + self.defaultVal = defaultVal + self.validRange = validRange + self.description = descr + self.ttip = ttip + # Some options may only be displayed for certain usermodes + self.users = usermodes + if usermodes == None: + self.users = set([USERMODE.Standard, USERMODE.Advanced, USERMODE.Expert]) + if self.preferType == 'str': + self.entryObj = QLineEdit() + elif self.preferType == 'num': + self.entryObj = QLineEdit() + elif self.preferType == 'file': + self.entryObj = QLineEdit() + elif self.preferType == 'bool': + self.entryObj = QCheckBox() + elif self.preferType == 'combo': + self.entryObj = QComboBox() - ########################################################################## - # bitcoind-management settings - self.chkManageSatoshi = QCheckBox(self.tr( - 'Let Armory run Bitcoin Core/bitcoind in the background')) - self.edtSatoshiExePath = QLineEdit() - self.edtSatoshiHomePath = QLineEdit() - self.edtArmoryDbdir = QLineEdit() - - self.edtSatoshiExePath.setMinimumWidth(tightSizeNChar(GETFONT('Fixed', 10), 40)[0]) - self.connect(self.chkManageSatoshi, SIGNAL(CLICKED), self.clickChkManage) - self.startChk = self.main.getSettingOrSetDefault('ManageSatoshi', not OS_MACOSX) - if self.startChk: - self.chkManageSatoshi.setChecked(True) - if OS_MACOSX: - self.chkManageSatoshi.setEnabled(False) - lblManageSatoshi = QRichLabel(\ - self.tr('Bitcoin Core/bitcoind management is not available on Mac/OSX')) - else: - if self.main.settings.hasSetting('SatoshiExe'): - satexe = self.main.settings.get('SatoshiExe') - sathome = BTC_HOME_DIR - if self.main.settings.hasSetting('SatoshiDatadir'): - sathome = self.main.settings.get('SatoshiDatadir') + def setEntryVal(self): + pass - lblManageSatoshi = QRichLabel( - self.tr('Bitcoin Software Management' - '

' - 'By default, Armory will manage the Bitcoin engine/software in the ' - 'background. You can choose to manage it yourself, or tell Armory ' - 'about non-standard installation configuration.')) - if self.main.settings.hasSetting('SatoshiExe'): - self.edtSatoshiExePath.setText(self.main.settings.get('SatoshiExe')) - self.edtSatoshiExePath.home(False) - if self.main.settings.hasSetting('SatoshiDatadir'): - self.edtSatoshiHomePath.setText(self.main.settings.get('SatoshiDatadir')) - self.edtSatoshiHomePath.home(False) - if self.main.settings.hasSetting('ArmoryDbdir'): - self.edtArmoryDbdir.setText(self.main.settings.get('ArmoryDbdir')) - self.edtArmoryDbdir.home(False) - - - lblDescrExe = QRichLabel(self.tr('Bitcoin Install Dir:')) - lblDefaultExe = QRichLabel(self.tr('Leave blank to have Armory search default ' - 'locations for your OS'), size=2) - - self.btnSetExe = createDirectorySelectButton(self, self.edtSatoshiExePath) - - layoutMgmt = QGridLayout() - layoutMgmt.addWidget(lblManageSatoshi, 0, 0, 1, 3) - layoutMgmt.addWidget(self.chkManageSatoshi, 1, 0, 1, 3) - - layoutMgmt.addWidget(lblDescrExe, 2, 0) - layoutMgmt.addWidget(self.edtSatoshiExePath, 2, 1) - layoutMgmt.addWidget(self.btnSetExe, 2, 2) - layoutMgmt.addWidget(lblDefaultExe, 3, 1, 1, 2) - - frmMgmt = QFrame() - frmMgmt.setLayout(layoutMgmt) - - self.clickChkManage() - ########################################################################## + def readEntryVal(self): + pass - lblPathing = QRichLabel(self.tr(' Blockchain and Database Paths' - '

' - 'Optional feature to specify custom paths for blockchain ' - 'data and Armory\'s database.' - )) - lblDescrHome = QRichLabel(self.tr('Bitcoin Home Dir:')) - lblDefaultHome = QRichLabel(self.tr('Leave blank to use default datadir ' - '(%s)' % BTC_HOME_DIR), size=2) - lblDescrDbdir = QRichLabel(self.tr('Armory Database Dir:')) - lblDefaultDbdir = QRichLabel(self.tr('Leave blank to use default datadir ' - '(%s)' % ARMORY_DB_DIR), size=2) + def setWidthChars(self, nChar): + self.entryObj.setMinimumWidth(relaxedSizeNChar(self.entryObj, nChar)[0]) - self.btnSetHome = createDirectorySelectButton(self, self.edtSatoshiHomePath) - self.btnSetDbdir = createDirectorySelectButton(self, self.edtArmoryDbdir) + def render(self): + """ + Return a map of qt objects to insert into the frame + """ + toDraw = [] + row = 0 + if len(self.description) > 0: + toDraw.append([QRichLabel(self.description), row, 0, 1, 4]) + row += 1 - layoutPath = QGridLayout() - layoutPath.addWidget(lblPathing, 0, 0, 1, 3) - layoutPath.addWidget(lblDescrHome, 1, 0) - layoutPath.addWidget(self.edtSatoshiHomePath, 1, 1) - layoutPath.addWidget(self.btnSetHome, 1, 2) - layoutPath.addWidget(lblDefaultHome, 2, 1, 1, 2) +################################################################################ +class QRadioButtonBackupCtr(QRadioButton): + def __init__(self, parent, txt, index): + super(QRadioButtonBackupCtr, self).__init__(txt) + self.parent = parent + self.index = index - layoutPath.addWidget(lblDescrDbdir, 3, 0) - layoutPath.addWidget(self.edtArmoryDbdir, 3, 1) - layoutPath.addWidget(self.btnSetDbdir, 3, 2) - layoutPath.addWidget(lblDefaultDbdir, 4, 1, 1, 2) - frmPaths = QFrame() - frmPaths.setLayout(layoutPath) + def enterEvent(self, ev): + pass + # self.parent.setDispFrame(self.index) + # self.setStyleSheet('QRadioButton { background-color : %s }' % \ + # htmlColor('SlightBkgdDark')) - ########################################################################## - lblDefaultUriTitle = QRichLabel(self.tr('Set Armory as default URL handler')) - lblDefaultURI = QRichLabel(self.tr( - 'Set Armory to be the default when you click on "bitcoin:" ' - 'links in your browser or in emails. ' - 'You can test if your operating system is supported by clicking ' - 'on a "bitcoin:" link right after clicking this button.')) - btnDefaultURI = QPushButton(self.tr('Set Armory as Default')) - frmBtnDefaultURI = makeHorizFrame([btnDefaultURI, 'Stretch']) - - self.chkAskURIAtStartup = QCheckBox(self.tr( - 'Check whether Armory is the default handler at startup')) - askuriDNAA = self.main.getSettingOrSetDefault('DNAA_DefaultApp', False) - self.chkAskURIAtStartup.setChecked(not askuriDNAA) - - def clickRegURI(): - self.main.setupUriRegistration(justDoIt=True) - QMessageBox.information(self, self.tr('Registered'), self.tr( - 'Armory just attempted to register itself to handle "bitcoin:" ' - 'links, but this does not work on all operating systems.'), QMessageBox.Ok) - - self.connect(btnDefaultURI, SIGNAL(CLICKED), clickRegURI) - - ############################################################### - # Minimize on Close - lblMinimizeDescr = QRichLabel(self.tr( - 'Minimize to System Tray ' - '
' - 'You can have Armory automatically minimize itself to your system ' - 'tray on open or close. Armory will stay open but run in the ' - 'background, and you will still receive notifications. Access Armory ' - 'through the icon on your system tray. ' - '

' - 'If you select "Minimize on close", the \'x\' on the top window bar will ' - 'minimize Armory instead of exiting the application. You can always use ' - '"File" -> "Quit Armory" to actually close it.')) - - moo = self.main.getSettingOrSetDefault('MinimizeOnOpen', False) - self.chkMinOnOpen = QCheckBox(self.tr('Minimize to system tray on open')) - if moo: - self.chkMinOnOpen.setChecked(True) - - moc = self.main.getSettingOrSetDefault('MinimizeOrClose', 'DontKnow') - self.chkMinOrClose = QCheckBox(self.tr('Minimize to system tray on close')) - - if moc == 'Minimize': - self.chkMinOrClose.setChecked(True) - - - ############################################################### - # System tray notifications. On OS X, notifications won't work on 10.7. - # OS X's built-in notification system was implemented starting in 10.8. - osxMinorVer = '0' - if OS_MACOSX: - osxMinorVer = OS_VARIANT[0].split(".")[1] - - lblNotify = QRichLabel(self.tr('Enable notifications from the system-tray:')) - self.chkBtcIn = QCheckBox(self.tr('Bitcoins Received')) - self.chkBtcOut = QCheckBox(self.tr('Bitcoins Sent')) - self.chkDiscon = QCheckBox(self.tr('Bitcoin Core/bitcoind disconnected')) - self.chkReconn = QCheckBox(self.tr('Bitcoin Core/bitcoind reconnected')) - - # FYI:If we're not on OS X, the if condition will never be hit. - if (OS_MACOSX) and (int(osxMinorVer) < 7): - lblNotify = QRichLabel(self.tr('Sorry! Notifications are not available ' \ - 'on your version of OS X.')) - self.chkBtcIn.setChecked(False) - self.chkBtcOut.setChecked(False) - self.chkDiscon.setChecked(False) - self.chkReconn.setChecked(False) - self.chkBtcIn.setEnabled(False) - self.chkBtcOut.setEnabled(False) - self.chkDiscon.setEnabled(False) - self.chkReconn.setEnabled(False) - else: - notifyBtcIn = self.main.getSettingOrSetDefault('NotifyBtcIn', True) - notifyBtcOut = self.main.getSettingOrSetDefault('NotifyBtcOut', True) - notifyDiscon = self.main.getSettingOrSetDefault('NotifyDiscon', True) - notifyReconn = self.main.getSettingOrSetDefault('NotifyReconn', True) - self.chkBtcIn.setChecked(notifyBtcIn) - self.chkBtcOut.setChecked(notifyBtcOut) - self.chkDiscon.setChecked(notifyDiscon) - self.chkReconn.setChecked(notifyReconn) - - ############################################################### - # Date format preferences - exampleTimeTuple = (2012, 4, 29, 19, 45, 0, -1, -1, -1) - self.exampleUnixTime = time.mktime(exampleTimeTuple) - exampleStr = unixTimeToFormatStr(self.exampleUnixTime, '%c') - lblDateFmt = QRichLabel(self.tr('Preferred Date Format:
')) - lblDateDescr = QRichLabel(self.tr( - 'You can specify how you would like dates ' - 'to be displayed using percent-codes to ' - 'represent components of the date. The ' - 'mouseover text of the "(?)" icon shows ' - 'the most commonly used codes/symbols. ' - 'The text next to it shows how ' - '"%s" would be shown with the ' - 'specified format.' % exampleStr)) - lblDateFmt.setAlignment(Qt.AlignTop) - fmt = self.main.getPreferredDateFormat() - ttipStr = self.tr('Use any of the following symbols:
') - fmtSymbols = [x[0] + ' = ' + x[1] for x in FORMAT_SYMBOLS] - ttipStr += '
'.join(fmtSymbols) - - fmtSymbols = [x[0] + '~' + x[1] for x in FORMAT_SYMBOLS] - lblStk = QRichLabel('; '.join(fmtSymbols)) - - self.edtDateFormat = QLineEdit() - self.edtDateFormat.setText(fmt) - self.ttipFormatDescr = self.main.createToolTipWidget(ttipStr) - - self.lblDateExample = QRichLabel('', doWrap=False) - self.connect(self.edtDateFormat, SIGNAL('textEdited(QString)'), self.doExampleDate) - self.doExampleDate() - self.btnResetFormat = QPushButton(self.tr("Reset to Default")) - - def doReset(): - self.edtDateFormat.setText(DEFAULT_DATE_FORMAT) - self.doExampleDate() - self.connect(self.btnResetFormat, SIGNAL(CLICKED), doReset) - - # Make a little subframe just for the date format stuff... everything - # fits nicer if I do this... - frmTop = makeHorizFrame([self.lblDateExample, STRETCH, self.ttipFormatDescr]) - frmMid = makeHorizFrame([self.edtDateFormat]) - frmBot = makeHorizFrame([self.btnResetFormat, STRETCH]) - fStack = makeVertFrame([frmTop, frmMid, frmBot, STRETCH]) - lblStk = makeVertFrame([lblDateFmt, lblDateDescr, STRETCH]) - subFrm = makeHorizFrame([lblStk, STRETCH, fStack]) - - - # Save/Cancel Button - self.btnCancel = QPushButton(self.tr("Cancel")) - self.btnAccept = QPushButton(self.tr("Save")) - self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) - self.connect(self.btnAccept, SIGNAL(CLICKED), self.accept) + def leaveEvent(self, ev): + pass + # self.parent.setDispFrame(-1) + # self.setStyleSheet('QRadioButton { background-color : %s }' % \ + # htmlColor('Background')) - ################################################################ - # User mode selection - self.cmbUsermode = QComboBox() - self.cmbUsermode.clear() - self.cmbUsermode.addItem(self.tr('Standard')) - self.cmbUsermode.addItem(self.tr('Advanced')) - self.cmbUsermode.addItem(self.tr('Expert')) - - self.usermodeInit = self.main.usermode - - if self.main.usermode == USERMODE.Standard: - self.cmbUsermode.setCurrentIndex(0) - elif self.main.usermode == USERMODE.Advanced: - self.cmbUsermode.setCurrentIndex(1) - elif self.main.usermode == USERMODE.Expert: - self.cmbUsermode.setCurrentIndex(2) - - lblUsermode = QRichLabel(self.tr('Armory user mode:')) - self.lblUsermodeDescr = QRichLabel('') - self.setUsermodeDescr() - - self.connect(self.cmbUsermode, SIGNAL('activated(int)'), self.setUsermodeDescr) - - ############################################################### - # Language preferences - self.lblLang = QRichLabel(self.tr('Preferred Language:
')) - self.lblLangDescr = QRichLabel(self.tr( - 'Specify which language you would like Armory to be displayed in.')) - self.cmbLang = QComboBox() - self.cmbLang.clear() - for lang in LANGUAGES: - self.cmbLang.addItem(QLocale(lang).nativeLanguageName() + " (" + lang + ")") - self.cmbLang.setCurrentIndex(LANGUAGES.index(self.main.language)) - self.langInit = self.main.language - frmLayout = QGridLayout() +################################################################################ +class DlgBackupCenter(ArmoryDialog): - i = 0 - frmLayout.addWidget(HLINE(), i, 0, 1, 3) + ############################################################################# + def __init__(self, parent, main, wlt): + super(DlgBackupCenter, self).__init__(parent, main) - i += 1 - frmLayout.addWidget(frmMgmt, i, 0, 1, 3) + self.wlt = wlt + wltID = wlt.uniqueIDB58 + wltName = wlt.labelName - i += 1 - frmLayout.addWidget(HLINE(), i, 0, 1, 3) + self.walletBackupFrame = WalletBackupFrame(parent, main) + self.walletBackupFrame.setWallet(wlt) + self.btnDone = QPushButton(self.tr('Done')) + self.connect(self.btnDone, SIGNAL(CLICKED), self.reject) + frmBottomBtns = makeHorizFrame([STRETCH, self.btnDone]) - i += 1 - frmLayout.addWidget(frmPaths, i, 0, 1, 3) + layoutDialog = QVBoxLayout() - i += 1 - frmLayout.addWidget(HLINE(), i, 0, 1, 3) + layoutDialog.addWidget(self.walletBackupFrame) - i += 1 - frmLayout.addWidget(lblDefaultUriTitle, i, 0) - i += 1 - frmLayout.addWidget(lblDefaultURI, i, 0, 1, 3) - i += 1 - frmLayout.addWidget(frmBtnDefaultURI, i, 0, 1, 3) - i += 1 - frmLayout.addWidget(self.chkAskURIAtStartup, i, 0, 1, 3) + layoutDialog.addWidget(frmBottomBtns) - i += 1 - frmLayout.addWidget(HLINE(), i, 0, 1, 3) + self.setLayout(layoutDialog) + self.setWindowTitle(self.tr("Backup Center")) + self.setMinimumSize(640, 350) - i += 1 - frmLayout.addWidget(subFrm, i, 0, 1, 3) +################################################################################ +class DlgSimpleBackup(ArmoryDialog): + def __init__(self, parent, main, wlt): + super(DlgSimpleBackup, self).__init__(parent, main) - i += 1 - frmLayout.addWidget(HLINE(), i, 0, 1, 3) + self.wlt = wlt - i += 1 - frmLayout.addWidget(lblMinimizeDescr, i, 0, 1, 3) + lblDescrTitle = QRichLabel(self.tr( + 'Protect Your Bitcoins -- Make a Wallet Backup!')) - i += 1 - frmLayout.addWidget(self.chkMinOnOpen, i, 0, 1, 3) + lblDescr = QRichLabel(self.tr( + 'A failed hard-drive or forgotten passphrase will lead to ' + 'permanent loss of bitcoins! Luckily, Armory wallets only ' + 'need to be backed up one time, and protect you in both ' + 'of these events. If you\'ve ever forgotten a password or had ' + 'a hardware failure, make a backup!')) - i += 1 - frmLayout.addWidget(self.chkMinOrClose, i, 0, 1, 3) + # ## Paper + lblPaper = QRichLabel(self.tr( + 'Use a printer or pen-and-paper to write down your wallet "seed."')) + btnPaper = QPushButton(self.tr('Make Paper Backup')) + # ## Digital + lblDigital = QRichLabel(self.tr( + 'Create an unencrypted copy of your wallet file, including imported ' + 'addresses.')) + btnDigital = QPushButton(self.tr('Make Digital Backup')) - i += 1 - frmLayout.addWidget(HLINE(), i, 0, 1, 3) + # ## Other + btnOther = QPushButton(self.tr('See Other Backup Options')) - i += 1 - frmLayout.addWidget(lblNotify, i, 0, 1, 3) + def backupDigital(): + if self.main.digitalBackupWarning(): + self.main.makeWalletCopy(self, self.wlt, 'Decrypt', 'decrypt') + self.accept() - i += 1 - frmLayout.addWidget(self.chkBtcIn, i, 0, 1, 3) + def backupPaper(): + OpenPaperBackupWindow('Single', self, self.main, self.wlt) + self.accept() - i += 1 - frmLayout.addWidget(self.chkBtcOut, i, 0, 1, 3) + def backupOther(): + self.accept() + DlgBackupCenter(self, self.main, self.wlt).exec_() - i += 1 - frmLayout.addWidget(self.chkDiscon, i, 0, 1, 3) + self.connect(btnPaper, SIGNAL(CLICKED), backupPaper) + self.connect(btnDigital, SIGNAL(CLICKED), backupDigital) + self.connect(btnOther, SIGNAL(CLICKED), backupOther) - i += 1 - frmLayout.addWidget(self.chkReconn, i, 0, 1, 3) + layout = QGridLayout() + layout.addWidget(lblPaper, 0, 0) + layout.addWidget(btnPaper, 0, 2) - i += 1 - frmLayout.addWidget(HLINE(), i, 0, 1, 3) + layout.addWidget(HLINE(), 1, 0, 1, 3) - i += 1 - frmLayout.addWidget(lblUsermode, i, 0) - frmLayout.addWidget(QLabel(''), i, 1) - frmLayout.addWidget(self.cmbUsermode, i, 2) + layout.addWidget(lblDigital, 2, 0) + layout.addWidget(btnDigital, 2, 2) - i += 1 - frmLayout.addWidget(self.lblUsermodeDescr, i, 0, 1, 3) + layout.addWidget(HLINE(), 3, 0, 1, 3) + layout.addWidget(makeHorizFrame([STRETCH, btnOther, STRETCH]), 4, 0, 1, 3) - i += 1 - frmLayout.addWidget(HLINE(), i, 0, 1, 3) + # layout.addWidget( VLINE(), 0,1, 5,1) - i += 1 - frmLayout.addWidget(self.lblLang, i, 0) - frmLayout.addWidget(QLabel(''), i, 1) - frmLayout.addWidget(self.cmbLang, i, 2) + layout.setContentsMargins(10, 5, 10, 5) + setLayoutStretchRows(layout, 1, 0, 1, 0, 0) + setLayoutStretchCols(layout, 1, 0, 0) - i += 1 - frmLayout.addWidget(self.lblLangDescr, i, 0, 1, 3) + frmGrid = QFrame() + frmGrid.setFrameStyle(STYLE_PLAIN) + frmGrid.setLayout(layout) + btnClose = QPushButton(self.tr('Done')) + self.connect(btnClose, SIGNAL(CLICKED), self.accept) + frmClose = makeHorizFrame([STRETCH, btnClose]) - frmOptions = QFrame() - frmOptions.setLayout(frmLayout) + frmAll = makeVertFrame([lblDescrTitle, lblDescr, frmGrid, frmClose]) + layoutAll = QVBoxLayout() + layoutAll.addWidget(frmAll) + self.setLayout(layoutAll) + self.sizeHint = lambda: QSize(400, 250) - self.settingsTab = QTabWidget() - self.settingsTab.addTab(frmOptions, self.tr("General")) + self.setWindowTitle(self.tr('Backup Options')) - #FeeChange tab - self.setupExtraTabs() - frmFeeChange = makeVertFrame([\ - self.frmFee, self.frmChange, self.frmAddrType, 'Stretch']) - self.settingsTab.addTab(frmFeeChange, self.tr("Fee and Address Types")) +################################################################################ +# Class that acts as a center where the user can decide what to do with the +# watch-only wallet. The data can be displayed, printed, or saved to a file as a +# wallet or as the watch-only data (i.e., root key & chain code). +class DlgExpWOWltData(ArmoryDialog): + """ + This dialog will be used to export a wallet's root public key and chain code. + """ + def __init__(self, wlt, parent, main): + super(DlgExpWOWltData, self).__init__(parent, main) - self.scrollOptions = QScrollArea() - self.scrollOptions.setWidget(self.settingsTab) + # Save a copy of the wallet. + self.wlt = wlt + self.main = main + # Get the chain code and uncompressed public key of info from the wallet, + # along with other useful info. + wltRootIDConcat, pkccET16Lines = wlt.getRootPKCCBackupData(True) + wltIDB58 = wlt.uniqueIDB58 + # Create the data export buttons. + expWltButton = QPushButton(self.tr('Export Watching-Only Wallet File')) + clipboardBtn = QPushButton(self.tr('Copy to clipboard')) + clipboardLbl = QRichLabel('', hAlign=Qt.AlignHCenter) + expDataButton = QPushButton(self.tr('Save to Text File')) + printWODataButton = QPushButton(self.tr('Print Root Data')) - dlgLayout = QVBoxLayout() - dlgLayout.addWidget(self.scrollOptions) - dlgLayout.addWidget(makeHorizFrame([STRETCH, self.btnCancel, self.btnAccept])) - self.setLayout(dlgLayout) + self.connect(expWltButton, SIGNAL(CLICKED), self.clickedExpWlt) + self.connect(expDataButton, SIGNAL(CLICKED), self.clickedExpData) + self.connect(printWODataButton, SIGNAL(CLICKED), \ + self.clickedPrintWOData) - self.setMinimumWidth(650) - self.setWindowTitle(self.tr('Armory Settings')) - ############################################################################# - def setupExtraTabs(self): - ########## - #fee - - feeByte = self.main.getSettingOrSetDefault('Default_FeeByte', MIN_FEE_BYTE) - txFee = self.main.getSettingOrSetDefault('Default_Fee', MIN_TX_FEE) - adjustFee = self.main.getSettingOrSetDefault('AdjustFee', True) - feeOpt = self.main.getSettingOrSetDefault('FeeOption', DEFAULT_FEE_TYPE) - blocksToConfirm = self.main.getSettingOrSetDefault(\ - "Default_FeeByte_BlocksToConfirm", NBLOCKS_TO_CONFIRM) - - def feeRadio(strArg): - self.radioAutoFee.setChecked(False) - - self.radioFeeByte.setChecked(False) - self.leFeeByte.setEnabled(False) - - self.radioFlatFee.setChecked(False) - self.leFlatFee.setEnabled(False) - - if strArg == 'Auto': - self.radioAutoFee.setChecked(True) - elif strArg == 'FeeByte': - self.radioFeeByte.setChecked(True) - self.leFeeByte.setEnabled(True) - elif strArg == 'FlatFee': - self.radioFlatFee.setChecked(True) - self.leFlatFee.setEnabled(True) - - self.feeOpt = strArg - - def getCallbck(strArg): - def callbck(): - return feeRadio(strArg) - return callbck - - labelFee = QRichLabel(self.tr("Fee
")) - - self.radioAutoFee = QRadioButton(self.tr("Auto fee/byte")) - self.connect(self.radioAutoFee, SIGNAL('clicked()'), getCallbck('Auto')) - self.sliderAutoFee = QSlider(Qt.Horizontal, self) - self.sliderAutoFee.setMinimum(2) - self.sliderAutoFee.setMaximum(6) - self.sliderAutoFee.setValue(blocksToConfirm) - self.lblSlider = QLabel() - - def getLblSliderText(): - blocksToConfirm = str(self.sliderAutoFee.value()) - return self.tr("Blocks to confirm: %s" % blocksToConfirm) - - def setLblSliderText(): - self.lblSlider.setText(getLblSliderText()) - - setLblSliderText() - self.sliderAutoFee.valueChanged.connect(setLblSliderText) - - toolTipAutoFee = self.main.createToolTipWidget(self.tr( - 'Fetch fee/byte from local Bitcoin node. ' - 'Defaults to manual fee/byte on failure.')) - - self.radioFeeByte = QRadioButton(self.tr("Manual fee/byte")) - self.connect(self.radioFeeByte, SIGNAL('clicked()'), getCallbck('FeeByte')) - self.leFeeByte = QLineEdit(str(feeByte)) - toolTipFeeByte = self.main.createToolTipWidget(self.tr('Values in satoshis/byte')) - - self.radioFlatFee = QRadioButton(self.tr("Flat fee")) - self.connect(self.radioFlatFee, SIGNAL('clicked()'), getCallbck('FlatFee')) - self.leFlatFee = QLineEdit(coin2str(txFee, maxZeros=0)) - toolTipFlatFee = self.main.createToolTipWidget(self.tr('Values in BTC')) - - self.checkAdjust = QCheckBox(self.tr("Auto-adjust fee/byte for better privacy")) - self.checkAdjust.setChecked(adjustFee) - feeToolTip = self.main.createToolTipWidget(self.tr( - 'Auto-adjust fee may increase your total fee using the selected fee/byte rate ' - 'as its basis in an attempt to align the amount of digits after the decimal ' - 'point between your spend values and change value.' - '

' - 'The purpose of this obfuscation technique is to make the change output ' - 'less obvious. ' - '

' - 'The auto-adjust fee feature only applies to fee/byte options ' - 'and does not inflate your fee by more that 10% of its original value.')) + # Let's put the window together. + layout = QVBoxLayout() - frmFeeLayout = QGridLayout() - frmFeeLayout.addWidget(labelFee, 0, 0, 1, 1) + self.dispText = self.tr( + 'Watch-Only Root ID:
%s' + '

' + 'Watch-Only Root Data:' % wltRootIDConcat) + for j in pkccET16Lines: + self.dispText += '
%s' % (j) - frmAutoFee = makeHorizFrame([self.radioAutoFee, self.lblSlider, toolTipAutoFee]) - frmFeeLayout.addWidget(frmAutoFee, 1, 0, 1, 1) - frmFeeLayout.addWidget(self.sliderAutoFee, 2, 0, 1, 2) + titleStr = self.tr('Watch-Only Wallet Export') - frmFeeByte = makeHorizFrame([self.radioFeeByte, self.leFeeByte, \ - toolTipFeeByte, STRETCH, STRETCH]) - frmFeeLayout.addWidget(frmFeeByte, 3, 0, 1, 1) + self.txtLongDescr = QTextBrowser() + self.txtLongDescr.setFont(GETFONT('Fixed', 9)) + self.txtLongDescr.setHtml(self.dispText) + w,h = tightSizeNChar(self.txtLongDescr, 20) + self.txtLongDescr.setMaximumHeight(9.5*h) - frmFlatFee = makeHorizFrame([self.radioFlatFee, self.leFlatFee, \ - toolTipFlatFee, STRETCH, STRETCH]) - frmFeeLayout.addWidget(frmFlatFee, 4, 0, 1, 1) + def clippy(): + clipb = QApplication.clipboard() + clipb.clear() + clipb.setText(str(self.txtLongDescr.toPlainText())) + clipboardLbl.setText(self.tr('Copied!')) - frmCheckAdjust = makeHorizFrame([self.checkAdjust, feeToolTip, STRETCH]) - frmFeeLayout.addWidget(frmCheckAdjust, 5, 0, 1, 2) + self.connect(clipboardBtn, SIGNAL('clicked()'), clippy) - feeRadio(feeOpt) - self.frmFee = QFrame() - self.frmFee.setFrameStyle(STYLE_RAISED) - self.frmFee.setLayout(frmFeeLayout) + lblDescr = QRichLabel(self.tr( + '
Export Watch-Only ' + 'Wallet: %s
' + '
' + 'Use a watching-only wallet on an online computer to distribute ' + 'payment addresses, verify transactions and monitor balances, but ' + 'without the ability to move the funds.' % (htmlColor('TextBlue'), wlt.uniqueIDB58))) - ######### - #change + lblTopHalf = QRichLabel(self.tr( + '
Entire Wallet File
' + '
' + '(Recommended) ' + 'An exact copy of your wallet file but without any of the private ' + 'signing keys. All existing comments and labels will be carried ' + 'with the file. Use this option if it is easy to transfer files ' + 'from this system to the target system.' % htmlColor('TextBlue'))) - def setChangeType(changeType): - self.changeType = changeType + lblBotHalf = QRichLabel(self.tr( + '
Only Root Data
' + '
' + 'Same as above, but only five lines of text that are easy to ' + 'print, email inline, or copy by hand. Only produces the ' + 'wallet addresses. No comments or labels are carried with ' + 'it.')) - from ui.AddressTypeSelectDialog import AddressLabelFrame - changeType = self.main.getSettingOrSetDefault('Default_ChangeType', DEFAULT_CHANGE_TYPE) - self.changeTypeFrame = AddressLabelFrame(self.main, setChangeType) + btnDone = QPushButton(self.tr('Done')) + self.connect(btnDone, SIGNAL('clicked()'), self.accept) - def changeRadio(strArg): - self.radioAutoChange.setChecked(False) - self.radioForce.setChecked(False) - self.changeTypeFrame.getFrame().setEnabled(False) - if strArg == 'Auto': - self.radioAutoChange.setChecked(True) - self.changeType = 'Auto' - elif strArg == 'Force': - self.radioForce.setChecked(True) - self.changeTypeFrame.getFrame().setEnabled(True) - self.changeType = self.changeTypeFrame.getType() - else: - self.changeTypeFrame.setType(strArg) - self.radioForce.setChecked(True) - self.changeTypeFrame.getFrame().setEnabled(True) - self.changeType = self.changeTypeFrame.getType() + frmButtons = makeVertFrame([clipboardBtn, + expDataButton, + printWODataButton, + clipboardLbl, + 'Stretch']) + layoutBottom = QHBoxLayout() + layoutBottom.addWidget(frmButtons, 0) + layoutBottom.addItem(QSpacerItem(5,5)) + layoutBottom.addWidget(self.txtLongDescr, 1) + layoutBottom.setSpacing(5) - def changeCallbck(strArg): - def callbck(): - return changeRadio(strArg) - return callbck + layout.addWidget(lblDescr) + layout.addItem(QSpacerItem(10, 10)) + layout.addWidget(HLINE()) + layout.addWidget(lblTopHalf, 1) + layout.addWidget(makeHorizFrame(['Stretch', expWltButton, 'Stretch'])) + layout.addItem(QSpacerItem(20, 20)) + layout.addWidget(HLINE()) + layout.addWidget(lblBotHalf, 1) + layout.addLayout(layoutBottom) + layout.addItem(QSpacerItem(20, 20)) + layout.addWidget(HLINE()) + layout.addWidget(makeHorizFrame(['Stretch', btnDone])) + layout.setSpacing(3) - labelChange = QRichLabel(self.tr("Change Address Type
")) + self.setLayout(layout) + self.setMinimumWidth(600) - self.radioAutoChange = QRadioButton(self.tr("Auto change")) - self.connect(self.radioAutoChange, SIGNAL('clicked()'), changeCallbck('Auto')) - toolTipAutoChange = self.main.createToolTipWidget(self.tr( - "Change address type will match the address type of recipient " - "addresses.
" + # TODO: Dear god this is terrible, but for my life I cannot figure + # out how to move the vbar, because you can't do it until + # the dialog is drawn which doesn't happen til after __init__. + self.callLater(0.05, self.resizeEvent) - "Favors P2SH when recipients are heterogenous.
" + self.setWindowTitle(titleStr) - "Will create nested SegWit change if inputs are SegWit and " - "recipient are P2SH.

" - "Pre 0.96 Armory cannot spend from P2SH address types" - )) + def resizeEvent(self, ev=None): + super(DlgExpWOWltData, self).resizeEvent(ev) + vbar = self.txtLongDescr.verticalScrollBar() + vbar.setValue(vbar.minimum()) - self.radioForce = QRadioButton(self.tr("Force a script type:")) - self.connect(self.radioForce, SIGNAL('clicked()'), changeCallbck('Force')) - changeRadio(changeType) + # The function that is executed when the user wants to back up the full + # watch-only wallet to a file. + def clickedExpWlt(self): + currPath = self.wlt.walletPath + if not self.wlt.watchingOnly: + pieces = os.path.splitext(currPath) + currPath = pieces[0] + '_WatchOnly' + pieces[1] - frmChangeLayout = QGridLayout() - frmChangeLayout.addWidget(labelChange, 0, 0, 1, 1) + saveLoc = self.main.getFileSave('Save Watching-Only Copy', \ + defaultFilename=currPath) + if not saveLoc.endswith('.wallet'): + saveLoc += '.wallet' - frmAutoChange = makeHorizFrame([self.radioAutoChange, \ - toolTipAutoChange, STRETCH]) - frmChangeLayout.addWidget(frmAutoChange, 1, 0, 1, 1) + if not self.wlt.watchingOnly: + self.wlt.forkOnlineWallet(saveLoc, self.wlt.labelName, \ + '(Watching-Only) ' + self.wlt.labelDescr) + else: + self.wlt.writeFreshWalletFile(saveLoc) - frmForce = makeHorizFrame([self.radioForce, self.changeTypeFrame.getFrame()]) - frmChangeLayout.addWidget(frmForce, 2, 0, 1, 1) - self.frmChange = QFrame() - self.frmChange.setFrameStyle(STYLE_RAISED) - self.frmChange.setLayout(frmChangeLayout) - ######### - #receive addr type + # The function that is executed when the user wants to save the watch-only + # data to a file. + def clickedExpData(self): + self.main.makeWalletCopy(self, self.wlt, 'PKCC', 'rootpubkey') - labelAddrType = QRichLabel(self.tr("Preferred Receive Address Type")) - def setAddrType(addrType): - self.addrType = addrType + # The function that is executed when the user wants to print the watch-only + # data. + def clickedPrintWOData(self): + self.result = DlgWODataPrintBackup(self, self.main, self.wlt).exec_() - self.addrType = self.main.getSettingOrSetDefault('Default_ReceiveType', DEFAULT_RECEIVE_TYPE) - self.addrTypeFrame = AddressLabelFrame(self.main, setAddrType) - self.addrTypeFrame.setType(self.addrType) - frmAddrLayout = QGridLayout() - frmAddrLayout.addWidget(labelAddrType, 0, 0, 1, 1) +################################################################################ +# Class that handles the printing of the watch-only wallet data. The formatting +# is mostly the same as a normal paper backup. Note that neither fragmented +# backups nor SecurePrint are used. +class DlgWODataPrintBackup(ArmoryDialog): + """ + Open up a "Make Paper Backup" dialog, so the user can print out a hard + copy of whatever data they need to recover their wallet should they lose + it. + """ + def __init__(self, parent, main, wlt): + super(DlgWODataPrintBackup, self).__init__(parent, main) - frmAddrTypeSelect = makeHorizFrame([self.addrTypeFrame.getFrame()]) + self.wlt = wlt - frmAddrLayout.addWidget(frmAddrTypeSelect, 2, 0, 1, 1) + # Create the scene and the view. + self.scene = SimplePrintableGraphicsScene(self, self.main) + self.view = QGraphicsView() + self.view.setRenderHint(QPainter.TextAntialiasing) + self.view.setScene(self.scene.getScene()) - self.frmAddrType = QFrame() - self.frmAddrType.setFrameStyle(STYLE_RAISED) - self.frmAddrType.setLayout(frmAddrLayout) + # Label displayed above the sheet to be printed. + lblDescr = QRichLabel(self.tr( + 'Print Watch-Only Wallet Root

' + 'The lines below are sufficient to calculate public keys ' + 'for every private key ever produced by the full wallet. ' + 'Importing this data to an online computer is sufficient ' + 'to receive and verify transactions, and monitor balances, ' + 'but without the ability to spend the funds.')) + lblDescr.setContentsMargins(5, 5, 5, 5) + frmDescr = makeHorizFrame([lblDescr], STYLE_RAISED) - ############################################################################# - def accept(self, *args): + # Buttons shown below the sheet to be printed. + self.btnPrint = QPushButton('&Print...') + self.btnPrint.setMinimumWidth(3 * tightSizeStr(self.btnPrint, 'Print...')[0]) + self.btnCancel = QPushButton('&Cancel') + self.connect(self.btnPrint, SIGNAL(CLICKED), self.print_) + self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) + frmButtons = makeHorizFrame([self.btnCancel, STRETCH, self.btnPrint]) - if self.chkManageSatoshi.isChecked(): - # Check valid path is supplied for bitcoin installation - pathExe = unicode(self.edtSatoshiExePath.text()).strip() - if len(pathExe) > 0: - if not os.path.exists(pathExe): - exeName = 'bitcoin-qt.exe' if OS_WINDOWS else 'bitcoin-qt' - QMessageBox.warning(self, self.tr('Invalid Path'),self.tr( - 'The path you specified for the Bitcoin software installation ' - 'does not exist. Please select the directory that contains %s ' - 'or leave it blank to have Armory search the default location ' - 'for your operating system' % exeName), QMessageBox.Ok) - return - if os.path.isfile(pathExe): - pathExe = os.path.dirname(pathExe) - self.main.writeSetting('SatoshiExe', pathExe) - else: - self.main.settings.delete('SatoshiExe') - - # Check path is supplied for bitcoind home directory - pathHome = str(self.edtSatoshiHomePath.text()).strip() - if len(pathHome) > 0: - if not os.path.exists(pathHome): - QMessageBox.warning(self, self.tr('Invalid Path'), self.tr( - 'The path you specified for the Bitcoin software home directory ' - 'does not exist. Only specify this directory if you use a ' - 'non-standard "-datadir=" option when running Bitcoin Core or ' - 'bitcoind. If you leave this field blank, the following ' - 'path will be used:

%s' % BTC_HOME_DIR), QMessageBox.Ok) - return - self.main.writeSetting('SatoshiDatadir', pathHome) - else: - self.main.settings.delete('SatoshiDatadir') - - # Check path is supplied for armory db directory - pathDbdir = str(self.edtArmoryDbdir.text()).strip() - if len(pathDbdir) > 0: - if not os.path.exists(pathDbdir): - QMessageBox.warning(self, self.tr('Invalid Path'), self.tr( - 'The path you specified for Armory\'s database directory ' - 'does not exist. Only specify this directory if you want ' - 'Armory to save its local database to a custom path. ' - 'If you leave this field blank, the following ' - 'path will be used:

%s' % ARMORY_DB_DIR), QMessageBox.Ok) - return - self.main.writeSetting('ArmoryDbdir', pathDbdir) - else: - self.main.settings.delete('ArmoryDbdir') + # Draw the sheet for the first time. + self.redrawBackup() + # Lay out the dialog. + layout = QVBoxLayout() + layout.addWidget(frmDescr) + layout.addWidget(self.view) + layout.addWidget(frmButtons) + setLayoutStretch(layout, 0, 1, 0) + self.setLayout(layout) + self.setWindowIcon(QIcon('./img/printer_icon.png')) + self.setWindowTitle('Print Watch-Only Root') - self.main.writeSetting('ManageSatoshi', self.chkManageSatoshi.isChecked()) + # Apparently I can't programmatically scroll until after it's painted + def scrollTop(): + vbar = self.view.verticalScrollBar() + vbar.setValue(vbar.minimum()) + self.callLater(0.01, scrollTop) - # Reset the DNAA flag as needed - askuriDNAA = self.chkAskURIAtStartup.isChecked() - self.main.writeSetting('DNAA_DefaultApp', not askuriDNAA) - if not self.main.setPreferredDateFormat(str(self.edtDateFormat.text())): - return + # Class called to redraw the print "canvas" when the data changes. + def redrawBackup(self): + self.createPrintScene() + self.view.update() - if not self.usermodeInit == self.cmbUsermode.currentIndex(): - self.main.setUserMode(self.cmbUsermode.currentIndex()) - if not self.langInit == self.cmbLang.currentText()[-3:-1]: - self.main.setLang(LANGUAGES[self.cmbLang.currentIndex()]) + # Class that handles the actual printing code. + def print_(self): + LOGINFO('Printing!') + self.printer = QPrinter(QPrinter.HighResolution) + self.printer.setPageSize(QPrinter.Letter) - if self.chkMinOrClose.isChecked(): - self.main.writeSetting('MinimizeOrClose', 'Minimize') - else: - self.main.writeSetting('MinimizeOrClose', 'Close') + if QPrintDialog(self.printer).exec_(): + painter = QPainter(self.printer) + painter.setRenderHint(QPainter.TextAntialiasing) - self.main.writeSetting('MinimizeOnOpen', self.chkMinOnOpen.isChecked()) + self.createPrintScene() + self.scene.getScene().render(painter) + painter.end() + self.accept() - # self.main.writeSetting('LedgDisplayFee', self.chkInclFee.isChecked()) - self.main.writeSetting('NotifyBtcIn', self.chkBtcIn.isChecked()) - self.main.writeSetting('NotifyBtcOut', self.chkBtcOut.isChecked()) - self.main.writeSetting('NotifyDiscon', self.chkDiscon.isChecked()) - self.main.writeSetting('NotifyReconn', self.chkReconn.isChecked()) + # Class that lays out the actual print "canvas" to be printed. + def createPrintScene(self): + # Do initial setup. + self.scene.gfxScene.clear() + self.scene.resetCursor() - #fee - self.main.writeSetting('FeeOption', self.feeOpt) - self.main.writeSetting('Default_FeeByte', str(self.leFeeByte.text())) - self.main.writeSetting('Default_Fee', str2coin(str(self.leFlatFee.text()))) - self.main.writeSetting('AdjustFee', self.checkAdjust.isChecked()) - self.main.writeSetting('Default_FeeByte_BlocksToConfirm', - self.sliderAutoFee.value()) + # Draw the background paper? + pr = self.scene.pageRect() + self.scene.drawRect(pr.width(), pr.height(), edgeColor=None, \ + fillColor=QColor(255, 255, 255)) + self.scene.resetCursor() - #change - self.main.writeSetting('Default_ChangeType', self.changeType) + INCH = self.scene.INCH + MARGIN = self.scene.MARGIN_PIXELS + wrap = 0.9 * self.scene.pageRect().width() - #addr type - self.main.writeSetting('Default_ReceiveType', self.addrType) - armoryengine.ArmoryUtils.DEFAULT_ADDR_TYPE = self.addrType + # Start drawing the page. + if USE_TESTNET or USE_REGTEST: + self.scene.drawPixmapFile('./img/armory_logo_green_h56.png') + else: + self.scene.drawPixmapFile('./img/armory_logo_h36.png') + self.scene.newLine() - try: - self.main.createCombinedLedger() - except: - pass - super(DlgSettings, self).accept(*args) + warnMsg = self.tr( + 'WARNING: This is not ' + 'a wallet backup! ' + '

Please make a regular digital or paper backup of your wallet ' + 'to keep it protected! This data simply lets you ' + 'monitor the funds in this wallet but gives you no ability to move any ' + 'funds.') + self.scene.drawText(warnMsg, GETFONT('Var', 9), wrapWidth=wrap) + self.scene.newLine(extra_dy=20) + self.scene.drawHLine() + self.scene.newLine(extra_dy=20) - ############################################################################# - def setUsermodeDescr(self): - strDescr = '' - modeIdx = self.cmbUsermode.currentIndex() - if modeIdx == USERMODE.Standard: - strDescr += \ - self.tr('"Standard" is for users that only need the core set of features ' - 'to send and receive bitcoins. This includes maintaining multiple ' - 'wallets, wallet encryption, and the ability to make backups ' - 'of your wallets.') - elif modeIdx == USERMODE.Advanced: - strDescr += \ - self.tr('"Advanced" mode provides ' - 'extra Armory features such as private key ' - 'importing & sweeping, message signing, and the offline wallet ' - 'interface. But, with advanced features come advanced risks...') - elif modeIdx == USERMODE.Expert: - strDescr += \ - self.tr('"Expert" mode is similar to "Advanced" but includes ' - 'access to lower-level info about transactions, scripts, keys ' - 'and network protocol. Most extra functionality is geared ' - 'towards Bitcoin software developers.') - self.lblUsermodeDescr.setText(strDescr) + # Print the wallet info. + colRect, rowHgt = self.scene.drawColumn(['Watch-Only Root Data', + 'Wallet ID:', + 'Wallet Name:']) + self.scene.moveCursor(15, 0) + colRect, rowHgt = self.scene.drawColumn(['', + self.wlt.uniqueIDB58, + self.wlt.labelName]) + self.scene.moveCursor(15, colRect.y() + colRect.height(), absolute=True) - ############################################################################# - def doExampleDate(self, qstr=None): - fmtstr = str(self.edtDateFormat.text()) - try: - self.lblDateExample.setText(self.tr('Sample: ') + unixTimeToFormatStr(self.exampleUnixTime, fmtstr)) - self.isValidFormat = True - except: - self.lblDateExample.setText(self.tr('Sample: [[invalid date format]]')) - self.isValidFormat = False - - ############################################################################# - def clickChkManage(self): - self.edtSatoshiExePath.setEnabled(self.chkManageSatoshi.isChecked()) - self.btnSetExe.setEnabled(self.chkManageSatoshi.isChecked()) + # Display warning about unprotected key data. + self.scene.newLine(extra_dy=20) + self.scene.drawHLine() + self.scene.newLine(extra_dy=20) + # Draw the description of the data. + descrMsg = self.tr( + 'The following five lines are sufficient to reproduce all public ' + 'keys matching the private keys produced by the full wallet.') + self.scene.drawText(descrMsg, GETFONT('var', 8), wrapWidth=wrap) + self.scene.newLine(extra_dy=10) -################################################################################ -class DlgExportTxHistory(ArmoryDialog): - def __init__(self, parent=None, main=None): - super(DlgExportTxHistory, self).__init__(parent, main) + # Prepare the data. + self.wltRootIDConcat, self.pkccET16Lines = \ + self.wlt.getRootPKCCBackupData(True) + Lines = [] + Prefix = [] + Prefix.append('Watch-Only Root ID:'); Lines.append(self.wltRootIDConcat) + Prefix.append('Watch-Only Root:'); Lines.append(self.pkccET16Lines[0]) + Prefix.append(''); Lines.append(self.pkccET16Lines[1]) + Prefix.append(''); Lines.append(self.pkccET16Lines[2]) + Prefix.append(''); Lines.append(self.pkccET16Lines[3]) - self.reversedLBdict = {v:k for k,v in self.main.lockboxIDMap.items()} + # Draw the prefix data. + origX, origY = self.scene.getCursorXY() + self.scene.moveCursor(10, 0) + colRect, rowHgt = self.scene.drawColumn(['' + l + '' \ + for l in Prefix]) - self.cmbWltSelect = QComboBox() - self.cmbWltSelect.clear() - self.cmbWltSelect.addItem(self.tr('My Wallets')) - self.cmbWltSelect.addItem(self.tr('Offline Wallets')) - self.cmbWltSelect.addItem(self.tr('Other Wallets')) + # Draw the data. + nudgeDown = 2 # because the differing font size makes it look unaligned + self.scene.moveCursor(10, nudgeDown) + self.scene.drawColumn(Lines, + font=GETFONT('Fixed', 8, bold=True), \ + rowHeight=rowHgt, + useHtml=False) - self.cmbWltSelect.insertSeparator(4) - self.cmbWltSelect.addItem(self.tr('All Wallets')) - self.cmbWltSelect.addItem(self.tr('All Lockboxes')) - self.cmbWltSelect.addItem(self.tr('All Wallets & Lockboxes')) + # Draw the rectangle around the data. + self.scene.moveCursor(MARGIN, colRect.y() - 2, absolute=True) + width = self.scene.pageRect().width() - 2 * MARGIN + self.scene.drawRect(width, colRect.height() + 7, \ + edgeColor=QColor(0, 0, 0), fillColor=None) - self.cmbWltSelect.insertSeparator(8) - for wltID in self.main.walletIDList: - self.cmbWltSelect.addItem(self.main.walletMap[wltID].labelName) + # Draw the QR-related text below the data. + self.scene.newLine(extra_dy=30) + self.scene.drawText(self.tr( + 'The following QR code is for convenience only. It contains the ' + 'exact same data as the five lines above. If you copy this data ' + 'by hand, you can safely ignore this QR code.'), wrapWidth=4 * INCH) - self.cmbWltSelect.insertSeparator(8 + len(self.main.walletIDList)) - for idx in self.reversedLBdict: - self.cmbWltSelect.addItem(self.main.allLockboxes[idx].shortName) + # Draw the QR code. + self.scene.moveCursor(20, 0) + x, y = self.scene.getCursorXY() + edgeRgt = self.scene.pageRect().width() - MARGIN + edgeBot = self.scene.pageRect().height() - MARGIN + qrSize = max(1.5 * INCH, min(edgeRgt - x, edgeBot - y, 2.0 * INCH)) + self.scene.drawQR('\n'.join(Lines), qrSize) + self.scene.newLine(extra_dy=25) + # Clear the data and create a vertical scroll bar. + Lines = None + vbar = self.view.verticalScrollBar() + vbar.setValue(vbar.minimum()) + self.view.update() - self.cmbSortSelect = QComboBox() - self.cmbSortSelect.clear() - self.cmbSortSelect.addItem(self.tr('Date (newest first)')) - self.cmbSortSelect.addItem(self.tr('Date (oldest first)')) +################################################################################ +class DlgFragBackup(ArmoryDialog): - self.cmbFileFormat = QComboBox() - self.cmbFileFormat.clear() - self.cmbFileFormat.addItem(self.tr('Comma-Separated Values (*.csv)')) + ############################################################################# + def __init__(self, parent, main, wlt): + super(DlgFragBackup, self).__init__(parent, main) + self.wlt = wlt - fmt = self.main.getPreferredDateFormat() - ttipStr = self.tr('Use any of the following symbols:
') - fmtSymbols = [x[0] + ' = ' + x[1] for x in FORMAT_SYMBOLS] - ttipStr += '
'.join(fmtSymbols) + lblDescrTitle = QRichLabel(self.tr( + 'Create M-of-N Fragmented Backup of "%s" (%s)' % (wlt.labelName, wlt.uniqueIDB58)), doWrap=False) + lblDescrTitle.setContentsMargins(5, 5, 5, 5) - self.edtDateFormat = QLineEdit() - self.edtDateFormat.setText(fmt) - self.ttipFormatDescr = self.main.createToolTipWidget(ttipStr) + self.lblAboveFrags = QRichLabel('') + self.lblAboveFrags.setContentsMargins(10, 0, 10, 0) - self.lblDateExample = QRichLabel('', doWrap=False) - self.connect(self.edtDateFormat, SIGNAL('textEdited(QString)'), self.doExampleDate) - self.doExampleDate() - self.btnResetFormat = QPushButton(self.tr("Reset to Default")) + frmDescr = makeVertFrame([lblDescrTitle, self.lblAboveFrags], \ + STYLE_RAISED) - def doReset(): - self.edtDateFormat.setText(DEFAULT_DATE_FORMAT) - self.doExampleDate() - self.connect(self.btnResetFormat, SIGNAL(CLICKED), doReset) + self.fragDisplayLastN = 0 + self.fragDisplayLastM = 0 + self.maxM = 5 if not self.main.usermode == USERMODE.Expert else 8 + self.maxN = 6 if not self.main.usermode == USERMODE.Expert else 12 + self.currMinN = 2 + self.maxmaxN = 12 + self.comboM = QComboBox() + self.comboN = QComboBox() - # Add the usual buttons - self.btnCancel = QPushButton(self.tr("Cancel")) - self.btnAccept = QPushButton(self.tr("Export")) - self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) - self.connect(self.btnAccept, SIGNAL(CLICKED), self.accept) - btnBox = makeHorizFrame([STRETCH, self.btnCancel, self.btnAccept]) + for M in range(2, self.maxM + 1): + self.comboM.addItem(str(M)) + for N in range(self.currMinN, self.maxN + 1): + self.comboN.addItem(str(N)) - dlgLayout = QGridLayout() + self.comboM.setCurrentIndex(1) + self.comboN.setCurrentIndex(2) - i = 0 - dlgLayout.addWidget(QRichLabel(self.tr('Export Format:')), i, 0) - dlgLayout.addWidget(self.cmbFileFormat, i, 1) + def updateM(): + self.updateComboN() + self.createFragDisplay() - i += 1 - dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + updateN = self.createFragDisplay - i += 1 - dlgLayout.addWidget(QRichLabel(self.tr('Wallet(s) to export:')), i, 0) - dlgLayout.addWidget(self.cmbWltSelect, i, 1) + self.connect(self.comboM, SIGNAL('activated(int)'), updateM) + self.connect(self.comboN, SIGNAL('activated(int)'), updateN) + self.comboM.setMinimumWidth(30) + self.comboN.setMinimumWidth(30) - i += 1 - dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + btnAccept = QPushButton(self.tr('Close')) + self.connect(btnAccept, SIGNAL(CLICKED), self.accept) + frmBottomBtn = makeHorizFrame([STRETCH, btnAccept]) - i += 1 - dlgLayout.addWidget(QRichLabel(self.tr('Sort Table:')), i, 0) - dlgLayout.addWidget(self.cmbSortSelect, i, 1) - i += 1 - dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + # We will hold all fragments here, in SBD objects. Destroy all of them + # before the dialog exits + self.secureRoot = self.wlt.addrMap['ROOT'].binPrivKey32_Plain.copy() + self.secureChain = self.wlt.addrMap['ROOT'].chaincode.copy() + self.secureMtrx = [] - i += 1 - dlgLayout.addWidget(QRichLabel(self.tr('Date Format:')), i, 0) - fmtfrm = makeHorizFrame([self.lblDateExample, STRETCH, self.ttipFormatDescr]) - dlgLayout.addWidget(fmtfrm, i, 1) + testChain = DeriveChaincodeFromRootKey(self.secureRoot) + if testChain == self.secureChain: + self.noNeedChaincode = True + self.securePrint = self.secureRoot + else: + self.securePrint = self.secureRoot + self.secureChain - i += 1 - dlgLayout.addWidget(self.btnResetFormat, i, 0) - dlgLayout.addWidget(self.edtDateFormat, i, 1) + self.chkSecurePrint = QCheckBox(self.trUtf8(u'Use SecurePrint\u200b\u2122 ' + 'to prevent exposing keys to printer or other devices')) - i += 1 - dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + self.scrollArea = QScrollArea() + self.createFragDisplay() + self.scrollArea.setWidgetResizable(True) - i += 1 - dlgLayout.addWidget(HLINE(), i, 0, 1, 2) + self.ttipSecurePrint = self.main.createToolTipWidget(self.trUtf8( + u'SecurePrint\u200b\u2122 encrypts your backup with a code displayed on ' + 'the screen, so that no other devices or processes has access to the ' + 'unencrypted private keys (either network devices when printing, or ' + 'other applications if you save a fragment to disk or USB device). ' + u'You must keep the SecurePrint\u200b\u2122 code with the backup!')) + self.lblSecurePrint = QRichLabel(self.trUtf8( + 'IMPORTANT: You must keep the ' + u'SecurePrint\u200b\u2122 encryption code with your backup! ' + u'Your SecurePrint\u200b\u2122 code is ' + '%s. ' + 'All fragments for a given wallet use the ' + 'same code.' % (htmlColor('TextWarn'), htmlColor('TextBlue'), self.backupData.sppass, \ + htmlColor('TextWarn')))) + self.connect(self.chkSecurePrint, SIGNAL(CLICKED), self.clickChkSP) + self.chkSecurePrint.setChecked(False) + self.lblSecurePrint.setVisible(False) + frmChkSP = makeHorizFrame([self.chkSecurePrint, self.ttipSecurePrint, STRETCH]) - i += 1 - dlgLayout.addWidget(btnBox, i, 0, 1, 2) + dlgLayout = QVBoxLayout() + dlgLayout.addWidget(frmDescr) + dlgLayout.addWidget(self.scrollArea) + dlgLayout.addWidget(frmChkSP) + dlgLayout.addWidget(self.lblSecurePrint) + dlgLayout.addWidget(frmBottomBtn) + setLayoutStretch(dlgLayout, 0, 1, 0, 0, 0) self.setLayout(dlgLayout) + self.setMinimumWidth(650) + self.setMinimumHeight(450) + self.setWindowTitle('Create Backup Fragments') ############################################################################# - def doExampleDate(self, qstr=None): - fmtstr = str(self.edtDateFormat.text()) - try: - exampleDateStr = unixTimeToFormatStr(1030501970, fmtstr) - self.lblDateExample.setText(self.tr('Example: %s' % exampleDateStr)) - self.isValidFormat = True - except: - self.lblDateExample.setText(self.tr('Example: [[invalid date format]]')) - self.isValidFormat = False - - ############################################################################# - def accept(self, *args): - if self.createFile_CSV(): - super(DlgExportTxHistory, self).accept(*args) + def clickChkSP(self): + self.lblSecurePrint.setVisible(self.chkSecurePrint.isChecked()) + self.createFragDisplay() ############################################################################# - def createFile_CSV(self): - if not self.isValidFormat: - QMessageBox.warning(self, self.tr('Invalid date format'), \ - self.tr('Cannot create CSV without a valid format for transaction ' - 'dates and times'), QMessageBox.Ok) - return False - - COL = LEDGERCOLS - - # This was pretty much copied from the createCombinedLedger method... - # I rarely do this, but modularizing this piece is a non-trivial - wltIDList = [] - typelist = [[wid, determineWalletType(self.main.walletMap[wid], self.main)[0]] \ - for wid in self.main.walletIDList] - currIdx = self.cmbWltSelect.currentIndex() - if currIdx >= 8: - idx = currIdx - 8 - if idx < len(self.main.walletIDList): - #picked a single wallet - wltIDList = [self.main.walletIDList[idx]] - else: - #picked a single lockbox - idx -= len(self.main.walletIDList) +1 - wltIDList = [self.reversedLBdict[idx]] - else: - listOffline = [t[0] for t in filter(lambda x: x[1] == WLTTYPES.Offline, typelist)] - listWatching = [t[0] for t in filter(lambda x: x[1] == WLTTYPES.WatchOnly, typelist)] - listCrypt = [t[0] for t in filter(lambda x: x[1] == WLTTYPES.Crypt, typelist)] - listPlain = [t[0] for t in filter(lambda x: x[1] == WLTTYPES.Plain, typelist)] - lockboxIDList = [t for t in self.main.lockboxIDMap] - - if currIdx == 0: - wltIDList = listOffline + listCrypt + listPlain - elif currIdx == 1: - wltIDList = listOffline - elif currIdx == 2: - wltIDList = listWatching - elif currIdx == 4: - wltIDList = self.main.walletIDList - elif currIdx == 5: - wltIDList = lockboxIDList - elif currIdx == 6: - wltIDList = self.main.walletIDList + lockboxIDList - else: - pass - - order = "ascending" - sortTxt = str(self.cmbSortSelect.currentText()) - if 'newest' in sortTxt: - order = "descending" - - totalFunds, spendFunds, unconfFunds = 0, 0, 0 - wltBalances = {} - for wltID in wltIDList: - if wltID in self.main.walletMap: - wlt = self.main.walletMap[wltID] - - totalFunds += wlt.getBalance('Total') - spendFunds += wlt.getBalance('Spendable') - unconfFunds += wlt.getBalance('Unconfirmed') - if order == "ascending": - wltBalances[wltID] = 0 # will be accumulated - else: - wltBalances[wltID] = wlt.getBalance('Total') + def updateComboN(self): + M = int(str(self.comboM.currentText())) + oldN = int(str(self.comboN.currentText())) + self.currMinN = M + self.comboN.clear() - else: - #lockbox - cppwlt = self.main.cppLockboxWltMap[wltID] - totalFunds += cppwlt.getFullBalance() - spendFunds += cppwlt.getSpendableBalance(TheBDM.getTopBlockHeight(), IGNOREZC) - unconfFunds += cppwlt.getUnconfirmedBalance(TheBDM.getTopBlockHeight(), IGNOREZC) - if order == "ascending": - wltBalances[wltID] = 0 # will be accumulated - else: - wltBalances[wltID] = cppwlt.getFullBalance() + for i, N in enumerate(range(self.currMinN, self.maxN + 1)): + self.comboN.addItem(str(N)) - if order == "ascending": - allBalances = 0 + if M > oldN: + self.comboN.setCurrentIndex(0) else: - allBalances = totalFunds - - - #prepare csv file - wltSelectStr = str(self.cmbWltSelect.currentText()).replace(' ', '_') - timestampStr = unixTimeToFormatStr(RightNow(), '%Y%m%d_%H%M') - filenamePrefix = 'ArmoryTxHistory_%s_%s' % (wltSelectStr, timestampStr) - fmtstr = str(self.cmbFileFormat.currentText()) - if 'csv' in fmtstr: - defaultName = filenamePrefix + '.csv' - fullpath = self.main.getFileSave('Save CSV File', \ - ['Comma-Separated Values (*.csv)'], \ - defaultName) + for i, N in enumerate(range(self.currMinN, self.maxN + 1)): + if N == oldN: + self.comboN.setCurrentIndex(i) - if len(fullpath) == 0: - return - f = open(fullpath, 'w') - f.write(self.tr('Export Date: %s\n' % unixTimeToFormatStr(RightNow()))) - f.write(self.tr('Total Funds: %s\n' % coin2str(totalFunds, maxZeros=0).strip())) - f.write(self.tr('Spendable Funds: %s\n' % coin2str(spendFunds, maxZeros=0).strip())) - f.write(self.tr('Unconfirmed Funds: %s\n' % coin2str(unconfFunds, maxZeros=0).strip())) - f.write('\n') + ############################################################################# + def createFragDisplay(self): + M = int(str(self.comboM.currentText())) + N = int(str(self.comboN.currentText())) - f.write(self.tr('Included Wallets:\n')) - for wltID in wltIDList: - if wltID in self.main.walletMap: - wlt = self.main.walletMap[wltID] - f.write('%s,%s\n' % (wltID, wlt.labelName.replace(',', ';'))) - else: - wlt = self.main.allLockboxes[self.main.lockboxIDMap[wltID]] - f.write(self.tr('%s (lockbox),%s\n' % (wltID, wlt.shortName.replace(',', ';')))) - f.write('\n') + #only recompute fragments if M or N changed + if self.fragDisplayLastN != N or \ + self.fragDisplayLastM != M: + self.recomputeFragData() + self.fragDisplayLastN = N + self.fragDisplayLastM = M - headerRow = [self.tr('Date'), self.tr('Transaction ID'), self.tr('Number of Confirmations'), self.tr('Wallet ID'), - self.tr('Wallet Name'), self.tr('Credit'), self.tr('Debit'), self.tr('Fee (paid by this wallet)'), - self.tr('Wallet Balance'), self.tr('Total Balance'), self.tr('Label')] + lblAboveM = QRichLabel(self.tr('Required Fragments '), hAlign=Qt.AlignHCenter, doWrap=False) + lblAboveN = QRichLabel(self.tr('Total Fragments '), hAlign=Qt.AlignHCenter) + frmComboM = makeHorizFrame([STRETCH, QLabel('M:'), self.comboM, STRETCH]) + frmComboN = makeHorizFrame([STRETCH, QLabel('N:'), self.comboN, STRETCH]) - f.write(','.join(unicode(header) for header in headerRow) + '\n') + btnPrintAll = QPushButton(self.tr('Print All Fragments')) + self.connect(btnPrintAll, SIGNAL(CLICKED), self.clickPrintAll) + leftFrame = makeVertFrame([STRETCH, \ + lblAboveM, \ + frmComboM, \ + lblAboveN, \ + frmComboN, \ + STRETCH, \ + HLINE(), \ + btnPrintAll, \ + STRETCH], STYLE_STYLED) - #get history - historyLedger = TheBDM.bdv().getHistoryForWalletSelection(wltIDList, order) + layout = QHBoxLayout() + layout.addWidget(leftFrame) - # Each value in COL.Amount will be exactly how much the wallet balance - # increased or decreased as a result of this transaction. - ledgerTable = self.main.convertLedgerToTable(historyLedger, - showSentToSelfAmt=True) + for f in range(N): + layout.addWidget(self.createFragFrm(f)) - # Sort the data chronologically first, compute the running balance for - # each row, then sort it the way that was requested by the user. - for row in ledgerTable: - if row[COL.toSelf] == False: - rawAmt = str2coin(row[COL.Amount]) - else: - #if SentToSelf, balance and total rolling balance should only take fee in account - rawAmt, fee_byte = getFeeForTx(hex_to_binary(row[COL.TxHash])) - rawAmt = -1 * rawAmt - if order == "ascending": - wltBalances[row[COL.WltID]] += rawAmt - allBalances += rawAmt + frmScroll = QFrame() + frmScroll.setFrameStyle(STYLE_SUNKEN) + frmScroll.setStyleSheet('QFrame { background-color : %s }' % \ + htmlColor('SlightBkgdDark')) + frmScroll.setLayout(layout) + self.scrollArea.setWidget(frmScroll) - row.append(wltBalances[row[COL.WltID]]) - row.append(allBalances) + BLUE = htmlColor('TextBlue') + self.lblAboveFrags.setText(self.tr( + 'Any %d of these ' + '%d' + 'fragments are sufficient to restore your wallet, and each fragment ' + 'has the ID, %s. All fragments with the ' + 'same fragment ID are compatible with each other!' % (BLUE, M, BLUE, N, BLUE, self.fragPrefixStr))) - if order == "descending": - wltBalances[row[COL.WltID]] -= rawAmt - allBalances -= rawAmt + ############################################################################# + def createFragFrm(self, idx): - for row in ledgerTable: - vals = [] + doMask = self.chkSecurePrint.isChecked() + M = int(str(self.comboM.currentText())) + N = int(str(self.comboN.currentText())) - fmtstr = str(self.edtDateFormat.text()) - unixTime = row[COL.UnixTime] - vals.append(unixTimeToFormatStr(unixTime, fmtstr)) - vals.append(hex_switchEndian(row[COL.TxHash])) - vals.append(row[COL.NumConf]) - vals.append(row[COL.WltID]) - if row[COL.WltID] in self.main.walletMap: - vals.append(self.main.walletMap[row[COL.WltID]].labelName.replace(',', ';')) - else: - vals.append(self.main.allLockboxes[self.main.lockboxIDMap[row[COL.WltID]]].shortName.replace(',', ';')) - - wltEffect = row[COL.Amount] - txFee, fee_byte = getFeeForTx(hex_to_binary(row[COL.TxHash])) - if float(wltEffect) >= 0: - if row[COL.toSelf] == False: - vals.append(wltEffect.strip()) - vals.append('') - vals.append('') - else: - vals.append(wltEffect.strip() + ' (STS)') - vals.append('') - vals.append(coin2str(txFee).strip()) - else: - vals.append('') - vals.append(wltEffect.strip()[1:]) # remove negative sign - vals.append(coin2str(txFee).strip()) + lblFragID = QRichLabel(self.tr('Fragment ID:
%s-%s
' % \ + (str(self.fragPrefixStr), str(idx + 1)))) + # lblWltID = QRichLabel('(%s)' % self.wlt.uniqueIDB58) + lblFragPix = QImageLabel(self.fragPixmapFn, size=(72, 72)) + if doMask: + ys = self.secureMtrxCrypt[idx][1].toBinStr()[:42] + else: + ys = self.secureMtrx[idx][1].toBinStr()[:42] - vals.append(coin2str(row[-2])) - vals.append(coin2str(row[-1])) - vals.append(row[COL.Comment]) + easyYs1 = makeSixteenBytesEasy(ys[:16 ]) + easyYs2 = makeSixteenBytesEasy(ys[ 16:32]) - f.write('%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,"%s"\n' % tuple(vals)) - - f.close() - return True - - -################################################################################ -# STUB STUB STUB STUB STUB -class ArmoryPref(object): - """ - Create a class that will handle arbitrary preferences for Armory. This - means that I can just create maps/lists of preferences, and auto-include - them in the preferences dialog, and know how to set/get them. This will - be subclassed for each unique/custom preference type that is needed. - """ - def __init__(self, prefName, dispStr, setType, defaultVal, validRange, descr, ttip, usermodes=None): - self.preference = prefName - self.displayStr = dispStr - self.preferType = setType - self.defaultVal = defaultVal - self.validRange = validRange - self.description = descr - self.ttip = ttip - - # Some options may only be displayed for certain usermodes - self.users = usermodes - if usermodes == None: - self.users = set([USERMODE.Standard, USERMODE.Advanced, USERMODE.Expert]) - - if self.preferType == 'str': - self.entryObj = QLineEdit() - elif self.preferType == 'num': - self.entryObj = QLineEdit() - elif self.preferType == 'file': - self.entryObj = QLineEdit() - elif self.preferType == 'bool': - self.entryObj = QCheckBox() - elif self.preferType == 'combo': - self.entryObj = QComboBox() - - - def setEntryVal(self): - pass - - def readEntryVal(self): - pass - - - def setWidthChars(self, nChar): - self.entryObj.setMinimumWidth(relaxedSizeNChar(self.entryObj, nChar)[0]) - - def render(self): - """ - Return a map of qt objects to insert into the frame - """ - toDraw = [] - row = 0 - if len(self.description) > 0: - toDraw.append([QRichLabel(self.description), row, 0, 1, 4]) - row += 1 - - -################################################################################ -class QRadioButtonBackupCtr(QRadioButton): - def __init__(self, parent, txt, index): - super(QRadioButtonBackupCtr, self).__init__(txt) - self.parent = parent - self.index = index - - - def enterEvent(self, ev): - pass - # self.parent.setDispFrame(self.index) - # self.setStyleSheet('QRadioButton { background-color : %s }' % \ - # htmlColor('SlightBkgdDark')) - - def leaveEvent(self, ev): - pass - # self.parent.setDispFrame(-1) - # self.setStyleSheet('QRadioButton { background-color : %s }' % \ - # htmlColor('Background')) - - -################################################################################ -class DlgBackupCenter(ArmoryDialog): - - ############################################################################# - def __init__(self, parent, main, wlt): - super(DlgBackupCenter, self).__init__(parent, main) - - self.wlt = wlt - wltID = wlt.uniqueIDB58 - wltName = wlt.labelName - - self.walletBackupFrame = WalletBackupFrame(parent, main) - self.walletBackupFrame.setWallet(wlt) - self.btnDone = QPushButton(self.tr('Done')) - self.connect(self.btnDone, SIGNAL(CLICKED), self.reject) - frmBottomBtns = makeHorizFrame([STRETCH, self.btnDone]) - - layoutDialog = QVBoxLayout() - - layoutDialog.addWidget(self.walletBackupFrame) - - layoutDialog.addWidget(frmBottomBtns) - - self.setLayout(layoutDialog) - self.setWindowTitle(self.tr("Backup Center")) - self.setMinimumSize(640, 350) - -################################################################################ -class DlgSimpleBackup(ArmoryDialog): - def __init__(self, parent, main, wlt): - super(DlgSimpleBackup, self).__init__(parent, main) - - self.wlt = wlt - - lblDescrTitle = QRichLabel(self.tr( - 'Protect Your Bitcoins -- Make a Wallet Backup!')) - - lblDescr = QRichLabel(self.tr( - 'A failed hard-drive or forgotten passphrase will lead to ' - 'permanent loss of bitcoins! Luckily, Armory wallets only ' - 'need to be backed up one time, and protect you in both ' - 'of these events. If you\'ve ever forgotten a password or had ' - 'a hardware failure, make a backup!')) - - # ## Paper - lblPaper = QRichLabel(self.tr( - 'Use a printer or pen-and-paper to write down your wallet "seed."')) - btnPaper = QPushButton(self.tr('Make Paper Backup')) - - # ## Digital - lblDigital = QRichLabel(self.tr( - 'Create an unencrypted copy of your wallet file, including imported ' - 'addresses.')) - btnDigital = QPushButton(self.tr('Make Digital Backup')) - - # ## Other - btnOther = QPushButton(self.tr('See Other Backup Options')) - - def backupDigital(): - if self.main.digitalBackupWarning(): - self.main.makeWalletCopy(self, self.wlt, 'Decrypt', 'decrypt') - self.accept() - - def backupPaper(): - OpenPaperBackupWindow('Single', self, self.main, self.wlt) - self.accept() - - def backupOther(): - self.accept() - DlgBackupCenter(self, self.main, self.wlt).exec_() - - self.connect(btnPaper, SIGNAL(CLICKED), backupPaper) - self.connect(btnDigital, SIGNAL(CLICKED), backupDigital) - self.connect(btnOther, SIGNAL(CLICKED), backupOther) - - layout = QGridLayout() - - layout.addWidget(lblPaper, 0, 0) - layout.addWidget(btnPaper, 0, 2) - - layout.addWidget(HLINE(), 1, 0, 1, 3) - - layout.addWidget(lblDigital, 2, 0) - layout.addWidget(btnDigital, 2, 2) - - layout.addWidget(HLINE(), 3, 0, 1, 3) - - layout.addWidget(makeHorizFrame([STRETCH, btnOther, STRETCH]), 4, 0, 1, 3) - - # layout.addWidget( VLINE(), 0,1, 5,1) - - layout.setContentsMargins(10, 5, 10, 5) - setLayoutStretchRows(layout, 1, 0, 1, 0, 0) - setLayoutStretchCols(layout, 1, 0, 0) - - frmGrid = QFrame() - frmGrid.setFrameStyle(STYLE_PLAIN) - frmGrid.setLayout(layout) - - btnClose = QPushButton(self.tr('Done')) - self.connect(btnClose, SIGNAL(CLICKED), self.accept) - frmClose = makeHorizFrame([STRETCH, btnClose]) - - frmAll = makeVertFrame([lblDescrTitle, lblDescr, frmGrid, frmClose]) - layoutAll = QVBoxLayout() - layoutAll.addWidget(frmAll) - self.setLayout(layoutAll) - self.sizeHint = lambda: QSize(400, 250) - - self.setWindowTitle(self.tr('Backup Options')) - - -################################################################################ -# Class that acts as a center where the user can decide what to do with the -# watch-only wallet. The data can be displayed, printed, or saved to a file as a -# wallet or as the watch-only data (i.e., root key & chain code). -class DlgExpWOWltData(ArmoryDialog): - """ - This dialog will be used to export a wallet's root public key and chain code. - """ - def __init__(self, wlt, parent, main): - super(DlgExpWOWltData, self).__init__(parent, main) - - # Save a copy of the wallet. - self.wlt = wlt - self.main = main - - # Get the chain code and uncompressed public key of info from the wallet, - # along with other useful info. - wltRootIDConcat, pkccET16Lines = wlt.getRootPKCCBackupData(True) - wltIDB58 = wlt.uniqueIDB58 - - # Create the data export buttons. - expWltButton = QPushButton(self.tr('Export Watching-Only Wallet File')) - clipboardBtn = QPushButton(self.tr('Copy to clipboard')) - clipboardLbl = QRichLabel('', hAlign=Qt.AlignHCenter) - expDataButton = QPushButton(self.tr('Save to Text File')) - printWODataButton = QPushButton(self.tr('Print Root Data')) - - - self.connect(expWltButton, SIGNAL(CLICKED), self.clickedExpWlt) - self.connect(expDataButton, SIGNAL(CLICKED), self.clickedExpData) - self.connect(printWODataButton, SIGNAL(CLICKED), \ - self.clickedPrintWOData) - - - # Let's put the window together. - layout = QVBoxLayout() - - self.dispText = self.tr( - 'Watch-Only Root ID:
%s' - '

' - 'Watch-Only Root Data:' % wltRootIDConcat) - for j in pkccET16Lines: - self.dispText += '
%s' % (j) - - titleStr = self.tr('Watch-Only Wallet Export') - - self.txtLongDescr = QTextBrowser() - self.txtLongDescr.setFont(GETFONT('Fixed', 9)) - self.txtLongDescr.setHtml(self.dispText) - w,h = tightSizeNChar(self.txtLongDescr, 20) - self.txtLongDescr.setMaximumHeight(9.5*h) - - def clippy(): - clipb = QApplication.clipboard() - clipb.clear() - clipb.setText(str(self.txtLongDescr.toPlainText())) - clipboardLbl.setText(self.tr('Copied!')) - - self.connect(clipboardBtn, SIGNAL('clicked()'), clippy) - - - lblDescr = QRichLabel(self.tr( - '
Export Watch-Only ' - 'Wallet: %s
' - '
' - 'Use a watching-only wallet on an online computer to distribute ' - 'payment addresses, verify transactions and monitor balances, but ' - 'without the ability to move the funds.' % (htmlColor('TextBlue'), wlt.uniqueIDB58))) - - lblTopHalf = QRichLabel(self.tr( - '
Entire Wallet File
' - '
' - '(Recommended) ' - 'An exact copy of your wallet file but without any of the private ' - 'signing keys. All existing comments and labels will be carried ' - 'with the file. Use this option if it is easy to transfer files ' - 'from this system to the target system.' % htmlColor('TextBlue'))) - - lblBotHalf = QRichLabel(self.tr( - '
Only Root Data
' - '
' - 'Same as above, but only five lines of text that are easy to ' - 'print, email inline, or copy by hand. Only produces the ' - 'wallet addresses. No comments or labels are carried with ' - 'it.')) - - btnDone = QPushButton(self.tr('Done')) - self.connect(btnDone, SIGNAL('clicked()'), self.accept) - - - frmButtons = makeVertFrame([clipboardBtn, - expDataButton, - printWODataButton, - clipboardLbl, - 'Stretch']) - layoutBottom = QHBoxLayout() - layoutBottom.addWidget(frmButtons, 0) - layoutBottom.addItem(QSpacerItem(5,5)) - layoutBottom.addWidget(self.txtLongDescr, 1) - layoutBottom.setSpacing(5) - - - layout.addWidget(lblDescr) - layout.addItem(QSpacerItem(10, 10)) - layout.addWidget(HLINE()) - layout.addWidget(lblTopHalf, 1) - layout.addWidget(makeHorizFrame(['Stretch', expWltButton, 'Stretch'])) - layout.addItem(QSpacerItem(20, 20)) - layout.addWidget(HLINE()) - layout.addWidget(lblBotHalf, 1) - layout.addLayout(layoutBottom) - layout.addItem(QSpacerItem(20, 20)) - layout.addWidget(HLINE()) - layout.addWidget(makeHorizFrame(['Stretch', btnDone])) - layout.setSpacing(3) - - self.setLayout(layout) - self.setMinimumWidth(600) - - # TODO: Dear god this is terrible, but for my life I cannot figure - # out how to move the vbar, because you can't do it until - # the dialog is drawn which doesn't happen til after __init__. - self.callLater(0.05, self.resizeEvent) - - self.setWindowTitle(titleStr) - - - def resizeEvent(self, ev=None): - super(DlgExpWOWltData, self).resizeEvent(ev) - vbar = self.txtLongDescr.verticalScrollBar() - vbar.setValue(vbar.minimum()) - - - # The function that is executed when the user wants to back up the full - # watch-only wallet to a file. - def clickedExpWlt(self): - currPath = self.wlt.walletPath - if not self.wlt.watchingOnly: - pieces = os.path.splitext(currPath) - currPath = pieces[0] + '_WatchOnly' + pieces[1] - - saveLoc = self.main.getFileSave('Save Watching-Only Copy', \ - defaultFilename=currPath) - if not saveLoc.endswith('.wallet'): - saveLoc += '.wallet' - - if not self.wlt.watchingOnly: - self.wlt.forkOnlineWallet(saveLoc, self.wlt.labelName, \ - '(Watching-Only) ' + self.wlt.labelDescr) - else: - self.wlt.writeFreshWalletFile(saveLoc) - - - - # The function that is executed when the user wants to save the watch-only - # data to a file. - def clickedExpData(self): - self.main.makeWalletCopy(self, self.wlt, 'PKCC', 'rootpubkey') - - - # The function that is executed when the user wants to print the watch-only - # data. - def clickedPrintWOData(self): - self.result = DlgWODataPrintBackup(self, self.main, self.wlt).exec_() - - -################################################################################ -# Class that handles the printing of the watch-only wallet data. The formatting -# is mostly the same as a normal paper backup. Note that neither fragmented -# backups nor SecurePrint are used. -class DlgWODataPrintBackup(ArmoryDialog): - """ - Open up a "Make Paper Backup" dialog, so the user can print out a hard - copy of whatever data they need to recover their wallet should they lose - it. - """ - def __init__(self, parent, main, wlt): - super(DlgWODataPrintBackup, self).__init__(parent, main) - - self.wlt = wlt - - # Create the scene and the view. - self.scene = SimplePrintableGraphicsScene(self, self.main) - self.view = QGraphicsView() - self.view.setRenderHint(QPainter.TextAntialiasing) - self.view.setScene(self.scene.getScene()) - - # Label displayed above the sheet to be printed. - lblDescr = QRichLabel(self.tr( - 'Print Watch-Only Wallet Root

' - 'The lines below are sufficient to calculate public keys ' - 'for every private key ever produced by the full wallet. ' - 'Importing this data to an online computer is sufficient ' - 'to receive and verify transactions, and monitor balances, ' - 'but without the ability to spend the funds.')) - lblDescr.setContentsMargins(5, 5, 5, 5) - frmDescr = makeHorizFrame([lblDescr], STYLE_RAISED) - - # Buttons shown below the sheet to be printed. - self.btnPrint = QPushButton('&Print...') - self.btnPrint.setMinimumWidth(3 * tightSizeStr(self.btnPrint, 'Print...')[0]) - self.btnCancel = QPushButton('&Cancel') - self.connect(self.btnPrint, SIGNAL(CLICKED), self.print_) - self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) - frmButtons = makeHorizFrame([self.btnCancel, STRETCH, self.btnPrint]) - - # Draw the sheet for the first time. - self.redrawBackup() - - # Lay out the dialog. - layout = QVBoxLayout() - layout.addWidget(frmDescr) - layout.addWidget(self.view) - layout.addWidget(frmButtons) - setLayoutStretch(layout, 0, 1, 0) - self.setLayout(layout) - self.setWindowIcon(QIcon('./img/printer_icon.png')) - self.setWindowTitle('Print Watch-Only Root') - - # Apparently I can't programmatically scroll until after it's painted - def scrollTop(): - vbar = self.view.verticalScrollBar() - vbar.setValue(vbar.minimum()) - self.callLater(0.01, scrollTop) - - - # Class called to redraw the print "canvas" when the data changes. - def redrawBackup(self): - self.createPrintScene() - self.view.update() - - - # Class that handles the actual printing code. - def print_(self): - LOGINFO('Printing!') - self.printer = QPrinter(QPrinter.HighResolution) - self.printer.setPageSize(QPrinter.Letter) - - if QPrintDialog(self.printer).exec_(): - painter = QPainter(self.printer) - painter.setRenderHint(QPainter.TextAntialiasing) - - self.createPrintScene() - self.scene.getScene().render(painter) - painter.end() - self.accept() - - - # Class that lays out the actual print "canvas" to be printed. - def createPrintScene(self): - # Do initial setup. - self.scene.gfxScene.clear() - self.scene.resetCursor() - - # Draw the background paper? - pr = self.scene.pageRect() - self.scene.drawRect(pr.width(), pr.height(), edgeColor=None, \ - fillColor=QColor(255, 255, 255)) - self.scene.resetCursor() - - INCH = self.scene.INCH - MARGIN = self.scene.MARGIN_PIXELS - wrap = 0.9 * self.scene.pageRect().width() - - # Start drawing the page. - if USE_TESTNET or USE_REGTEST: - self.scene.drawPixmapFile('./img/armory_logo_green_h56.png') - else: - self.scene.drawPixmapFile('./img/armory_logo_h36.png') - self.scene.newLine() - - warnMsg = self.tr( - 'WARNING: This is not ' - 'a wallet backup! ' - '

Please make a regular digital or paper backup of your wallet ' - 'to keep it protected! This data simply lets you ' - 'monitor the funds in this wallet but gives you no ability to move any ' - 'funds.') - self.scene.drawText(warnMsg, GETFONT('Var', 9), wrapWidth=wrap) - - self.scene.newLine(extra_dy=20) - self.scene.drawHLine() - self.scene.newLine(extra_dy=20) - - # Print the wallet info. - colRect, rowHgt = self.scene.drawColumn(['Watch-Only Root Data', - 'Wallet ID:', - 'Wallet Name:']) - self.scene.moveCursor(15, 0) - colRect, rowHgt = self.scene.drawColumn(['', - self.wlt.uniqueIDB58, - self.wlt.labelName]) - - self.scene.moveCursor(15, colRect.y() + colRect.height(), absolute=True) - - # Display warning about unprotected key data. - self.scene.newLine(extra_dy=20) - self.scene.drawHLine() - self.scene.newLine(extra_dy=20) - - # Draw the description of the data. - descrMsg = self.tr( - 'The following five lines are sufficient to reproduce all public ' - 'keys matching the private keys produced by the full wallet.') - self.scene.drawText(descrMsg, GETFONT('var', 8), wrapWidth=wrap) - self.scene.newLine(extra_dy=10) - - # Prepare the data. - self.wltRootIDConcat, self.pkccET16Lines = \ - self.wlt.getRootPKCCBackupData(True) - Lines = [] - Prefix = [] - Prefix.append('Watch-Only Root ID:'); Lines.append(self.wltRootIDConcat) - Prefix.append('Watch-Only Root:'); Lines.append(self.pkccET16Lines[0]) - Prefix.append(''); Lines.append(self.pkccET16Lines[1]) - Prefix.append(''); Lines.append(self.pkccET16Lines[2]) - Prefix.append(''); Lines.append(self.pkccET16Lines[3]) - - # Draw the prefix data. - origX, origY = self.scene.getCursorXY() - self.scene.moveCursor(10, 0) - colRect, rowHgt = self.scene.drawColumn(['' + l + '' \ - for l in Prefix]) - - # Draw the data. - nudgeDown = 2 # because the differing font size makes it look unaligned - self.scene.moveCursor(10, nudgeDown) - self.scene.drawColumn(Lines, - font=GETFONT('Fixed', 8, bold=True), \ - rowHeight=rowHgt, - useHtml=False) - - # Draw the rectangle around the data. - self.scene.moveCursor(MARGIN, colRect.y() - 2, absolute=True) - width = self.scene.pageRect().width() - 2 * MARGIN - self.scene.drawRect(width, colRect.height() + 7, \ - edgeColor=QColor(0, 0, 0), fillColor=None) - - # Draw the QR-related text below the data. - self.scene.newLine(extra_dy=30) - self.scene.drawText(self.tr( - 'The following QR code is for convenience only. It contains the ' - 'exact same data as the five lines above. If you copy this data ' - 'by hand, you can safely ignore this QR code.'), wrapWidth=4 * INCH) - - # Draw the QR code. - self.scene.moveCursor(20, 0) - x, y = self.scene.getCursorXY() - edgeRgt = self.scene.pageRect().width() - MARGIN - edgeBot = self.scene.pageRect().height() - MARGIN - qrSize = max(1.5 * INCH, min(edgeRgt - x, edgeBot - y, 2.0 * INCH)) - self.scene.drawQR('\n'.join(Lines), qrSize) - self.scene.newLine(extra_dy=25) - - # Clear the data and create a vertical scroll bar. - Lines = None - vbar = self.view.verticalScrollBar() - vbar.setValue(vbar.minimum()) - self.view.update() - - -################################################################################ -class DlgFragBackup(ArmoryDialog): - - ############################################################################# - def __init__(self, parent, main, wlt): - super(DlgFragBackup, self).__init__(parent, main) - - self.wlt = wlt - - lblDescrTitle = QRichLabel(self.tr( - 'Create M-of-N Fragmented Backup of "%s" (%s)' % (wlt.labelName, wlt.uniqueIDB58)), doWrap=False) - lblDescrTitle.setContentsMargins(5, 5, 5, 5) - - self.lblAboveFrags = QRichLabel('') - self.lblAboveFrags.setContentsMargins(10, 0, 10, 0) - - frmDescr = makeVertFrame([lblDescrTitle, self.lblAboveFrags], \ - STYLE_RAISED) - - self.fragDisplayLastN = 0 - self.fragDisplayLastM = 0 - - self.maxM = 5 if not self.main.usermode == USERMODE.Expert else 8 - self.maxN = 6 if not self.main.usermode == USERMODE.Expert else 12 - self.currMinN = 2 - self.maxmaxN = 12 - - self.comboM = QComboBox() - self.comboN = QComboBox() - - for M in range(2, self.maxM + 1): - self.comboM.addItem(str(M)) - - for N in range(self.currMinN, self.maxN + 1): - self.comboN.addItem(str(N)) - - self.comboM.setCurrentIndex(1) - self.comboN.setCurrentIndex(2) - - def updateM(): - self.updateComboN() - self.createFragDisplay() - - updateN = self.createFragDisplay - - self.connect(self.comboM, SIGNAL('activated(int)'), updateM) - self.connect(self.comboN, SIGNAL('activated(int)'), updateN) - self.comboM.setMinimumWidth(30) - self.comboN.setMinimumWidth(30) - - btnAccept = QPushButton(self.tr('Close')) - self.connect(btnAccept, SIGNAL(CLICKED), self.accept) - frmBottomBtn = makeHorizFrame([STRETCH, btnAccept]) - - # We will hold all fragments here, in SBD objects. Destroy all of them - # before the dialog exits - self.secureRoot = self.wlt.addrMap['ROOT'].binPrivKey32_Plain.copy() - self.secureChain = self.wlt.addrMap['ROOT'].chaincode.copy() - self.secureMtrx = [] - - testChain = DeriveChaincodeFromRootKey(self.secureRoot) - if testChain == self.secureChain: - self.noNeedChaincode = True - self.securePrint = self.secureRoot - else: - self.securePrint = self.secureRoot + self.secureChain - - self.chkSecurePrint = QCheckBox(self.trUtf8(u'Use SecurePrint\u200b\u2122 ' - 'to prevent exposing keys to printer or other devices')) - - self.scrollArea = QScrollArea() - self.createFragDisplay() - self.scrollArea.setWidgetResizable(True) - - self.ttipSecurePrint = self.main.createToolTipWidget(self.trUtf8( - u'SecurePrint\u200b\u2122 encrypts your backup with a code displayed on ' - 'the screen, so that no other devices or processes has access to the ' - 'unencrypted private keys (either network devices when printing, or ' - 'other applications if you save a fragment to disk or USB device). ' - u'You must keep the SecurePrint\u200b\u2122 code with the backup!')) - self.lblSecurePrint = QRichLabel(self.trUtf8( - 'IMPORTANT: You must keep the ' - u'SecurePrint\u200b\u2122 encryption code with your backup! ' - u'Your SecurePrint\u200b\u2122 code is ' - '%s. ' - 'All fragments for a given wallet use the ' - 'same code.' % (htmlColor('TextWarn'), htmlColor('TextBlue'), self.backupData.sppass, \ - htmlColor('TextWarn')))) - self.connect(self.chkSecurePrint, SIGNAL(CLICKED), self.clickChkSP) - self.chkSecurePrint.setChecked(False) - self.lblSecurePrint.setVisible(False) - frmChkSP = makeHorizFrame([self.chkSecurePrint, self.ttipSecurePrint, STRETCH]) - - dlgLayout = QVBoxLayout() - dlgLayout.addWidget(frmDescr) - dlgLayout.addWidget(self.scrollArea) - dlgLayout.addWidget(frmChkSP) - dlgLayout.addWidget(self.lblSecurePrint) - dlgLayout.addWidget(frmBottomBtn) - setLayoutStretch(dlgLayout, 0, 1, 0, 0, 0) - - self.setLayout(dlgLayout) - self.setMinimumWidth(650) - self.setMinimumHeight(450) - self.setWindowTitle('Create Backup Fragments') - - - ############################################################################# - def clickChkSP(self): - self.lblSecurePrint.setVisible(self.chkSecurePrint.isChecked()) - self.createFragDisplay() - - - ############################################################################# - def updateComboN(self): - M = int(str(self.comboM.currentText())) - oldN = int(str(self.comboN.currentText())) - self.currMinN = M - self.comboN.clear() - - for i, N in enumerate(range(self.currMinN, self.maxN + 1)): - self.comboN.addItem(str(N)) - - if M > oldN: - self.comboN.setCurrentIndex(0) - else: - for i, N in enumerate(range(self.currMinN, self.maxN + 1)): - if N == oldN: - self.comboN.setCurrentIndex(i) - - - - ############################################################################# - def createFragDisplay(self): - M = int(str(self.comboM.currentText())) - N = int(str(self.comboN.currentText())) - - #only recompute fragments if M or N changed - if self.fragDisplayLastN != N or \ - self.fragDisplayLastM != M: - self.recomputeFragData() - - self.fragDisplayLastN = N - self.fragDisplayLastM = M - - lblAboveM = QRichLabel(self.tr('Required Fragments '), hAlign=Qt.AlignHCenter, doWrap=False) - lblAboveN = QRichLabel(self.tr('Total Fragments '), hAlign=Qt.AlignHCenter) - frmComboM = makeHorizFrame([STRETCH, QLabel('M:'), self.comboM, STRETCH]) - frmComboN = makeHorizFrame([STRETCH, QLabel('N:'), self.comboN, STRETCH]) - - btnPrintAll = QPushButton(self.tr('Print All Fragments')) - self.connect(btnPrintAll, SIGNAL(CLICKED), self.clickPrintAll) - leftFrame = makeVertFrame([STRETCH, \ - lblAboveM, \ - frmComboM, \ - lblAboveN, \ - frmComboN, \ - STRETCH, \ - HLINE(), \ - btnPrintAll, \ - STRETCH], STYLE_STYLED) - - layout = QHBoxLayout() - layout.addWidget(leftFrame) - - for f in range(N): - layout.addWidget(self.createFragFrm(f)) - - - frmScroll = QFrame() - frmScroll.setFrameStyle(STYLE_SUNKEN) - frmScroll.setStyleSheet('QFrame { background-color : %s }' % \ - htmlColor('SlightBkgdDark')) - frmScroll.setLayout(layout) - self.scrollArea.setWidget(frmScroll) - - BLUE = htmlColor('TextBlue') - self.lblAboveFrags.setText(self.tr( - 'Any %d of these ' - '%d' - 'fragments are sufficient to restore your wallet, and each fragment ' - 'has the ID, %s. All fragments with the ' - 'same fragment ID are compatible with each other!' % (BLUE, M, BLUE, N, BLUE, self.fragPrefixStr))) - - - ############################################################################# - def createFragFrm(self, idx): - - doMask = self.chkSecurePrint.isChecked() - M = int(str(self.comboM.currentText())) - N = int(str(self.comboN.currentText())) - - lblFragID = QRichLabel(self.tr('Fragment ID:
%s-%s
' % \ - (str(self.fragPrefixStr), str(idx + 1)))) - # lblWltID = QRichLabel('(%s)' % self.wlt.uniqueIDB58) - lblFragPix = QImageLabel(self.fragPixmapFn, size=(72, 72)) - if doMask: - ys = self.secureMtrxCrypt[idx][1].toBinStr()[:42] - else: - ys = self.secureMtrx[idx][1].toBinStr()[:42] - - easyYs1 = makeSixteenBytesEasy(ys[:16 ]) - easyYs2 = makeSixteenBytesEasy(ys[ 16:32]) - - binID = base58_to_binary(self.uniqueFragSetID) - ID = ComputeFragIDLineHex(M, idx, binID, doMask, addSpaces=True) - - fragPreview = 'ID: %s...
' % ID[:12] - fragPreview += 'F1: %s...
' % easyYs1[:12] - fragPreview += 'F2: %s... ' % easyYs2[:12] - lblPreview = QRichLabel(fragPreview) - lblPreview.setFont(GETFONT('Fixed', 9)) - - lblFragIdx = QRichLabel('#%d' % (idx + 1), size=4, color='TextBlue', \ - hAlign=Qt.AlignHCenter) - - frmTopLeft = makeVertFrame([lblFragID, lblFragIdx, STRETCH]) - frmTopRight = makeVertFrame([lblFragPix, STRETCH]) - - frmPaper = makeVertFrame([lblPreview]) - frmPaper.setStyleSheet('QFrame { background-color : #ffffff }') - - fnPrint = lambda: self.clickPrintFrag(idx) - fnSave = lambda: self.clickSaveFrag(idx) - - btnPrintFrag = QPushButton(self.tr('View/Print')) - btnSaveFrag = QPushButton(self.tr('Save to File')) - self.connect(btnPrintFrag, SIGNAL(CLICKED), fnPrint) - self.connect(btnSaveFrag, SIGNAL(CLICKED), fnSave) - frmButtons = makeHorizFrame([btnPrintFrag, btnSaveFrag]) - - - layout = QGridLayout() - layout.addWidget(frmTopLeft, 0, 0, 1, 1) - layout.addWidget(frmTopRight, 0, 1, 1, 1) - layout.addWidget(frmPaper, 1, 0, 1, 2) - layout.addWidget(frmButtons, 2, 0, 1, 2) - layout.setSizeConstraint(QLayout.SetFixedSize) - - outFrame = QFrame() - outFrame.setFrameStyle(STYLE_STYLED) - outFrame.setLayout(layout) - return outFrame - - - ############################################################################# - def clickPrintAll(self): - self.clickPrintFrag(range(int(str(self.comboN.currentText())))) - - ############################################################################# - def clickPrintFrag(self, zindex): - if not isinstance(zindex, (list, tuple)): - zindex = [zindex] - fragData = {} - fragData['M'] = int(str(self.comboM.currentText())) - fragData['N'] = int(str(self.comboN.currentText())) - fragData['FragIDStr'] = self.fragPrefixStr - fragData['FragPixmap'] = self.fragPixmapFn - fragData['Range'] = zindex - fragData['Secure'] = self.chkSecurePrint.isChecked() - fragData['fragSetID'] = self.uniqueFragSetID - dlg = DlgPrintBackup(self, self.main, self.wlt, 'Fragments', \ - self.secureMtrx, self.secureMtrxCrypt, fragData, \ - self.secureRoot, self.secureChain) - dlg.exec_() - - ############################################################################# - def clickSaveFrag(self, zindex): - saveMtrx = self.secureMtrx - doMask = False - if self.chkSecurePrint.isChecked(): - response = QMessageBox.question(self, self.tr('Secure Backup?'), self.trUtf8( - u'You have selected to use SecurePrint\u200b\u2122 for the printed ' - 'backups, which can also be applied to fragments saved to file. ' - u'Doing so will require you store the SecurePrint\u200b\u2122 ' - 'code with the backup, but it will prevent unencrypted key data from ' - 'touching any disks.

Do you want to encrypt the fragment ' - u'file with the same SecurePrint\u200b\u2122 code?'), \ - QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) - - if response == QMessageBox.Yes: - saveMtrx = self.secureMtrxCrypt - doMask = True - elif response == QMessageBox.No: - pass - else: - return - - - wid = self.wlt.uniqueIDB58 - pref = self.fragPrefixStr - fnum = zindex + 1 - M = self.M - sec = 'secure.' if doMask else '' - defaultFn = 'wallet_%s_%s_num%d_need%d.%sfrag' % (wid, pref, fnum, M, sec) - #print 'FragFN:', defaultFn - savepath = self.main.getFileSave('Save Fragment', \ - ['Wallet Fragments (*.frag)'], \ - defaultFn) - - if len(toUnicode(savepath)) == 0: - return - - fout = open(savepath, 'w') - fout.write('Wallet ID: %s\n' % wid) - fout.write('Create Date: %s\n' % unixTimeToFormatStr(RightNow())) - fout.write('Fragment ID: %s-#%d\n' % (pref, fnum)) - fout.write('Frag Needed: %d\n' % M) - fout.write('\n\n') - - try: - yBin = saveMtrx[zindex][1].toBinStr() - binID = base58_to_binary(self.uniqueFragSetID) - IDLine = ComputeFragIDLineHex(M, zindex, binID, doMask, addSpaces=True) - if len(yBin) == 32: - fout.write('ID: ' + IDLine + '\n') - fout.write('F1: ' + makeSixteenBytesEasy(yBin[:16 ]) + '\n') - fout.write('F2: ' + makeSixteenBytesEasy(yBin[ 16:]) + '\n') - elif len(yBin) == 64: - fout.write('ID: ' + IDLine + '\n') - fout.write('F1: ' + makeSixteenBytesEasy(yBin[:16 ]) + '\n') - fout.write('F2: ' + makeSixteenBytesEasy(yBin[ 16:32 ]) + '\n') - fout.write('F3: ' + makeSixteenBytesEasy(yBin[ 32:48 ]) + '\n') - fout.write('F4: ' + makeSixteenBytesEasy(yBin[ 48:]) + '\n') - else: - LOGERROR('yBin is not 32 or 64 bytes! It is %s bytes', len(yBin)) - finally: - yBin = None - - fout.close() - - qmsg = self.tr( - 'The fragment was successfully saved to the following location: ' - '

%s

' % savepath) - - if doMask: - qmsg += self.trUtf8( - 'Important: ' - 'The fragment was encrypted with the ' - u'SecurePrint\u200b\u2122 encryption code. You must keep this ' - 'code with the backup in order to use it! The code is ' - 'case-sensitive! ' - '

%s' - '

' - 'The above code is case-sensitive!' \ - % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ - self.backupData.sppass)) - - QMessageBox.information(self, self.tr('Success'), qmsg, QMessageBox.Ok) - - - - ############################################################################# - def destroyFrags(self): - if len(self.secureMtrx) == 0: - return - - if isinstance(self.secureMtrx[0], (list, tuple)): - for sbdList in self.secureMtrx: - for sbd in sbdList: - sbd.destroy() - for sbdList in self.secureMtrxCrypt: - for sbd in sbdList: - sbd.destroy() - else: - for sbd in self.secureMtrx: - sbd.destroy() - for sbd in self.secureMtrxCrypt: - sbd.destroy() - - self.secureMtrx = [] - self.secureMtrxCrypt = [] - - - ############################################################################# - def destroyEverything(self): - self.secureRoot.destroy() - self.secureChain.destroy() - self.securePrint.destroy() - self.destroyFrags() - - ############################################################################# - def recomputeFragData(self): - """ - Only M is needed, since N doesn't change - """ - - M = int(str(self.comboM.currentText())) - N = int(str(self.comboN.currentText())) - # Make sure only local variables contain non-SBD data - self.destroyFrags() - self.uniqueFragSetID = \ - binary_to_base58(SecureBinaryData().GenerateRandom(6).toBinStr()) - insecureData = SplitSecret(self.securePrint, M, self.maxmaxN) - for x, y in insecureData: - self.secureMtrx.append([SecureBinaryData(x), SecureBinaryData(y)]) - insecureData, x, y = None, None, None - - ##### - # Now we compute the SecurePrint(TM) versions of the fragments - SECPRINT = HardcodedKeyMaskParams() - MASK = lambda x: SECPRINT['FUNC_MASK'](x, ekey=self.binCrypt32) - if not self.randpass or not self.binCrypt32: - self.randpass = SECPRINT['FUNC_PWD'](self.secureRoot + self.secureChain) - self.binCrypt32 = SECPRINT['FUNC_KDF'](self.randpass) - self.secureMtrxCrypt = [] - for sbdX, sbdY in self.secureMtrx: - self.secureMtrxCrypt.append([sbdX.copy(), MASK(sbdY)]) - ##### - - self.M, self.N = M, N - self.fragPrefixStr = ComputeFragIDBase58(self.M, \ - base58_to_binary(self.uniqueFragSetID)) - self.fragPixmapFn = './img/frag%df.png' % M - - - ############################################################################# - def accept(self): - self.destroyEverything() - super(DlgFragBackup, self).accept() - - ############################################################################# - def reject(self): - self.destroyEverything() - super(DlgFragBackup, self).reject() - - - - -################################################################################ -class DlgUniversalRestoreSelect(ArmoryDialog): - - ############################################################################# - def __init__(self, parent, main): - super(DlgUniversalRestoreSelect, self).__init__(parent, main) - - - lblDescrTitle = QRichLabel(self.tr('Restore Wallet from Backup')) - lblDescr = QRichLabel(self.tr('You can restore any kind of backup ever created by Armory using ' - 'one of the options below. If you have a list of private keys ' - 'you should open the target wallet and select "Import/Sweep ' - 'Private Keys."')) - - self.rdoSingle = QRadioButton(self.tr('Single-Sheet Backup (printed)')) - self.rdoFragged = QRadioButton(self.tr('Fragmented Backup (incl. mix of paper and files)')) - self.rdoDigital = QRadioButton(self.tr('Import digital backup or watching-only wallet')) - self.rdoWOData = QRadioButton(self.tr('Import watching-only wallet data')) - self.chkTest = QCheckBox(self.tr('This is a test recovery to make sure my backup works')) - btngrp = QButtonGroup(self) - btngrp.addButton(self.rdoSingle) - btngrp.addButton(self.rdoFragged) - btngrp.addButton(self.rdoDigital) - btngrp.addButton(self.rdoWOData) - btngrp.setExclusive(True) - - self.rdoSingle.setChecked(True) - self.connect(self.rdoSingle, SIGNAL(CLICKED), self.clickedRadio) - self.connect(self.rdoFragged, SIGNAL(CLICKED), self.clickedRadio) - self.connect(self.rdoDigital, SIGNAL(CLICKED), self.clickedRadio) - self.connect(self.rdoWOData, SIGNAL(CLICKED), self.clickedRadio) - - self.btnOkay = QPushButton(self.tr('Continue')) - self.btnCancel = QPushButton(self.tr('Cancel')) - buttonBox = QDialogButtonBox() - buttonBox.addButton(self.btnOkay, QDialogButtonBox.AcceptRole) - buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) - self.connect(self.btnOkay, SIGNAL(CLICKED), self.clickedOkay) - self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) - - - layout = QVBoxLayout() - layout.addWidget(lblDescrTitle) - layout.addWidget(lblDescr) - layout.addWidget(HLINE()) - layout.addWidget(self.rdoSingle) - layout.addWidget(self.rdoFragged) - layout.addWidget(self.rdoDigital) - layout.addWidget(self.rdoWOData) - layout.addWidget(HLINE()) - layout.addWidget(self.chkTest) - layout.addWidget(buttonBox) - self.setLayout(layout) - self.setMinimumWidth(450) - - def clickedRadio(self): - if self.rdoDigital.isChecked(): - self.chkTest.setChecked(False) - self.chkTest.setEnabled(False) - else: - self.chkTest.setEnabled(True) - - def clickedOkay(self): - # ## Test backup option - - doTest = self.chkTest.isChecked() - - if self.rdoSingle.isChecked(): - self.accept() - dlg = DlgRestoreSingle(self.parent, self.main, doTest) - if dlg.exec_(): - self.main.addWalletToApplication(dlg.newWallet) - LOGINFO('Wallet Restore Complete!') - - elif self.rdoFragged.isChecked(): - self.accept() - dlg = DlgRestoreFragged(self.parent, self.main, doTest) - if dlg.exec_(): - self.main.addWalletToApplication(dlg.newWallet) - LOGINFO('Wallet Restore Complete!') - elif self.rdoDigital.isChecked(): - self.main.execGetImportWltName() - self.accept() - elif self.rdoWOData.isChecked(): - # Attempt to restore the root public key & chain code for a wallet. - # When done, ask for a wallet rescan. - self.accept() - dlg = DlgRestoreWOData(self.parent, self.main, doTest) - if dlg.exec_(): - LOGINFO('Watching-Only Wallet Restore Complete! Will ask for a' \ - 'rescan.') - self.main.addWalletToApplication(dlg.newWallet) - - -################################################################################ -# Create a special QLineEdit with a masked input -# Forces the cursor to start at position 0 whenever there is no input -class MaskedInputLineEdit(QLineEdit): - - def __init__(self, inputMask): - super(MaskedInputLineEdit, self).__init__() - self.setInputMask(inputMask) - fixFont = GETFONT('Fix', 9) - self.setFont(fixFont) - self.setMinimumWidth(tightSizeStr(fixFont, inputMask)[0] + 10) - self.connect(self, SIGNAL('cursorPositionChanged(int,int)'), self.controlCursor) - - def controlCursor(self, oldpos, newpos): - if newpos != 0 and len(str(self.text()).strip()) == 0: - self.setCursorPosition(0) - - -def checkSecurePrintCode(context, SECPRINT, securePrintCode): - result = True - try: - if len(securePrintCode) < 9: - QMessageBox.critical(context, context.tr('Invalid Code'), context.trUtf8( - u'You didn\'t enter a full SecurePrint\u200b\u2122 code. This ' - 'code is needed to decrypt your backup file.'), QMessageBox.Ok) - result = False - elif not SECPRINT['FUNC_CHKPWD'](securePrintCode): - QMessageBox.critical(context, context.trUtf8(u'Bad SecurePrint\u200b\u2122 Code'), context.trUtf8( - u'The SecurePrint\u200b\u2122 code you entered has an error ' - 'in it. Note that the code is case-sensitive. Please verify ' - 'you entered it correctly and try again.'), QMessageBox.Ok) - result = False - except NonBase58CharacterError as e: - QMessageBox.critical(context, context.trUtf8(u'Bad SecurePrint\u200b\u2122 Code'), context.trUtf8( - u'The SecurePrint\u200b\u2122 code you entered has unrecognized characters ' - 'in it. %s Only the following characters are allowed: %s' % (e.message, BASE58CHARS)), QMessageBox.Ok) - result = False - return result - -################################################################################ -class DlgRestoreSingle(ArmoryDialog): - ############################################################################# - def __init__(self, parent, main, thisIsATest=False, expectWltID=None): - super(DlgRestoreSingle, self).__init__(parent, main) - - self.newWltID = None - self.callbackId = None - self.thisIsATest = thisIsATest - self.testWltID = expectWltID - headerStr = '' - if thisIsATest: - lblDescr = QRichLabel(self.tr( - 'Test a Paper Backup ' - '

' - 'Use this window to test a single-sheet paper backup. If your ' - 'backup includes imported keys, those will not be covered by this test.')) - else: - lblDescr = QRichLabel(self.tr( - 'Restore a Wallet from Paper Backup ' - '

' - 'Use this window to restore a single-sheet paper backup. ' - 'If your backup includes extra pages with ' - 'imported keys, please restore the base wallet first, then ' - 'double-click the restored wallet and select "Import Private ' - 'Keys" from the right-hand menu.')) - - - lblType = QRichLabel(self.tr('Backup Type:'), doWrap=False) - - self.version135Button = QRadioButton(self.tr('Version 1.35 (4 lines)'), self) - self.version135aButton = QRadioButton(self.tr('Version 1.35a (4 lines Unencrypted)'), self) - self.version135aSPButton = QRadioButton(self.trUtf8(u'Version 1.35a (4 lines + SecurePrint\u200b\u2122)'), self) - self.version135cButton = QRadioButton(self.tr('Version 1.35c (2 lines Unencrypted)'), self) - self.version135cSPButton = QRadioButton(self.trUtf8(u'Version 1.35c (2 lines + SecurePrint\u200b\u2122)'), self) - self.backupTypeButtonGroup = QButtonGroup(self) - self.backupTypeButtonGroup.addButton(self.version135Button) - self.backupTypeButtonGroup.addButton(self.version135aButton) - self.backupTypeButtonGroup.addButton(self.version135aSPButton) - self.backupTypeButtonGroup.addButton(self.version135cButton) - self.backupTypeButtonGroup.addButton(self.version135cSPButton) - self.version135cButton.setChecked(True) - self.connect(self.backupTypeButtonGroup, SIGNAL('buttonClicked(int)'), self.changeType) - - layoutRadio = QVBoxLayout() - layoutRadio.addWidget(self.version135Button) - layoutRadio.addWidget(self.version135aButton) - layoutRadio.addWidget(self.version135aSPButton) - layoutRadio.addWidget(self.version135cButton) - layoutRadio.addWidget(self.version135cSPButton) - layoutRadio.setSpacing(0) - - radioButtonFrame = QFrame() - radioButtonFrame.setLayout(layoutRadio) - - frmBackupType = makeVertFrame([lblType, radioButtonFrame]) - - self.lblSP = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code:'), doWrap=False) - self.editSecurePrint = QLineEdit() - self.prfxList = [QLabel(self.tr('Root Key:')), QLabel(''), QLabel(self.tr('Chaincode:')), QLabel('')] - - inpMask = '
%s' % errorMsg \ - ), QMessageBox.Ok) - - self.reject() - return - - result, extra = self.processCallbackPayload(payload) - if result == False: - TheBDM.unregisterCustomPrompt(self.callbackId) - - reply = ClientProto_pb2.RestoreReply() - reply.result = result - - if extra != None: - reply.extra = bytes(extra, 'utf-8') - - TheBridge.callbackFollowUp(reply, self.callbackId, callerId) - - ############################################################################# - def processCallbackPayload(self, payload): - msg = ClientProto_pb2.RestorePrompt() - msg.ParseFromString(payload) - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Id") or \ - msg.promptType == ClientProto_pb2.RestorePromptType.Value("ChecksumError"): - #check the id generated by this backup - - newWltID = msg.extra - if len(newWltID) > 0: - if self.thisIsATest: - # Stop here if this was just a test - verifyRecoveryTestID(self, newWltID, self.testWltID) - - #return false to caller to end the restore process - return False, None - - # return result of id comparison - dlgOwnWlt = None - if newWltID in self.main.walletMap: - dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) - - if (dlgOwnWlt.exec_()): - #TODO: deal with replacement code - if dlgOwnWlt.output == 0: - return False, None - else: - return False, None - else: - reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), \ - self.tr('The data you entered corresponds to a wallet with a wallet ID: \n\n' - '%s\n\nDoes this ID match the "Wallet Unique ID" ' - 'printed on your paper backup? If not, click "No" and reenter ' - 'key and chain-code data again.' % newWltID), \ - QMessageBox.Yes | QMessageBox.No) - if reply == QMessageBox.Yes: - #return true to caller to proceed with restore operation - self.newWltID = newWltID - return True, None - - #reconstructed wallet id is invalid if we get this far - lineNumber = -1 - canBeSalvaged = True - if len(msg.checksums) != self.lineCount: - canBeSalvaged = False - - for i in range(0, len(msg.checksums)): - if msg.checksums[i] < 0 or msg.checksums[i] == UINT8_MAX: - lineNumber = i + 1 - break - - if lineNumber == -1 or canBeSalvaged == False: - QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( - 'Encountered an unkonwn error when restoring this backup. Aborting.', \ - QMessageBox.Ok)) - - self.reject() - return False, None - - reply = QMessageBox.critical(self, self.tr('Invalid Data'), self.tr( - 'There is an error in the data you entered that could not be ' - 'fixed automatically. Please double-check that you entered the ' - 'text exactly as it appears on the wallet-backup page.

' - 'The error occured on line #%d.' % lineNumber), \ - QMessageBox.Ok) - LOGERROR('Error in wallet restore field') - self.prfxList[i].setText(\ - '' + str(self.prfxList[i].text()) + '') - - return False, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Passphrase"): - #return new wallet's private keys password - passwd = [] - if self.chkEncrypt.isChecked(): - dlgPasswd = DlgChangePassphrase(self, self.main) - if dlgPasswd.exec_(): - passwd = str(dlgPasswd.edtPasswd1.text()) - return True, passwd - else: - QMessageBox.critical(self, self.tr('Cannot Encrypt'), \ - self.tr('You requested your restored wallet be encrypted, but no ' - 'valid passphrase was supplied. Aborting wallet recovery.'), \ - QMessageBox.Ok) - self.reject() - return False, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Control"): - #TODO: need UI to input control passphrase - return True, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Success"): - if self.newWltID == None or len(self.newWltID) == 0: - LOGERROR("wallet import did not yield an id") - raise Exception("wallet import did not yield an id") - - self.newWallet = PyBtcWallet() - self.newWallet.loadFromBridge(self.newWltID) - self.accept() - - return True, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("FormatError") or \ - msg.promptType == ClientProto_pb2.RestorePromptType.Value("Failure"): - - QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( - 'Encountered an unkonwn error when restoring this backup. Aborting.', \ - QMessageBox.Ok)) - - self.reject() - return False, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("DecryptError"): - #TODO: notify of invalid SP pass - pass - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("TypeError"): - #TODO: wallet type conveyed by backup is unknown - pass - - else: - #TODO: unknown error - return False, None - - - ############################################################################# - def verifyUserInput(self): - - root = [] - for i in range(2): - root.append(str(self.edtList[i].text())) - - chaincode = [] - if self.isLongForm: - for i in range(2): - chaincode.append(str(self.edtList[i+2].text())) - - self.lineCount = len(root) + len(chaincode) - - spPass = "" - if self.doMask: - #add secureprint passphrase if this backup is encrypted - spPass = str(self.editSecurePrint.text()).strip() - - ''' - verifyBackupString is a method that will trigger multiple callbacks - during the course of its execution. Unlike a password request callback - which only requires to generate a dedicated dialog to retrieve passwords - from users, verifyBackupString set of notifications is complex and comes - with branches. - - A dedicated callbackId is generated for this interaction and passed to - TheBDM callback map along with a py side method to handle the protobuf - packet from the C++ side. - - The C++ method is called with that id. - ''' - def callback(payload, callerId): - self.main.signalExecution.executeMethod(\ - self.processCallback, payload, callerId) - - self.callbackId = TheBDM.registerCustomPrompt(callback) - TheBridge.restoreWallet(root, chaincode, spPass, self.callbackId) - return - - if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: - QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ - self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) - return - if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfBytes() == -1: - QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ - self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) - return - if nError > 0: - pluralStr = 'error' if nError == 1 else 'errors' - - msg = self.tr( - 'Detected errors in the data you entered. ' - 'Armory attempted to fix the errors but it is not ' - 'always right. Be sure to verify the "Wallet Unique ID" ' - 'closely on the next window.') - - QMessageBox.question(self, self.tr('Errors Corrected'), msg, \ - QMessageBox.Ok) - - -# Class that will create the watch-only wallet data (root public key & chain -# code) restoration window. -################################################################################ -class DlgRestoreWOData(ArmoryDialog): - ############################################################################# - def __init__(self, parent, main, thisIsATest=False, expectWltID=None): - super(DlgRestoreWOData, self).__init__(parent, main) - - self.thisIsATest = thisIsATest - self.testWltID = expectWltID - headerStr = '' - lblDescr = '' - - # Write the text at the top of the window. - if thisIsATest: - lblDescr = QRichLabel(self.tr( - 'Test a Watch-Only Wallet Restore ' - '

' - 'Use this window to test the restoration of a watch-only wallet using ' - 'the wallet\'s data. You can either type the data on a root data ' - 'printout or import the data from a file.')) - else: - lblDescr = QRichLabel(self.tr( - 'Restore a Watch-Only Wallet ' - '

' - 'Use this window to restore a watch-only wallet using the wallet\'s ' - 'data. You can either type the data on a root data printout or import ' - 'the data from a file.')) - - # Create the line that will contain the imported ID. - self.rootIDLabel = QRichLabel(self.tr('Watch-Only Root ID:'), doWrap=False) - inpMask = '
'), - QMessageBox.Ok) - LOGERROR('Error in root ID restore field') - LOGERROR('Error Type: %s', errType) - LOGERROR('Error Value: %s', errVal) - return - - # Save the version/key byte and the root ID. For now, ignore the version. - inRootVer = inRootChecked[0] # 1 byte - inRootID = inRootChecked[1:7] # 6 bytes - - # Read in the root data (public key & chain code) and handle any errors. - for i in range(nLine): - hasError = False - try: - rawEntry = str(self.pkccList[i].text()) - rawBin, err = readSixteenEasyBytes(rawEntry.replace(' ', '')) - if err == 'Error_2+': # 2+ bytes are wrong, so we need to stop. - hasError = True - elif err == 'Fixed_1': # 1 byte is wrong, so we may be okay. - nError += 1 - except: - hasError = True - - # If the root ID is busted, stop. - if hasError: - lineNumber = i+1 - reply = QMessageBox.critical(self, self.tr('Invalid Data'), self.tr( - 'There is an error in the root data you entered that could not be ' - 'fixed automatically. Please double-check that you entered the ' - 'text exactly as it appears on the wallet-backup page.

' - 'The error occured on line #%d.' % lineNumber), QMessageBox.Ok) - LOGERROR('Error in root data restore field') - return - - # If we've gotten this far, save the incoming line. - inputLines.append(rawBin) - - # Set up the root ID data. - pkVer = binary_to_int(inRootVer) & PYROOTPKCCVERMASK # Ignored for now. - pkSignByte = ((binary_to_int(inRootVer) & PYROOTPKCCSIGNMASK) >> 7) + 2 - rootPKComBin = int_to_binary(pkSignByte) + ''.join(inputLines[:2]) - rootPubKey = CryptoECDSA().UncompressPoint(SecureBinaryData(rootPKComBin)) - rootChainCode = SecureBinaryData(''.join(inputLines[2:])) - - # Now we should have a fully-plaintext root key and chain code, and can - # get some related data. - root = PyBtcAddress().createFromPublicKeyData(rootPubKey) - root.chaincode = rootChainCode - first = root.extendAddressChain() - newWltID = binary_to_base58(inRootID) - - # Stop here if this was just a test - if self.thisIsATest: - verifyRecoveryTestID(self, newWltID, self.testWltID) - return - - # If we already have the wallet, don't replace it, otherwise proceed. - dlgOwnWlt = None - if newWltID in self.main.walletMap: - QMessageBox.warning(self, self.tr('Wallet Already Exists'), self.tr( - 'The wallet already exists and will not be ' - 'replaced.'), QMessageBox.Ok) - self.reject() - return - else: - # Make sure the user is restoring the wallet they want to restore. - reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), \ - self.tr('The data you entered corresponds to a wallet with a wallet ' - 'ID: \n\n\t%s\n\nDoes this ' - 'ID match the "Wallet Unique ID" you intend to restore? ' - 'If not, click "No" and enter the key and chain-code data ' - 'again.' % binary_to_base58(inRootID)), QMessageBox.Yes | QMessageBox.No) - if reply == QMessageBox.No: - return - - # Create the wallet. - self.newWallet = PyBtcWallet().createNewWalletFromPKCC(rootPubKey, \ - rootChainCode) - - # Create some more addresses and show a progress bar while restoring. - nPool = 1000 - fillAddrPoolProgress = DlgProgress(self, self.main, HBar=1, - Title=self.tr("Computing New Addresses")) - fillAddrPoolProgress.exec_(self.newWallet.fillAddressPool, nPool) - - self.accept() - - -################################################################################ -class DlgRestoreFragged(ArmoryDialog): - def __init__(self, parent, main, thisIsATest=False, expectWltID=None): - super(DlgRestoreFragged, self).__init__(parent, main) - - self.thisIsATest = thisIsATest - self.testWltID = expectWltID - headerStr = '' - if thisIsATest: - headerStr = self.tr('Testing a ' - 'Fragmented Backup') - else: - headerStr = self.tr('Restore Wallet from Fragments') - - descr = self.trUtf8( - '%s

' - 'Use this form to enter all the fragments to be restored. Fragments ' - 'can be stored on a mix of paper printouts, and saved files. ' - u'If any of the fragments require a SecurePrint\u200b\u2122 code, ' - 'you will only have to enter it once, since that code is the same for ' - 'all fragments of any given wallet.' % headerStr) - - if self.thisIsATest: - descr += self.tr('

' - 'For testing purposes, you may enter more fragments than needed ' - 'and Armory will test all subsets of the entered fragments to verify ' - 'that each one still recovers the wallet successfully.') - - lblDescr = QRichLabel(descr) - - frmDescr = makeHorizFrame([lblDescr], STYLE_RAISED) - - # HLINE - - self.scrollFragInput = QScrollArea() - self.scrollFragInput.setWidgetResizable(True) - self.scrollFragInput.setMinimumHeight(150) - - lblFragList = QRichLabel(self.tr('Input Fragments Below:'), doWrap=False, bold=True) - self.btnAddFrag = QPushButton(self.tr('+Frag')) - self.btnRmFrag = QPushButton(self.tr('-Frag')) - self.btnRmFrag.setVisible(False) - self.connect(self.btnAddFrag, SIGNAL(CLICKED), self.addFragment) - self.connect(self.btnRmFrag, SIGNAL(CLICKED), self.removeFragment) - self.chkEncrypt = QCheckBox(self.tr('Encrypt Restored Wallet')) - self.chkEncrypt.setChecked(True) - frmAddRm = makeHorizFrame([self.chkEncrypt, STRETCH, self.btnRmFrag, self.btnAddFrag]) - - self.fragDataMap = {} - self.tableSize = 2 - self.wltType = UNKNOWN - self.fragIDPrefix = UNKNOWN - - doItText = self.tr('Test Backup') if thisIsATest else self.tr('Restore from Fragments') - - btnExit = QPushButton(self.tr('Cancel')) - self.btnRestore = QPushButton(doItText) - self.connect(btnExit, SIGNAL(CLICKED), self.reject) - self.connect(self.btnRestore, SIGNAL(CLICKED), self.processFrags) - frmBtns = makeHorizFrame([btnExit, STRETCH, self.btnRestore]) - - self.lblRightFrm = QRichLabel('', hAlign=Qt.AlignHCenter) - self.lblSecureStr = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code:'), \ - hAlign=Qt.AlignHCenter, - doWrap=False, - color='TextWarn') - self.displaySecureString = QLineEdit() - self.imgPie = QRichLabel('', hAlign=Qt.AlignHCenter) - self.imgPie.setMinimumWidth(96) - self.imgPie.setMinimumHeight(96) - self.lblReqd = QRichLabel('', hAlign=Qt.AlignHCenter) - self.lblWltID = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) - self.lblFragID = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) - self.lblSecureStr.setVisible(False) - self.displaySecureString.setVisible(False) - self.displaySecureString.setMaximumWidth(relaxedSizeNChar(self.displaySecureString, 16)[0]) - # The Secure String is now edited in DlgEnterOneFrag, It is only displayed here - self.displaySecureString.setEnabled(False) - frmSecPair = makeVertFrame([self.lblSecureStr, self.displaySecureString]) - frmSecCtr = makeHorizFrame([STRETCH, frmSecPair, STRETCH]) - - frmWltInfo = makeVertFrame([STRETCH, - self.lblRightFrm, - self.imgPie, - self.lblReqd, - self.lblWltID, - self.lblFragID, - HLINE(), - frmSecCtr, - 'Strut(200)', - STRETCH], STYLE_SUNKEN) - - - fragmentsLayout = QGridLayout() - fragmentsLayout.addWidget(frmDescr, 0, 0, 1, 2) - fragmentsLayout.addWidget(frmAddRm, 1, 0, 1, 1) - fragmentsLayout.addWidget(self.scrollFragInput, 2, 0, 1, 1) - fragmentsLayout.addWidget(frmWltInfo, 1, 1, 2, 1) - setLayoutStretchCols(fragmentsLayout, 1, 0) - - walletRestoreTabs = QTabWidget() - fragmentsFrame = QFrame() - fragmentsFrame.setLayout(fragmentsLayout) - walletRestoreTabs.addTab(fragmentsFrame, self.tr("Fragments")) - self.advancedOptionsTab = AdvancedOptionsFrame(parent, main) - walletRestoreTabs.addTab(self.advancedOptionsTab, self.tr("Advanced Options")) - - self.chkEncrypt.setChecked(not thisIsATest) - self.chkEncrypt.setVisible(not thisIsATest) - self.advancedOptionsTab.setEnabled(not thisIsATest) - if not thisIsATest: - self.connect(self.chkEncrypt, SIGNAL(CLICKED), self.onEncryptCheckboxChange) - - layout = QVBoxLayout() - layout.addWidget(walletRestoreTabs) - layout.addWidget(frmBtns) - self.setLayout(layout) - self.setMinimumWidth(650) - self.setMinimumHeight(500) - self.sizeHint = lambda: QSize(800, 650) - self.setWindowTitle(self.tr('Restore wallet from fragments')) - - self.makeFragInputTable() - self.checkRestoreParams() - - ############################################################################# - # Hide advanced options whenver the restored wallet is unencrypted - def onEncryptCheckboxChange(self): - self.advancedOptionsTab.setEnabled(self.chkEncrypt.isChecked()) - - def makeFragInputTable(self, addCount=0): - - self.tableSize += addCount - newLayout = QGridLayout() - newFrame = QFrame() - self.fragsDone = [] - newLayout.addWidget(HLINE(), 0, 0, 1, 5) - for i in range(self.tableSize): - btnEnter = QPushButton(self.tr('Type Data')) - btnLoad = QPushButton(self.tr('Load File')) - btnClear = QPushButton(self.tr('Clear')) - lblFragID = QRichLabel('', doWrap=False) - lblSecure = QLabel('') - if i in self.fragDataMap: - M, fnum, wltID, doMask, fid = ReadFragIDLineBin(self.fragDataMap[i][0]) - self.fragsDone.append(fnum) - lblFragID.setText('' + fid + '') - if doMask: - lblFragID.setText('' + fid + '', color='TextWarn') - - - self.connect(btnEnter, SIGNAL(CLICKED), \ - functools.partial(self.dataEnter, fnum=i)) - self.connect(btnLoad, SIGNAL(CLICKED), \ - functools.partial(self.dataLoad, fnum=i)) - self.connect(btnClear, SIGNAL(CLICKED), \ - functools.partial(self.dataClear, fnum=i)) - - - newLayout.addWidget(btnEnter, 2 * i + 1, 0) - newLayout.addWidget(btnLoad, 2 * i + 1, 1) - newLayout.addWidget(btnClear, 2 * i + 1, 2) - newLayout.addWidget(lblFragID, 2 * i + 1, 3) - newLayout.addWidget(lblSecure, 2 * i + 1, 4) - newLayout.addWidget(HLINE(), 2 * i + 2, 0, 1, 5) - - btnFrame = QFrame() - btnFrame.setLayout(newLayout) - - frmFinal = makeVertFrame([btnFrame, STRETCH], STYLE_SUNKEN) - self.scrollFragInput.setWidget(frmFinal) - - self.btnAddFrag.setVisible(self.tableSize < 12) - self.btnRmFrag.setVisible(self.tableSize > 2) - - - ############################################################################# - def addFragment(self): - self.makeFragInputTable(1) - - ############################################################################# - def removeFragment(self): - self.makeFragInputTable(-1) - toRemove = [] - for key, val in self.fragDataMap.iteritems(): - if key >= self.tableSize: - toRemove.append(key) - - # Have to do this in a separate loop, cause you can't remove items - # from a map while you are iterating over them - for key in toRemove: - self.dataClear(key) + binID = base58_to_binary(self.uniqueFragSetID) + ID = ComputeFragIDLineHex(M, idx, binID, doMask, addSpaces=True) + fragPreview = 'ID: %s...
' % ID[:12] + fragPreview += 'F1: %s...
' % easyYs1[:12] + fragPreview += 'F2: %s... ' % easyYs2[:12] + lblPreview = QRichLabel(fragPreview) + lblPreview.setFont(GETFONT('Fixed', 9)) - ############################################################################# - def dataEnter(self, fnum): - dlg = DlgEnterOneFrag(self, self.main, self.fragsDone, self.wltType, self.displaySecureString.text()) - if dlg.exec_(): - LOGINFO('Good data from enter_one_frag exec! %d', fnum) - self.displaySecureString.setText(dlg.editSecurePrint.text()) - self.addFragToTable(fnum, dlg.fragData) - self.makeFragInputTable() + lblFragIdx = QRichLabel('#%d' % (idx + 1), size=4, color='TextBlue', \ + hAlign=Qt.AlignHCenter) + frmTopLeft = makeVertFrame([lblFragID, lblFragIdx, STRETCH]) + frmTopRight = makeVertFrame([lblFragPix, STRETCH]) - ############################################################################# - def dataLoad(self, fnum): - LOGINFO('Loading data for entry, %d', fnum) - toLoad = str(self.main.getFileLoad('Load Fragment File', \ - ['Wallet Fragments (*.frag)'])) + frmPaper = makeVertFrame([lblPreview]) + frmPaper.setStyleSheet('QFrame { background-color : #ffffff }') - if len(toLoad) == 0: - return + fnPrint = lambda: self.clickPrintFrag(idx) + fnSave = lambda: self.clickSaveFrag(idx) - if not os.path.exists(toLoad): - LOGERROR('File just chosen does not exist! %s', toLoad) - QMessageBox.critical(self, self.tr('File Does Not Exist'), self.tr( - 'The file you select somehow does not exist...? ' - '

%s

Try a different file' % toLoad), \ - QMessageBox.Ok) + btnPrintFrag = QPushButton(self.tr('View/Print')) + btnSaveFrag = QPushButton(self.tr('Save to File')) + self.connect(btnPrintFrag, SIGNAL(CLICKED), fnPrint) + self.connect(btnSaveFrag, SIGNAL(CLICKED), fnSave) + frmButtons = makeHorizFrame([btnPrintFrag, btnSaveFrag]) - fragMap = {} - with open(toLoad, 'r') as fin: - allData = [line.strip() for line in fin.readlines()] - fragMap = {} - for line in allData: - if line[:2].lower() in ['id', 'x1', 'x2', 'x3', 'x4', \ - 'y1', 'y2', 'y3', 'y4', \ - 'f1', 'f2', 'f3', 'f4']: - fragMap[line[:2].lower()] = line[3:].strip().replace(' ', '') - - - cList, nList = [], [] - if len(fragMap) == 9: - cList, nList = ['x', 'y'], ['1', '2', '3', '4'] - elif len(fragMap) == 5: - cList, nList = ['f'], ['1', '2', '3', '4'] - elif len(fragMap) == 3: - cList, nList = ['f'], ['1', '2'] - else: - LOGERROR('Unexpected number of lines in the frag file, %d', len(fragMap)) - return - fragData = [] - fragData.append(hex_to_binary(fragMap['id'])) - for c in cList: - for n in nList: - mapKey = c + n - rawBin, err = readSixteenEasyBytes(fragMap[c + n]) - if err == 'Error_2+': - QMessageBox.critical(self, self.tr('Fragment Error'), self.tr( - 'There was an unfixable error in the fragment file: ' - '

File: %s
Line: %s
' % (toLoad, mapKey)), \ - QMessageBox.Ok) - return - fragData.append(SecureBinaryData(rawBin)) - rawBin = None + layout = QGridLayout() + layout.addWidget(frmTopLeft, 0, 0, 1, 1) + layout.addWidget(frmTopRight, 0, 1, 1, 1) + layout.addWidget(frmPaper, 1, 0, 1, 2) + layout.addWidget(frmButtons, 2, 0, 1, 2) + layout.setSizeConstraint(QLayout.SetFixedSize) - self.addFragToTable(fnum, fragData) - self.makeFragInputTable() + outFrame = QFrame() + outFrame.setFrameStyle(STYLE_STYLED) + outFrame.setLayout(layout) + return outFrame ############################################################################# - def dataClear(self, fnum): - if not fnum in self.fragDataMap: - return - - for i in range(1, 3): - self.fragDataMap[fnum][i].destroy() - del self.fragDataMap[fnum] - self.makeFragInputTable() - self.checkRestoreParams() - + def clickPrintAll(self): + self.clickPrintFrag(range(int(str(self.comboN.currentText())))) ############################################################################# - def checkRestoreParams(self): - showRightFrm = False - self.btnRestore.setEnabled(False) - self.lblRightFrm.setText(self.tr( - 'Start entering fragments into the table to left...')) - for row, data in self.fragDataMap.iteritems(): - showRightFrm = True - M, fnum, setIDBin, doMask, idBase58 = ReadFragIDLineBin(data[0]) - self.lblRightFrm.setText(self.tr('Wallet Being Restored:')) - self.imgPie.setPixmap(QPixmap('./img/frag%df.png' % M).scaled(96,96)) - self.lblReqd.setText(self.tr('Frags Needed: %s' % M)) - self.lblFragID.setText(self.tr('Fragments: %s' % idBase58.split('-')[0])) - self.btnRestore.setEnabled(len(self.fragDataMap) >= M) - break - - anyMask = False - for row, data in self.fragDataMap.iteritems(): - M, fnum, wltIDBin, doMask, idBase58 = ReadFragIDLineBin(data[0]) - if doMask: - anyMask = True - break - # If all of the rows with a Mask have been removed clear the securePrintCode - if not anyMask: - self.displaySecureString.setText('') - self.lblSecureStr.setVisible(anyMask) - self.displaySecureString.setVisible(anyMask) - - if not showRightFrm: - self.fragIDPrefix = UNKNOWN - self.wltType = UNKNOWN - - self.imgPie.setVisible(showRightFrm) - self.lblReqd.setVisible(showRightFrm) - self.lblWltID.setVisible(showRightFrm) - self.lblFragID.setVisible(showRightFrm) - + def clickPrintFrag(self, zindex): + if not isinstance(zindex, (list, tuple)): + zindex = [zindex] + fragData = {} + fragData['M'] = int(str(self.comboM.currentText())) + fragData['N'] = int(str(self.comboN.currentText())) + fragData['FragIDStr'] = self.fragPrefixStr + fragData['FragPixmap'] = self.fragPixmapFn + fragData['Range'] = zindex + fragData['Secure'] = self.chkSecurePrint.isChecked() + fragData['fragSetID'] = self.uniqueFragSetID + dlg = DlgPrintBackup(self, self.main, self.wlt, 'Fragments', \ + self.secureMtrx, self.secureMtrxCrypt, fragData, \ + self.secureRoot, self.secureChain) + dlg.exec_() ############################################################################# - def addFragToTable(self, tableIndex, fragData): - - if len(fragData) == 9: - currType = '0' - elif len(fragData) == 5: - currType = BACKUP_TYPE_135A - elif len(fragData) == 3: - currType = BACKUP_TYPE_135C - else: - LOGERROR('How\'d we get fragData of size: %d', len(fragData)) - return - - if self.wltType == UNKNOWN: - self.wltType = currType - elif not self.wltType == currType: - QMessageBox.critical(self, self.tr('Mixed fragment types'), self.tr( - 'You entered a fragment for a different wallet type. Please check ' - 'that all fragments are for the same wallet, of the same version, ' - 'and require the same number of fragments.'), QMessageBox.Ok) - LOGERROR('Mixing frag types! How did that happen?') - return - + def clickSaveFrag(self, zindex): + saveMtrx = self.secureMtrx + doMask = False + if self.chkSecurePrint.isChecked(): + response = QMessageBox.question(self, self.tr('Secure Backup?'), self.trUtf8( + u'You have selected to use SecurePrint\u200b\u2122 for the printed ' + 'backups, which can also be applied to fragments saved to file. ' + u'Doing so will require you store the SecurePrint\u200b\u2122 ' + 'code with the backup, but it will prevent unencrypted key data from ' + 'touching any disks.

Do you want to encrypt the fragment ' + u'file with the same SecurePrint\u200b\u2122 code?'), \ + QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) - M, fnum, wltIDBin, doMask, idBase58 = ReadFragIDLineBin(fragData[0]) - # If we don't know the Secure String Yet we have to get it - if doMask and len(str(self.displaySecureString.text()).strip()) == 0: - dlg = DlgEnterSecurePrintCode(self, self.main) - if dlg.exec_(): - self.displaySecureString.setText(dlg.editSecurePrint.text()) + if response == QMessageBox.Yes: + saveMtrx = self.secureMtrxCrypt + doMask = True + elif response == QMessageBox.No: + pass else: return - if self.fragIDPrefix == UNKNOWN: - self.fragIDPrefix = idBase58.split('-')[0] - elif not self.fragIDPrefix == idBase58.split('-')[0]: - QMessageBox.critical(self, self.tr('Multiple Wallets'), self.tr( - 'The fragment you just entered is actually for a different wallet ' - 'than the previous fragments you entered. Please double-check that ' - 'all the fragments you are entering belong to the same wallet and ' - 'have the "number of needed fragments" (M-value, in M-of-N).'), \ - QMessageBox.Ok) - LOGERROR('Mixing fragments of different wallets! %s', idBase58) - return + wid = self.wlt.uniqueIDB58 + pref = self.fragPrefixStr + fnum = zindex + 1 + M = self.M + sec = 'secure.' if doMask else '' + defaultFn = 'wallet_%s_%s_num%d_need%d.%sfrag' % (wid, pref, fnum, M, sec) + #print 'FragFN:', defaultFn + savepath = self.main.getFileSave('Save Fragment', \ + ['Wallet Fragments (*.frag)'], \ + defaultFn) - if not self.verifyNonDuplicateFrag(fnum): - QMessageBox.critical(self, self.tr('Duplicate Fragment'), self.tr( - 'You just input fragment #%s, but that fragment has already been ' - 'entered!' % fnum), QMessageBox.Ok) + if len(toUnicode(savepath)) == 0: return + fout = open(savepath, 'w') + fout.write('Wallet ID: %s\n' % wid) + fout.write('Create Date: %s\n' % unixTimeToFormatStr(RightNow())) + fout.write('Fragment ID: %s-#%d\n' % (pref, fnum)) + fout.write('Frag Needed: %d\n' % M) + fout.write('\n\n') + try: + yBin = saveMtrx[zindex][1].toBinStr() + binID = base58_to_binary(self.uniqueFragSetID) + IDLine = ComputeFragIDLineHex(M, zindex, binID, doMask, addSpaces=True) + if len(yBin) == 32: + fout.write('ID: ' + IDLine + '\n') + fout.write('F1: ' + makeSixteenBytesEasy(yBin[:16 ]) + '\n') + fout.write('F2: ' + makeSixteenBytesEasy(yBin[ 16:]) + '\n') + elif len(yBin) == 64: + fout.write('ID: ' + IDLine + '\n') + fout.write('F1: ' + makeSixteenBytesEasy(yBin[:16 ]) + '\n') + fout.write('F2: ' + makeSixteenBytesEasy(yBin[ 16:32 ]) + '\n') + fout.write('F3: ' + makeSixteenBytesEasy(yBin[ 32:48 ]) + '\n') + fout.write('F4: ' + makeSixteenBytesEasy(yBin[ 48:]) + '\n') + else: + LOGERROR('yBin is not 32 or 64 bytes! It is %s bytes', len(yBin)) + finally: + yBin = None - if currType == '0': - X = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 5)])) - Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(5, 9)])) - elif currType == BACKUP_TYPE_135A: - X = SecureBinaryData(int_to_binary(fnum + 1, widthBytes=64, endOut=BIGENDIAN)) - Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 5)])) - elif currType == BACKUP_TYPE_135C: - X = SecureBinaryData(int_to_binary(fnum + 1, widthBytes=32, endOut=BIGENDIAN)) - Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 3)])) - - self.fragDataMap[tableIndex] = [fragData[0][:], X.copy(), Y.copy()] - - X.destroy() - Y.destroy() - self.checkRestoreParams() - - ############################################################################# - def verifyNonDuplicateFrag(self, fnum): - for row, data in self.fragDataMap.iteritems(): - rowFrag = ReadFragIDLineBin(data[0])[1] - if fnum == rowFrag: - return False - - return True - + fout.close() + qmsg = self.tr( + 'The fragment was successfully saved to the following location: ' + '

%s

' % savepath) - ############################################################################# - def processFrags(self): - if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: - QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ - self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) - return - if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfBytes() == -1: - QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ - self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) - return - SECPRINT = HardcodedKeyMaskParams() - pwd, ekey = '', '' - if self.displaySecureString.isVisible(): - pwd = str(self.displaySecureString.text()).strip() - maskKey = SECPRINT['FUNC_KDF'](pwd) + if doMask: + qmsg += self.trUtf8( + 'Important: ' + 'The fragment was encrypted with the ' + u'SecurePrint\u200b\u2122 encryption code. You must keep this ' + 'code with the backup in order to use it! The code is ' + 'case-sensitive! ' + '

%s' + '

' + 'The above code is case-sensitive!' \ + % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ + self.backupData.sppass)) - fragMtrx, M = [], -1 - for row, trip in self.fragDataMap.iteritems(): - M, fnum, wltID, doMask, fid = ReadFragIDLineBin(trip[0]) - X, Y = trip[1], trip[2] - if doMask: - LOGINFO('Row %d needs unmasking' % row) - Y = SECPRINT['FUNC_UNMASK'](Y, ekey=maskKey) - else: - LOGINFO('Row %d is already unencrypted' % row) - fragMtrx.append([X.toBinStr(), Y.toBinStr()]) + QMessageBox.information(self, self.tr('Success'), qmsg, QMessageBox.Ok) - typeToBytes = {'0': 64, BACKUP_TYPE_135A: 64, BACKUP_TYPE_135C: 32} - nBytes = typeToBytes[self.wltType] - if self.thisIsATest and len(fragMtrx) > M: - self.testFragSubsets(fragMtrx, M) + ############################################################################# + def destroyFrags(self): + if len(self.secureMtrx) == 0: return + if isinstance(self.secureMtrx[0], (list, tuple)): + for sbdList in self.secureMtrx: + for sbd in sbdList: + sbd.destroy() + for sbdList in self.secureMtrxCrypt: + for sbd in sbdList: + sbd.destroy() + else: + for sbd in self.secureMtrx: + sbd.destroy() + for sbd in self.secureMtrxCrypt: + sbd.destroy() - SECRET = ReconstructSecret(fragMtrx, M, nBytes) - for i in range(len(fragMtrx)): - fragMtrx[i] = [] - - LOGINFO('Final length of frag mtrx: %d', len(fragMtrx)) - LOGINFO('Final length of secret: %d', len(SECRET)) - - priv, chain = '', '' - if len(SECRET) == 64: - priv = SecureBinaryData(SECRET[:32 ]) - chain = SecureBinaryData(SECRET[ 32:]) - elif len(SECRET) == 32: - priv = SecureBinaryData(SECRET) - chain = DeriveChaincodeFromRootKey(priv) - + self.secureMtrx = [] + self.secureMtrxCrypt = [] - # If we got here, the data is valid, let's create the wallet and accept the dlg - # Now we should have a fully-plaintext rootkey and chaincode - root = PyBtcAddress().createFromPlainKeyData(priv) - root.chaincode = chain - first = root.extendAddressChain() - newWltID = binary_to_base58((ADDRBYTE + first.getAddr160()[:5])[::-1]) + ############################################################################# + def destroyEverything(self): + self.secureRoot.destroy() + self.secureChain.destroy() + self.securePrint.destroy() + self.destroyFrags() - # If this is a test, then bail - if self.thisIsATest: - verifyRecoveryTestID(self, newWltID, self.testWltID) - return + ############################################################################# + def recomputeFragData(self): + """ + Only M is needed, since N doesn't change + """ - dlgOwnWlt = None - if newWltID in self.main.walletMap: - dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) + M = int(str(self.comboM.currentText())) + N = int(str(self.comboN.currentText())) + # Make sure only local variables contain non-SBD data + self.destroyFrags() + self.uniqueFragSetID = \ + binary_to_base58(SecureBinaryData().GenerateRandom(6).toBinStr()) + insecureData = SplitSecret(self.securePrint, M, self.maxmaxN) + for x, y in insecureData: + self.secureMtrx.append([SecureBinaryData(x), SecureBinaryData(y)]) + insecureData, x, y = None, None, None - if (dlgOwnWlt.exec_()): - if dlgOwnWlt.output == 0: - return - else: - self.reject() - return + ##### + # Now we compute the SecurePrint(TM) versions of the fragments + SECPRINT = HardcodedKeyMaskParams() + MASK = lambda x: SECPRINT['FUNC_MASK'](x, ekey=self.binCrypt32) + if not self.randpass or not self.binCrypt32: + self.randpass = SECPRINT['FUNC_PWD'](self.secureRoot + self.secureChain) + self.binCrypt32 = SECPRINT['FUNC_KDF'](self.randpass) + self.secureMtrxCrypt = [] + for sbdX, sbdY in self.secureMtrx: + self.secureMtrxCrypt.append([sbdX.copy(), MASK(sbdY)]) + ##### - reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), self.tr( - 'The data you entered corresponds to a wallet with the ' - 'ID:
{%s}
Does this ID ' - 'match the "Wallet Unique ID" printed on your paper backup? ' - 'If not, click "No" and reenter key and chain-code data ' - 'again.' % newWltID), QMessageBox.Yes | QMessageBox.No) - if reply == QMessageBox.No: - return + self.M, self.N = M, N + self.fragPrefixStr = ComputeFragIDBase58(self.M, \ + base58_to_binary(self.uniqueFragSetID)) + self.fragPixmapFn = './img/frag%df.png' % M - passwd = [] - if self.chkEncrypt.isChecked(): - dlgPasswd = DlgChangePassphrase(self, self.main) - if dlgPasswd.exec_(): - passwd = SecureBinaryData(str(dlgPasswd.edtPasswd1.text())) - else: - QMessageBox.critical(self, self.tr('Cannot Encrypt'), self.tr( - 'You requested your restored wallet be encrypted, but no ' - 'valid passphrase was supplied. Aborting wallet ' - 'recovery.'), QMessageBox.Ok) - return + ############################################################################# + def accept(self): + self.destroyEverything() + super(DlgFragBackup, self).accept() - shortl = '' - longl = '' - nPool = 1000 + ############################################################################# + def reject(self): + self.destroyEverything() + super(DlgFragBackup, self).reject() - if dlgOwnWlt is not None: - if dlgOwnWlt.Meta is not None: - shortl = ' - %s' % (dlgOwnWlt.Meta['shortLabel']) - longl = dlgOwnWlt.Meta['longLabel'] - nPool = max(nPool, dlgOwnWlt.Meta['naddress']) - if passwd: - self.newWallet = PyBtcWallet().createNewWallet(\ - plainRootKey=priv, \ - chaincode=chain, \ - shortLabel='Restored - ' + newWltID + shortl, \ - longLabel=longl, \ - withEncrypt=True, \ - securePassphrase=passwd, \ - kdfTargSec=self.advancedOptionsTab.getKdfSec(), \ - kdfMaxMem=self.advancedOptionsTab.getKdfBytes(), - isActuallyNew=False, \ - doRegisterWithBDM=False) - else: - self.newWallet = PyBtcWallet().createNewWallet(\ - plainRootKey=priv, \ - chaincode=chain, \ - shortLabel='Restored - ' + newWltID +shortl, \ - longLabel=longl, \ - withEncrypt=False, \ - isActuallyNew=False, \ - doRegisterWithBDM=False) +################################################################################ +# Create a special QLineEdit with a masked input +# Forces the cursor to start at position 0 whenever there is no input +class MaskedInputLineEdit(QLineEdit): + def __init__(self, inputMask): + super(MaskedInputLineEdit, self).__init__() + self.setInputMask(inputMask) + fixFont = GETFONT('Fix', 9) + self.setFont(fixFont) + self.setMinimumWidth(tightSizeStr(fixFont, inputMask)[0] + 10) + self.connect(self, SIGNAL('cursorPositionChanged(int,int)'), self.controlCursor) - # Will pop up a little "please wait..." window while filling addr pool - fillAddrPoolProgress = DlgProgress(self, self.parent, HBar=1, - Title=self.tr("Computing New Addresses")) - fillAddrPoolProgress.exec_(self.newWallet.fillAddressPool, nPool) - - if dlgOwnWlt is not None: - if dlgOwnWlt.Meta is not None: - from armoryengine.PyBtcWallet import WLT_UPDATE_ADD - for n_cmt in range(0, dlgOwnWlt.Meta['ncomments']): - entrylist = [] - entrylist = list(dlgOwnWlt.Meta[n_cmt]) - self.newWallet.walletFileSafeUpdate([[WLT_UPDATE_ADD, entrylist[2], entrylist[1], entrylist[0]]]) - - self.newWallet = PyBtcWallet().readWalletFile(self.newWallet.walletPath) - self.accept() + def controlCursor(self, oldpos, newpos): + if newpos != 0 and len(str(self.text()).strip()) == 0: + self.setCursorPosition(0) - ############################################################################# - def testFragSubsets(self, fragMtrx, M): - # If the user entered multiple fragments - fragMap = {} - for x, y in fragMtrx: - fragMap[binary_to_int(x, BIGENDIAN) - 1] = [x, y] - typeToBytes = {'0': 64, BACKUP_TYPE_135A: 64, BACKUP_TYPE_135C: 32} - - isRandom, results = testReconstructSecrets(fragMap, M, 100) - def privAndChainFromRow(secret): - priv, chain = None, None - if len(secret) == 64: - priv = SecureBinaryData(secret[:32 ]) - chain = SecureBinaryData(secret[ 32:]) - return (priv, chain) - elif len(secret) == 32: - priv = SecureBinaryData(secret) - chain = DeriveChaincodeFromRootKey(priv) - return (priv, chain) - else: - LOGERROR('Root secret is %s bytes ?!' % len(secret)) - raise KeyDataError - results = [(row[0], privAndChainFromRow(row[1])) for row in results] - subsAndIDs = [(row[0], calcWalletIDFromRoot(*row[1])) for row in results] +def checkSecurePrintCode(context, SECPRINT, securePrintCode): + result = True + try: + if len(securePrintCode) < 9: + QMessageBox.critical(context, context.tr('Invalid Code'), context.trUtf8( + u'You didn\'t enter a full SecurePrint\u200b\u2122 code. This ' + 'code is needed to decrypt your backup file.'), QMessageBox.Ok) + result = False + elif not SECPRINT['FUNC_CHKPWD'](securePrintCode): + QMessageBox.critical(context, context.trUtf8(u'Bad SecurePrint\u200b\u2122 Code'), context.trUtf8( + u'The SecurePrint\u200b\u2122 code you entered has an error ' + 'in it. Note that the code is case-sensitive. Please verify ' + 'you entered it correctly and try again.'), QMessageBox.Ok) + result = False + except NonBase58CharacterError as e: + QMessageBox.critical(context, context.trUtf8(u'Bad SecurePrint\u200b\u2122 Code'), context.trUtf8( + u'The SecurePrint\u200b\u2122 code you entered has unrecognized characters ' + 'in it. %s Only the following characters are allowed: %s' % (e.message, BASE58CHARS)), QMessageBox.Ok) + result = False + return result - DlgShowTestResults(self, isRandom, subsAndIDs, \ - M, len(fragMtrx), self.testWltID).exec_() ########################################################################## @@ -7686,7 +5138,6 @@ def verifyUserInput(self): self.accept() - ################################################################################ def verifyRecoveryTestID(parent, computedWltID, expectedWltID=None): @@ -7744,591 +5195,6 @@ def verifyRecoveryTestID(parent, computedWltID, expectedWltID=None): 'Expected wallet ID: %s
' '
' % (computedWltID, expectedWltID ))) -################################################################################ -class DlgReplaceWallet(ArmoryDialog): - - ############################################################################# - def __init__(self, WalletID, parent, main): - super(DlgReplaceWallet, self).__init__(parent, main) - - lblDesc = QLabel(self.tr( - 'You already have this wallet loaded!
' - 'You can choose to:
' - '- Cancel wallet restore operation
' - '- Set new password and fix any errors
' - '- Overwrite old wallet (delete comments & labels)
')) - - self.WalletID = WalletID - self.main = main - self.Meta = None - self.output = 0 - - self.wltPath = main.walletMap[WalletID].walletPath - - self.btnAbort = QPushButton(self.tr('Cancel')) - self.btnReplace = QPushButton(self.tr('Overwrite')) - self.btnSaveMeta = QPushButton(self.tr('Merge')) - - self.connect(self.btnAbort, SIGNAL('clicked()'), self.reject) - self.connect(self.btnReplace, SIGNAL('clicked()'), self.Replace) - self.connect(self.btnSaveMeta, SIGNAL('clicked()'), self.SaveMeta) - - layoutDlg = QGridLayout() - - layoutDlg.addWidget(lblDesc, 0, 0, 4, 4) - layoutDlg.addWidget(self.btnAbort, 4, 0, 1, 1) - layoutDlg.addWidget(self.btnSaveMeta, 4, 1, 1, 1) - layoutDlg.addWidget(self.btnReplace, 4, 2, 1, 1) - - self.setLayout(layoutDlg) - self.setWindowTitle('Wallet already exists') - - ######### - def Replace(self): - self.main.removeWalletFromApplication(self.WalletID) - - datestr = RightNowStr('%Y-%m-%d-%H%M') - homedir = os.path.dirname(self.wltPath) - - oldpath = os.path.join(homedir, self.WalletID, datestr) - try: - if not os.path.exists(oldpath): - os.makedirs(oldpath) - except: - LOGEXCEPT('Cannot create new folder in dataDir! Missing credentials?') - self.reject() - return - - oldname = os.path.basename(self.wltPath) - self.newname = os.path.join(oldpath, '%s_old.wallet' % (oldname[0:-7])) - - os.rename(self.wltPath, self.newname) - - backup = '%s_backup.wallet' % (self.wltPath[0:-7]) - if os.path.exists(backup): - os.remove(backup) - - self.output =1 - self.accept() - - ######### - def SaveMeta(self): - from armoryengine.PyBtcWalletRecovery import PyBtcWalletRecovery - - metaProgress = DlgProgress(self, self.main, Title=self.tr('Ripping Meta Data')) - getMeta = PyBtcWalletRecovery() - self.Meta = metaProgress.exec_(getMeta.ProcessWallet, - WalletPath=self.wltPath, - Mode=RECOVERMODE.Meta, - Progress=metaProgress.UpdateText) - self.Replace() - - -############################################################################### -class DlgWltRecoverWallet(ArmoryDialog): - def __init__(self, parent=None, main=None): - super(DlgWltRecoverWallet, self).__init__(parent, main) - - self.edtWalletPath = QLineEdit() - self.edtWalletPath.setFont(GETFONT('Fixed', 9)) - edtW,edtH = tightSizeNChar(self.edtWalletPath, 50) - self.edtWalletPath.setMinimumWidth(edtW) - self.btnWalletPath = QPushButton(self.tr('Browse File System')) - - self.connect(self.btnWalletPath, SIGNAL('clicked()'), self.selectFile) - - lblDesc = QRichLabel(self.tr( - 'Wallet Recovery Tool: ' - '
' - 'This tool will recover data from damaged or inconsistent ' - 'wallets. Specify a wallet file and Armory will analyze the ' - 'wallet and fix any errors with it. ' - '

' - 'If any problems are found with the specified ' - 'wallet, Armory will provide explanation and instructions to ' - 'transition to a new wallet.' % htmlColor('TextWarn'))) - lblDesc.setScaledContents(True) - - lblWalletPath = QRichLabel(self.tr('Wallet Path:')) - - self.selectedWltID = None - - def doWltSelect(): - dlg = DlgWalletSelect(self, self.main, self.tr('Select Wallet...'), '') - if dlg.exec_(): - self.selectedWltID = dlg.selectedID - wlt = self.parent.walletMap[dlg.selectedID] - self.edtWalletPath.setText(wlt.walletPath) - - self.btnWltSelect = QPushButton(self.tr("Select Loaded Wallet")) - self.connect(self.btnWltSelect, SIGNAL(CLICKED), doWltSelect) - - layoutMgmt = QGridLayout() - wltSltQF = QFrame() - wltSltQF.setFrameStyle(STYLE_SUNKEN) - - layoutWltSelect = QGridLayout() - layoutWltSelect.addWidget(lblWalletPath, 0,0, 1, 1) - layoutWltSelect.addWidget(self.edtWalletPath, 0,1, 1, 3) - layoutWltSelect.addWidget(self.btnWltSelect, 1,0, 1, 2) - layoutWltSelect.addWidget(self.btnWalletPath, 1,2, 1, 2) - layoutWltSelect.setColumnStretch(0, 0) - layoutWltSelect.setColumnStretch(1, 1) - layoutWltSelect.setColumnStretch(2, 1) - layoutWltSelect.setColumnStretch(3, 0) - - wltSltQF.setLayout(layoutWltSelect) - - layoutMgmt.addWidget(makeHorizFrame([lblDesc], STYLE_SUNKEN), 0,0, 2,4) - layoutMgmt.addWidget(wltSltQF, 2, 0, 3, 4) - - self.rdbtnStripped = QRadioButton('', parent=self) - self.connect(self.rdbtnStripped, SIGNAL('event()'), self.rdClicked) - lblStripped = QLabel(self.tr('Stripped Recovery
Only attempts to \ - recover the wallet\'s rootkey and chaincode')) - layout_StrippedH = QGridLayout() - layout_StrippedH.addWidget(self.rdbtnStripped, 0, 0, 1, 1) - layout_StrippedH.addWidget(lblStripped, 0, 1, 2, 19) - - self.rdbtnBare = QRadioButton('') - lblBare = QLabel(self.tr('Bare Recovery
Attempts to recover all private key related data')) - layout_BareH = QGridLayout() - layout_BareH.addWidget(self.rdbtnBare, 0, 0, 1, 1) - layout_BareH.addWidget(lblBare, 0, 1, 2, 19) - - self.rdbtnFull = QRadioButton('') - self.rdbtnFull.setChecked(True) - lblFull = QLabel(self.tr('Full Recovery
Attempts to recover as much data as possible')) - layout_FullH = QGridLayout() - layout_FullH.addWidget(self.rdbtnFull, 0, 0, 1, 1) - layout_FullH.addWidget(lblFull, 0, 1, 2, 19) - - self.rdbtnCheck = QRadioButton('') - lblCheck = QLabel(self.tr('Consistency Check
Checks wallet consistency. Works with both full and watch only
wallets.' - ' Unlocking of encrypted wallets is not mandatory')) - layout_CheckH = QGridLayout() - layout_CheckH.addWidget(self.rdbtnCheck, 0, 0, 1, 1) - layout_CheckH.addWidget(lblCheck, 0, 1, 3, 19) - - - layoutMode = QGridLayout() - layoutMode.addLayout(layout_StrippedH, 0, 0, 2, 4) - layoutMode.addLayout(layout_BareH, 2, 0, 2, 4) - layoutMode.addLayout(layout_FullH, 4, 0, 2, 4) - layoutMode.addLayout(layout_CheckH, 6, 0, 3, 4) - - - #self.rdnGroup = QButtonGroup() - #self.rdnGroup.addButton(self.rdbtnStripped) - #self.rdnGroup.addButton(self.rdbtnBare) - #self.rdnGroup.addButton(self.rdbtnFull) - #self.rdnGroup.addButton(self.rdbtnCheck) - - - layoutMgmt.addLayout(layoutMode, 5, 0, 9, 4) - """ - wltModeQF = QFrame() - wltModeQF.setFrameStyle(STYLE_SUNKEN) - wltModeQF.setLayout(layoutMode) - - layoutMgmt.addWidget(wltModeQF, 5, 0, 9, 4) - wltModeQF.setVisible(False) - - - btnShowAllOpts = QLabelButton(self.tr("All Recovery Options>>>")) - frmBtn = makeHorizFrame(['Stretch', btnShowAllOpts, 'Stretch'], STYLE_SUNKEN) - layoutMgmt.addWidget(frmBtn, 5, 0, 9, 4) - - def expandOpts(): - wltModeQF.setVisible(True) - btnShowAllOpts.setVisible(False) - self.connect(btnShowAllOpts, SIGNAL('clicked()'), expandOpts) - - if not self.main.usermode==USERMODE.Expert: - frmBtn.setVisible(False) - """ - - self.btnRecover = QPushButton(self.tr('Recover')) - self.btnCancel = QPushButton(self.tr('Cancel')) - layout_btnH = QHBoxLayout() - layout_btnH.addWidget(self.btnRecover, 1) - layout_btnH.addWidget(self.btnCancel, 1) - - def updateBtn(qstr): - if os.path.exists(str(qstr).strip()): - self.btnRecover.setEnabled(True) - self.btnRecover.setToolTip('') - else: - self.btnRecover.setEnabled(False) - self.btnRecover.setToolTip(self.tr('The entered path does not exist')) - - updateBtn('') - self.connect(self.edtWalletPath, SIGNAL('textChanged(QString)'), updateBtn) - - - layoutMgmt.addLayout(layout_btnH, 14, 1, 1, 2) - - self.connect(self.btnRecover, SIGNAL('clicked()'), self.accept) - self.connect(self.btnCancel , SIGNAL('clicked()'), self.reject) - - self.setLayout(layoutMgmt) - self.layout().setSizeConstraint(QLayout.SetFixedSize) - self.setWindowTitle(self.tr('Wallet Recovery Tool')) - self.setMinimumWidth(550) - - def rdClicked(self): - # TODO: Why does this do nohting? Was it a stub that was forgotten? - LOGINFO("clicked") - - def promptWalletRecovery(self): - """ - Prompts the user with a window asking for wallet path and recovery mode. - Proceeds to Recover the wallet. Prompt for password if the wallet is locked - """ - if self.exec_(): - path = unicode(self.edtWalletPath.text()) - mode = RECOVERMODE.Bare - if self.rdbtnStripped.isChecked(): - mode = RECOVERMODE.Stripped - elif self.rdbtnFull.isChecked(): - mode = RECOVERMODE.Full - elif self.rdbtnCheck.isChecked(): - mode = RECOVERMODE.Check - - if mode==RECOVERMODE.Full and self.selectedWltID: - # Funnel all standard, full recovery operations through the - # inconsistent-wallet-dialog. - wlt = self.main.walletMap[self.selectedWltID] - dlgRecoveryUI = DlgCorruptWallet(wlt, [], self.main, self, False) - dlgRecoveryUI.exec_(dlgRecoveryUI.doFixWallets()) - else: - # This is goatpig's original behavior - preserved for any - # non-loaded wallets or non-full recovery operations. - if self.selectedWltID: - wlt = self.main.walletMap[self.selectedWltID] - else: - wlt = path - - dlgRecoveryUI = DlgCorruptWallet(wlt, [], self.main, self, False) - dlgRecoveryUI.exec_(dlgRecoveryUI.ProcessWallet(mode)) - else: - return False - - def selectFile(self): - # Had to reimplement the path selection here, because the way this was - # implemented doesn't let me access self.main.getFileLoad - ftypes = self.tr('Wallet files (*.wallet);; All files (*)') - if not OS_MACOSX: - pathSelect = unicode(QFileDialog.getOpenFileName(self, \ - self.tr('Recover Wallet'), \ - ARMORY_HOME_DIR, \ - ftypes)) - else: - pathSelect = unicode(QFileDialog.getOpenFileName(self, \ - self.tr('Recover Wallet'), \ - ARMORY_HOME_DIR, \ - ftypes, \ - options=QFileDialog.DontUseNativeDialog)) - - self.edtWalletPath.setText(pathSelect) - - -################################################################################# -''' -class DlgCorruptWallet(DlgProgress): - def __init__(self, wallet, status, main=None, parent=None, alreadyFailed=True): - super(DlgProgress, self).__init__(parent, main) - - self.connectDlg() - - self.main = main - self.walletList = [] - self.logDirs = [] - - self.running = 1 - self.status = 1 - self.isFixing = False - self.needToSubmitLogs = False - self.checkMode = RECOVERMODE.NotSet - - self.lock = threading.Lock() - self.condVar = threading.Condition(self.lock) - - mainLayout = QVBoxLayout() - - self.connect(self, SIGNAL('UCF'), self.UCF) - self.connect(self, SIGNAL('Show'), self.show) - self.connect(self, SIGNAL('Exec'), self.run_lock) - self.connect(self, SIGNAL('SNP'), self.setNewProgress) - self.connect(self, SIGNAL('LFW'), self.LFW) - self.connect(self, SIGNAL('SRD'), self.SRD) - - if alreadyFailed: - titleStr = self.tr('Wallet Consistency Check Failed!') - else: - titleStr = self.tr('Perform Wallet Consistency Check') - - lblDescr = QRichLabel(self.tr( - '%2 ' - '

' - 'Armory software now detects and prevents certain kinds of ' - 'hardware errors that could lead to problems with your wallet. ' - '
').arg(htmlColor('TextWarn'), titleStr)) - - lblDescr.setAlignment(Qt.AlignCenter) - - - if alreadyFailed: - self.lblFirstMsg = QRichLabel(self.tr( - 'Armory has detected that wallet file Wallet "%1" (%2) ' - 'is inconsistent and should be further analyzed to ensure that your ' - 'funds are protected. ' - '

' - 'This error will pop up every time you start ' - 'Armory until the wallet has been analyzed and fixed!').arg(wallet.labelName, wallet.uniqueIDB58, htmlColor('TextWarn'))) - elif isinstance(wallet, PyBtcWallet): - self.lblFirstMsg = QRichLabel(self.tr( - 'Armory will perform a consistency check on Wallet "%1" (%2) ' - 'and determine if any further action is required to keep your funds ' - 'protected. This check is normally performed on startup on all ' - 'your wallets, but you can click below to force another ' - 'check.').arg(wallet.labelName, wallet.uniqueIDB58)) - else: - self.lblFirstMsg = QRichLabel('') - - self.QDS = QDialog() - self.lblStatus = QLabel('') - self.addStatus(wallet, status) - self.QDSlo = QVBoxLayout() - self.QDS.setLayout(self.QDSlo) - - self.QDSlo.addWidget(self.lblFirstMsg) - self.QDSlo.addWidget(self.lblStatus) - - self.lblStatus.setVisible(False) - self.lblFirstMsg.setVisible(True) - - saStatus = QScrollArea() - saStatus.setWidgetResizable(True) - saStatus.setWidget(self.QDS) - saStatus.setMinimumHeight(250) - saStatus.setMinimumWidth(500) - - - layoutButtons = QGridLayout() - layoutButtons.setColumnStretch(0, 1) - layoutButtons.setColumnStretch(4, 1) - self.btnClose = QPushButton(self.tr('Hide')) - self.btnFixWallets = QPushButton(self.tr('Run Analysis and Recovery Tool')) - self.btnFixWallets.setDisabled(True) - self.connect(self.btnFixWallets, SIGNAL('clicked()'), self.doFixWallets) - self.connect(self.btnClose, SIGNAL('clicked()'), self.hide) - layoutButtons.addWidget(self.btnClose, 0, 1, 1, 1) - layoutButtons.addWidget(self.btnFixWallets, 0, 2, 1, 1) - - self.lblDescr2 = QRichLabel('') - self.lblDescr2.setAlignment(Qt.AlignCenter) - - self.lblFixRdy = QRichLabel(self.tr( - 'Your wallets will be ready to fix once the scan is over
' - 'You can hide this window until then
')) - - self.lblFixRdy.setAlignment(Qt.AlignCenter) - - self.frmBottomMsg = makeVertFrame(['Space(5)', - HLINE(), - self.lblDescr2, - self.lblFixRdy, - HLINE()]) - - self.frmBottomMsg.setVisible(False) - - - mainLayout.addWidget(lblDescr) - mainLayout.addWidget(saStatus) - mainLayout.addWidget(self.frmBottomMsg) - mainLayout.addLayout(layoutButtons) - - self.setLayout(mainLayout) - self.layout().setSizeConstraint(QLayout.SetFixedSize) - self.setWindowTitle(self.tr('Wallet Error')) - - def addStatus(self, wallet, status): - if wallet: - strStatus = ''.join(status) + str(self.lblStatus.text()) - self.lblStatus.setText(strStatus) - - self.walletList.append(wallet) - - def show(self): - super(DlgCorruptWallet, self).show() - self.activateWindow() - - def run_lock(self): - self.btnClose.setVisible(False) - self.hide() - super(DlgProgress, self).exec_() - self.walletList = None - - def UpdateCanFix(self, conditions, canFix=False): - self.emit(SIGNAL('UCF'), conditions, canFix) - - def UCF(self, conditions, canFix=False): - self.lblFixRdy.setText('') - if canFix: - self.btnFixWallets.setEnabled(True) - self.btnClose.setText(self.tr('Close')) - self.btnClose.setVisible(False) - self.connect(self.btnClose, SIGNAL('clicked()'), self.reject) - self.hide() - - def doFixWallets(self): - self.lblFixRdy.hide() - self.adjustSize() - - self.lblStatus.setVisible(True) - self.lblFirstMsg.setVisible(False) - self.frmBottomMsg.setVisible(False) - - from armoryengine.PyBtcWalletRecovery import FixWalletList - self.btnClose.setDisabled(True) - self.btnFixWallets.setDisabled(True) - self.isFixing = True - - self.lblStatus.hide() - self.QDSlo.removeWidget(self.lblStatus) - - for wlt in self.walletList: - self.main.removeWalletFromApplication(wlt.uniqueIDB58) - - FixWalletList(self.walletList, self, Progress=self.UpdateText, async=True) - self.adjustSize() - - def ProcessWallet(self, mode=RECOVERMODE.Full): - #Serves as the entry point for non processing wallets that arent loaded - #or fully processed. Only takes 1 wallet at a time - - if len(self.walletList) > 0: - wlt = None - wltPath = '' - - if isinstance(self.walletList[0], str) or \ - isinstance(self.walletList[0], unicode): - wltPath = self.walletList[0] - else: - wlt = self.walletList[0] - - self.lblDesc = QLabel('') - self.QDSlo.addWidget(self.lblDesc) - - self.lblFixRdy.hide() - self.adjustSize() - - self.frmBottomMsg.setVisible(False) - self.lblStatus.setVisible(True) - self.lblFirstMsg.setVisible(False) - - from armoryengine.PyBtcWalletRecovery import ParseWallet - self.btnClose.setDisabled(True) - self.btnFixWallets.setDisabled(True) - self.isFixing = True - - self.checkMode = mode - ParseWallet(wltPath, wlt, mode, self, - Progress=self.UpdateText, async=True) - - def UpdateDlg(self, text=None, HBar=None, Title=None): - if text is not None: self.lblDesc.setText(text) - self.adjustSize() - - def accept(self): - self.main.emit(SIGNAL('checkForNegImports')) - super(DlgCorruptWallet, self).accept() - - def reject(self): - if not self.isFixing: - super(DlgProgress, self).reject() - self.main.emit(SIGNAL('checkForNegImports')) - - def sigSetNewProgress(self, status): - self.emit(SIGNAL('SNP'), status) - - def setNewProgress(self, status): - self.lblDesc = QLabel('') - self.QDSlo.addWidget(self.lblDesc) - #self.QDS.adjustSize() - status[0] = 1 - - def setRecoveryDone(self, badWallets, goodWallets, fixedWallets, fixers): - self.emit(SIGNAL('SRD'), badWallets, goodWallets, fixedWallets, fixers) - - def SRD(self, badWallets, goodWallets, fixedWallets, fixerObjs): - self.btnClose.setEnabled(True) - self.btnClose.setVisible(True) - self.btnClose.setText(self.tr('Continue')) - self.btnFixWallets.setVisible(False) - self.btnClose.disconnect(self, SIGNAL('clicked()'), self.hide) - self.btnClose.connect(self, SIGNAL('clicked()'), self.accept) - self.isFixing = False - self.frmBottomMsg.setVisible(True) - - anyNegImports = False - for fixer in fixerObjs: - if len(fixer.negativeImports) > 0: - anyNegImports = True - break - - - if len(badWallets) > 0: - self.lblDescr2.setText(self.tr( - 'Failed to fix wallets!').arg(htmlColor('TextWarn'))) - self.main.statusBar().showMessage('Failed to fix wallets!', 150000) - elif len(goodWallets) == len(fixedWallets) and not anyNegImports: - self.lblDescr2.setText(self.tr( - 'Wallet(s) consistent, nothing to ' - 'fix.', "", len(goodWallets)).arg(htmlColor("TextBlue"))) - self.main.statusBar().showMessage( \ - self.tr("Wallet(s) consistent!", "", len(goodWallets)) % \ - 15000) - elif len(fixedWallets) > 0 or anyNegImports: - if self.checkMode != RECOVERMODE.Check: - self.lblDescr2.setText(self.tr( - ' ' - 'There may still be issues with your ' - 'wallet! ' - '
' - 'It is important that you send us the recovery logs ' - 'and an email address so the Armory team can check for ' - 'further risk to your funds!
').arg(htmlColor('TextWarn'))) - #self.main.statusBar().showMessage('Wallets fixed!', 15000) - else: - self.lblDescr2.setText(self.tr('

\ - Consistency check failed!

')) - self.adjustSize() - - - def loadFixedWallets(self, wallets): - self.emit(SIGNAL('LFW'), wallets) - - def LFW(self, wallets): - for wlt in wallets: - newWallet = PyBtcWallet().readWalletFile(wlt) - self.main.addWalletToApplication(newWallet, False) - - self.main.emit(SIGNAL('checkForkedImport')) - - - # Decided that we can just add all the logic to - #def checkForkedSubmitLogs(self): - #forkedImports = [] - #for wlt in self.walletMap: - #if self.walletMap[wlt].hasForkedImports: - #dlgIWR = DlgInconsistentWltReport(self, self.main, self.logDirs) - #if dlgIWR.exec_(): - #return - #return -''' - ################################################################################# class DlgFactoryReset(ArmoryDialog): def __init__(self, main=None, parent=None): diff --git a/ui/AddressTypeSelectDialog.py b/ui/AddressTypeSelectDialog.py index 239137b86..f8d956ce3 100755 --- a/ui/AddressTypeSelectDialog.py +++ b/ui/AddressTypeSelectDialog.py @@ -12,7 +12,7 @@ from PySide2.QtWidgets import QPushButton, QGridLayout, QFrame, QLabel, \ QRadioButton -from qtdialogs.qtdefines import ArmoryDialog, STYLE_RAISED, QLabelButton +from qtdialogs.qtdefines import STYLE_RAISED, QLabelButton from armoryengine.BDM import TheBDM from armoryengine.CppBridge import TheBridge from armoryengine.PyBtcAddress import AddressEntryType_P2PKH, \ @@ -20,6 +20,8 @@ AddressEntryType_Multisig, AddressEntryType_Uncompressed, \ AddressEntryType_P2SH, AddressEntryType_P2WSH +from qtdialogs.ArmoryDialog import ArmoryDialog + selectorDescriptions = {} selectorDescriptions[AddressEntryType_P2PKH] = str( 'Legacy address type. Backwards compatible.' diff --git a/ui/CoinControlUI.py b/ui/CoinControlUI.py index 6f98d3c6c..ae36f1607 100755 --- a/ui/CoinControlUI.py +++ b/ui/CoinControlUI.py @@ -8,8 +8,9 @@ # # ############################################################################## -from qtdialogs.qtdefines import ArmoryDialog, QRichLabel, makeHorizFrame, \ +from qtdialogs.qtdefines import QRichLabel, makeHorizFrame, \ saveTableView, restoreTableView +from qtdialogs.ArmoryDialog import ArmoryDialog from ui.TreeViewGUI import CoinControlTreeModel, RBFTreeModel diff --git a/ui/FeeSelectUI.py b/ui/FeeSelectUI.py index 9020df33e..cda8892c0 100755 --- a/ui/FeeSelectUI.py +++ b/ui/FeeSelectUI.py @@ -12,8 +12,9 @@ from PySide2.QtWidgets import QFrame, QRadioButton, QLineEdit, QGridLayout, \ QLabel, QPushButton, QCheckBox, QSlider, QComboBox -from qtdialogs.qtdefines import ArmoryDialog, STYLE_RAISED, GETFONT, \ +from qtdialogs.qtdefines import STYLE_RAISED, GETFONT, \ tightSizeNChar, QLabelButton, makeHorizFrame, STYLE_NONE +from qtdialogs.ArmoryDialog import ArmoryDialog from armoryengine.ArmoryUtils import str2coin, coin2str from armoryengine.CoinSelection import estimateFee, NBLOCKS_TO_CONFIRM, \ FEEBYTE_CONSERVATIVE, FEEBYTE_ECONOMICAL diff --git a/ui/MultiSigDialogs.py b/ui/MultiSigDialogs.py index b8514fce9..e90383990 100644 --- a/ui/MultiSigDialogs.py +++ b/ui/MultiSigDialogs.py @@ -7,35 +7,40 @@ # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # ################################################################################ -from armoryengine.MultiSigUtils import MultiSigLockbox, calcLockboxID,\ - createLockboxEntryStr, readLockboxEntryStr, isMofNNonStandardToSpend -from armoryengine.ArmoryUtils import LB_MAXM, LB_MAXN - -from qtdialogs.qtdefines import ArmoryDialog -from qtdialogs.qtdialogs import STRETCH +import textwrap +from io import StringIO -from qtdialogs.DlgSetComment import DlgSetComment -from qtdialogs.DlgUnlockWallet import DlgUnlockWallet -from qtdialogs.DlgSendBitcoins import DlgSendBitcoins -from qtdialogs.DlgDispTxInfo import DlgDispTxInfo +from PySide2.QtWidgets import QApplication, QCheckBox, QComboBox, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QHeaderView, QLabel, QLayout, QLineEdit, QMenu, QMessageBox, QPlainTextEdit, QPushButton, QScrollArea, QSpacerItem, QSplitter, QStackedWidget, QTabWidget, QTableView, QTextEdit, QVBoxLayout, QWidget +from PySide2.QtCore import QByteArray, QUrl, Qt, SIGNAL +from PySide2.QtGui import QCursor, QDesktopServices, QIcon, QPixmap + +from armorycolors import htmlColor +from qtdialogs.ArmoryDialog import ArmoryDialog +from armoryengine.ArmoryUtils import BLOCKEXPLORE_NAME, BLOCKEXPLORE_URL_ADDR, BadAddressError, CPP_TXOUT_MULTISIG, CheckHash160, DATATYPE, DEFAULT_DATE_FORMAT, LB_MAXM, LB_MAXN, LOGDEBUG, LOGERROR, LOGEXCEPT, LOGINFO, NegativeValueError, RightNow, SignerException, TooMuchPrecisionError, USE_REGTEST, USE_TESTNET, addrStr_is_p2sh, addrStr_to_hash160, binScript_to_p2shAddrStr, binary_to_hex, checkAddrStrValid, coin2strNZS, getBlockID, hash160_to_addrStr, hash160_to_p2pkhash_script, hex_switchEndian, hex_to_binary, isLikelyDataType, pubkeylist_to_multisig_script, scrAddr_to_addrStr, script_to_addrStr, script_to_p2sh_script, script_to_scrAddr, str2coin, unixTimeToFormatStr +from armoryengine.BDM import BDM_BLOCKCHAIN_READY, TheBDM +from armoryengine.CoinSelection import PySelectCoins, sumTxOutList +from armoryengine.Transaction import BASE_SCRIPT, DecoratedTxOut, PyTx, TXIN_SIGSTAT, UnsignedTransaction, UnsignedTxInput, convertScriptToOpStrings, getTxOutScriptType +from armorymodels import ArmoryTableView, LEDGERCOLS, LedgerDispDelegate, PromissoryCollectModel +#from CppBlockUtils import CryptoECDSA, SecureBinaryData +from qtdialogs.DlgBrowserWarn import DlgBrowserWarn +from qtdialogs.DlgDispTxInfo import DlgDispTxInfo +from qtdialogs.DlgQRCodeDisplay import DlgQRCodeDisplay from qtdialogs.DlgRequestPayment import DlgRequestPayment -from qtdialogs.DlgQRCodeDisplay import DlgQRCodeDisplay -from qtdialogs.DlgBrowserWarn import DlgBrowserWarn -from qtdialogs.DlgAddressBook import DlgAddressBook, createAddrBookButton - +from qtdialogs.DlgSendBitcoins import DlgSendBitcoins +from qtdialogs.DlgSetComment import DlgSetComment +from qtdialogs.DlgUnlockWallet import DlgUnlockWallet +from qtdialogs.qtdefines import GETFONT, HLINE, HORIZONTAL, MSGBOX, NETWORKMODE, QLabelButton, QMoneyLabel, QRichLabel, STRETCH, STYLE_RAISED, STYLE_STYLED, STYLE_SUNKEN, USERMODE, VERTICAL, WLTTYPES, determineWalletType, initialColResize, makeHorizFrame, makeVertFrame, relaxedSizeNChar, restoreTableView, saveTableView, tightSizeNChar, tightSizeStr +from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA +from ui.MultiSigModels import LOCKBOXCOLS, LockboxDisplayModel, LockboxDisplayProxy +from ui.WalletFrames import SelectWalletFrame -from ui.MultiSigModels import \ - LockboxDisplayModel, LockboxDisplayProxy, LOCKBOXCOLS -from armoryengine.CoinSelection import PySelectCoins, PyUnspentTxOut, \ - pprintUnspentTxOutList -from io import StringIO -import textwrap +from armoryengine.MultiSigUtils import * ############################################################################# class DlgLockboxEditor(ArmoryDialog): - ############################################################################# + ############################################################################# def __init__(self, parent, main, maxM=LB_MAXM, maxN=LB_MAXN, loadBox=None): super(DlgLockboxEditor, self).__init__(parent, main) @@ -53,33 +58,33 @@ def __init__(self, parent, main, maxM=LB_MAXM, maxN=LB_MAXN, loadBox=None): 'the address strings most Bitcoin users are familiar with.
' '
Click for more info.')) - def openMoreInfo(*args): + def openMoreInfo(*args): QMessageBox.information(self, self.tr('Public Key Information'), self.tr( - 'A public key is much longer than an ' - 'address string, and always starts with "02", "03" or "04". ' - 'Most wallet applications do not provide an easy way to access ' - 'a public key associated with a given address. This is easiest ' - 'if everyone is using Armory. ' - '

' - 'The address book buttons next to each input box below will show you ' - 'normal address strings, but will enter the correct public ' - 'key of the address you select.' - '

' - 'If you are creating this lockbox with other ' - 'Armory users, they can use the "Select Public Key" button ' - 'from the Lockbox Manager dashboard to pick a key and enter ' - 'their contact info. You can use the "Import" button ' - 'on each public key line to import the data they send you.'), - QMessageBox.Ok) - - + 'A public key is much longer than an ' + 'address string, and always starts with "02", "03" or "04". ' + 'Most wallet applications do not provide an easy way to access ' + 'a public key associated with a given address. This is easiest ' + 'if everyone is using Armory. ' + '

' + 'The address book buttons next to each input box below will show you ' + 'normal address strings, but will enter the correct public ' + 'key of the address you select.' + '

' + 'If you are creating this lockbox with other ' + 'Armory users, they can use the "Select Public Key" button ' + 'from the Lockbox Manager dashboard to pick a key and enter ' + 'their contact info. You can use the "Import" button ' + 'on each public key line to import the data they send you.'), + QMessageBox.Ok) + + lblDescr3.setOpenExternalLinks(False) self.connect(lblDescr3, SIGNAL('linkActivated(const QString &)'), \ openMoreInfo) - self.createDate = long(RightNow()) + self.createDate = int(RightNow()) self.loadedID = None self.comboM = QComboBox() self.comboN = QComboBox() @@ -95,7 +100,7 @@ def openMoreInfo(*args): self.comboN.setFont(GETFONT('Var', 14, bold=True)) self.lblMasterIcon = QLabel() - # Used to optimize update-on-every-key-press + # Used to optimize update-on-every-key-press self.prevPubKeyStr = ['']*self.maxN @@ -123,7 +128,7 @@ def openMoreInfo(*args): hAlign=Qt.AlignRight) - addrWidgets = self.main.createAddressEntryWidgets(self, '', 60, 2, + addrWidgets = self.main.createAddressEntryWidgets(self, '', 60, 2, getPubKey=True, showLockboxes=False, selectMineOnly=True) self.widgetMap[i]['QLE_PUBK'] = addrWidgets['QLE_ADDR'] self.widgetMap[i]['BTN_BOOK'] = addrWidgets['BTN_BOOK'] @@ -134,14 +139,14 @@ def openMoreInfo(*args): self.widgetMap[i]['BTN_NAME'].setContentsMargins(0,0,0,0) self.widgetMap[i]['LBL_DETECT'].setWordWrap(False) - # METADATA for a DecoratedPublicKey helps lite wallets - # identify their own keys, or authenticate keys of others. - # When a pubkey block is imported we store the public key and - # its metadata here indexed by pubkey. Later, we serialize - # these into the wallet definition. We index by public - # key with it so that we can identify if the user changed the - # public key since they imported this data in which case we - # should zero-out the METADATA + # METADATA for a DecoratedPublicKey helps lite wallets + # identify their own keys, or authenticate keys of others. + # When a pubkey block is imported we store the public key and + # its metadata here indexed by pubkey. Later, we serialize + # these into the wallet definition. We index by public + # key with it so that we can identify if the user changed the + # public key since they imported this data in which case we + # should zero-out the METADATA self.widgetMap[i]['METADATA'] = {} @@ -161,15 +166,15 @@ def importClick(): createImportCallback(i)) self.prevPubKeyStr[i] = '' - - #self.widgetMap[i]['QLE_PUBK'].setFont(GETFONT('Fixed', 9)) + + #self.widgetMap[i]['QLE_PUBK'].setFont(GETFONT('Fixed', 9)) w,h = tightSizeNChar(self.widgetMap[i]['QLE_PUBK'], 50) self.widgetMap[i]['QLE_PUBK'].setMinimumWidth(w) - - + + self.btnCancel = QPushButton(self.tr('Exit')) self.btnContinue = QPushButton(self.tr('Save Lockbox')) - #self.btnContinue.setEnabled(False) + #self.btnContinue.setEnabled(False) self.connect(self.btnContinue, SIGNAL('clicked()'), self.doContinue) self.connect(self.btnCancel, SIGNAL('clicked()'), self.reject) self.lblFinal = QRichLabel('') @@ -184,12 +189,12 @@ def importClick(): self.longDescr = u'' self.connect(self.btnLongDescr, SIGNAL('clicked()'), self.setLongDescr) - frmName = makeHorizFrame(['Stretch', + frmName = makeHorizFrame(['Stretch', QLabel('Lockbox Name:'), self.edtBoxName, self.btnLongDescr, 'Stretch']) - + layoutPubKeys = QGridLayout() @@ -204,7 +209,7 @@ def importClick(): layoutThisRow.addWidget(self.widgetMap[i]['QLE_PUBK'], 0,3) layoutThisRow.addWidget(self.widgetMap[i]['BTN_BOOK'], 0,4) - + layoutDetect = QHBoxLayout() layoutDetect.addWidget(self.widgetMap[i]['LBL_DETECT']) layoutDetect.addStretch() @@ -234,10 +239,10 @@ def importClick(): frmTop = makeVertFrame([lblDescr, lblDescr2, lblDescr3], STYLE_RAISED) - - - # Create the M,N select frame (stolen from frag-create dialog + + + # Create the M,N select frame (stolen from frag-create dialog lblMNSelect = QRichLabel(self.tr('Create ' 'Multi-Sig Lockbox' % htmlColor("TextBlue")), \ doWrap=False, hAlign=Qt.AlignHCenter) @@ -269,10 +274,10 @@ def importClick(): frmMNSelect.setLayout(layoutMNSelect) - frmFinish = makeHorizFrame([self.btnCancel, - 'Stretch', + frmFinish = makeHorizFrame([self.btnCancel, + 'Stretch', btnClear, - 'Stretch', + 'Stretch', self.btnContinue]) layoutMaster = QVBoxLayout() @@ -286,16 +291,16 @@ def importClick(): if loadBox is not None: self.fillForm(loadBox) - + self.setLayout(layoutMaster) self.setWindowTitle('Multi-Sig Lockbox Editor') self.setMinimumWidth(750) - - ############################################################################# + + ############################################################################# def clickNameButton(self, i): - currName = unicode(self.widgetMap[i]['LBL_NAME'].text()) + currName = str(self.widgetMap[i]['LBL_NAME'].text()) if not currName: dlgComm = DlgSetComment(self, self.main, currName, self.tr('Add public key ID or contact info')) else: @@ -304,7 +309,7 @@ def clickNameButton(self, i): self.widgetMap[i]['LBL_NAME'].setText(dlgComm.edtComment.text()) - ############################################################################# + ############################################################################# def clickImportButton(self, i): title = self.tr("Import Public Key Block") @@ -319,7 +324,7 @@ def clickImportButton(self, i): 'lockbox creation window.') ftypes = ['Public Key Blocks (*.lockbox.pub)'] - dlgImport = DlgImportAsciiBlock(self, self.main, + dlgImport = DlgImportAsciiBlock(self, self.main, title, descr, ftypes, DecoratedPublicKey) dlgImport.exec_() if dlgImport.returnObj: @@ -334,7 +339,7 @@ def clickImportButton(self, i): self.widgetMap[i]['METADATA'][binPub] = [wltLoc, authMeth, authData] - ############################################################################# + ############################################################################# def setLongDescr(self): class DlgSetLongDescr(ArmoryDialog): @@ -365,14 +370,14 @@ def __init__(self, parent, currDescr=''): dlg = DlgSetLongDescr(self, self.longDescr) if dlg.exec_(): - self.longDescr = unicode(dlg.descr.toPlainText()) - - + self.longDescr = str(dlg.descr.toPlainText()) + + - ############################################################################# + ############################################################################# def isPotentiallyValidHexPubKey(self, pkstr): - # Don't check for valid pub keys in 65-byte fields; it would be slow - # (this will be run after every key press) + # Don't check for valid pub keys in 65-byte fields; it would be slow + # (this will be run after every key press) if len(pkstr) == 33*2: return pkstr[:2] in ['02','03'] elif len(pkstr) == 65*2: @@ -381,32 +386,32 @@ def isPotentiallyValidHexPubKey(self, pkstr): return False - ############################################################################# + ############################################################################# def updateLabels(self, *args, **kwargs): - # Disable the continue button if not all keys are in + # Disable the continue button if not all keys are in M = int(str(self.comboM.currentText())) N = int(str(self.comboN.currentText())) for i in range(N): pkStr = str(self.widgetMap[i]['QLE_PUBK'].text()).strip() if not self.isPotentiallyValidHexPubKey(pkStr): - #self.btnContinue.setEnabled(False) + #self.btnContinue.setEnabled(False) self.lblFinal.setText('') break else: self.formFilled = True - #self.btnContinue.setEnabled(True) + #self.btnContinue.setEnabled(True) self.lblFinal.setText(self.tr( - 'Using the %d public keys above, ' - 'a multi-sig lockbox will be created requiring ' - '%d signatures to spend ' - 'money.' % (htmlColor('TextBlue'), M, htmlColor('TextBlue'), N))) + 'Using the %d public keys above, ' + 'a multi-sig lockbox will be created requiring ' + '%d signatures to spend ' + 'money.' % (htmlColor('TextBlue'), M, htmlColor('TextBlue'), N))) - - - ############################################################################# + + + ############################################################################# def updateWidgetTable_M(self, idxM): currN = int(str(self.comboN.currentText())) currM = idxM + 1 @@ -423,17 +428,17 @@ def updateWidgetTable_M(self, idxM): self.comboN.setCurrentIndex(setIndex) self.updateWidgetTable(currM, currN) - ############################################################################# + ############################################################################# def updateWidgetTable_N(self, idxN): currM = int(str(self.comboM.currentText())) self.updateWidgetTable(currM, idxN+self.minN) - ############################################################################# + ############################################################################# def updateWidgetTable(self, M, N): self.imgPie = QPixmap(':/frag%df.png' % M) - # Do the bulk of processing stuff + # Do the bulk of processing stuff for i in range(self.maxN): self.widgetMap[i]['IMG_ICON'].setPixmap(self.imgPie.scaled(40,40)) @@ -445,11 +450,11 @@ def updateWidgetTable(self, M, N): self.widgetMap[i]['QLE_PUBK'].setText('') self.updateLabels() - - ############################################################################# + + ############################################################################# def clearAll(self): self.edtBoxName.clear() self.longDescr = '' @@ -458,8 +463,8 @@ def clearAll(self): if key in ['QLE_PUBK', 'LBL_NAME']: widget.clear() - - ############################################################################# + + ############################################################################# def fillForm(self, lboxObj): self.edtBoxName.setText(lboxObj.shortName) @@ -482,9 +487,9 @@ def setCombo(cmb, val): self.updateWidgetTable(lboxObj.M, lboxObj.N) self.updateLabels() - - - ############################################################################# + + + ############################################################################# def doContinue(self): currM = int(str(self.comboM.currentText())) @@ -498,7 +503,7 @@ def doContinue(self): 'of the lockbox.'), QMessageBox.Ok) return - # If we got here, we already know all the public keys are valid strings + # If we got here, we already know all the public keys are valid strings pubKeyList = [] acceptedBlankComment = False for i in range(currN): @@ -515,25 +520,25 @@ def doContinue(self): if isValid: pkBin = hex_to_binary(pkHex) isValid = self.isPotentiallyValidHexPubKey(pkHex) - if len(pkBin) == 65: - if not CryptoECDSA().VerifyPublicKeyValid(SecureBinaryData(pkBin)): - isValid = False - - if not isValid: +# if len(pkBin) == 65: +# if not CryptoECDSA().VerifyPublicKeyValid(SecureBinaryData(pkBin)): +# isValid = False + + if not isValid: QMessageBox.critical(self, self.tr('Invalid Public Key'), self.tr( 'The data specified for public key %d is not valid. ' 'Please double-check the data was entered correctly.' % (i+1)), QMessageBox.Ok) return - keyComment = unicode(self.widgetMap[i]['LBL_NAME'].text()) - #self.widgetMap[i]['METADATA'][binPub] = [wltLoc, authMeth, authData] + keyComment = str(self.widgetMap[i]['LBL_NAME'].text()) + #self.widgetMap[i]['METADATA'][binPub] = [wltLoc, authMeth, authData] extras = [None, None, None] if pkBin in self.widgetMap[i]['METADATA']: extras = self.widgetMap[i]['METADATA'][pkBin][:] pubKeyList.append(DecoratedPublicKey(pkBin, keyComment, *extras)) - # Finally, throw a warning if the comment is not set + # Finally, throw a warning if the comment is not set strComment = str(self.widgetMap[i]['LBL_NAME'].text()).strip() if len(strComment)==0 and not acceptedBlankComment: reply =QMessageBox.warning(self, self.tr('Empty Name/ID Field'), self.tr( @@ -544,21 +549,21 @@ def doContinue(self): 'for each party, such as name, email and/or phone number. ' '

' 'Continue with some fields blank? ' - '
(click "No" to go back and finish filling in the form)'), + '
(click "No" to go back and finish filling in the form)'), QMessageBox.Yes | QMessageBox.No) if reply==QMessageBox.Yes: acceptedBlankComment = True else: return - - # Sort the public keys lexicographically + + # Sort the public keys lexicographically dPubKeys = sorted(pubKeyList, key=lambda lbKey: lbKey.binPubKey) - binPubKeys = [p.binPubKey for p in dPubKeys] + binPubKeys = [p.binPubKey for p in dPubKeys] - txOutScript = pubkeylist_to_multisig_script(binPubKeys, currM) + txOutScript = pubkeylist_to_multisig_script(binPubKeys, currM) opCodeList = convertScriptToOpStrings(txOutScript) scraddr = script_to_scrAddr(txOutScript) @@ -577,7 +582,7 @@ def doContinue(self): if not reply==QMessageBox.Ok: return else: - self.createDate = long(RightNow()) + self.createDate = int(RightNow()) if not USE_TESTNET and isMofNNonStandardToSpend(currM, currN) and not USE_REGTEST: reply = QMessageBox.warning(self, self.tr('Non-Standard to Spend'), self.tr( @@ -599,9 +604,9 @@ def doContinue(self): LOGINFO('HR Script: \n ' + '\n '.join(opCodeList)) LOGINFO('Lockbox ID: ' + lockboxID) - self.lockbox = MultiSigLockbox( toUnicode(self.edtBoxName.text()), - toUnicode(self.longDescr), - currM, + self.lockbox = MultiSigLockbox( str(self.edtBoxName.text()), + str(self.longDescr), + currM, currN, dPubKeys, self.createDate) @@ -633,39 +638,39 @@ def doExportLockbox(parent, main, lockbox): ftypes = ['Lockbox definitions (*.lockbox.def)'] defaultFN = 'Lockbox_%s_.lockbox.def' % lockbox.asciiID - DlgExportAsciiBlock(parent, main, lockbox, title, descr, - ftypes, defaultFN).exec_() + DlgExportAsciiBlock(parent, main, lockbox, title, descr, + ftypes, defaultFN).exec_() + - ################################################################################ class DlgLockboxManager(ArmoryDialog): def __init__(self, parent, main): super(DlgLockboxManager, self).__init__(parent, main) - #if not USE_TESTNET: - #QMessageBox.warning(self, self.tr('Dangerous Feature!'), self.tr( - #'Multi-signature transactions are an ' - #'EXPERIMENTAL feature in this version of Armory. It is ' - #'not intended to be used with real money, until all ' - #'the warnings like this one go away.' - #'

' - #'Use at your own risk!'), QMessageBox.Ok) + #if not USE_TESTNET: + #QMessageBox.warning(self, self.tr('Dangerous Feature!'), self.tr( + #'Multi-signature transactions are an ' + #'EXPERIMENTAL feature in this version of Armory. It is ' + #'not intended to be used with real money, until all ' + #'the warnings like this one go away.' + #'

' + #'Use at your own risk!'), QMessageBox.Ok) if len(self.main.allLockboxes) > 0: lblDescr = QRichLabel(self.tr( - 'Manage Multi-Sig Lockboxes ' - '
Double-click on a lockbox to edit' % htmlColor('TextBlue')), hAlign=Qt.AlignHCenter) + 'Manage Multi-Sig Lockboxes ' + '
Double-click on a lockbox to edit' % htmlColor('TextBlue')), hAlign=Qt.AlignHCenter) else: lblDescr = QRichLabel(self.tr( - 'Manage Multi-Sig Lockboxes ' % htmlColor('TextBlue')), hAlign=Qt.AlignHCenter) + 'Manage Multi-Sig Lockboxes ' % htmlColor('TextBlue')), hAlign=Qt.AlignHCenter) frmDescr = makeVertFrame([lblDescr], STYLE_RAISED) - # For the dashboard + # For the dashboard self.updateDashboardFuncs = [] - + self.lboxModel = LockboxDisplayModel(self.main, \ self.main.allLockboxes, \ self.main.getPreferredDateFormat()) @@ -686,12 +691,12 @@ def __init__(self, parent, main): self.lboxView.customContextMenuRequested.connect(self.showLboxContextMenu) self.connect( \ - self.lboxView, - SIGNAL('clicked(QModelIndex)'), + self.lboxView, + SIGNAL('clicked(QModelIndex)'), self.singleClickLockbox) self.connect( \ - self.lboxView, - SIGNAL('doubleClicked(QModelIndex)'), + self.lboxView, + SIGNAL('doubleClicked(QModelIndex)'), self.dblClickLockbox) self.txtLockboxInfo = QTextEdit() @@ -743,14 +748,14 @@ def __init__(self, parent, main): self.ledgerView.customContextMenuRequested.connect(self.showContextMenuLedger) - # Setup the details tab + # Setup the details tab self.tabDetails = QWidget() layoutDetails = QHBoxLayout() - #layoutDetails.addWidget(frmManageBtns) # Removed when added dash tab + #layoutDetails.addWidget(frmManageBtns) # Removed when added dash tab layoutDetails.addWidget(self.txtLockboxInfo, 1) self.tabDetails.setLayout(layoutDetails) - # Setup the ledger tab + # Setup the ledger tab self.tabLedger = QWidget() layoutLedger = QVBoxLayout() layoutLedger.addWidget(self.ledgerView) @@ -758,7 +763,7 @@ def __init__(self, parent, main): layoutLedger.addWidget(bottomRow) self.tabLedger.setLayout(layoutLedger) - # Creates self.stkDashboard + # Creates self.stkDashboard self.createLockboxDashboardTab() self.tabbedDisplay = QTabWidget() @@ -805,7 +810,7 @@ def __init__(self, parent, main): self.changeLBFilter() - ############################################################################# + ############################################################################# def createLockboxDashboardTab(self): ORGANIZER = 'Organizer' @@ -814,172 +819,172 @@ def createLockboxDashboardTab(self): self.allDashButtons = [{}, {}] - # We need two of these dictionaries: we're going to put the widgets - # directly into them, and we need one for each of the two stack pages + # We need two of these dictionaries: we're going to put the widgets + # directly into them, and we need one for each of the two stack pages for i in [0,1]: self.allDashButtons[i] = \ - { - 'CreateLB': { \ - 'button': self.tr('Create Lockbox'), - 'callbk': self.doCreate, - 'organiz': True, - 'lbltxt': self.tr('Collect public keys'), - 'tiptxt': self.tr('Create a lockbox by collecting public keys ' - 'from each device or person that will be ' - 'a signing authority over the funds. Once ' - 'created you will be given a chunk of text ' - 'to send to each party so they can recognize ' - 'and sign transactions related to the ' - 'lockbox.'), - 'select': None, - 'offline': None}, - - 'SelectKey': { \ - 'button': self.tr('Select Public Key'), - 'callbk': self.doSelectKey, - 'organiz': False, - 'lbltxt': self.tr('Send to organizer'), - 'tiptxt': self.tr('In order to create a lockbox all devices ' - 'and/or parties need to provide a public key ' - 'that they control to be merged by the ' - 'organizer. Once all keys are collected, ' - 'the organizer will send you the final ' - 'lockbox definition to import.'), - 'select': None, - 'offline': None}, - - 'ExportLB': { \ - 'button': self.tr('Export Lockbox'), - 'callbk': self.doExport, - 'organiz': False, - 'lbltxt': self.tr('Send to other devices or parties'), - 'tiptxt': self.tr('Export a lockbox definition to be imported ' - 'by other devices or parties. Normally the ' - 'lockbox organizer will do this after all public ' - 'keys are collected, but any participant who ' - 'already has it can send it, such as if one ' - 'party/device accidentally deletes it.'), - 'select': self.tr('Select lockbox to export'), - 'offline': None}, - - 'ImportLB': { \ - 'button': self.tr('Import Lockbox'), - 'callbk': self.doImport, - 'organiz': False, - 'lbltxt': self.tr('From organizer or other device'), - 'tiptxt': self.tr('Import a lockbox definition to begin ' - 'tracking its funds and to be able to ' - 'sign related transactions. ' - 'Normally, the organizer will send you ' - 'the data to import after you ' - 'provide a public key from one of your ' - 'wallets.'), - 'select': None, - 'offline': None}, - - 'EditLB': { \ - 'button': self.tr('Edit Lockbox'), - 'callbk': self.doEdit, - 'organiz': False, - 'lbltxt': '', - 'tiptxt': self.tr('Edit an existing lockbox'), - 'select': self.tr('Select lockbox to edit'), - 'offline': None}, - - #'RegFund': { \ - #'button': self.tr('Fund Lockbox'), - #'callbk': self.doFundIt, - #'organiz': False, - #'lbltxt': self.tr('Fund selected lockbox from any wallet'), - #'tiptxt': self.tr('If you would like to fund this lockbox - #'from another lockbox, select the funding ' - #'lockbox in the table and click the ' - #'"Create Spending Tx" button. Use the ' - #'address book to select this lockbox as the ' - #'recipient of that transaction. ' - #'

' - #'If multiple people will be funding ' - #'this lockbox and not all of them are fully ' - #'trusted, click the "Simul" checkbox on the ' - #'left to see the Simulfunding options.'), - #'select': self.tr('Select a lockbox to fund
'), - #'offline': self.tr('Must be online to fund
')}, - # Added
to the labels to force to be two lines... this - # is a hack to make sure that the row inits to a reasonable - # size on open - - - 'MergeProm': { \ - 'button': self.tr('Merge Promissory Notes'), - 'callbk': self.doMergeProm, - 'organiz': True, - 'lbltxt': '', - 'tiptxt': self.tr('Collect promissory notes from all funders ' - 'of a Simulfunding transaction. Use this to ' - 'merge them into a single transaction that ' - 'the funders can review and sign.'), - 'select': None, - 'offline': None}, - - 'CreateProm': { \ - 'button': self.tr('Create Promissory Note'), - 'callbk': self.doCreateProm, - 'organiz': False, - 'lbltxt': self.tr('Make a funding commitment to a lockbox'), - 'tiptxt': self.tr('A "promissory note" provides blockchain ' - 'information about how your wallet will ' - 'contribute funds to a Simulfunding transaction. ' - 'A promissory note does not ' - 'move any money in your wallet. The organizer ' - 'will create a single transaction that includes ' - 'all promissory notes and you will be able to ' - 'review it in its entirety before signing.'), - 'select': self.tr('Select lockbox to commit funds to'), - 'offline': self.tr('Must be online to create')}, - - 'RevSign': { \ - 'button': self.tr('Review and Sign'), - 'callbk': self.doReview, - 'organiz': False, - 'lbltxt': self.tr('Multi-sig spend or Simulfunding'), - 'tiptxt': self.tr('Review and sign any lockbox-related ' - 'transaction that requires multiple ' - 'signatures. This includes spending ' - 'transactions from a regular lockbox, ' - 'as well as completing a Simulfunding ' - 'transaction.'), - 'select': None, - 'offline': None}, - - 'CreateTx': { \ - 'button': self.tr('Create Spending Tx'), - 'callbk': self.doSpend, - 'organiz': True, - 'lbltxt': self.tr('Send bitcoins from lockbox'), - 'tiptxt': self.tr('Create a proposed transaction sending bitcoins ' - 'to an address, wallet or another lockbox. ' - 'The transaction will not be final until enough ' - 'signatures have been collected and then ' - 'broadcast from an online computer.'), - 'select': self.tr('Select lockbox to spend from'), - 'offline': self.tr('Must be online to spend')}, - - - 'MergeSigs': { \ - 'button': self.tr('Collect Sigs && Broadcast'), - 'callbk': self.doReview, - 'organiz': True, - 'lbltxt': self.tr('Merge signatures to finalize'), - 'tiptxt': self.tr('Merge signatures and broadcast transaction'), - 'select': None, - 'offline': self.tr('(must be online to broadcast)')}, - } - - - # We will have two pages on the stack. The first one is for regular - # funding with all the Simulfunding options missing. The second one - # is re-arranged (but mostly the same widgets) but with the additional - # Simulfunding widgets + { + 'CreateLB': { \ + 'button': self.tr('Create Lockbox'), + 'callbk': self.doCreate, + 'organiz': True, + 'lbltxt': self.tr('Collect public keys'), + 'tiptxt': self.tr('Create a lockbox by collecting public keys ' + 'from each device or person that will be ' + 'a signing authority over the funds. Once ' + 'created you will be given a chunk of text ' + 'to send to each party so they can recognize ' + 'and sign transactions related to the ' + 'lockbox.'), + 'select': None, + 'offline': None}, + + 'SelectKey': { \ + 'button': self.tr('Select Public Key'), + 'callbk': self.doSelectKey, + 'organiz': False, + 'lbltxt': self.tr('Send to organizer'), + 'tiptxt': self.tr('In order to create a lockbox all devices ' + 'and/or parties need to provide a public key ' + 'that they control to be merged by the ' + 'organizer. Once all keys are collected, ' + 'the organizer will send you the final ' + 'lockbox definition to import.'), + 'select': None, + 'offline': None}, + + 'ExportLB': { \ + 'button': self.tr('Export Lockbox'), + 'callbk': self.doExport, + 'organiz': False, + 'lbltxt': self.tr('Send to other devices or parties'), + 'tiptxt': self.tr('Export a lockbox definition to be imported ' + 'by other devices or parties. Normally the ' + 'lockbox organizer will do this after all public ' + 'keys are collected, but any participant who ' + 'already has it can send it, such as if one ' + 'party/device accidentally deletes it.'), + 'select': self.tr('Select lockbox to export'), + 'offline': None}, + + 'ImportLB': { \ + 'button': self.tr('Import Lockbox'), + 'callbk': self.doImport, + 'organiz': False, + 'lbltxt': self.tr('From organizer or other device'), + 'tiptxt': self.tr('Import a lockbox definition to begin ' + 'tracking its funds and to be able to ' + 'sign related transactions. ' + 'Normally, the organizer will send you ' + 'the data to import after you ' + 'provide a public key from one of your ' + 'wallets.'), + 'select': None, + 'offline': None}, + + 'EditLB': { \ + 'button': self.tr('Edit Lockbox'), + 'callbk': self.doEdit, + 'organiz': False, + 'lbltxt': '', + 'tiptxt': self.tr('Edit an existing lockbox'), + 'select': self.tr('Select lockbox to edit'), + 'offline': None}, + + #'RegFund': { \ + #'button': self.tr('Fund Lockbox'), + #'callbk': self.doFundIt, + #'organiz': False, + #'lbltxt': self.tr('Fund selected lockbox from any wallet'), + #'tiptxt': self.tr('If you would like to fund this lockbox + #'from another lockbox, select the funding ' + #'lockbox in the table and click the ' + #'"Create Spending Tx" button. Use the ' + #'address book to select this lockbox as the ' + #'recipient of that transaction. ' + #'

' + #'If multiple people will be funding ' + #'this lockbox and not all of them are fully ' + #'trusted, click the "Simul" checkbox on the ' + #'left to see the Simulfunding options.'), + #'select': self.tr('Select a lockbox to fund
'), + #'offline': self.tr('Must be online to fund
')}, + # Added
to the labels to force to be two lines... this + # is a hack to make sure that the row inits to a reasonable + # size on open + + + 'MergeProm': { \ + 'button': self.tr('Merge Promissory Notes'), + 'callbk': self.doMergeProm, + 'organiz': True, + 'lbltxt': '', + 'tiptxt': self.tr('Collect promissory notes from all funders ' + 'of a Simulfunding transaction. Use this to ' + 'merge them into a single transaction that ' + 'the funders can review and sign.'), + 'select': None, + 'offline': None}, + + 'CreateProm': { \ + 'button': self.tr('Create Promissory Note'), + 'callbk': self.doCreateProm, + 'organiz': False, + 'lbltxt': self.tr('Make a funding commitment to a lockbox'), + 'tiptxt': self.tr('A "promissory note" provides blockchain ' + 'information about how your wallet will ' + 'contribute funds to a Simulfunding transaction. ' + 'A promissory note does not ' + 'move any money in your wallet. The organizer ' + 'will create a single transaction that includes ' + 'all promissory notes and you will be able to ' + 'review it in its entirety before signing.'), + 'select': self.tr('Select lockbox to commit funds to'), + 'offline': self.tr('Must be online to create')}, + + 'RevSign': { \ + 'button': self.tr('Review and Sign'), + 'callbk': self.doReview, + 'organiz': False, + 'lbltxt': self.tr('Multi-sig spend or Simulfunding'), + 'tiptxt': self.tr('Review and sign any lockbox-related ' + 'transaction that requires multiple ' + 'signatures. This includes spending ' + 'transactions from a regular lockbox, ' + 'as well as completing a Simulfunding ' + 'transaction.'), + 'select': None, + 'offline': None}, + + 'CreateTx': { \ + 'button': self.tr('Create Spending Tx'), + 'callbk': self.doSpend, + 'organiz': True, + 'lbltxt': self.tr('Send bitcoins from lockbox'), + 'tiptxt': self.tr('Create a proposed transaction sending bitcoins ' + 'to an address, wallet or another lockbox. ' + 'The transaction will not be final until enough ' + 'signatures have been collected and then ' + 'broadcast from an online computer.'), + 'select': self.tr('Select lockbox to spend from'), + 'offline': self.tr('Must be online to spend')}, + + + 'MergeSigs': { \ + 'button': self.tr('Collect Sigs && Broadcast'), + 'callbk': self.doReview, + 'organiz': True, + 'lbltxt': self.tr('Merge signatures to finalize'), + 'tiptxt': self.tr('Merge signatures and broadcast transaction'), + 'select': None, + 'offline': self.tr('(must be online to broadcast)')}, + } + + + # We will have two pages on the stack. The first one is for regular + # funding with all the Simulfunding options missing. The second one + # is re-arranged (but mostly the same widgets) but with the additional + # Simulfunding widgets self.stkDashboard = QStackedWidget() simultxt = 'Simulfund' @@ -993,7 +998,7 @@ def createLockboxDashboardTab(self): 'Simulfunding options in the table.') ttipSimulA = self.main.createToolTipWidget(ttipSimulTxt) ttipSimulB = self.main.createToolTipWidget(ttipSimulTxt) - + def clickSimulA(): self.chkSimulfundB.setChecked(self.chkSimulfundA.isChecked()) @@ -1004,7 +1009,7 @@ def clickSimulB(): self.chkSimulfundA.setChecked(self.chkSimulfundB.isChecked()) stk = 1 if self.chkSimulfundB.isChecked() else 0 self.stkDashboard.setCurrentIndex(stk) - + self.chkSimulfundB.setChecked(self.chkSimulfundA.isChecked()) self.connect(self.chkSimulfundA, SIGNAL('clicked()'), clickSimulA) @@ -1017,10 +1022,10 @@ def createHeaderCell(headStr, extraWidgList=None): lbl = QRichLabel(headStr, bold=True, size=4, hAlign=Qt.AlignHCenter, vAlign=Qt.AlignVCenter) - - + + if extraWidgList is None: - frm = makeVertFrame([lbl], cellStyle) + frm = makeVertFrame([lbl], cellStyle) else: botLayout = QHBoxLayout() for widg in extraWidgList: @@ -1032,7 +1037,7 @@ def createHeaderCell(headStr, extraWidgList=None): frm = QFrame() frm.setLayout(cellLayout) frm.setFrameStyle(cellStyle) - + return frm @@ -1050,7 +1055,7 @@ def createCell(stk, btnKeyList, direct=HORIZONTAL): if btnMap['organiz']: btnMap['BTN'].setAutoFillBackground(True) btnMap['BTN'].setStyleSheet(\ - 'QPushButton { background-color : %s }' % htmlColor('SlightMoreBlue')) + 'QPushButton { background-color : %s }' % htmlColor('SlightMoreBlue')) layout.addWidget(btnMap['BTN'], 0,0) layout.addWidget(btnMap['TTIP'], 0,1) @@ -1068,7 +1073,7 @@ def createCell(stk, btnKeyList, direct=HORIZONTAL): def updateWidgets(stkPage, btnKey): btnMap = self.allDashButtons[stkPage][btnKey] def updateFunc(hasSelect, isOnline): - # Default to regular label + # Default to regular label lbltxt = btnMap['lbltxt'] orgtxt = 'Organizer
' % \ htmlColor('TextBlue') @@ -1098,38 +1103,38 @@ def updateFunc(hasSelect, isOnline): else: btnMap['LBL'].setText(lbltxt) - # Semi-hack: - # The 'MergeSigs' button is the only one that kinda makes - # sense to not work offline, but there may be isolated - # cases where the user would merge without intending to - # broadcast. Having it disabled in offline mode would - # make them go mad. So I'm going to explicitly make sure - # that just that button is always enabled, even though - # it might look like a bug. + # Semi-hack: + # The 'MergeSigs' button is the only one that kinda makes + # sense to not work offline, but there may be isolated + # cases where the user would merge without intending to + # broadcast. Having it disabled in offline mode would + # make them go mad. So I'm going to explicitly make sure + # that just that button is always enabled, even though + # it might look like a bug. if btnKey=='MergeSigs': btnMap['BTN'].setEnabled(True) return updateFunc - # Add the func to the list of things to call on context change + # Add the func to the list of things to call on context change self.updateDashFuncs.append(updateWidgets(stk, key)) frmCell = QFrame() frmCell.setLayout(layoutMulti) frmCell.setFrameStyle(STYLE_RAISED) - - # This was calibrated to linux, but at least it will work on one OS - # The alternative was squished text on every OS + + # This was calibrated to linux, but at least it will work on one OS + # The alternative was squished text on every OS w,h = tightSizeNChar(btnMap['LBL'], 30) frmCell.setMinimumHeight(5.5*h) return frmCell - ##### REGULAR FUNDING - Special Cell #### - # This FUND row for regular funding will be totally unlike the others - # Create it here instead of with the the createCell() function + ##### REGULAR FUNDING - Special Cell #### + # This FUND row for regular funding will be totally unlike the others + # Create it here instead of with the the createCell() function self.lblDispAddr = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) self.lblDispAddr.setTextInteractionFlags(Qt.TextSelectableByMouse | \ Qt.TextSelectableByKeyboard) @@ -1137,8 +1142,8 @@ def updateFunc(hasSelect, isOnline): self.btnQRCodeDisp = QPushButton(self.tr('QR Code')) self.btnFundRequest = QPushButton(self.tr('Request Payment')) self.btnCopyClip = QPushButton(self.tr('Copy Address')) - - #segwit checkbox + + #segwit checkbox self.chkSegWit = QCheckBox("SegWit") self.chkSegWit.setChecked(False) @@ -1149,10 +1154,10 @@ def checkSegWit(): else: lbox.setScriptType(LBTYPE_P2SH) updateRegFundCell(True, TheBDM.getState() == BDM_BLOCKCHAIN_READY) - + self.connect(self.chkSegWit, SIGNAL('clicked()'), checkSegWit) - def funcCopyClip(): + def funcCopyClip(): lbox = self.getSelectedLockbox() if not lbox: return @@ -1162,7 +1167,7 @@ def funcCopyClip(): clipb.setText(scrAddr_to_addrStr(lbox.getAddr())) self.main.signalExecution.callLater(1, lambda: self.btnCopyClip.setText('Copy Address')) - def funcReqPayment(): + def funcReqPayment(): lbox = self.getSelectedLockbox() if not lbox: return @@ -1174,7 +1179,7 @@ def funcQRCode(): if not lbox: return p2shAddr = scrAddr_to_addrStr(lbox.getAddr()) - lboxDisp = 'Lockbox %d-of-%d: "%s" (%s)' % (lbox.M, lbox.N, + lboxDisp = 'Lockbox %d-of-%d: "%s" (%s)' % (lbox.M, lbox.N, lbox.shortName, lbox.uniqueIDB58) DlgQRCodeDisplay(self, self.main, p2shAddr, p2shAddr, lboxDisp).exec_() @@ -1203,9 +1208,9 @@ def updateRegFundCell(hasSelect, isOnline): self.lblDispAddr.setEnabled(True) self.chkSegWit.setEnabled(True) self.lblDispAddr.setText(self.tr( - 'Anyone can send funds to this lockbox using this ' - 'Bitcoin address:
%s' % p2shAddr)) - + 'Anyone can send funds to this lockbox using this ' + 'Bitcoin address:
%s' % p2shAddr)) + def setSWCheckBox(arg1, arg2): lbox = self.getSelectedLockbox() if lbox == None: @@ -1215,7 +1220,7 @@ def setSWCheckBox(arg1, arg2): self.chkSegWit.setChecked(False) else: self.chkSegWit.setChecked(True) - + self.updateDashFuncs.append(updateRegFundCell) self.updateDashFuncs.append(setSWCheckBox) @@ -1238,21 +1243,21 @@ def setSWCheckBox(arg1, arg2): frmFundRegCell.setLayout(layoutFundRow) frmFundRegCell.setFrameStyle(STYLE_RAISED) - ##### REGULAR FUNDING - Special Cell #### - + ##### REGULAR FUNDING - Special Cell #### + - # First frame is for regular funding. Switch to frmMulti if chkSimulfundA + # First frame is for regular funding. Switch to frmMulti if chkSimulfundA frmSingle = QFrame() frmSingleLayout = QGridLayout() firstRow = createCell(0, ['CreateLB', 'SelectKey', 'ExportLB', 'ImportLB'], HORIZONTAL) - #secondRow = createCell(0, ['RegFund'], HORIZONTAL) + #secondRow = createCell(0, ['RegFund'], HORIZONTAL) thirdRow = createCell(0, ['CreateTx', 'RevSign', 'MergeSigs'], HORIZONTAL) frmSingleLayout.addWidget(createHeaderCell('CREATE'), 0,0) frmSingleLayout.addWidget(firstRow, 0,1, 1,3) - frmSingleLayout.addWidget(createHeaderCell('FUND', + frmSingleLayout.addWidget(createHeaderCell('FUND', [self.chkSimulfundA, ttipSimulA]), 1,0) frmSingleLayout.addWidget(frmFundRegCell, 1,1, 1,3) @@ -1271,7 +1276,7 @@ def setSWCheckBox(arg1, arg2): self.stkDashboard.addWidget(frmSingle) - # Second frame is for Simulfunding + # Second frame is for Simulfunding frmMulti = QFrame() frmMultiLayout = QGridLayout() @@ -1283,7 +1288,7 @@ def setSWCheckBox(arg1, arg2): frmMultiLayout.addWidget(createHeaderCell('CREATE'), 0,0) frmMultiLayout.addWidget(firstRow, 0,1, 1,3) - frmMultiLayout.addWidget(createHeaderCell('FUND', + frmMultiLayout.addWidget(createHeaderCell('FUND', [self.chkSimulfundB, ttipSimulB]), 1,0) frmMultiLayout.addWidget(secondRow, 1,1, 1,2) frmMultiLayout.addWidget(lastCol, 1,3, 2,1) @@ -1305,18 +1310,18 @@ def setSWCheckBox(arg1, arg2): frmMulti.setFrameStyle(STYLE_STYLED) self.stkDashboard.addWidget(frmMulti) - # Default is to use frmSingle + # Default is to use frmSingle self.stkDashboard.setCurrentIndex(0) - ############################################################################# + ############################################################################# def updateTxCommentFromView(self, view): index = view.selectedIndexes()[0] row, col = index.row(), index.column() currComment = str(view.model().index(row, LEDGERCOLS.Comment).data().toString()) if not currComment: dialog = DlgSetComment(self, self.main, currComment, self.tr('Add Transaction Comment')) - else: + else: dialog = DlgSetComment(self, self.main, currComment, self.tr('Change Transaction Comment')) if dialog.exec_(): newComment = str(dialog.edtComment.text()) @@ -1329,18 +1334,18 @@ def updateTxCommentFromView(self, view): self.main.walletMap[wltID].setComment(hex_to_binary(txHash), newComment) self.main.walletListChanged() - ############################################################################# + ############################################################################# def dblClickLedger(self, index): if index.column()==LEDGERCOLS.Comment: self.updateTxCommentFromView(self.ledgerView) else: self.showLedgerTx() - - ############################################################################# + + ############################################################################# def showLedgerTx(self): row = self.ledgerView.selectedIndexes()[0].row() txHash = str(self.ledgerView.model().index(row, LEDGERCOLS.TxHash).data().toString()) - txtime = unicode(self.ledgerView.model().index(row, LEDGERCOLS.DateStr).data().toString()) + txtime = str(self.ledgerView.model().index(row, LEDGERCOLS.DateStr).data().toString()) pytx = None txHashBin = hex_to_binary(txHash) @@ -1366,8 +1371,8 @@ def showLedgerTx(self): break DlgDispTxInfo( pytx, wlt, self, self.main, txtime=txtime).exec_() - - ############################################################################# + + ############################################################################# def showContextMenuLedger(self): menu = QMenu(self.ledgerView) @@ -1403,10 +1408,10 @@ def showContextMenuLedger(self): except: LOGEXCEPT('Failed to open webbrowser') QMessageBox.critical(self, self.tr('Could not open browser'), \ - self.tr('Armory encountered an error opening your web browser. To view ' - 'this transaction on blockchain.info, please copy and paste ' - 'the following URL into your browser: ' - '

%s' % blkExploreURL), QMessageBox.Ok) + self.tr('Armory encountered an error opening your web browser. To view ' + 'this transaction on blockchain.info, please copy and paste ' + 'the following URL into your browser: ' + '

%s' % blkExploreURL), QMessageBox.Ok) elif action==actCopyTxID: clipb = QApplication.clipboard() clipb.clear() @@ -1414,16 +1419,16 @@ def showContextMenuLedger(self): elif action==actComment: self.updateTxCommentFromView(self.ledgerView) - - ############################################################################# + + ############################################################################# def showLboxContextMenu(self, pos): menu = QMenu(self.lboxView) std = (self.main.usermode == USERMODE.Standard) adv = (self.main.usermode == USERMODE.Advanced) dev = (self.main.usermode == USERMODE.Expert) - + if True: actionCopyAddr = menu.addAction(self.tr("Copy P2SH address")) if True: actionShowQRCode = menu.addAction(self.tr("Display address QR code")) if not USE_TESTNET: @@ -1434,27 +1439,27 @@ def showLboxContextMenu(self, pos): if dev: actionCopyHash160 = menu.addAction(self.tr("Copy hash160 value (hex)")) if True: actionCopyBalance = menu.addAction(self.tr("Copy balance")) if True: actionRemoveLB = menu.addAction(self.tr("Delete Lockbox")) - #if ENABLE_SUPERNODE is False: - # actionRescanLB = menu.addAction(self.tr("Rescan Lockbox")) + #if ENABLE_SUPERNODE is False: + # actionRescanLB = menu.addAction(self.tr("Rescan Lockbox")) selectedIndexes = self.lboxView.selectedIndexes() if len(selectedIndexes)>0: idx = selectedIndexes[0] action = menu.exec_(QCursor.pos()) - - - # Get data on a given row, easily + + + # Get data on a given row, easily def getModelStr(col): model = self.lboxView.model() qstr = model.index(idx.row(), col).data().toString() return str(qstr).strip() - - + + lboxId = getModelStr(LOCKBOXCOLS.ID) lbox = self.main.getLockboxByID(lboxId) p2shAddr = script_to_addrStr(script_to_p2sh_script(lbox.binScript)) if lbox else None - + if action == actionCopyAddr: clippy = p2shAddr elif action == actionBlkChnInfo: @@ -1463,12 +1468,12 @@ def getModelStr(col): DlgBrowserWarn(urlToOpen).exec_() except: QMessageBox.critical(self, self.tr('Could not open browser'), self.tr( - 'Armory encountered an error opening your web browser. To view ' - 'this address on %s, please copy and paste ' - 'the following URL into your browser: ' - '

' - '%s' % (BLOCKEXPLORE_NAME, urlToOpen, - urlToOpen)), QMessageBox.Ok) + 'Armory encountered an error opening your web browser. To view ' + 'this address on %s, please copy and paste ' + 'the following URL into your browser: ' + '

' + '%s' % (BLOCKEXPLORE_NAME, urlToOpen, + urlToOpen)), QMessageBox.Ok) return elif action == actionShowQRCode: DlgQRCodeDisplay(self, self.main, p2shAddr, p2shAddr, createLockboxEntryStr(lboxId)).exec_() @@ -1492,7 +1497,7 @@ def getModelStr(col): if reply[1]==True: self.main.writeSetting('DNAA_P2SHCompatWarn', True) - + DlgRequestPayment(self, self.main, p2shAddr).exec_() return elif dev and action == actionCopyHash160: @@ -1503,7 +1508,7 @@ def getModelStr(col): dispInfo = self.main.getDisplayStringForScript(lbox.getScript()) reply = QMessageBox.warning(self, self.tr('Confirm Delete'), self.tr( '"Removing" a lockbox does not delete any signing keys, so you ' - 'maintain signing authority for any coins that are sent there. ' + 'maintain signing authority for any coins that are sent there. ' 'However, it will remove it from the list of lockboxes, and you ' 'will have to re-import it later in order to send any funds ' 'to or from the lockbox.' @@ -1518,33 +1523,33 @@ def getModelStr(col): self.singleClickLockbox() return - - #elif action == actionRescanLB: - # dispInfo = self.main.getDisplayStringForScript(lbox.binScript) - # reply = QMessageBox.warning(self, self.tr('Confirm Rescan'), self.tr( - # 'Rescaning a Lockbox will make it unavailable for the duration ' - # 'of the process.' - # '

' - # 'You are about to rescan the following lockbox: ' - # '

' - # '%s' % (htmlColor('TextBlue'), - # dispInfo['String'])), QMessageBox.Yes | QMessageBox.No) - # - # if reply==QMessageBox.Yes: - # lwlt = self.main.cppLockboxWltMap[lbox.uniqueIDB58] - # lwlt.forceScan() - # self.lboxModel.reset() - # - # return + + #elif action == actionRescanLB: + # dispInfo = self.main.getDisplayStringForScript(lbox.binScript) + # reply = QMessageBox.warning(self, self.tr('Confirm Rescan'), self.tr( + # 'Rescaning a Lockbox will make it unavailable for the duration ' + # 'of the process.' + # '

' + # 'You are about to rescan the following lockbox: ' + # '

' + # '%s' % (htmlColor('TextBlue'), + # dispInfo['String'])), QMessageBox.Yes | QMessageBox.No) + # + # if reply==QMessageBox.Yes: + # lwlt = self.main.cppLockboxWltMap[lbox.uniqueIDB58] + # lwlt.forceScan() + # self.lboxModel.reset() + # + # return else: return - + clipb = QApplication.clipboard() clipb.clear() clipb.setText(str(clippy).strip()) - ############################################################################# + ############################################################################# def updateButtonDisable(self): noSelection = (self.getSelectedLBID() is None) isOffline = (not TheBDM.getState()==BDM_BLOCKCHAIN_READY) @@ -1562,14 +1567,14 @@ def updateButtonDisable(self): if noSelection: self.txtLockboxInfo.setText(self.tr('

' - 'Select a lockbox from the table above to view its info
' - '
' % htmlColor('DisableFG'))) + 'Select a lockbox from the table above to view its info ' + '
' % htmlColor('DisableFG'))) for fn in self.updateDashFuncs: - # Whoops, made the args inverses of what the func takes, oh well + # Whoops, made the args inverses of what the func takes, oh well fn(not noSelection, not isOffline) - - ############################################################################# + + ############################################################################# def getSelectedLBID(self): selection = self.lboxView.selectedIndexes() if len(selection)==0: @@ -1578,43 +1583,42 @@ def getSelectedLBID(self): idCol = LOCKBOXCOLS.ID return str(self.lboxView.model().index(row, idCol).data().toString()) - ############################################################################# + ############################################################################# def getSelectedLockbox(self): lbID = self.getSelectedLBID() if lbID: return self.main.getLockboxByID(lbID) return None - - ############################################################################# + + ############################################################################# def resetLBSelection(self): self.lboxView.clearSelection() self.singleClickLockbox(None, []) - ############################################################################# + ############################################################################# def getDisplayRichText(self, lb, tr=None, dateFmt=None): if dateFmt is None: dateFmt = DEFAULT_DATE_FORMAT if tr is None: - tr = lambda x: unicode(x) + tr = lambda x: str(x) EMPTYLINE = u'' - shortName = toUnicode(lb.shortName) + shortName = str(lb.shortName) if len(shortName.strip())==0: shortName = u'') longDescr = textwrap.fill(longDescr, width=60) - formattedDate = unixTimeToFormatStr(lb.createDate, dateFmt) - lines = QStringList() + lines = [] lines.append(self.tr('
Lockbox Information for ' '%s
' % (htmlColor("TextBlue"), lb.uniqueIDB58))) lines.append(self.tr('Multisig:      %d-of-%d' % (lb.M, lb.N))) @@ -1641,7 +1645,7 @@ def getDisplayRichText(self, lb, tr=None, dateFmt=None): lines.append(self.tr('
')) return lines.join('
') - ############################################################################# + ############################################################################# def singleClickLockbox(self, index=None, *args): lb = self.getSelectedLockbox() if lb: @@ -1652,7 +1656,7 @@ def singleClickLockbox(self, index=None, *args): self.updateButtonDisable() - ############################################################################# + ############################################################################# def dblClickLockbox(self, index, *args): lb = self.getSelectedLockbox() if lb: @@ -1661,31 +1665,31 @@ def dblClickLockbox(self, index, *args): self.singleClickLockbox() - ############################################################################# + ############################################################################# def doCreate(self): dlg = DlgLockboxEditor(self, self.main).exec_() if dlg: self.lboxModel.reset() self.singleClickLockbox() self.updateButtonDisable() - - ############################################################################# + + ############################################################################# def doSelectKey(self): dlg = DlgSelectPublicKey(self, self.main).exec_() - ############################################################################# + ############################################################################# def doImport(self): dlg = DlgImportLockbox(self, self.main) if dlg.exec_(): if dlg.importedLockbox is not None: self.main.updateOrAddLockbox(dlg.importedLockbox, isFresh=False) - + self.lboxModel.reset() self.singleClickLockbox() self.updateButtonDisable() - - ############################################################################# + + ############################################################################# def doEdit(self): lb = self.getSelectedLockbox() DlgLockboxEditor(self, self.main, loadBox=lb).exec_() @@ -1693,29 +1697,29 @@ def doEdit(self): self.singleClickLockbox() self.updateButtonDisable() - ############################################################################# + ############################################################################# def doExport(self): lb = self.getSelectedLockbox() doExportLockbox(self, self.main, lb) self.updateButtonDisable() - ############################################################################# + ############################################################################# def doMergeProm(self): lb = self.getSelectedLockbox() lbID = None if lb is None else lb.uniqueIDB58 DlgMergePromNotes(self, self.main, lbID).exec_() - - ############################################################################# + + ############################################################################# def doCreateProm(self): lb = self.getSelectedLockbox() lbID = None if lb is None else lb.uniqueIDB58 DlgCreatePromNote(self, self.main, lbID).exec_() - - ############################################################################# + + ############################################################################# def doReview(self): title = self.tr("Import Signature Collector") descr = self.tr( @@ -1726,7 +1730,7 @@ def doReview(self): 'a file, which is saved by default with a ' '*.sigcollect.tx extension.') ftypes = ['Signature Collectors (*.sigcollect.tx)'] - dlgImport = DlgImportAsciiBlock(self, self.main, + dlgImport = DlgImportAsciiBlock(self, self.main, title, descr, ftypes, UnsignedTransaction) dlgImport.exec_() if dlgImport.returnObj: @@ -1734,7 +1738,7 @@ def doReview(self): DlgMultiSpendReview(self, self.main, ustx).exec_() - ############################################################################# + ############################################################################# def doDelete(self): lb = self.getSelectedLockbox() dispInfo = self.main.getDisplayStringForScript(lb.binScript, 100, 2, @@ -1758,8 +1762,8 @@ def doDelete(self): self.updateButtonDisable() - - ############################################################################# + + ############################################################################# def doFundIt(self): reply = QMessageBox.warning(self, self.tr('[WARNING]'), self.tr( @@ -1782,18 +1786,18 @@ def doFundIt(self): if not reply==QMessageBox.Ok: - return + return lbID = self.getSelectedLBID() lb = self.main.getLockboxByID(lbID) - prefillMap = {'lockbox': lbID, + prefillMap = {'lockbox': lbID, 'message': self.tr('Funding %d-of-%d' % (lb.M, lb.N)) } DlgSendBitcoins(None, self, self.main, \ wltIDList=prefillMap, spendFromLockboxID=lbID).exec_() self.updateButtonDisable() - ############################################################################# + ############################################################################# def doSimul(self): lbID = self.getSelectedLBID() @@ -1816,7 +1820,7 @@ def doSimul(self): 'Files containing signature-collecting data usually end with ' '*.sigcollect.tx.') ftypes = ['Signature Collectors (*.sigcollect.tx)'] - dlgImport = DlgImportAsciiBlock(self, self.main, + dlgImport = DlgImportAsciiBlock(self, self.main, title, descr, ftypes, UnsignedTransaction) dlgImport.exec_() if dlgImport.returnObj: @@ -1826,52 +1830,52 @@ def doSimul(self): self.updateButtonDisable() - ############################################################################# + ############################################################################# def doSpend(self): lbID = self.getSelectedLBID() dlg = DlgSendBitcoins(None, self, self.main, spendFromLockboxID=lbID) dlg.exec_() self.updateButtonDisable() - - ############################################################################# + + ############################################################################# def saveGeometrySettings(self): self.main.writeSetting('LockboxGeometry', self.saveGeometry().toHex()) self.main.writeSetting('LockboxAddrCols', saveTableView(self.lboxView)) self.main.writeSetting('LockboxLedgerCols', saveTableView(self.ledgerView)) - ############################################################################# + ############################################################################# def closeEvent(self, event): self.saveGeometrySettings() super(DlgLockboxManager, self).closeEvent(event) - ############################################################################# + ############################################################################# def accept(self, *args): self.saveGeometrySettings() self.main.lbDialogModel = None super(DlgLockboxManager, self).accept(*args) - ############################################################################# + ############################################################################# def reject(self, *args): self.saveGeometrySettings() - self.main.lbDialogModel = None + self.main.lbDialogModel = None super(DlgLockboxManager, self).reject(*args) - - ############################################################################# + + ############################################################################# def changeLBFilter(self): lbIDList = [] for lb in self.main.allLockboxes: if lb.isEnabled: lbIDList.append(lb.uniqueIDB58) - - self.main.currentLBPage = 0 - #TheBDM.bdv().updateLockboxesLedgerFilter(lbIDList) + + self.main.currentLBPage = 0 + #TheBDM.bdv().updateLockboxesLedgerFilter(lbIDList) ################################################################################ class DlgFundLockbox(ArmoryDialog): def __init__(self, parent, main): super(DlgFundLockbox, self).__init__(parent, main) - + self.selection = None lblDescr = QRichLabel(self.tr( @@ -1935,7 +1939,7 @@ def doReview(self): class DlgSpendFromLockbox(ArmoryDialog): def __init__(self, parent, main): super(DlgSpendFromLockbox, self).__init__(parent, main) - + self.selection = None lblDescr = QRichLabel(self.tr( @@ -1950,8 +1954,8 @@ def __init__(self, parent, main): if TheBDM.getState()==BDM_BLOCKCHAIN_READY: lblCreate = QRichLabel(self.tr( - 'I am creating a new proposed spending transaction and will pass ' - 'it to each party or device that needs to sign it')) + 'I am creating a new proposed spending transaction and will pass ' + 'it to each party or device that needs to sign it')) else: btnCreate.setEnabled(False) lblCreate = QRichLabel(self.tr('Transaction creation is not available when offline.')) @@ -2003,12 +2007,12 @@ def doReview(self): class DlgSimulfundSelect(ArmoryDialog): def __init__(self, parent, main, lbID): super(DlgSimulfundSelect, self).__init__(parent, main) - + self.selection = None lbox = self.main.getLockboxByID(lbID) - #dispStr = '%s-of-%s: %s (%s)' % \ - #(htmlColor('TextBlue'), lbox.M, lbox.N, lbox.shortName, lbox.uniqueIDB58) + #dispStr = '%s-of-%s: %s (%s)' % \ + #(htmlColor('TextBlue'), lbox.M, lbox.N, lbox.shortName, lbox.uniqueIDB58) dispStr = self.main.getDisplayStringForScript(lbox.binScript)['String'] lblTitle = QRichLabel(self.tr( @@ -2034,7 +2038,7 @@ def __init__(self, parent, main, lbID): '

' 'You are currently handling a Simulfunding operation for lockbox: ' '
%s.' % dispStr)) - + btnCreate = QPushButton(self.tr('Create Promissory Note')) btnCollect = QPushButton(self.tr('Collect and Merge Notes')) @@ -2119,7 +2123,7 @@ def __init__(self, parent, main, titleStr, descrStr, fileTypes, importType): btnDone = QPushButton(self.tr("Done")) btnCancel = QPushButton(self.tr("Cancel")) - + self.connect(btnLoad, SIGNAL('clicked()'), self.loadfile) self.connect(btnDone, SIGNAL('clicked()'), self.clickedDone) self.connect(btnCancel, SIGNAL('clicked()'), self.reject) @@ -2136,10 +2140,10 @@ def __init__(self, parent, main, titleStr, descrStr, fileTypes, importType): self.setWindowTitle(titleStr) self.setMinimumWidth(450) - ############################################################################# + ############################################################################# def loadfile(self): loadPath = self.main.getFileLoad(self.tr('Load Data'), self.fileTypes) - + if not loadPath: return with open(loadPath) as f: @@ -2147,7 +2151,7 @@ def loadfile(self): self.txtAscii.setPlainText(data) - ############################################################################# + ############################################################################# def clickedDone(self): try: txt = str(self.txtAscii.toPlainText()).strip() @@ -2157,10 +2161,10 @@ def clickedDone(self): QMessageBox.warning(self, self.tr('Error'), self.tr( 'There was an error reading the ASCII block entered. Please ' 'make sure it was entered/copied correctly, and that you have ' - 'copied the header and footer lines that start with "=====".'), + 'copied the header and footer lines that start with "=====".'), QMessageBox.Ok) return - + self.accept() @@ -2213,7 +2217,7 @@ def __init__(self, parent, main): frmDescr = makeVertFrame([lblDescr], STYLE_RAISED) - addrWidgets = self.main.createAddressEntryWidgets(self, '', 60, 2, + addrWidgets = self.main.createAddressEntryWidgets(self, '', 60, 2, getPubKey=True, showLockboxes=False) self.edtPubKey = addrWidgets['QLE_ADDR'] @@ -2221,11 +2225,11 @@ def __init__(self, parent, main): self.lblDetect = addrWidgets['LBL_DETECT'] self.lblDetect.setVisible(True) - #btnExportKey = QPushButton(self.tr('Send to Organizer')) - #self.connect(btnExportKey, SIGNAL('clicked()'), self.doExportKey) - #frmButtons = makeHorizFrame([QRichLabel(self.tr('When finished:')), - #btnExportKey, - #'Stretch']) + #btnExportKey = QPushButton(self.tr('Send to Organizer')) + #self.connect(btnExportKey, SIGNAL('clicked()'), self.doExportKey) + #frmButtons = makeHorizFrame([QRichLabel(self.tr('When finished:')), + #btnExportKey, + #'Stretch']) layoutAddrEntry = QGridLayout() layoutAddrEntry.addWidget(lblSelect, 0,0) @@ -2235,7 +2239,7 @@ def __init__(self, parent, main): layoutAddrEntry.addWidget(lblContact, 2,0) layoutAddrEntry.addWidget(self.edtContact, 2,1) layoutAddrEntry.addWidget(ttipContact, 2,2) - #layoutAddrEntry.addWidget(frmButtons, 3,0, 1,3) + #layoutAddrEntry.addWidget(frmButtons, 3,0, 1,3) layoutAddrEntry.setColumnStretch(0,0) layoutAddrEntry.setColumnStretch(1,1) layoutAddrEntry.setColumnStretch(2,0) @@ -2262,15 +2266,15 @@ def __init__(self, parent, main): self.setWindowIcon(QIcon(self.main.iconfile)) - ############################################################################# + ############################################################################# def collectKeyData(self): try: binPub = hex_to_binary(str(self.edtPubKey.text()).strip()) if binPub[0] in ['\x02', '\x03'] and len(binPub)==33: pass # valid key - elif binPub[0]=='\x04' and len(binPub)==65: - if not CryptoECDSA().VerifyPublicKeyValid(SecureBinaryData(binPub)): - raise BadAddressError('Public key starting with 0x04 is invalid') +# elif binPub[0]=='\x04' and len(binPub)==65: +# if not CryptoECDSA().VerifyPublicKeyValid(SecureBinaryData(binPub)): +# raise BadAddressError('Public key starting with 0x04 is invalid') else: raise BadAddressError('Invalid pub key entered, or not a pub key') @@ -2283,17 +2287,17 @@ def collectKeyData(self): '"02", "03" or "04".'), QMessageBox.Ok) return None - comm = unicode(self.edtContact.text()).strip() + comm = str(self.edtContact.text()).strip() dPubKey = DecoratedPublicKey(binPub, comm) return dPubKey.serializeAscii() - - - ############################################################################# + + + ############################################################################# def doExportKey(self): toCopy = self.collectKeyData() if not toCopy: - return + return dPubKey = DecoratedPublicKey().unserializeAscii(toCopy) @@ -2304,21 +2308,21 @@ def doExportKey(self): 'to be used to create the lockbox. This data is not sensitive ' 'and it is appropriate be sent via email or transferred via USB storage. ' ) - + ftypes = ['Public Key Blocks (*.lockbox.pub)'] defaultFN = 'PubKey_%s_.lockbox.pub' % dPubKey.pubKeyID - - DlgExportAsciiBlock(self, self.main, dPubKey, title, descr, - ftypes, defaultFN).exec_() - + DlgExportAsciiBlock(self, self.main, dPubKey, title, descr, + ftypes, defaultFN).exec_() + + def doDone(self): if self.collectKeyData() is None: - return + return self.doExportKey() self.accept() - + ################################################################################ @@ -2383,14 +2387,14 @@ def clipcopy(self): def mailLB(self): - # Iterate over the text block and get the public key ID. - # WARNING: For now, the code assumes there will be only one ID returned. - # If this changes in the future, the code must be adjusted as necessary. + # Iterate over the text block and get the public key ID. + # WARNING: For now, the code assumes there will be only one ID returned. + # If this changes in the future, the code must be adjusted as necessary. blockIO = StringIO(self.asciiBlock) pkID = getBlockID(blockIO, self.exportObj.BLKSTRING+'-')[0] - # Prepare to send an email with the public key. For now, the email text - # is the public key and nothing else. + # Prepare to send an email with the public key. For now, the email text + # is the public key and nothing else. subj = (self.exportObj.EMAILSUBJ) % self.exportObj.asciiID body = (self.exportObj.EMAILBODY) urlText = 'mailto:?subject=%s&body=%s\n\n%s' % (subj, body, self.asciiBlock) @@ -2433,7 +2437,7 @@ def __init__(self, parent, main): btnDone = QPushButton(self.tr("Done")) btnCancel = QPushButton(self.tr("Cancel")) - + self.connect(btnLoad, SIGNAL('clicked()'), self.loadfile) self.connect(btnDone, SIGNAL('clicked()'), self.clickedDone) self.connect(btnCancel, SIGNAL('clicked()'), self.reject) @@ -2451,7 +2455,7 @@ def __init__(self, parent, main): self.setMinimumWidth(450) - ############################################################################# + ############################################################################# def loadfile(self): boxPath = self.main.getFileLoad(self.tr('Load Lockbox'), ['Lockboxes (*.lockbox.def)']) @@ -2462,7 +2466,7 @@ def loadfile(self): self.txtBoxBlock.setPlainText(data) - ############################################################################# + ############################################################################# def clickedDone(self): txt = str(self.txtBoxBlock.toPlainText()).strip() try: @@ -2471,8 +2475,8 @@ def clickedDone(self): LOGEXCEPT('Error unserializing the entered text') if self.importedLockbox == None: QMessageBox.critical(self, self.tr('Non-lockbox'), self.tr( - 'You are attempting to load something that is not a Lockbox. ' - 'Please clear the display and try again.'), QMessageBox.Ok) + 'You are attempting to load something that is not a Lockbox. ' + 'Please clear the display and try again.'), QMessageBox.Ok) else: lbID = self.importedLockbox.uniqueIDB58 if not self.main.getLockboxByID(lbID) is None: @@ -2484,10 +2488,10 @@ def clickedDone(self): 'may be different. Would you like to overwrite the lockbox ' 'information already stored for %s?' % (lbID,lbID)), \ QMessageBox.Yes | QMessageBox.Cancel) - + if not reply==QMessageBox.Yes: return - + self.accept() @@ -2497,12 +2501,12 @@ def clickedDone(self): ################################################################################ class DlgMultiSpendReview(ArmoryDialog): - ############################################################################# + ############################################################################# def __init__(self, parent, main, ustx): super(DlgMultiSpendReview, self).__init__(parent, main) LOGDEBUG('Debugging information for multi-spend USTX') - #ustx.pprint() + #ustx.pprint() lblDescr = QRichLabel(self.tr( 'The following transaction is a proposed spend of funds controlled ' @@ -2520,7 +2524,7 @@ def __init__(self, parent, main, ustx): CHKW,CHKH = 32,32 PIEW,PIEH = 32,32 - # These need to return copies + # These need to return copies self.pixGreen = lambda: QPixmap(':/keyhole_green.png').scaled(KEYW,KEYH) self.pixGray = lambda: QPixmap(':/keyhole_gray.png' ).scaled(KEYW,KEYH) self.pixBlue = lambda: QPixmap(':/keyhole_blue.png' ).scaled(KEYW,KEYH) @@ -2533,7 +2537,7 @@ def __init__(self, parent, main, ustx): self.feeAmt = self.ustx.calculateFee() - # Some simple container classes + # Some simple container classes class InputBundle(object): def __init__(self): self.binScript = '' @@ -2546,7 +2550,7 @@ def __init__(self): self.keyholePixmap = [] self.keyUnrelated = [] - # Some simple container classes + # Some simple container classes class OutputBundle(object): def __init__(self): self.recvAmt = 0 @@ -2558,17 +2562,17 @@ def __init__(self): self.inputBundles = {} self.outputBundles = {} - # NOTE: This will do some weird things if we have a contrib that - # gets back more than he puts in... i.e. he will be required - # to sign, but he will be receiving money, instead of sending - # it. Right now, if that happens, he will show up as an - # input bundle with a negative send amount + # NOTE: This will do some weird things if we have a contrib that + # gets back more than he puts in... i.e. he will be required + # to sign, but he will be receiving money, instead of sending + # it. Right now, if that happens, he will show up as an + # input bundle with a negative send amount - # Accumulate and prepare all static info (that doesn't change with sigs) + # Accumulate and prepare all static info (that doesn't change with sigs) self.maxN = 0 canPotentiallySignAny = False for ustxi in self.ustx.ustxInputs: - hrStr,idStr = self.main.getContribStr(ustxi.txoScript, + hrStr,idStr = self.main.getContribStr(ustxi.txoScript, ustxi.contribID, ustxi.contribLabel) @@ -2581,7 +2585,7 @@ def __init__(self): iBundle.lockbox = self.main.getLockboxByID(idStr.split(':')[-1]) canPotentiallySignAny = True - # Check whether we have the capability to sign this lockbox + # Check whether we have the capability to sign this lockbox iBundle.binScript = iBundle.lockbox.binScript M,N = iBundle.lockbox.M, iBundle.lockbox.N self.maxN = max(N, self.maxN) @@ -2613,7 +2617,7 @@ def __init__(self): iBundle.wltSignRightNow = [None] iBundle.keyholePixmap = [None] M,N = 1,1 - self.maxN = 1 + self.maxN = 1 if idStr[:3] in ['WLT']: canPotentiallySignAny = True iBundle.keyholePixmap[0] = QLabel() @@ -2628,15 +2632,15 @@ def __init__(self): iBundle.wltSignRightNow[0] = [wltID, a160, None, None] iBundle.keyholePixmap[0].setPixmap(self.pixGreen()) else: - # In these cases, nothing really to do + # In these cases, nothing really to do pass - - - # The output bundles are quite a bit simpler + + + # The output bundles are quite a bit simpler isReceivingAny = False for dtxo in self.ustx.decorTxOuts: - hrStr,idStr = self.main.getContribStr(dtxo.binScript, + hrStr,idStr = self.main.getContribStr(dtxo.binScript, dtxo.contribID, dtxo.contribLabel) if idStr in self.inputBundles: @@ -2655,33 +2659,34 @@ def __init__(self): if not canPotentiallySignAny: if not isReceivingAny: QMessageBox.warning(self, self.tr("Unrelated Multi-Spend"), self.tr( - 'The signature-collector you loaded appears to be ' - 'unrelated to any of the wallets or lockboxes that you have ' - 'available. If you were expecting to be able to sign for a ' - 'lockbox input, you need to import the lockbox definition ' - 'first. Any other person or device with the lockbox loaded ' - 'can export it to be imported by this device.'), QMessageBox.Ok) + 'The signature-collector you loaded appears to be ' + 'unrelated to any of the wallets or lockboxes that you have ' + 'available. If you were expecting to be able to sign for a ' + 'lockbox input, you need to import the lockbox definition ' + 'first. Any other person or device with the lockbox loaded ' + 'can export it to be imported by this device.'), QMessageBox.Ok) else: QMessageBox.warning(self, self.tr("Cannot Sign"), self.tr( - 'The signature-collector you loaded is sending money to one ' - 'of your wallets or lockboxes, but does not have any inputs ' - 'for which you can sign. ' - 'If you were expecting to be able to sign for a ' - 'lockbox input, you need to import the lockbox definition ' - 'first. Any other person or device with the lockbox loaded ' - 'can export it to be imported by this device.'), QMessageBox.Ok) - + 'The signature-collector you loaded is sending money to one ' + 'of your wallets or lockboxes, but does not have any inputs ' + 'for which you can sign. ' + 'If you were expecting to be able to sign for a ' + 'lockbox input, you need to import the lockbox definition ' + 'first. Any other person or device with the lockbox loaded ' + 'can export it to be imported by this device.'), QMessageBox.Ok) + layoutInputs = QGridLayout() layoutOutputs = QGridLayout() self.iWidgets = {} self.oWidgets = {} - - - self.signerType = SIGNER_DEFAULT - def setSignerType(_type): - self.signerType = _type + + + #HighPrioTODO: Replace with the bridge. + #self.signerType = SIGNER_DEFAULT + #def setSignerType(_type): + # self.signerType = _type iin = 0 iout = 0 @@ -2693,17 +2698,17 @@ def setSignerType(_type): iBundle = self.inputBundles[idStr] - + contribID = '' contribLabel = '' if len(iBundle.ustxiList) > 0: contribID = iBundle.ustxiList[0].contribID.strip() contribLabel = iBundle.ustxiList[0].contribLabel.strip() - # The header line lists the name and value and any multisig pies + # The header line lists the name and value and any multisig pies if not contribLabel: iWidgMap['HeadLbl'] = QRichLabel(self.tr('Spending: %s' % (htmlColor('TextBlue'), iBundle.dispStr)), doWrap=False) - else: + else: if contribID: contribID = ' (%s)' % contribID iWidgMap['HeadLbl'] = QRichLabel(self.tr('Contributor: %s%s' % (htmlColor('TextBlue'), contribLabel, contribID)), doWrap=False) @@ -2712,7 +2717,7 @@ def setSignerType(_type): val = iBundle.sendAmt iWidgMap['Amount'] = QMoneyLabel(-val, txtSize=12, wBold=True) - # These are images that show up to N=5 + # These are images that show up to N=5 iWidgMap['HeadImg'] = [None]*self.maxN iWidgMap['KeyImg'] = [None]*self.maxN iWidgMap['KeyLbl'] = [None]*self.maxN @@ -2727,17 +2732,19 @@ def setSignerType(_type): iWidgMap['SignBtn'][i] = QPushButton('') iWidgMap['ChkImg'][i].setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - # Now actually insert the widgets into a layout + # Now actually insert the widgets into a layout headerLine = [iWidgMap['HeadLbl']] headerLine.extend(iWidgMap['HeadImg']) headerLine.append('Stretch') headerLine.append(iWidgMap['Amount']) layoutInputs.addWidget(makeHorizFrame(headerLine), topRow,0, 1,4) - def createSignCallback(idstring, nIdx): - def doSign(): - self.doSignForInput(idstring, nIdx, self.signerType) - return doSign + #HighPrioTODO: Replace with the bridge. + #def createSignCallback(idstring, nIdx): + #HighPrioTODO: Replace with the bridge. + #def doSign(): + # self.doSignForInput(idstring, nIdx, self.signerType) + #return doSign for i in range(self.maxN): @@ -2748,8 +2755,9 @@ def doSign(): layoutInputs.addWidget(iWidgMap['KeyImg' ][i], row,2) layoutInputs.addWidget(iWidgMap['KeyLbl' ][i], row,3) - self.connect(iWidgMap['SignBtn'][i], SIGNAL('clicked()'), \ - createSignCallback(idStr, i)) + #HighPrioTODO: Replace with the bridge. + #self.connect(iWidgMap['SignBtn'][i], SIGNAL('clicked()'), \ + # createSignCallback(idStr, i)) lbox = iBundle.lockbox M = lbox.M if lbox else 1 @@ -2774,13 +2782,13 @@ def doSign(): elif iBundle.wltSignRightNow[i]: wltID = iBundle.wltSignRightNow[i][0] - wltName = '' + wltName = '' if wltID: wltName = self.main.walletMap[wltID].getDisplayStr() if not comm: if not wltName: - dispStr = self.tr('[[Unknown Signer]]') + dispStr = self.tr('[[Unknown Signer]]') else: dispStr = wltName else: @@ -2797,11 +2805,11 @@ def doSign(): iWidgMap['KeyImg' ][i].setMinimumSize(KEYW,KEYH) iWidgMap['ChkImg' ][i].setMinimumSize(CHKW,CHKH) iWidgMap['KeyLbl' ][i].setWordWrap(False) - + if not lbox: iWidgMap['HeadImg'][i].setVisible(False) - - + + for widgetName,widgetList in iWidgMap.iteritems(): if widgetName in ['HeadLbl', 'Amount']: @@ -2812,7 +2820,7 @@ def doSign(): N = lbox.N if lbox else 1 if i >= N: widgetList[i].setVisible(False) - + layoutInputs.setColumnStretch(0,0) layoutInputs.setColumnStretch(1,0) @@ -2821,8 +2829,8 @@ def doSign(): layoutInputs.setColumnStretch(4,0) - # Maybe one day we'll do full listing of lockboxes on the output side - # But for now, it will only further complicate things... + # Maybe one day we'll do full listing of lockboxes on the output side + # But for now, it will only further complicate things... for idStr in self.outputBundles: lbox = self.outputBundles[idStr].lockbox M,N = [1,1] if lbox is None else [lbox.M, lbox.N] @@ -2839,13 +2847,13 @@ def doSign(): val = self.outputBundles[idStr].recvAmt oWidgMap['Amount'] = QMoneyLabel(val, txtSize=12, wBold=True) - # These are the pie images + # These are the pie images oWidgMap['HeadImg'] = [None]*N for i in range(N): oWidgMap['HeadImg'][i] = QLabel() - # Now actually insert the widgets into a layout + # Now actually insert the widgets into a layout headerLine = [oWidgMap['HeadLbl']] headerLine.extend(oWidgMap['HeadImg']) headerLine.append('Stretch') @@ -2856,10 +2864,10 @@ def doSign(): for i in range(N): oWidgMap['HeadImg'][i].setPixmap(self.pixPie(M)) oWidgMap['HeadImg'][i].setMinimumSize(PIEW,PIEH) - - # Add a fee row if needed + + # Add a fee row if needed if self.feeAmt > 0: row = (self.maxN+1)*iout lblFee = QRichLabel('Transaction Fee') @@ -2868,8 +2876,8 @@ def doSign(): frmTxFee = makeHorizFrame([lblFee, 'Stretch', lblAmt]) layoutOutputs.addWidget(HLINE(), row,0, 1,5) layoutOutputs.addWidget(frmTxFee, row+1,0, 1,5) - - + + frmInputs = QFrame() frmInputs.setLayout(layoutInputs) @@ -2878,8 +2886,8 @@ def doSign(): frmOutputs = QFrame() frmOutputs.setLayout(layoutOutputs) frmOutputs.setFrameStyle(STYLE_STYLED) - - self.signerSelectFrm = SignerLabelFrame(main, self.ustx, setSignerType) + + #self.signerSelectFrm = SignerLabelFrame(main, self.ustx, setSignerType) @@ -2906,20 +2914,21 @@ def doSign(): self.connect(self.btnFinalExport, SIGNAL('clicked()'), self.doExport) self.connect(self.doneButton, SIGNAL('clicked()'), self.accept) - frmMain = makeVertFrame([lblDescr, - HLINE(), - HLINE(), - frmInputs, - HLINE(), - self.signerSelectFrm.getFrame(), - HLINE(), - frmOutputs, - HLINE(), - frmButtons]) + frmMain = makeVertFrame([lblDescr, + HLINE(), + HLINE(), + frmInputs, + HLINE(), +#HighPrioTODO: Replace with cpp bridge +# self.signerSelectFrm.getFrame(), + HLINE(), + frmOutputs, + HLINE(), + frmButtons]) - # Actually, this dialog will not handle changing USTX objects yet - # For now, need to pre-select your USTX, then load this dialog with it - #self.btnLoadImport.setVisible(False) + # Actually, this dialog will not handle changing USTX objects yet + # For now, need to pre-select your USTX, then load this dialog with it + #self.btnLoadImport.setVisible(False) layoutMain = QVBoxLayout() @@ -2928,17 +2937,17 @@ def doSign(): self.setWindowTitle(self.tr('Review and Sign')) self.setMinimumWidth(750) - - # Evaluate SigningStatus returns per-wallet details if a wlt is given + + # Evaluate SigningStatus returns per-wallet details if a wlt is given self.relevancyMap = {} self.canSignMap = {} self.alreadySigned = {} self.evalSigStat() - - - - ############################################################################# + + + + ############################################################################# def doSignForInput(self, idStr, keyIdx): ib = self.inputBundles[idStr] wltID, a160, dkey, ckey = ib.wltSignRightNow[keyIdx] @@ -2954,40 +2963,47 @@ def doSignForInput(self, idStr, keyIdx): try: if ib.lockbox: - #if any input is not of legacy type, force cpp signer - if self.ustx.isSegWit(): - signerType = SIGNER_CPP + #if any input is not of legacy type, force cpp signer - # If a lockbox, all USTXIs require the same signing key +#HighPrioTODO: Replace with cpp bridge + #if self.ustx.isSegWit(): + # signerType = SIGNER_CPP + + # If a lockbox, all USTXIs require the same signing key for ustxi in ib.ustxiList: addrObj = wlt.getAddrObjectForHash(a160) - ustxi.createAndInsertSignature(\ - self.ustx.pytxObj, addrObj.binPrivKey32_Plain, signerType=signerType) + + #HighPrioTODO: Replace with the bridge. + #ustxi.createAndInsertSignature(\ + # self.ustx.pytxObj, addrObj.binPrivKey32_Plain, signerType=signerType) else: - # Not lockboxes... may have to access multiple keys in wallet - - #if any input is not of legacy type, force cpp signer - if self.ustx.isSegWit(): - signerType = SIGNER_CPP + # Not lockboxes... may have to access multiple keys in wallet + + #if any input is not of legacy type, force cpp signer + #HighPrioTODO: Replace with the bridge. + #if self.ustx.isSegWit(): + # signerType = SIGNER_CPP for ustxi in ib.ustxiList: a160 = CheckHash160(ustxi.scrAddrs[0]) addrObj = wlt.getAddrObjectForHash(a160) - ustxi.createAndInsertSignature(\ - self.ustx.pytxObj, addrObj.binPrivKey32_Plain, signerType=signerType) + + #HighPrioTODO: Replace with the bridge. + #ustxi.createAndInsertSignature(\ + # self.ustx.pytxObj, addrObj.binPrivKey32_Plain, signerType=signerType) self.evalSigStat() except SignerException as e: QMessageBox.critical(self, self.tr('Signer Error'), e.message, QMessageBox.Ok) - ############################################################################# + ############################################################################# def evalSigStat(self): self.relevancyMap = {} self.canSignMap = {} self.alreadySigned = {} - # Not sure if we really need this... + # Not sure if we really need this... for wltID,pyWlt in self.main.walletMap.iteritems(): txss = self.ustx.evaluateSigningStatus(pyWlt.cppWallet) self.relevancyMap[wltID] = txss.wltIsRelevant @@ -2995,26 +3011,26 @@ def evalSigStat(self): self.alreadySigned[wltID] = txss.wltAlreadySigned - # This is complex, for sure. - # The outermost loop goes over all inputs and outputs - # Then goes over all N public keys + # This is complex, for sure. + # The outermost loop goes over all inputs and outputs + # Then goes over all N public keys for idStr,ib in self.inputBundles.iteritems(): iWidgMap = self.iWidgets[idStr] - # Since we are calling this without a wlt, each key state can only - # be either ALREADY_SIGNED or NO_SIGNATURE (no WLT* possible) + # Since we are calling this without a wlt, each key state can only + # be either ALREADY_SIGNED or NO_SIGNATURE (no WLT* possible) isigstat = ib.ustxiList[0].evaluateSigningStatus(pytx=self.ustx.pytxObj) if ib.lockbox: - N = ib.lockbox.N + N = ib.lockbox.N entryKeyIndex = [None]*N - #match signing dialog entries to pubkeys in the ustx + #match signing dialog entries to pubkeys in the ustx for v in range(N): ms_key = ib.ustxiList[0].pubKeys[v] for w in range(N): try: - #check signable wallets first + #check signable wallets first if ms_key in ib.wltSignRightNow[w]: entryKeyIndex[v] = w break @@ -3022,7 +3038,7 @@ def evalSigStat(self): pass try: - #check offline wallets otherwise + #check offline wallets otherwise if ms_key in ib.wltOfflineSign[w]: entryKeyIndex[v] = w break @@ -3030,14 +3046,14 @@ def evalSigStat(self): pass try: - #lastly, check unrelated keys + #lastly, check unrelated keys if ms_key in ib.keyUnrelated[w]: entryKeyIndex[v] = w break except: continue - #set default index if there are no matches (unkonwn key) + #set default index if there are no matches (unkonwn key) if entryKeyIndex[v] == None: entryKeyIndex[v] = v else: @@ -3089,18 +3105,18 @@ def evalSigStat(self): keyImg.setPixmap(self.pixWhite()) signBtn.setVisible(False) - # Now modify the window/buttons based on the whole transaction state - # (i.e. Can broadcast, etc) + # Now modify the window/buttons based on the whole transaction state + # (i.e. Can broadcast, etc) txss = self.ustx.evaluateSigningStatus() if txss.canBroadcast: if not self.main.netMode == NETWORKMODE.Full: self.lblFinalMsg.setText(self.tr( - 'This transaction has enough signatures and ' - 'can be broadcast from any online computer (you are currently offline)' % htmlColor('TextGreen'))) + 'This transaction has enough signatures and ' + 'can be broadcast from any online computer (you are currently offline)' % htmlColor('TextGreen'))) else: self.lblFinalMsg.setText(self.tr( - 'This transaction has enough signatures and ' - 'can be broadcast' % htmlColor('TextGreen'))) + 'This transaction has enough signatures and ' + 'can be broadcast' % htmlColor('TextGreen'))) self.btnFinalBroad.setVisible(True) self.btnFinalBroad.setEnabled(self.main.netMode == NETWORKMODE.Full) self.btnFinalExport.setVisible(True) @@ -3117,11 +3133,11 @@ def evalSigStat(self): self.btnFinalExport.setEnabled(True) self.lblFinalChk.setPixmap(QPixmap()) - + def doExport(self): - #class DlgExportAsciiBlock(ArmoryDialog): - #def __init__(self, parent, main, exportObj, title, descr, fileTypes, defaultFN) + #class DlgExportAsciiBlock(ArmoryDialog): + #def __init__(self, parent, main, exportObj, title, descr, fileTypes, defaultFN) title = self.tr("Export Signature Collector") descr = self.tr( 'The text below includes all data about this multi-sig transaction, ' @@ -3134,9 +3150,9 @@ def doExport(self): 'that would compromise the security of any of the signing devices.') ftypes = ['Signature Collectors (*.sigcollect.tx)'] defaultFN = 'MultisigTransaction_%s_.sigcollect.tx' % self.ustx.uniqueIDB58 - - DlgExportAsciiBlock(self, self.main, self.ustx, title, descr, - ftypes, defaultFN).exec_() + + DlgExportAsciiBlock(self, self.main, self.ustx, title, descr, + ftypes, defaultFN).exec_() def doImport(self): @@ -3152,37 +3168,37 @@ def doImport(self): dlg = DlgImportAsciiBlock(self, self.main, title, descr, ftypes, importType) if dlg.exec_(): - # Merge signatures if the current ustx ID matchs the imported file + # Merge signatures if the current ustx ID matchs the imported file if self.ustx.uniqueIDB58 == dlg.returnObj.uniqueIDB58: for i in range(len(dlg.returnObj.ustxInputs)): for j in range(len(dlg.returnObj.ustxInputs[i].signatures)): if len(self.ustx.ustxInputs[i].signatures[j]) > 0: dlg.returnObj.ustxInputs[i].signatures[j] = \ - self.ustx.ustxInputs[i].signatures[j] - - # FIXME: This is a serious hack because I didn't have time to implement - # reloading an existing dialog with a new USTX, so I just recurse - # for now (it's because all the layouts are set in the __init__ - # function, etc... - self.accept() + self.ustx.ustxInputs[i].signatures[j] + + # FIXME: This is a serious hack because I didn't have time to implement + # reloading an existing dialog with a new USTX, so I just recurse + # for now (it's because all the layouts are set in the __init__ + # function, etc... + self.accept() DlgMultiSpendReview(self.parent, self.main, dlg.returnObj).exec_() def doBroadcast(self): finalTx = self.ustx.getSignedPyTx(doVerifySigs=True) if not finalTx: - #self.ustx.evaluateSigningStatus().pprint() + #self.ustx.evaluateSigningStatus().pprint() QMessageBox.critical(self, self.tr('Invalid Signatures'), self.tr( - 'Somehow not all inputs have valid sigantures! You can choose ' + 'Somehow not all inputs have valid sigantures! You can choose ' 'to attempt to broadcast anyway, in case you think Armory is ' 'not evaluating the transaction state correctly. ' '

' 'Otherwise, please confirm that you have created signatures ' 'from the correct wallets. Perhaps try collecting signatures ' 'again...?'), QMessageBox.Ok) - - finalTx = self.ustx.getSignedPyTx(doVerifySigs=False) + finalTx = self.ustx.getSignedPyTx(doVerifySigs=False) + self.main.broadcastTransaction(finalTx) try: @@ -3193,13 +3209,13 @@ def doBroadcast(self): except: pass self.accept() - + ################################################################################ class DlgCreatePromNote(ArmoryDialog): - ############################################################################# + ############################################################################# def __init__(self, parent, main, defaultIDorAddr=None, skipExport=False): super(DlgCreatePromNote, self).__init__(parent, main) @@ -3239,11 +3255,11 @@ def __init__(self, parent, main, defaultIDorAddr=None, skipExport=False): def selectWalletFunc(wlt): self.spendFromWltID = wlt.uniqueIDB58 - wltFrame = SelectWalletFrame(self, self.main, HORIZONTAL, - selectWltCallback=selectWalletFunc) - + wltFrame = SelectWalletFrame(self, self.main, HORIZONTAL, + selectWltCallback=selectWalletFunc) - # Create the frame that specifies the target of the funding + + # Create the frame that specifies the target of the funding lblAddress = QRichLabel(self.tr('Address:')) lblAmount = QRichLabel(self.tr('Amount:')) @@ -3258,13 +3274,13 @@ def selectWalletFunc(wlt): else: startStr = createLockboxEntryStr(defaultIDorAddr) - aewMap = self.main.createAddressEntryWidgets(self, startStr, + aewMap = self.main.createAddressEntryWidgets(self, startStr, maxDetectLen=72, boldDetectParts=2) self.edtFundTarget = aewMap['QLE_ADDR'] self.btnSelectTarg = aewMap['BTN_BOOK'] self.lblAutoDetect = aewMap['LBL_DETECT'] self.parseEntryFunc = aewMap['CALLBACK_GETSCRIPT'] - + self.lblAutoDetect.setWordWrap(False) self.edtAmountBTC = QLineEdit() @@ -3286,7 +3302,7 @@ def selectWalletFunc(wlt): 'This label will be attached to the promissory note to help identify ' 'who is committing these funds. If you do not fill this in, each ' 'other party signing will see [[Unknown Signer]] for the ID.')) - + frmKeyComment = makeHorizFrame([lblComment, self.edtKeyLabel, ttipFunder]) @@ -3295,7 +3311,7 @@ def selectWalletFunc(wlt): gboxInLayout.addWidget(lblNoteSrc) gboxInLayout.addWidget(wltFrame) gboxInLayout.addWidget(frmKeyComment) - gboxIn.setLayout(gboxInLayout) + gboxIn.setLayout(gboxInLayout) gboxOut = QGroupBox(self.tr('Funding Destination')) gboxOutLayout = QGridLayout() @@ -3341,11 +3357,11 @@ def selectWalletFunc(wlt): self.setWindowTitle('Create Promissory Note') - #self.updateTargetLabel() + #self.updateTargetLabel() self.setMinimumWidth(600) - ############################################################################# + ############################################################################# def updateTargetLabel(self): try: addrText = str(self.edtFundTarget.text()) @@ -3380,7 +3396,7 @@ def updateTargetLabel(self): self.lblTargetID.setVisible(False) - ############################################################################# + ############################################################################# def doContinue(self): @@ -3393,15 +3409,15 @@ def doContinue(self): 'or after restarting Armory'), QMessageBox.Ok) return False - # TODO: Expand this to allow Simulfunding from lockbox(es) + # TODO: Expand this to allow Simulfunding from lockbox(es) wlt = self.main.walletMap.get(self.spendFromWltID, None) lbox = self.main.getLockboxByID(self.spendFromWltID) if lbox is not None: LOGERROR('Simulfunding from lockbox not currently implemented') QMessageBox.critical(self, self.tr('Lockbox Selected'), self.tr( - 'Currently, Armory does not implement Simulfunding with lockbox ' - 'inputs. Please choose a regular wallet as your input'), - QMessageBox.Ok) + 'Currently, Armory does not implement Simulfunding with lockbox ' + 'inputs. Please choose a regular wallet as your input'), + QMessageBox.Ok) elif wlt is None: LOGERROR('No wallet in map with ID: "%s"' % self.spendFromWltID) QMessageBox.critical(self, self.tr('No Wallet Selected'), self.tr( @@ -3409,7 +3425,7 @@ def doContinue(self): QMessageBox.Ok) return False - # Read the user-supplied BTC value to contribute + # Read the user-supplied BTC value to contribute try: valueStr = str(self.edtAmountBTC.text()) valueAmt = str2coin(valueStr) @@ -3439,8 +3455,8 @@ def doContinue(self): QMessageBox.Ok) LOGEXCEPT('Invalid amount specified: "%s"', valueStr) return False - - # Read the fee string + + # Read the fee string try: feeStr = str(self.edtFeeBTC.text()) feeAmt = str2coin(feeStr) @@ -3487,13 +3503,13 @@ def doContinue(self): 'separate transactions.'), QMessageBox.Ok) return False - # Create the target DTXO + # Create the target DTXO targetScript = self.parseEntryFunc()['Script'] dtxoTarget = DecoratedTxOut(targetScript, valueAmt) - # Create the change DTXO - # TODO: Expand this to allow Simulfunding from lockbox(es) - #pprintUnspentTxOutList(utxoSelect) + # Create the change DTXO + # TODO: Expand this to allow Simulfunding from lockbox(es) + #pprintUnspentTxOutList(utxoSelect) changeAmt = sumTxOutList(utxoSelect) - (valueAmt + feeAmt) dtxoChange = None if changeAmt > 0: @@ -3503,7 +3519,7 @@ def doContinue(self): else: LOGINFO('Had exact change for prom note: dtxoChange=None') - # If we got here, we can carry through with creating the prom note + # If we got here, we can carry through with creating the prom note ustxiList = [] for i in range(len(utxoSelect)): utxo = utxoSelect[i] @@ -3513,11 +3529,11 @@ def doContinue(self): if not cppTx.isInitialized(): LOGERROR('UTXO was supplied for which we could not find prev Tx') QMessageBox.warning(self, self.tr('Transaction Not Found'), self.trUtf8( - 'There was an error creating the promissory note -- the selected ' - 'coins were not found in the blockchain. Please go to ' - '"Help"\xe2\x86\x92"Submit Bug Report" from ' - 'the main window and submit your log files so the Armory team ' - 'can review this error.'), QMessageBox.Ok) + 'There was an error creating the promissory note -- the selected ' + 'coins were not found in the blockchain. Please go to ' + '"Help"\xe2\x86\x92"Submit Bug Report" from ' + 'the main window and submit your log files so the Armory team ' + 'can review this error.'), QMessageBox.Ok) rawTx = cppTx.serialize() utxoScrAddr = utxo.getRecipientScrAddr() @@ -3529,26 +3545,27 @@ def doContinue(self): if len(p2shScript) > 0: p2shKey = binary_to_hex(script_to_scrAddr(script_to_p2sh_script( p2shScript))) - p2shMap[p2shKey] = p2shScript + p2shMap[p2shKey] = p2shScript p2shMap[BASE_SCRIPT] = p2shScript - try: - scriptType = Cpp.BtcUtils().getTxOutScriptTypeInt(p2shScript) - if scriptType == CPP_TXOUT_P2WPKH: - nestedScript = binary_to_hex(p2shScript[2:]) - pubkey = SecureBinaryData(aobj.getPubKey()) - compressed_key = CryptoECDSA().CompressPoint(pubkey) - p2shMap[nestedScript] = compressed_key.toBinStr() - except: - pass - - ustxiList.append(UnsignedTxInput(rawTx, txoIdx, p2shMap, pubKeys)) + #HighPrioTODO: Replace with the bridge. + #try: + #scriptType = Cpp.BtcUtils().getTxOutScriptTypeInt(p2shScript) + #if scriptType == CPP_TXOUT_P2WPKH: + # nestedScript = binary_to_hex(p2shScript[2:]) + # pubkey = SecureBinaryData(aobj.getPubKey()) + # compressed_key = CryptoECDSA().CompressPoint(pubkey) + # p2shMap[nestedScript] = compressed_key.toBinStr() + #except: + # pass + ustxiList.append(UnsignedTxInput(rawTx, txoIdx, p2shMap, pubKeys)) + funderStr = str(self.edtKeyLabel.text()).strip() - self.finalPromNote = MultiSigPromissoryNote(dtxoTarget, feeAmt, + self.finalPromNote = MultiSigPromissoryNote(dtxoTarget, feeAmt, ustxiList, dtxoChange, funderStr) - + LOGINFO('Successfully created prom note: %s' % self.finalPromNote.promID) @@ -3566,22 +3583,22 @@ def doContinue(self): ftypes = ['Promissory Notes (*.promnote)'] defaultFN = 'Contrib_%s_%sBTC.promnote' % \ (self.finalPromNote.promID, coin2strNZS(valueAmt)) - - - if not DlgExportAsciiBlock(self, self.main, self.finalPromNote, title, - descr, ftypes, defaultFN).exec_(): + + + if not DlgExportAsciiBlock(self, self.main, self.finalPromNote, title, + descr, ftypes, defaultFN).exec_(): return False else: self.accept() - - - + + + ################################################################################ class DlgMergePromNotes(ArmoryDialog): - ############################################################################# + ############################################################################# def __init__(self, parent, main, lboxID=None): super(DlgMergePromNotes, self).__init__(parent, main) @@ -3591,7 +3608,7 @@ def __init__(self, parent, main, lboxID=None): self.promNotes = [] self.promIDSet = set([]) - # Will be none + # Will be none if lboxID is None: self.lbox = None self.promMustMatch = None @@ -3604,7 +3621,7 @@ def __init__(self, parent, main, lboxID=None): 'Merge Promissory Notes ' '' % htmlColor('TextBlue')), hAlign=Qt.AlignHCenter, doWrap=False) - + lblDescr = QRichLabel(self.tr( 'Collect promissory notes from two or more parties ' 'to combine them into a single Simulfunding transaction. Once ' @@ -3613,9 +3630,9 @@ def __init__(self, parent, main, lboxID=None): if self.lbox: - #lbTargStr = 'Lockbox %s-of-%s: %s (%s)' % \ - #(htmlColor('TextBlue'), self.lbox.M, self.lbox.N, - #self.lbox.shortName, self.lbox.uniqueIDB58) + #lbTargStr = 'Lockbox %s-of-%s: %s (%s)' % \ + #(htmlColor('TextBlue'), self.lbox.M, self.lbox.N, + #self.lbox.shortName, self.lbox.uniqueIDB58) lbTargStr = self.main.getDisplayStringForScript(self.lbox.getScript()) lbTargStr = lbTargStr['String'] gboxTarget = QGroupBox(self.tr('Lockbox Being Funded')) @@ -3631,9 +3648,9 @@ def __init__(self, parent, main, lboxID=None): self.lblCurrFee = QMoneyLabel(0, maxZeros=2) self.lblPayUnits = QRichLabel('BTC') self.lblFeeUnits = QRichLabel('BTC') - + gboxTargetLayout = QGridLayout() gboxTargetLayout.addWidget(self.lblTarg, 1,0, 1,6) @@ -3654,10 +3671,10 @@ def __init__(self, parent, main, lboxID=None): gboxTargetLayout.setColumnStretch(3,0) gboxTargetLayout.setColumnStretch(4,0) gboxTargetLayout.setColumnStretch(5,1) - gboxTarget.setLayout(gboxTargetLayout) + gboxTarget.setLayout(gboxTargetLayout) - # For when there's no prom note yet + # For when there's no prom note yet self.gboxLoaded = QGroupBox(self.tr('Loaded Promissory Notes')) lblNoInfo = QRichLabel(self.tr( 'No Promissory Notes Have Been Added'), @@ -3698,20 +3715,20 @@ def __init__(self, parent, main, lboxID=None): btnFinish = QPushButton(self.tr('Continue')) self.connect(btnCancel, SIGNAL('clicked()'), self.reject) self.connect(btnFinish, SIGNAL('clicked()'), self.mergeNotesCreateUSTX) - frmButtons = makeHorizFrame([btnCancel, - 'Stretch', - self.chkBareMS, - self.ttipBareMS, - btnFinish]) - - # If this was opened with default lockbox, set visibility, save ms script - # If opened generic, this will be set first time importNote() is called + frmButtons = makeHorizFrame([btnCancel, + 'Stretch', + self.chkBareMS, + self.ttipBareMS, + btnFinish]) + + # If this was opened with default lockbox, set visibility, save ms script + # If opened generic, this will be set first time importNote() is called self.chkBareMS.setVisible(False) self.ttipBareMS.setVisible(False) if self.lbox is not None: self.chkBareMS.setVisible(True) self.ttipBareMS.setVisible(True) - self.msTarget = self.lbox.binScript + self.msTarget = self.lbox.binScript mainLayout = QVBoxLayout() @@ -3729,21 +3746,21 @@ def __init__(self, parent, main, lboxID=None): self.setLayout(mainLayout) self.setMinimumWidth(700) - - - ############################################################################# + + + ############################################################################# def importNote(self): title = self.tr('Import Promissory Note') descr = self.tr( - 'Import a promissory note to add to this Simulfunding transaction') + 'Import a promissory note to add to this Simulfunding transaction') ftypes = ['Promissory Notes (*.promnote)'] - dlgImport = DlgImportAsciiBlock(self, self.main, + dlgImport = DlgImportAsciiBlock(self, self.main, title, descr, ftypes, MultiSigPromissoryNote) dlgImport.exec_() promnote = None if dlgImport.returnObj: promnote = dlgImport.returnObj - #promnote.pprint() + #promnote.pprint() if not promnote: QMessageBox.critical(self, self.tr('Invalid Promissory Note'), self.tr( @@ -3754,7 +3771,7 @@ def importNote(self): self.addNote(promnote) - ############################################################################# + ############################################################################# def createPromAdd(self): if not TheBDM.getState()==BDM_BLOCKCHAIN_READY: QMessageBox.warning(self, self.tr("Not Online"), self.tr( @@ -3762,8 +3779,8 @@ def createPromAdd(self): 'transactions or promissory notes. You can only merge ' 'pre-existing promissory notes at this time.'), QMessageBox.Ok) return - - + + defaultTarg = None if self.promMustMatch: for lbox in self.main.allLockboxes: @@ -3772,57 +3789,57 @@ def createPromAdd(self): break else: defaultTarg = scrAddr_to_addrStr(self.promMustMatch) - - + + dlg = DlgCreatePromNote(self, self.main, defaultTarg, skipExport=True) dlg.exec_() if dlg.finalPromNote: self.addNote(dlg.finalPromNote) - - ############################################################################# + + ############################################################################# def addNote(self, promnote): if promnote.promID in self.promIDSet: - QMessageBox.critical(self, self.tr('Already Loaded'), self.tr('This ' + QMessageBox.critical(self, self.tr('Already Loaded'), self.tr('This ' 'promissory note has already been loaded!'), QMessageBox.Ok) return - # reduceScript returns the same scrAddr for a bare multi-sig as it does - # for it's P2SH form - targetScript = promnote.dtxoTarget.binScript + # reduceScript returns the same scrAddr for a bare multi-sig as it does + # for it's P2SH form + targetScript = promnote.dtxoTarget.binScript promTarget = self.reduceScript(targetScript) - - # If loaded from main window menu, we have nothing to match yet; set it + + # If loaded from main window menu, we have nothing to match yet; set it if not self.promMustMatch: self.promMustMatch = promTarget contribStr = self.main.getContribStr(targetScript)[0] self.lblTarg.setText('%s' % \ - (htmlColor('TextBlue'), contribStr)) + (htmlColor('TextBlue'), contribStr)) - # If this is a multi-sig target, or it's a P2SH multisig we recognize - # then provide the option to use bare multi-sig (which may be - # desriable in certain contexts). + # If this is a multi-sig target, or it's a P2SH multisig we recognize + # then provide the option to use bare multi-sig (which may be + # desriable in certain contexts). self.chkBareMS.setVisible(False) self.ttipBareMS.setVisible(False) for lbID,cppWlt in self.main.cppLockboxWltMap.iteritems(): if cppWlt.hasScrAddress(promTarget): LOGINFO('Have lockbox for the funding target: %s' % lbID) - lb = self.main.getLockboxByID(lbID) + lb = self.main.getLockboxByID(lbID) if lb and lb.binScript and \ - getTxOutScriptType(lb.binScript)==CPP_TXOUT_MULTISIG: - self.msTarget = lb.binScript - self.chkBareMS.setVisible(True) - self.ttipBareMS.setVisible(True) + getTxOutScriptType(lb.binScript)==CPP_TXOUT_MULTISIG: + self.msTarget = lb.binScript + self.chkBareMS.setVisible(True) + self.ttipBareMS.setVisible(True) break - - # By now, we should always know what target addr ... make sure it matches + + # By now, we should always know what target addr ... make sure it matches if not promTarget==self.promMustMatch: QMessageBox.critical(self, self.tr('Mismatched Funding Target'), self.tr( 'The promissory note you loaded is for a different funding target. ' @@ -3842,34 +3859,34 @@ def addNote(self, promnote): - ############################################################################# + ############################################################################# def reduceScript(self, script): scrType = getTxOutScriptType(script) if scrType==CPP_TXOUT_MULTISIG: - # This is already + # This is already script = script_to_p2sh_script(script) return script_to_scrAddr(script) - - ############################################################################# + + ############################################################################# def updatePromTable(self): if len(self.promNotes)==0: self.promLoadStacked.setCurrentIndex(0) else: self.promLoadStacked.setCurrentIndex(1) self.promModel.reset() - - - ############################################################################# + + + ############################################################################# def mergeNotesCreateUSTX(self): if len(self.promNotes)==0: QMessageBox.warning(self, self.tr('Nothing Loaded'), self.tr( 'No promissory notes were loaded. Cannot create Simulfunding ' 'transaction.'), QMessageBox.Ok) - return + return if len(self.promNotes)==1: reply = QMessageBox.warning(self, self.tr('Merging One Note'), self.tr( @@ -3892,7 +3909,7 @@ def mergeNotesCreateUSTX(self): ustxiList = [] dtxoList = [] - # We've already made sure all promNotes have the same target + # We've already made sure all promNotes have the same target firstDtxo = self.promNotes[0].dtxoTarget dtxoTarget = DecoratedTxOut().unserialize(firstDtxo.serialize()) @@ -3941,12 +3958,12 @@ def mergeNotesCreateUSTX(self): ################################################################################ class DlgSelectMultiSigOption(ArmoryDialog): - ############################################################################# + ############################################################################# def __init__(self, parent, main): super(DlgSelectMultiSigOption, self).__init__(parent, main) self.btnCreate = QPushButton(self.tr('Create/Manage lockboxes')) - #self.btnImport = QPushButton(self.tr('Import multi-sig lockbox')) + #self.btnImport = QPushButton(self.tr('Import multi-sig lockbox')) self.btnFund = QPushButton(self.tr('Fund a lockbox')) self.btnSpend = QPushButton(self.tr('Spend from a lockbox')) @@ -3974,11 +3991,11 @@ def __init__(self, parent, main): self.lblCreate = QRichLabel(self.tr( - 'Collect public keys to create an "address" that can be used ' - 'to send funds to the multi-sig container')) - #self.lblImport = QRichLabel(self.tr( - #'If someone has already created the lockbox you can add it ' - #'to your lockbox list')) + 'Collect public keys to create an "address" that can be used ' + 'to send funds to the multi-sig container')) + #self.lblImport = QRichLabel(self.tr( + #'If someone has already created the lockbox you can add it ' + #'to your lockbox list')) self.lblFund = QRichLabel(self.tr( 'Send money to an lockbox simultaneously with other ' 'parties involved in the lockbox')) @@ -4049,16 +4066,16 @@ def __init__(self, parent, main): self.setWindowTitle(self.tr('Multi-Sig Lockboxes')) - ############################################################################# + ############################################################################# def openCreate(self): DlgLockboxEditor(self, self.main).exec_() - ############################################################################# + ############################################################################# def openFund(self): DlgFundLockbox(self, self.main).exec_() - ############################################################################# + ############################################################################# def openSpend(self): - DlgSpendFromLockbox(self, self.main).exec_() + DlgSpendFromLockbox(self, self.main).exec_() \ No newline at end of file diff --git a/ui/TxFramesOffline.py b/ui/TxFramesOffline.py index 4e8941158..96218490e 100644 --- a/ui/TxFramesOffline.py +++ b/ui/TxFramesOffline.py @@ -20,11 +20,12 @@ GETFONT, QRichLabel, HLINE, QLabelButton, USERMODE, \ VERTICAL, HORIZONTAL, STYLE_RAISED, relaxedSizeNChar, STYLE_SUNKEN, \ relaxedSizeStr, makeLayoutFrame, tightSizeStr, NETWORKMODE, \ - MsgBoxWithDNAA, MSGBOX + MSGBOX from qtdialogs.qtdialogs import STRETCH from qtdialogs.DlgDispTxInfo import DlgDispTxInfo, extractTxInfo from qtdialogs.DlgConfirmSend import DlgConfirmSend +from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA from armoryengine.Transaction import UnsignedTransaction from armoryengine.ArmoryUtils import LOGEXCEPT, LOGERROR, LOGINFO, \ @@ -580,4 +581,4 @@ def copyTxHex(self): clipb.clear() clipb.setText(binary_to_hex(\ self.ustxObj.getSignedPyTx().serialize())) - self.lblCopied.setText(self.tr('Copied!')) \ No newline at end of file + self.lblCopied.setText(self.tr('Copied!')) diff --git a/ui/Wizards.py b/ui/Wizards.py index 86aa57912..8b9eef758 100755 --- a/ui/Wizards.py +++ b/ui/Wizards.py @@ -8,8 +8,8 @@ # # ################################################################################ -from PySide2.QtWidgets import QWizard, QWizardPage, QVBoxLayout -from PySide2.QtCore import Qt +from PySide2.QtWidgets import QWizard, QWizardPage, QVBoxLayout, QMessageBox +from PySide2.QtCore import Qt, SIGNAL from PySide2.QtGui import QIcon from armoryengine.ArmoryUtils import USE_TESTNET, USE_REGTEST, int_to_binary @@ -20,7 +20,6 @@ from qtdialogs.qtdefines import USERMODE, GETFONT, AddToRunningDialogsList from armoryengine.PyBtcWallet import PyBtcWallet from armoryengine.BDM import TheBDM, BDM_OFFLINE, BDM_UNINITIALIZED -from qtdialogs.DlgProgress import DlgProgress from qtdialogs.DlgOfflineTx import ReviewOfflineTxFrame # This class is intended to be an abstract Wizard class that @@ -34,7 +33,7 @@ def __init__(self, parent, main): self.main = main self.setFont(GETFONT('var')) self.setWindowFlags(Qt.Window) - # Need to adjust the wizard frame size whenever the page changes. + # Need to adjust the wizard frame size whenever the page changes. self.currentIdChanged.connect(self.fitContents) if USE_TESTNET: self.setWindowTitle('Armory - Bitcoin Wallet Management [TESTNET]') @@ -54,8 +53,8 @@ def exec_(self): return super(ArmoryWizard, self).exec_() # This class is intended to be an abstract Wizard Page class that -# will hold all of the functionality that is common to all -# Wizard pages in Armory. +# will hold all of the functionality that is common to all +# Wizard pages in Armory. # The layout is QVBoxLayout and holds a single QFrame (self.pageFrame) class ArmoryWizardPage(QWizardPage): def __init__(self, wizard, pageFrame): @@ -64,8 +63,8 @@ def __init__(self, wizard, pageFrame): self.pageLayout = QVBoxLayout() self.pageLayout.addWidget(self.pageFrame) self.setLayout(self.pageLayout) - - # override this method to implement validators + + # override this method to implement validators def validatePage(self): return True @@ -84,30 +83,30 @@ def __init__(self, parent, main): self.setWindowTitle(self.tr("Wallet Creation Wizard")) self.setOption(QWizard.HaveFinishButtonOnEarlyPages, on=True) self.setOption(QWizard.IgnoreSubTitles, on=True) - - self.walletCreationId, self.manualEntropyId, self.setPassphraseId, self.verifyPassphraseId, self.walletBackupId, self.WOWId = range(6) - # Page 1: Create Wallet + self.walletCreationId, self.manualEntropyId, self.setPassphraseId, self.verifyPassphraseId, self.walletBackupId, self.WOWId = range(6) + + # Page 1: Create Wallet self.walletCreationPage = WalletCreationPage(self) self.setPage(self.walletCreationId, self.walletCreationPage) - - # Page 1.5: Add manual entropy + + # Page 1.5: Add manual entropy self.manualEntropyPage = ManualEntropyPage(self) self.setPage(self.manualEntropyId, self.manualEntropyPage) - - # Page 2: Set Passphrase + + # Page 2: Set Passphrase self.setPassphrasePage = SetPassphrasePage(self) self.setPage(self.setPassphraseId, self.setPassphrasePage) - - # Page 3: Verify Passphrase + + # Page 3: Verify Passphrase self.verifyPassphrasePage = VerifyPassphrasePage(self) self.setPage(self.verifyPassphraseId, self.verifyPassphrasePage) - # Page 4: Create Paper Backup + # Page 4: Create Paper Backup self.walletBackupPage = WalletBackupPage(self) self.setPage(self.walletBackupId, self.walletBackupPage) - - # Page 5: Create Watching Only Wallet -- but only if expert, or offline + + # Page 5: Create Watching Only Wallet -- but only if expert, or offline self.hasCWOWPage = False if self.main.usermode==USERMODE.Expert or TheBDM.getState() == BDM_OFFLINE: self.hasCWOWPage = True @@ -115,38 +114,38 @@ def __init__(self, parent, main): self.setPage(self.WOWId, self.createWOWPage) self.setButtonLayout([QWizard.BackButton, - QWizard.Stretch, - QWizard.NextButton, - QWizard.FinishButton]) + QWizard.Stretch, + QWizard.NextButton, + QWizard.FinishButton]) def initializePage(self, *args, **kwargs): if self.currentPage() == self.verifyPassphrasePage: self.verifyPassphrasePage.setPassphrase( - self.setPassphrasePage.pageFrame.getPassphrase()) + self.setPassphrasePage.pageFrame.getPassphrase()) elif self.hasCWOWPage and self.currentPage() == self.createWOWPage: self.createWOWPage.pageFrame.setWallet(self.newWallet) - + if self.currentPage() == self.walletBackupPage: self.createNewWalletFromWizard() self.walletBackupPage.pageFrame.setPassphrase( - self.setPassphrasePage.pageFrame.getPassphrase()) + self.setPassphrasePage.pageFrame.getPassphrase()) self.walletBackupPage.pageFrame.setWallet(self.newWallet) - - # Hide the back button on wallet backup page + + # Hide the back button on wallet backup page self.setButtonLayout([QWizard.Stretch, - QWizard.NextButton, - QWizard.FinishButton]) + QWizard.NextButton, + QWizard.FinishButton]) elif self.currentPage() == self.walletCreationPage: - # Hide the back button on the first page + # Hide the back button on the first page self.setButtonLayout([QWizard.Stretch, - QWizard.NextButton, - QWizard.FinishButton]) + QWizard.NextButton, + QWizard.FinishButton]) else: self.setButtonLayout([QWizard.BackButton, - QWizard.Stretch, - QWizard.NextButton, - QWizard.FinishButton]) + QWizard.Stretch, + QWizard.NextButton, + QWizard.FinishButton]) def done(self, event): if self.newWallet and not self.walletBackupPage.pageFrame.isBackupCreated: reply = QMessageBox.question(self, self.tr('Wallet Backup Warning'), self.tr('' @@ -163,10 +162,10 @@ def done(self, event): 'up your wallet?'), \ QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.No: - # Stay in the wizard + # Stay in the wizard return None return super(WalletWizard, self).done(event) - + def createNewWalletFromWizard(self): entropy = None if self.walletCreationPage.isManualEncryption(): @@ -174,35 +173,35 @@ def createNewWalletFromWizard(self): else: entropy = self.main.getExtraEntropyForKeyGen() self.newWallet = PyBtcWallet().createNewWallet( - passphrase=self.setPassphrasePage.pageFrame.getPassphrase(), - kdfTargSec=self.walletCreationPage.pageFrame.getKdfSec(), - kdfMaxMem=self.walletCreationPage.pageFrame.getKdfBytes(), - shortLabel=self.walletCreationPage.pageFrame.getName(), - longLabel=self.walletCreationPage.pageFrame.getDescription(), - extraEntropy=entropy) - - # Reopening from file helps make sure everything is correct -- don't - # let the user use a wallet that triggers errors on reading it + passphrase=self.setPassphrasePage.pageFrame.getPassphrase(), + kdfTargSec=self.walletCreationPage.pageFrame.getKdfSec(), + kdfMaxMem=self.walletCreationPage.pageFrame.getKdfBytes(), + shortLabel=self.walletCreationPage.pageFrame.getName(), + longLabel=self.walletCreationPage.pageFrame.getDescription(), + extraEntropy=entropy) + + # Reopening from file helps make sure everything is correct -- don't + # let the user use a wallet that triggers errors on reading it self.main.addWalletToApplication(self.newWallet, walletIsNew=True) def cleanupPage(self, *args, **kwargs): if self.hasCWOWPage and self.currentPage() == self.createWOWPage: self.setButtonLayout([QWizard.Stretch, - QWizard.NextButton, - QWizard.FinishButton]) - # If we are backing up from setPassphrasePage must be going - # to the first page. + QWizard.NextButton, + QWizard.FinishButton]) + # If we are backing up from setPassphrasePage must be going + # to the first page. elif self.currentPage() == self.setPassphrasePage: - # Hide the back button on the first page + # Hide the back button on the first page self.setButtonLayout([QWizard.Stretch, - QWizard.NextButton, - QWizard.FinishButton]) + QWizard.NextButton, + QWizard.FinishButton]) else: self.setButtonLayout([QWizard.BackButton, - QWizard.Stretch, - QWizard.NextButton, - QWizard.FinishButton]) - + QWizard.Stretch, + QWizard.NextButton, + QWizard.FinishButton]) + class ManualEntropyPage(ArmoryWizardPage): def __init__(self, wizard): @@ -226,10 +225,10 @@ def __init__(self, wizard): self.wizard = wizard self.setTitle(wizard.tr("Step 1: Create Wallet")) self.setSubTitle(wizard.tr( - 'Create a new wallet for managing your funds. ' - 'The name and description can be changed at any time.')) - - # override this method to implement validators + 'Create a new wallet for managing your funds. ' + 'The name and description can be changed at any time.')) + + # override this method to implement validators def validatePage(self): result = True if self.pageFrame.getKdfSec() == -1: @@ -268,28 +267,28 @@ def isComplete(self): def nextId(self): return self.wizard.verifyPassphraseId - + class VerifyPassphrasePage(ArmoryWizardPage): def __init__(self, wizard): - super(VerifyPassphrasePage, self).__init__(wizard, + super(VerifyPassphrasePage, self).__init__(wizard, VerifyPassphraseFrame(wizard, wizard.main, wizard.tr("Verify Passphrase"))) self.wizard = wizard self.passphrase = None self.setTitle(wizard.tr("Step 3: Verify Passphrase")) def setPassphrase(self, passphrase): - self.passphrase = passphrase + self.passphrase = passphrase def validatePage(self): result = self.passphrase == str(self.pageFrame.edtPasswd3.text()) if not result: QMessageBox.critical(self, self.tr('Invalid Passphrase'), \ - self.tr('You entered your confirmation passphrase incorrectly!'), QMessageBox.Ok) + self.tr('You entered your confirmation passphrase incorrectly!'), QMessageBox.Ok) return result def nextId(self): return self.wizard.walletBackupId - + class WalletBackupPage(ArmoryWizardPage): def __init__(self, wizard): super(WalletBackupPage, self).__init__(wizard, @@ -314,7 +313,7 @@ def __init__(self, wizard): def nextId(self): return -1 - + ############################### Offline TX Wizard ############################## # Offline TX Wizard has these pages: # 1. Create Transaction @@ -327,16 +326,16 @@ def __init__(self, parent, main, wlt, prefill=None, onlyOfflineWallets=False): self.setOption(QWizard.IgnoreSubTitles, on=True) self.setOption(QWizard.HaveCustomButton1, on=True) self.setOption(QWizard.HaveFinishButtonOnEarlyPages, on=True) - - # Page 1: Create Offline TX + + # Page 1: Create Offline TX self.createTxPage = CreateTxPage(self, wlt, prefill, onlyOfflineWallets=onlyOfflineWallets) self.addPage(self.createTxPage) - - # Page 2: Sign Offline TX + + # Page 2: Sign Offline TX self.reviewOfflineTxPage = ReviewOfflineTxPage(self) self.addPage(self.reviewOfflineTxPage) - - # Page 3: Broadcast Offline TX + + # Page 3: Broadcast Offline TX self.signBroadcastOfflineTxPage = SignBroadcastOfflineTxPage(self) self.addPage(self.signBroadcastOfflineTxPage) @@ -344,12 +343,12 @@ def __init__(self, parent, main, wlt, prefill=None, onlyOfflineWallets=False): self.setButtonText(QWizard.CustomButton1, self.tr('Send!')) self.connect(self, SIGNAL('customButtonClicked(int)'), self.sendClicked) self.setButtonLayout([QWizard.CancelButton, - QWizard.BackButton, - QWizard.Stretch, - QWizard.NextButton, - QWizard.CustomButton1]) + QWizard.BackButton, + QWizard.Stretch, + QWizard.NextButton, + QWizard.CustomButton1]) + - def initializePage(self, *args, **kwargs): if self.currentPage() == self.createTxPage: @@ -362,8 +361,8 @@ def initializePage(self, *args, **kwargs): QWizard.FinishButton]) self.reviewOfflineTxPage.pageFrame.setTxDp(self.createTxPage.txdp) self.reviewOfflineTxPage.pageFrame.setWallet( - self.createTxPage.pageFrame.wlt) - + self.createTxPage.pageFrame.wlt) + def cleanupPage(self, *args, **kwargs): if self.currentPage() == self.reviewOfflineTxPage: self.updateOnSelectWallet(self.createTxPage.pageFrame.wlt) @@ -372,20 +371,20 @@ def cleanupPage(self, *args, **kwargs): def sendClicked(self, customButtonIndex): self.createTxPage.pageFrame.createTxAndBroadcast() self.accept() - + def updateOnSelectWallet(self, wlt): if wlt.watchingOnly: self.setButtonLayout([QWizard.CancelButton, - QWizard.BackButton, - QWizard.Stretch, - QWizard.NextButton]) + QWizard.BackButton, + QWizard.Stretch, + QWizard.NextButton]) else: self.setButtonLayout([QWizard.CancelButton, - QWizard.BackButton, - QWizard.Stretch, - QWizard.NextButton, - QWizard.CustomButton1]) - + QWizard.BackButton, + QWizard.Stretch, + QWizard.NextButton, + QWizard.CustomButton1]) + class CreateTxPage(ArmoryWizardPage): def __init__(self, wizard, wlt, prefill=None, onlyOfflineWallets=False): super(CreateTxPage, self).__init__(wizard, @@ -395,10 +394,10 @@ def __init__(self, wizard, wlt, prefill=None, onlyOfflineWallets=False): onlyOfflineWallets=onlyOfflineWallets)) self.setTitle(self.tr("Step 1: Create Transaction")) self.txdp = None - + def validatePage(self): result = self.pageFrame.validateInputsGetTxDP() - # the validator also computes the transaction and returns it or False if not valid + # the validator also computes the transaction and returns it or False if not valid if result: self.txdp = result result = True @@ -406,16 +405,16 @@ def validatePage(self): def updateOnSelectWallet(self, wlt): self.wizard().updateOnSelectWallet(wlt) - + class ReviewOfflineTxPage(ArmoryWizardPage): def __init__(self, wizard): super(ReviewOfflineTxPage, self).__init__(wizard, ReviewOfflineTxFrame(wizard, wizard.main, self.tr("Review Offline Transaction"))) self.setTitle(self.tr("Step 2: Review Offline Transaction")) self.setFinalPage(True) - + class SignBroadcastOfflineTxPage(ArmoryWizardPage): def __init__(self, wizard): super(SignBroadcastOfflineTxPage, self).__init__(wizard, SignBroadcastOfflineTxFrame(wizard, wizard.main, self.tr("Sign/Broadcast Offline Transaction"))) - self.setTitle(self.tr("Step 3: Sign/Broadcast Offline Transaction")) + self.setTitle(self.tr("Step 3: Sign/Broadcast Offline Transaction")) \ No newline at end of file diff --git a/ui/toolsDialogs.py b/ui/toolsDialogs.py index 35b30aa4f..be35f04f1 100755 --- a/ui/toolsDialogs.py +++ b/ui/toolsDialogs.py @@ -8,16 +8,21 @@ # # ################################################################################ -from PySide2.QtWidgets import QWidget - -from armoryengine.ArmoryUtils import isASCII -from armorycolors import htmlColor -from qtdialogs.qtdialogs import MIN_PASSWD_WIDTH, DlgPasswd3 +from PySide2.QtWidgets import QApplication, QDialogButtonBox, QGridLayout, \ + QLabel, QLineEdit, QMessageBox, QPushButton, QTabWidget, QTextEdit, \ + QVBoxLayout, QWidget +from PySide2.QtGui import QIcon +from PySide2.QtCore import SIGNAL + +from armoryengine.ArmoryUtils import ADDRBYTE, LOGWARN, P2SHBYTE, \ + addrStr_to_hash160, isASCII +from jasvet import ASv0, ASv1B64, ASv1CS, readSigBlock, verifySignature +from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.DlgUnlockWallet import DlgUnlockWallet +from qtdialogs.qtdefines import MSGBOX, QRichLabel, makeHorizFrame +from qtdialogs.MsgBoxCustom import MsgBoxCustom from qtdialogs.DlgAddressBook import createAddrBookButton -from qtdialogs.qtdefines import ArmoryDialog - class MessageSigningVerificationDialog(ArmoryDialog): def __init__(self, parent=None, main=None): From 3c2c23d45463ba6dfabe2eae87c7715c909e7ddc Mon Sep 17 00:00:00 2001 From: goatpig Date: Thu, 27 Jan 2022 20:15:49 +0100 Subject: [PATCH 02/47] import LOG functions in PyBtcWallet.py --- armoryengine/PyBtcWallet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/armoryengine/PyBtcWallet.py b/armoryengine/PyBtcWallet.py index 8cafd897d..83634b1fc 100644 --- a/armoryengine/PyBtcWallet.py +++ b/armoryengine/PyBtcWallet.py @@ -15,7 +15,8 @@ import shutil from armoryengine.ArmoryUtils import AddressEntryType_Default, UINT32_MAX, \ - emptyFunc, PYBTCWALLET_VERSION, USE_TESTNET, USE_REGTEST, CLI_OPTIONS + emptyFunc, PYBTCWALLET_VERSION, USE_TESTNET, USE_REGTEST, CLI_OPTIONS, \ + LOGINFO, LOGEXCEPT, LOGWARN, LOGERROR from armoryengine.BinaryPacker import * from armoryengine.BinaryUnpacker import * from armoryengine.Timer import Timer, TimeThisFunction From 0afbcc53702bf901824cf5b709e2e4d50ef7023e Mon Sep 17 00:00:00 2001 From: goatpig Date: Thu, 27 Jan 2022 20:19:15 +0100 Subject: [PATCH 03/47] Fix missing imports, minor bugs --- ArmoryQt.py | 16 +++++++++------- qtdialogs/DlgDispTxInfo.py | 5 +++-- qtdialogs/DlgNewAddress.py | 3 ++- ui/TreeViewGUI.py | 6 +++--- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ArmoryQt.py b/ArmoryQt.py index 99503d30e..aa27c45ef 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -35,27 +35,28 @@ import time import traceback import glob +from struct import pack from PySide2.QtGui import QDesktopServices, QPixmap, QPalette, \ QCursor, QIcon from PySide2.QtCore import Qt, QTranslator, Signal, QByteArray, \ - QSize, QModelIndex + QSize, QModelIndex, QBuffer, QIODevice from PySide2.QtWidgets import QMainWindow, QSystemTrayIcon, \ QMenu, QAction, QGridLayout, QTabWidget, QScrollArea, \ QComboBox, QSizePolicy, QActionGroup, QMessageBox, QLabel, \ QTableView, QPushButton, QFrame, QWidget, QProgressBar, QVBoxLayout, \ - QLineEdit, QFileDialog + QLineEdit, QFileDialog, QApplication, QWhatsThis from armorycolors import Colors, htmlColor, QAPP from armoryengine.ArmoryUtils import HMAC256, GUI_LANGUAGE, \ - OS_MACOSX, OS_WINDOWS, AllowAsync, USE_TESTNET, USE_REGTEST, \ + OS_LINUX, OS_MACOSX, OS_WINDOWS, AllowAsync, USE_TESTNET, USE_REGTEST, \ CLI_OPTIONS, SettingsFile, getVersionString, BTCARMORY_VERSION, \ LOGINFO, LOGWARN, LOGDEBUG, LOGEXCEPT, LOGERROR, INTERNET_STATUS, \ enum, GetExecDir, RightNow, CLI_ARGS, ARMORY_HOME_DIR, DEFAULT, \ ARMORY_DB_DIR, coin2str, DEFAULT_DATE_FORMAT, \ unixTimeToFormatStr, binary_to_hex, BTC_HOME_DIR, secondsToHumanTime, \ LEVELDB_BLKDATA, LOGRAWDATA, LOGPPRINT, hex_to_binary, \ - getRandomHexits_NotSecure, coin2strNZS + getRandomHexits_NotSecure, coin2strNZS, bytesToHumanSize, hash256 from armoryengine.Block import PyBlock from armoryengine.Decorators import RemoveRepeatingExtensions @@ -1185,7 +1186,7 @@ def getExtraEntropyForKeyGen(self): LOGEXCEPT('Error getting extra entropy from filesystem') - source3 = '' + source3 = bytes() try: pixDesk = QPixmap.grabWindow(QApplication.desktop().winId()) pixRaw = QByteArray() @@ -1463,7 +1464,7 @@ def warnNewUSTXFormat(self): 'transactions. This format is not compatible with ' 'versions of Armory before 0.92. ' '

' - 'To continue, the other system will need to be upgraded to ' + 'To continue, the other system will need to be upgraded ' 'to version 0.92 or later. If you cannot upgrade the other ' 'system, you will need to reinstall an older version of Armory ' 'on this system.'), dnaaMsg=self.tr('Do not show this warning again')) @@ -3328,7 +3329,7 @@ def clickReceiveCoins(self): elif len(self.walletMap)==1: loading = LoadingDisp(self, self) loading.show() - wltID = self.walletMap.keys()[0] + wltID = next(iter(self.walletMap)) else: wltSelect = self.walletsView.selectedIndexes() if len(wltSelect)>0: @@ -5188,6 +5189,7 @@ def doTheSystemTrayThing(self): dispLines.append(str(self.tr('From: %s' % wltName ))) dispLines.append(str(self.tr('To: %s' % recipStr))) except Exception as e: + #TODO: fix this LOGERROR('tx broadcast systray display failed with error: %s' % e) if title: diff --git a/qtdialogs/DlgDispTxInfo.py b/qtdialogs/DlgDispTxInfo.py index 55172ca3c..0e7962edc 100644 --- a/qtdialogs/DlgDispTxInfo.py +++ b/qtdialogs/DlgDispTxInfo.py @@ -17,7 +17,8 @@ addrStr_to_hash160, script_to_scrAddr, BIGENDIAN, binary_to_hex, \ hex_to_binary, coin2str, coin2strNZS, LOGEXCEPT, LOGERROR, \ CPP_TXIN_SCRIPT_NAMES, CPP_TXOUT_SCRIPT_NAMES, int_to_hex, \ - script_to_scrAddr, scrAddr_to_addrStr, unixTimeToFormatStr + script_to_scrAddr, scrAddr_to_addrStr, unixTimeToFormatStr, \ + UINT32_MAX from armoryengine.BDM import TheBDM, BDM_BLOCKCHAIN_READY from armoryengine.Transaction import UnsignedTransaction, \ @@ -945,7 +946,7 @@ def extractTxInfo(pytx, rcvTime=None): txTime = 'Unknown' elif rcvTime == -1: txTime = '[[Not broadcast yet]]' - elif isinstance(rcvTime, basestring): + elif isinstance(rcvTime, str): txTime = rcvTime else: txTime = unixTimeToFormatStr(rcvTime) diff --git a/qtdialogs/DlgNewAddress.py b/qtdialogs/DlgNewAddress.py index 03fc54fd0..3db80d3c7 100644 --- a/qtdialogs/DlgNewAddress.py +++ b/qtdialogs/DlgNewAddress.py @@ -9,7 +9,8 @@ from PySide2.QtCore import Qt from PySide2.QtGui import QPalette, QFont from PySide2.QtWidgets import QFrame, QGridLayout, QPushButton, QLabel, \ - QLineEdit, QHBoxLayout, QDialogButtonBox, QTextEdit, QMessageBox + QLineEdit, QHBoxLayout, QDialogButtonBox, QTextEdit, QMessageBox, \ + QApplication from armorycolors import Colors from qtdialogs.qtdefines import ArmoryDialog, determineWalletType, \ diff --git a/ui/TreeViewGUI.py b/ui/TreeViewGUI.py index 3a42098a8..c3d7eac18 100644 --- a/ui/TreeViewGUI.py +++ b/ui/TreeViewGUI.py @@ -614,11 +614,11 @@ def setup(self): #arrange by script type for utxo in utxoList: binAddr = utxo.getRecipientScrAddr() - addrStr = TheBridge.getAddrStrForScrAddr(binAddr) - - addrObj = self.wallet.getAddrByHash160(binAddr[1:]) + addrObj = self.wallet.getAddrByHash(binAddr) if addrObj == None: continue + + addrStr = addrObj.getAddressString() addrType = addrObj.addrType addrDict = self.treeData['UTXO'][addrType] From a1868dcf676eb2a6e017ea5294eff16a3c0d5e7f Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 28 Jan 2022 10:18:24 +0100 Subject: [PATCH 04/47] guard around SBD setup from empty strings --- cppForSwig/SecureBinaryData.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cppForSwig/SecureBinaryData.h b/cppForSwig/SecureBinaryData.h index a47d949c8..87fb06a0e 100644 --- a/cppForSwig/SecureBinaryData.h +++ b/cppForSwig/SecureBinaryData.h @@ -144,6 +144,9 @@ class SecureBinaryData : public BinaryData static SecureBinaryData fromString(const std::string& str) { + if (str.empty()) + return {}; + SecureBinaryData sbd(str.size()); memcpy(sbd.getPtr(), str.c_str(), str.size()); return sbd; From 28bc489872d8542232f6874d20ceeeff4c9e0b67 Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 28 Jan 2022 10:22:46 +0100 Subject: [PATCH 05/47] remove obsolete leveldbwin folder --- cppForSwig/leveldbwin/README.txt | 5 - .../build/msvc10/snappy/snappy.vcxproj | 164 --- .../msvc10/snappy/snappy.vcxproj.filters | 41 - .../leveldbwin/snappy_src/snappy-internal.h | 150 --- .../snappy_src/snappy-sinksource.cc | 72 -- .../leveldbwin/snappy_src/snappy-sinksource.h | 136 --- .../snappy_src/snappy-stubs-internal.cc | 42 - .../snappy_src/snappy-stubs-internal.h | 477 -------- .../snappy_src/snappy-stubs-public.h | 85 -- cppForSwig/leveldbwin/snappy_src/snappy.cc | 1030 ----------------- cppForSwig/leveldbwin/snappy_src/snappy.h | 155 --- 11 files changed, 2357 deletions(-) delete mode 100644 cppForSwig/leveldbwin/README.txt delete mode 100644 cppForSwig/leveldbwin/build/msvc10/snappy/snappy.vcxproj delete mode 100644 cppForSwig/leveldbwin/build/msvc10/snappy/snappy.vcxproj.filters delete mode 100644 cppForSwig/leveldbwin/snappy_src/snappy-internal.h delete mode 100644 cppForSwig/leveldbwin/snappy_src/snappy-sinksource.cc delete mode 100644 cppForSwig/leveldbwin/snappy_src/snappy-sinksource.h delete mode 100644 cppForSwig/leveldbwin/snappy_src/snappy-stubs-internal.cc delete mode 100644 cppForSwig/leveldbwin/snappy_src/snappy-stubs-internal.h delete mode 100644 cppForSwig/leveldbwin/snappy_src/snappy-stubs-public.h delete mode 100644 cppForSwig/leveldbwin/snappy_src/snappy.cc delete mode 100644 cppForSwig/leveldbwin/snappy_src/snappy.h diff --git a/cppForSwig/leveldbwin/README.txt b/cppForSwig/leveldbwin/README.txt deleted file mode 100644 index 5bec1c5d1..000000000 --- a/cppForSwig/leveldbwin/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -This file directory is maintained in the repo solely for snappy compression support. - -The code itself doesn't require snappy, but the original port needed it in order to -compile on Windows in MSVS. As soon as I figure out how to cut the umbilical cord, -I will remove the snappy project from the MSVS solution and remove this directory. \ No newline at end of file diff --git a/cppForSwig/leveldbwin/build/msvc10/snappy/snappy.vcxproj b/cppForSwig/leveldbwin/build/msvc10/snappy/snappy.vcxproj deleted file mode 100644 index e7a2de166..000000000 --- a/cppForSwig/leveldbwin/build/msvc10/snappy/snappy.vcxproj +++ /dev/null @@ -1,164 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - - - - - - - - - - - - - {72639F93-D2E6-4220-AA46-E24C502E470C} - Win32Proj - snappy - - - - StaticLibrary - true - Unicode - v110 - - - StaticLibrary - true - Unicode - v110 - - - StaticLibrary - false - true - Unicode - v110 - - - StaticLibrary - false - true - Unicode - v110 - - - - - - - - - - - - - - - - - - - $(ProjectName)_d - $(SolutionDir)libs\IntermediateBuildFiles\$(Configuration).$(ProjectName)\ - $(SolutionDir)libs\$(Platform)\ - - - $(ProjectName)_d - - - $(SolutionDir)libs\IntermediateBuildFiles\$(Configuration).$(ProjectName)\ - $(SolutionDir)libs\$(Platform)\ - - - $(SolutionDir)libs\$(Platform) - - - - - - Level3 - Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) - ..\..\..\snappy_src;..\..\..\win32_impl_src\; - - - Windows - true - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) - ..\..\..\snappy_src;..\..\..\win32_impl_src\; - - - Windows - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - ..\..\..\snappy_src;..\..\..\win32_impl_src\; - MultiThreaded - - - Windows - true - true - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - ..\..\..\snappy_src;..\..\..\win32_impl_src\; - - - Windows - true - true - true - - - - - - \ No newline at end of file diff --git a/cppForSwig/leveldbwin/build/msvc10/snappy/snappy.vcxproj.filters b/cppForSwig/leveldbwin/build/msvc10/snappy/snappy.vcxproj.filters deleted file mode 100644 index fa3e05130..000000000 --- a/cppForSwig/leveldbwin/build/msvc10/snappy/snappy.vcxproj.filters +++ /dev/null @@ -1,41 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - \ No newline at end of file diff --git a/cppForSwig/leveldbwin/snappy_src/snappy-internal.h b/cppForSwig/leveldbwin/snappy_src/snappy-internal.h deleted file mode 100644 index a32eda59f..000000000 --- a/cppForSwig/leveldbwin/snappy_src/snappy-internal.h +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2008 Google Inc. All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Internals shared between the Snappy implementation and its unittest. - -#ifndef UTIL_SNAPPY_SNAPPY_INTERNAL_H_ -#define UTIL_SNAPPY_SNAPPY_INTERNAL_H_ - -#include "snappy-stubs-internal.h" - -namespace snappy { -namespace internal { - -class WorkingMemory { - public: - WorkingMemory() : large_table_(NULL) { } - ~WorkingMemory() { delete[] large_table_; } - - // Allocates and clears a hash table using memory in "*this", - // stores the number of buckets in "*table_size" and returns a pointer to - // the base of the hash table. - uint16* GetHashTable(size_t input_size, int* table_size); - - private: - uint16 small_table_[1<<10]; // 2KB - uint16* large_table_; // Allocated only when needed - - DISALLOW_COPY_AND_ASSIGN(WorkingMemory); -}; - -// Flat array compression that does not emit the "uncompressed length" -// prefix. Compresses "input" string to the "*op" buffer. -// -// REQUIRES: "input_length <= kBlockSize" -// REQUIRES: "op" points to an array of memory that is at least -// "MaxCompressedLength(input_length)" in size. -// REQUIRES: All elements in "table[0..table_size-1]" are initialized to zero. -// REQUIRES: "table_size" is a power of two -// -// Returns an "end" pointer into "op" buffer. -// "end - op" is the compressed size of "input". -char* CompressFragment(const char* input, - size_t input_length, - char* op, - uint16* table, - const int table_size); - -// Return the largest n such that -// -// s1[0,n-1] == s2[0,n-1] -// and n <= (s2_limit - s2). -// -// Does not read *s2_limit or beyond. -// Does not read *(s1 + (s2_limit - s2)) or beyond. -// Requires that s2_limit >= s2. -// -// Separate implementation for x86_64, for speed. Uses the fact that -// x86_64 is little endian. -#if defined(ARCH_K8) -static inline int FindMatchLength(const char* s1, - const char* s2, - const char* s2_limit) { - DCHECK_GE(s2_limit, s2); - int matched = 0; - - // Find out how long the match is. We loop over the data 64 bits at a - // time until we find a 64-bit block that doesn't match; then we find - // the first non-matching bit and use that to calculate the total - // length of the match. - while (PREDICT_TRUE(s2 <= s2_limit - 8)) { - if (PREDICT_FALSE(UNALIGNED_LOAD64(s2) == UNALIGNED_LOAD64(s1 + matched))) { - s2 += 8; - matched += 8; - } else { - // On current (mid-2008) Opteron models there is a 3% more - // efficient code sequence to find the first non-matching byte. - // However, what follows is ~10% better on Intel Core 2 and newer, - // and we expect AMD's bsf instruction to improve. - uint64 x = UNALIGNED_LOAD64(s2) ^ UNALIGNED_LOAD64(s1 + matched); - int matching_bits = Bits::FindLSBSetNonZero64(x); - matched += matching_bits >> 3; - return matched; - } - } - while (PREDICT_TRUE(s2 < s2_limit)) { - if (PREDICT_TRUE(s1[matched] == *s2)) { - ++s2; - ++matched; - } else { - return matched; - } - } - return matched; -} -#else -static inline int FindMatchLength(const char* s1, - const char* s2, - const char* s2_limit) { - // Implementation based on the x86-64 version, above. - DCHECK_GE(s2_limit, s2); - int matched = 0; - - while (s2 <= s2_limit - 4 && - UNALIGNED_LOAD32(s2) == UNALIGNED_LOAD32(s1 + matched)) { - s2 += 4; - matched += 4; - } - if (LittleEndian::IsLittleEndian() && s2 <= s2_limit - 4) { - uint32 x = UNALIGNED_LOAD32(s2) ^ UNALIGNED_LOAD32(s1 + matched); - int matching_bits = Bits::FindLSBSetNonZero(x); - matched += matching_bits >> 3; - } else { - while ((s2 < s2_limit) && (s1[matched] == *s2)) { - ++s2; - ++matched; - } - } - return matched; -} -#endif - -} // end namespace internal -} // end namespace snappy - -#endif // UTIL_SNAPPY_SNAPPY_INTERNAL_H_ diff --git a/cppForSwig/leveldbwin/snappy_src/snappy-sinksource.cc b/cppForSwig/leveldbwin/snappy_src/snappy-sinksource.cc deleted file mode 100644 index 1017895f9..000000000 --- a/cppForSwig/leveldbwin/snappy_src/snappy-sinksource.cc +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include - -#include "snappy-sinksource.h" - -namespace snappy { - -Source::~Source() { } - -Sink::~Sink() { } - -char* Sink::GetAppendBuffer(size_t length, char* scratch) { - return scratch; -} - -ByteArraySource::~ByteArraySource() { } - -size_t ByteArraySource::Available() const { return left_; } - -const char* ByteArraySource::Peek(size_t* len) { - *len = left_; - return ptr_; -} - -void ByteArraySource::Skip(size_t n) { - left_ -= n; - ptr_ += n; -} - -UncheckedByteArraySink::~UncheckedByteArraySink() { } - -void UncheckedByteArraySink::Append(const char* data, size_t n) { - // Do no copying if the caller filled in the result of GetAppendBuffer() - if (data != dest_) { - memcpy(dest_, data, n); - } - dest_ += n; -} - -char* UncheckedByteArraySink::GetAppendBuffer(size_t len, char* scratch) { - return dest_; -} - - -} diff --git a/cppForSwig/leveldbwin/snappy_src/snappy-sinksource.h b/cppForSwig/leveldbwin/snappy_src/snappy-sinksource.h deleted file mode 100644 index 430baeabb..000000000 --- a/cppForSwig/leveldbwin/snappy_src/snappy-sinksource.h +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#ifndef UTIL_SNAPPY_SNAPPY_SINKSOURCE_H_ -#define UTIL_SNAPPY_SNAPPY_SINKSOURCE_H_ - -#include - - -namespace snappy { - -// A Sink is an interface that consumes a sequence of bytes. -class Sink { - public: - Sink() { } - virtual ~Sink(); - - // Append "bytes[0,n-1]" to this. - virtual void Append(const char* bytes, size_t n) = 0; - - // Returns a writable buffer of the specified length for appending. - // May return a pointer to the caller-owned scratch buffer which - // must have at least the indicated length. The returned buffer is - // only valid until the next operation on this Sink. - // - // After writing at most "length" bytes, call Append() with the - // pointer returned from this function and the number of bytes - // written. Many Append() implementations will avoid copying - // bytes if this function returned an internal buffer. - // - // If a non-scratch buffer is returned, the caller may only pass a - // prefix of it to Append(). That is, it is not correct to pass an - // interior pointer of the returned array to Append(). - // - // The default implementation always returns the scratch buffer. - virtual char* GetAppendBuffer(size_t length, char* scratch); - - private: - // No copying - Sink(const Sink&); - void operator=(const Sink&); -}; - -// A Source is an interface that yields a sequence of bytes -class Source { - public: - Source() { } - virtual ~Source(); - - // Return the number of bytes left to read from the source - virtual size_t Available() const = 0; - - // Peek at the next flat region of the source. Does not reposition - // the source. The returned region is empty iff Available()==0. - // - // Returns a pointer to the beginning of the region and store its - // length in *len. - // - // The returned region is valid until the next call to Skip() or - // until this object is destroyed, whichever occurs first. - // - // The returned region may be larger than Available() (for example - // if this ByteSource is a view on a substring of a larger source). - // The caller is responsible for ensuring that it only reads the - // Available() bytes. - virtual const char* Peek(size_t* len) = 0; - - // Skip the next n bytes. Invalidates any buffer returned by - // a previous call to Peek(). - // REQUIRES: Available() >= n - virtual void Skip(size_t n) = 0; - - private: - // No copying - Source(const Source&); - void operator=(const Source&); -}; - -// A Source implementation that yields the contents of a flat array -class ByteArraySource : public Source { - public: - ByteArraySource(const char* p, size_t n) : ptr_(p), left_(n) { } - virtual ~ByteArraySource(); - virtual size_t Available() const; - virtual const char* Peek(size_t* len); - virtual void Skip(size_t n); - private: - const char* ptr_; - size_t left_; -}; - -// A Sink implementation that writes to a flat array without any bound checks. -class UncheckedByteArraySink : public Sink { - public: - explicit UncheckedByteArraySink(char* dest) : dest_(dest) { } - virtual ~UncheckedByteArraySink(); - virtual void Append(const char* data, size_t n); - virtual char* GetAppendBuffer(size_t len, char* scratch); - - // Return the current output pointer so that a caller can see how - // many bytes were produced. - // Note: this is not a Sink method. - char* CurrentDestination() const { return dest_; } - private: - char* dest_; -}; - - -} - -#endif // UTIL_SNAPPY_SNAPPY_SINKSOURCE_H_ diff --git a/cppForSwig/leveldbwin/snappy_src/snappy-stubs-internal.cc b/cppForSwig/leveldbwin/snappy_src/snappy-stubs-internal.cc deleted file mode 100644 index 6ed334371..000000000 --- a/cppForSwig/leveldbwin/snappy_src/snappy-stubs-internal.cc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include -#include - -#include "snappy-stubs-internal.h" - -namespace snappy { - -void Varint::Append32(string* s, uint32 value) { - char buf[Varint::kMax32]; - const char* p = Varint::Encode32(buf, value); - s->append(buf, p - buf); -} - -} // namespace snappy diff --git a/cppForSwig/leveldbwin/snappy_src/snappy-stubs-internal.h b/cppForSwig/leveldbwin/snappy_src/snappy-stubs-internal.h deleted file mode 100644 index cc51e26ab..000000000 --- a/cppForSwig/leveldbwin/snappy_src/snappy-stubs-internal.h +++ /dev/null @@ -1,477 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Various stubs for the open-source version of Snappy. - -#ifndef UTIL_SNAPPY_OPENSOURCE_SNAPPY_STUBS_INTERNAL_H_ -#define UTIL_SNAPPY_OPENSOURCE_SNAPPY_STUBS_INTERNAL_H_ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include - -#include -#include -#include - -#ifdef HAVE_SYS_MMAN_H -#include -#endif - -#include "snappy-stubs-public.h" - -#if defined(__x86_64__) - -// Enable 64-bit optimized versions of some routines. -#define ARCH_K8 1 - -#endif - -// Needed by OS X, among others. -#ifndef MAP_ANONYMOUS -#define MAP_ANONYMOUS MAP_ANON -#endif - -// Pull in std::min, std::ostream, and the likes. This is safe because this -// header file is never used from any public header files. -using namespace std; - -// The size of an array, if known at compile-time. -// Will give unexpected results if used on a pointer. -// We undefine it first, since some compilers already have a definition. -#ifdef ARRAYSIZE -#undef ARRAYSIZE -#endif -#define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a))) - -// Static prediction hints. -#ifdef HAVE_BUILTIN_EXPECT -#define PREDICT_FALSE(x) (__builtin_expect(x, 0)) -#define PREDICT_TRUE(x) (__builtin_expect(!!(x), 1)) -#else -#define PREDICT_FALSE(x) x -#define PREDICT_TRUE(x) x -#endif - -// This is only used for recomputing the tag byte table used during -// decompression; for simplicity we just remove it from the open-source -// version (anyone who wants to regenerate it can just do the call -// themselves within main()). -#define DEFINE_bool(flag_name, default_value, description) \ - bool FLAGS_ ## flag_name = default_value; -#define DECLARE_bool(flag_name) \ - extern bool FLAGS_ ## flag_name; -#define REGISTER_MODULE_INITIALIZER(name, code) - -namespace snappy { - -static const uint32 kuint32max = static_cast(0xFFFFFFFF); -static const int64 kint64max = static_cast(0x7FFFFFFFFFFFFFFFLL); - -// Logging. - -#define LOG(level) LogMessage() -#define VLOG(level) true ? (void)0 : \ - snappy::LogMessageVoidify() & snappy::LogMessage() - -class LogMessage { - public: - LogMessage() { } - ~LogMessage() { - cerr << endl; - } - - LogMessage& operator<<(const std::string& msg) { - cerr << msg; - return *this; - } - LogMessage& operator<<(int x) { - cerr << x; - return *this; - } -}; - -// Asserts, both versions activated in debug mode only, -// and ones that are always active. - -#define CRASH_UNLESS(condition) \ - PREDICT_TRUE(condition) ? (void)0 : \ - snappy::LogMessageVoidify() & snappy::LogMessageCrash() - -class LogMessageCrash : public LogMessage { - public: - LogMessageCrash() { } - ~LogMessageCrash() { - cerr << endl; - abort(); - } -}; - -// This class is used to explicitly ignore values in the conditional -// logging macros. This avoids compiler warnings like "value computed -// is not used" and "statement has no effect". - -class LogMessageVoidify { - public: - LogMessageVoidify() { } - // This has to be an operator with a precedence lower than << but - // higher than ?: - void operator&(const LogMessage&) { } -}; - -#define CHECK(cond) CRASH_UNLESS(cond) -#define CHECK_LE(a, b) CRASH_UNLESS((a) <= (b)) -#define CHECK_GE(a, b) CRASH_UNLESS((a) >= (b)) -#define CHECK_EQ(a, b) CRASH_UNLESS((a) == (b)) -#define CHECK_NE(a, b) CRASH_UNLESS((a) != (b)) -#define CHECK_LT(a, b) CRASH_UNLESS((a) < (b)) -#define CHECK_GT(a, b) CRASH_UNLESS((a) > (b)) - -#ifdef NDEBUG - -#define DCHECK(cond) CRASH_UNLESS(true) -#define DCHECK_LE(a, b) CRASH_UNLESS(true) -#define DCHECK_GE(a, b) CRASH_UNLESS(true) -#define DCHECK_EQ(a, b) CRASH_UNLESS(true) -#define DCHECK_NE(a, b) CRASH_UNLESS(true) -#define DCHECK_LT(a, b) CRASH_UNLESS(true) -#define DCHECK_GT(a, b) CRASH_UNLESS(true) - -#else - -#define DCHECK(cond) CHECK(cond) -#define DCHECK_LE(a, b) CHECK_LE(a, b) -#define DCHECK_GE(a, b) CHECK_GE(a, b) -#define DCHECK_EQ(a, b) CHECK_EQ(a, b) -#define DCHECK_NE(a, b) CHECK_NE(a, b) -#define DCHECK_LT(a, b) CHECK_LT(a, b) -#define DCHECK_GT(a, b) CHECK_GT(a, b) - -#endif - -// Potentially unaligned loads and stores. - -#if defined(__i386__) || defined(__x86_64__) || defined(__powerpc__) - -#define UNALIGNED_LOAD16(_p) (*reinterpret_cast(_p)) -#define UNALIGNED_LOAD32(_p) (*reinterpret_cast(_p)) -#define UNALIGNED_LOAD64(_p) (*reinterpret_cast(_p)) - -#define UNALIGNED_STORE16(_p, _val) (*reinterpret_cast(_p) = (_val)) -#define UNALIGNED_STORE32(_p, _val) (*reinterpret_cast(_p) = (_val)) -#define UNALIGNED_STORE64(_p, _val) (*reinterpret_cast(_p) = (_val)) - -#else - -// These functions are provided for architectures that don't support -// unaligned loads and stores. - -inline uint16 UNALIGNED_LOAD16(const void *p) { - uint16 t; - memcpy(&t, p, sizeof t); - return t; -} - -inline uint32 UNALIGNED_LOAD32(const void *p) { - uint32 t; - memcpy(&t, p, sizeof t); - return t; -} - -inline uint64 UNALIGNED_LOAD64(const void *p) { - uint64 t; - memcpy(&t, p, sizeof t); - return t; -} - -inline void UNALIGNED_STORE16(void *p, uint16 v) { - memcpy(p, &v, sizeof v); -} - -inline void UNALIGNED_STORE32(void *p, uint32 v) { - memcpy(p, &v, sizeof v); -} - -inline void UNALIGNED_STORE64(void *p, uint64 v) { - memcpy(p, &v, sizeof v); -} - -#endif - -// The following guarantees declaration of the byte swap functions. -#ifdef WORDS_BIGENDIAN - -#ifdef _MSC_VER -#include -#define bswap_16(x) _byteswap_ushort(x) -#define bswap_32(x) _byteswap_ulong(x) -#define bswap_64(x) _byteswap_uint64(x) - -#elif defined(__APPLE__) -// Mac OS X / Darwin features -#include -#define bswap_16(x) OSSwapInt16(x) -#define bswap_32(x) OSSwapInt32(x) -#define bswap_64(x) OSSwapInt64(x) - -#else -#include -#endif - -#endif // WORDS_BIGENDIAN - -// Convert to little-endian storage, opposite of network format. -// Convert x from host to little endian: x = LittleEndian.FromHost(x); -// convert x from little endian to host: x = LittleEndian.ToHost(x); -// -// Store values into unaligned memory converting to little endian order: -// LittleEndian.Store16(p, x); -// -// Load unaligned values stored in little endian converting to host order: -// x = LittleEndian.Load16(p); -class LittleEndian { - public: - // Conversion functions. -#ifdef WORDS_BIGENDIAN - - static uint16 FromHost16(uint16 x) { return bswap_16(x); } - static uint16 ToHost16(uint16 x) { return bswap_16(x); } - - static uint32 FromHost32(uint32 x) { return bswap_32(x); } - static uint32 ToHost32(uint32 x) { return bswap_32(x); } - - static bool IsLittleEndian() { return false; } - -#else // !defined(WORDS_BIGENDIAN) - - static uint16 FromHost16(uint16 x) { return x; } - static uint16 ToHost16(uint16 x) { return x; } - - static uint32 FromHost32(uint32 x) { return x; } - static uint32 ToHost32(uint32 x) { return x; } - - static bool IsLittleEndian() { return true; } - -#endif // !defined(WORDS_BIGENDIAN) - - // Functions to do unaligned loads and stores in little-endian order. - static uint16 Load16(const void *p) { - return ToHost16(UNALIGNED_LOAD16(p)); - } - - static void Store16(void *p, uint16 v) { - UNALIGNED_STORE16(p, FromHost16(v)); - } - - static uint32 Load32(const void *p) { - return ToHost32(UNALIGNED_LOAD32(p)); - } - - static void Store32(void *p, uint32 v) { - UNALIGNED_STORE32(p, FromHost32(v)); - } -}; - -// Some bit-manipulation functions. -class Bits { - public: - // Return floor(log2(n)) for positive integer n. Returns -1 iff n == 0. - static int Log2Floor(uint32 n); - - // Return the first set least / most significant bit, 0-indexed. Returns an - // undefined value if n == 0. FindLSBSetNonZero() is similar to ffs() except - // that it's 0-indexed. - static int FindLSBSetNonZero(uint32 n); - static int FindLSBSetNonZero64(uint64 n); - - private: - DISALLOW_COPY_AND_ASSIGN(Bits); -}; - -#ifdef HAVE_BUILTIN_CTZ - -inline int Bits::Log2Floor(uint32 n) { - return n == 0 ? -1 : 31 ^ __builtin_clz(n); -} - -inline int Bits::FindLSBSetNonZero(uint32 n) { - return __builtin_ctz(n); -} - -inline int Bits::FindLSBSetNonZero64(uint64 n) { - return __builtin_ctzll(n); -} - -#else // Portable versions. - -inline int Bits::Log2Floor(uint32 n) { - if (n == 0) - return -1; - int log = 0; - uint32 value = n; - for (int i = 4; i >= 0; --i) { - int shift = (1 << i); - uint32 x = value >> shift; - if (x != 0) { - value = x; - log += shift; - } - } - assert(value == 1); - return log; -} - -inline int Bits::FindLSBSetNonZero(uint32 n) { - int rc = 31; - for (int i = 4, shift = 1 << 4; i >= 0; --i) { - const uint32 x = n << shift; - if (x != 0) { - n = x; - rc -= shift; - } - shift >>= 1; - } - return rc; -} - -// FindLSBSetNonZero64() is defined in terms of FindLSBSetNonZero(). -inline int Bits::FindLSBSetNonZero64(uint64 n) { - const uint32 bottombits = static_cast(n); - if (bottombits == 0) { - // Bottom bits are zero, so scan in top bits - return 32 + FindLSBSetNonZero(static_cast(n >> 32)); - } else { - return FindLSBSetNonZero(bottombits); - } -} - -#endif // End portable versions. - -// Variable-length integer encoding. -class Varint { - public: - // Maximum lengths of varint encoding of uint32. - static const int kMax32 = 5; - - // Attempts to parse a varint32 from a prefix of the bytes in [ptr,limit-1]. - // Never reads a character at or beyond limit. If a valid/terminated varint32 - // was found in the range, stores it in *OUTPUT and returns a pointer just - // past the last byte of the varint32. Else returns NULL. On success, - // "result <= limit". - static const char* Parse32WithLimit(const char* ptr, const char* limit, - uint32* OUTPUT); - - // REQUIRES "ptr" points to a buffer of length sufficient to hold "v". - // EFFECTS Encodes "v" into "ptr" and returns a pointer to the - // byte just past the last encoded byte. - static char* Encode32(char* ptr, uint32 v); - - // EFFECTS Appends the varint representation of "value" to "*s". - static void Append32(string* s, uint32 value); -}; - -inline const char* Varint::Parse32WithLimit(const char* p, - const char* l, - uint32* OUTPUT) { - const unsigned char* ptr = reinterpret_cast(p); - const unsigned char* limit = reinterpret_cast(l); - uint32 b, result; - if (ptr >= limit) return NULL; - b = *(ptr++); result = b & 127; if (b < 128) goto done; - if (ptr >= limit) return NULL; - b = *(ptr++); result |= (b & 127) << 7; if (b < 128) goto done; - if (ptr >= limit) return NULL; - b = *(ptr++); result |= (b & 127) << 14; if (b < 128) goto done; - if (ptr >= limit) return NULL; - b = *(ptr++); result |= (b & 127) << 21; if (b < 128) goto done; - if (ptr >= limit) return NULL; - b = *(ptr++); result |= (b & 127) << 28; if (b < 16) goto done; - return NULL; // Value is too long to be a varint32 - done: - *OUTPUT = result; - return reinterpret_cast(ptr); -} - -inline char* Varint::Encode32(char* sptr, uint32 v) { - // Operate on characters as unsigneds - unsigned char* ptr = reinterpret_cast(sptr); - static const int B = 128; - if (v < (1<<7)) { - *(ptr++) = v; - } else if (v < (1<<14)) { - *(ptr++) = v | B; - *(ptr++) = v>>7; - } else if (v < (1<<21)) { - *(ptr++) = v | B; - *(ptr++) = (v>>7) | B; - *(ptr++) = v>>14; - } else if (v < (1<<28)) { - *(ptr++) = v | B; - *(ptr++) = (v>>7) | B; - *(ptr++) = (v>>14) | B; - *(ptr++) = v>>21; - } else { - *(ptr++) = v | B; - *(ptr++) = (v>>7) | B; - *(ptr++) = (v>>14) | B; - *(ptr++) = (v>>21) | B; - *(ptr++) = v>>28; - } - return reinterpret_cast(ptr); -} - -// If you know the internal layout of the std::string in use, you can -// replace this function with one that resizes the string without -// filling the new space with zeros (if applicable) -- -// it will be non-portable but faster. -inline void STLStringResizeUninitialized(string* s, size_t new_size) { - s->resize(new_size); -} - -// Return a mutable char* pointing to a string's internal buffer, -// which may not be null-terminated. Writing through this pointer will -// modify the string. -// -// string_as_array(&str)[i] is valid for 0 <= i < str.size() until the -// next call to a string method that invalidates iterators. -// -// As of 2006-04, there is no standard-blessed way of getting a -// mutable reference to a string's internal buffer. However, issue 530 -// (http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-defects.html#530) -// proposes this as the method. It will officially be part of the standard -// for C++0x. This should already work on all current implementations. -inline char* string_as_array(string* str) { - return str->empty() ? NULL : &*str->begin(); -} - -} // namespace snappy - -#endif // UTIL_SNAPPY_OPENSOURCE_SNAPPY_STUBS_INTERNAL_H_ diff --git a/cppForSwig/leveldbwin/snappy_src/snappy-stubs-public.h b/cppForSwig/leveldbwin/snappy_src/snappy-stubs-public.h deleted file mode 100644 index b089baca9..000000000 --- a/cppForSwig/leveldbwin/snappy_src/snappy-stubs-public.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2011 Google Inc. All Rights Reserved. -// Author: sesse@google.com (Steinar H. Gunderson) -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Various type stubs for the open-source version of Snappy. -// -// This file cannot include config.h, as it is included from snappy.h, -// which is a public header. Instead, snappy-stubs-public.h is generated by -// from snappy-stubs-public.h.in at configure time. - -#ifndef UTIL_SNAPPY_OPENSOURCE_SNAPPY_STUBS_PUBLIC_H_ -#define UTIL_SNAPPY_OPENSOURCE_SNAPPY_STUBS_PUBLIC_H_ - -#if 1 -#include -#endif - -#if 1 -#include -#endif - -#define SNAPPY_MAJOR 1 -#define SNAPPY_MINOR 0 -#define SNAPPY_PATCHLEVEL 3 -#define SNAPPY_VERSION \ - ((SNAPPY_MAJOR << 16) | (SNAPPY_MINOR << 8) | SNAPPY_PATCHLEVEL) - -#include - -namespace snappy { - -#if 1 -typedef int8_t int8; -typedef uint8_t uint8; -typedef int16_t int16; -typedef uint16_t uint16; -typedef int32_t int32; -typedef uint32_t uint32; -typedef int64_t int64; -typedef uint64_t uint64; -#else -typedef signed char int8; -typedef unsigned char uint8; -typedef short int16; -typedef unsigned short uint16; -typedef int int32; -typedef unsigned int uint32; -typedef long long int64; -typedef unsigned long long uint64; -#endif - -typedef std::string string; - -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) - -} // namespace snappy - -#endif // UTIL_SNAPPY_OPENSOURCE_SNAPPY_STUBS_PUBLIC_H_ diff --git a/cppForSwig/leveldbwin/snappy_src/snappy.cc b/cppForSwig/leveldbwin/snappy_src/snappy.cc deleted file mode 100644 index c79edb58a..000000000 --- a/cppForSwig/leveldbwin/snappy_src/snappy.cc +++ /dev/null @@ -1,1030 +0,0 @@ -// Copyright 2005 Google Inc. All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "snappy.h" -#include "snappy-internal.h" -#include "snappy-sinksource.h" - -#include - -#include -#include -#include - - -namespace snappy { - -// Any hash function will produce a valid compressed bitstream, but a good -// hash function reduces the number of collisions and thus yields better -// compression for compressible input, and more speed for incompressible -// input. Of course, it doesn't hurt if the hash function is reasonably fast -// either, as it gets called a lot. -static inline uint32 HashBytes(uint32 bytes, int shift) { - uint32 kMul = 0x1e35a7bd; - return (bytes * kMul) >> shift; -} -static inline uint32 Hash(const char* p, int shift) { - return HashBytes(UNALIGNED_LOAD32(p), shift); -} - -size_t MaxCompressedLength(size_t source_len) { - // Compressed data can be defined as: - // compressed := item* literal* - // item := literal* copy - // - // The trailing literal sequence has a space blowup of at most 62/60 - // since a literal of length 60 needs one tag byte + one extra byte - // for length information. - // - // Item blowup is trickier to measure. Suppose the "copy" op copies - // 4 bytes of data. Because of a special check in the encoding code, - // we produce a 4-byte copy only if the offset is < 65536. Therefore - // the copy op takes 3 bytes to encode, and this type of item leads - // to at most the 62/60 blowup for representing literals. - // - // Suppose the "copy" op copies 5 bytes of data. If the offset is big - // enough, it will take 5 bytes to encode the copy op. Therefore the - // worst case here is a one-byte literal followed by a five-byte copy. - // I.e., 6 bytes of input turn into 7 bytes of "compressed" data. - // - // This last factor dominates the blowup, so the final estimate is: - return 32 + source_len + source_len/6; -} - -enum { - LITERAL = 0, - COPY_1_BYTE_OFFSET = 1, // 3 bit length + 3 bits of offset in opcode - COPY_2_BYTE_OFFSET = 2, - COPY_4_BYTE_OFFSET = 3 -}; - -// Copy "len" bytes from "src" to "op", one byte at a time. Used for -// handling COPY operations where the input and output regions may -// overlap. For example, suppose: -// src == "ab" -// op == src + 2 -// len == 20 -// After IncrementalCopy(src, op, len), the result will have -// eleven copies of "ab" -// ababababababababababab -// Note that this does not match the semantics of either memcpy() -// or memmove(). -static inline void IncrementalCopy(const char* src, char* op, int len) { - DCHECK_GT(len, 0); - do { - *op++ = *src++; - } while (--len > 0); -} - -// Equivalent to IncrementalCopy except that it can write up to ten extra -// bytes after the end of the copy, and that it is faster. -// -// The main part of this loop is a simple copy of eight bytes at a time until -// we've copied (at least) the requested amount of bytes. However, if op and -// src are less than eight bytes apart (indicating a repeating pattern of -// length < 8), we first need to expand the pattern in order to get the correct -// results. For instance, if the buffer looks like this, with the eight-byte -// and patterns marked as intervals: -// -// abxxxxxxxxxxxx -// [------] src -// [------] op -// -// a single eight-byte copy from to will repeat the pattern once, -// after which we can move two bytes without moving : -// -// ababxxxxxxxxxx -// [------] src -// [------] op -// -// and repeat the exercise until the two no longer overlap. -// -// This allows us to do very well in the special case of one single byte -// repeated many times, without taking a big hit for more general cases. -// -// The worst case of extra writing past the end of the match occurs when -// op - src == 1 and len == 1; the last copy will read from byte positions -// [0..7] and write to [4..11], whereas it was only supposed to write to -// position 1. Thus, ten excess bytes. - -namespace { - -const int kMaxIncrementCopyOverflow = 10; - -} // namespace - -static inline void IncrementalCopyFastPath(const char* src, char* op, int len) { - while (op - src < 8) { - UNALIGNED_STORE64(op, UNALIGNED_LOAD64(src)); - len -= op - src; - op += op - src; - } - while (len > 0) { - UNALIGNED_STORE64(op, UNALIGNED_LOAD64(src)); - src += 8; - op += 8; - len -= 8; - } -} - -static inline char* EmitLiteral(char* op, - const char* literal, - int len, - bool allow_fast_path) { - int n = len - 1; // Zero-length literals are disallowed - if (n < 60) { - // Fits in tag byte - *op++ = LITERAL | (n << 2); - - // The vast majority of copies are below 16 bytes, for which a - // call to memcpy is overkill. This fast path can sometimes - // copy up to 15 bytes too much, but that is okay in the - // main loop, since we have a bit to go on for both sides: - // - // - The input will always have kInputMarginBytes = 15 extra - // available bytes, as long as we're in the main loop, and - // if not, allow_fast_path = false. - // - The output will always have 32 spare bytes (see - // MaxCompressedLength). - if (allow_fast_path && len <= 16) { - UNALIGNED_STORE64(op, UNALIGNED_LOAD64(literal)); - UNALIGNED_STORE64(op + 8, UNALIGNED_LOAD64(literal + 8)); - return op + len; - } - } else { - // Encode in upcoming bytes - char* base = op; - int count = 0; - op++; - while (n > 0) { - *op++ = n & 0xff; - n >>= 8; - count++; - } - assert(count >= 1); - assert(count <= 4); - *base = LITERAL | ((59+count) << 2); - } - memcpy(op, literal, len); - return op + len; -} - -static inline char* EmitCopyLessThan64(char* op, int offset, int len) { - DCHECK_LE(len, 64); - DCHECK_GE(len, 4); - DCHECK_LT(offset, 65536); - - if ((len < 12) && (offset < 2048)) { - int len_minus_4 = len - 4; - assert(len_minus_4 < 8); // Must fit in 3 bits - *op++ = COPY_1_BYTE_OFFSET | ((len_minus_4) << 2) | ((offset >> 8) << 5); - *op++ = offset & 0xff; - } else { - *op++ = COPY_2_BYTE_OFFSET | ((len-1) << 2); - LittleEndian::Store16(op, offset); - op += 2; - } - return op; -} - -static inline char* EmitCopy(char* op, int offset, int len) { - // Emit 64 byte copies but make sure to keep at least four bytes reserved - while (len >= 68) { - op = EmitCopyLessThan64(op, offset, 64); - len -= 64; - } - - // Emit an extra 60 byte copy if have too much data to fit in one copy - if (len > 64) { - op = EmitCopyLessThan64(op, offset, 60); - len -= 60; - } - - // Emit remainder - op = EmitCopyLessThan64(op, offset, len); - return op; -} - - -bool GetUncompressedLength(const char* start, size_t n, size_t* result) { - uint32 v = 0; - const char* limit = start + n; - if (Varint::Parse32WithLimit(start, limit, &v) != NULL) { - *result = v; - return true; - } else { - return false; - } -} - -namespace internal { -uint16* WorkingMemory::GetHashTable(size_t input_size, int* table_size) { - // Use smaller hash table when input.size() is smaller, since we - // fill the table, incurring O(hash table size) overhead for - // compression, and if the input is short, we won't need that - // many hash table entries anyway. - assert(kMaxHashTableSize >= 256); - int htsize = 256; - while (htsize < kMaxHashTableSize && htsize < input_size) { - htsize <<= 1; - } - CHECK_EQ(0, htsize & (htsize - 1)) << ": must be power of two"; - CHECK_LE(htsize, kMaxHashTableSize) << ": hash table too large"; - - uint16* table; - if (htsize <= ARRAYSIZE(small_table_)) { - table = small_table_; - } else { - if (large_table_ == NULL) { - large_table_ = new uint16[kMaxHashTableSize]; - } - table = large_table_; - } - - *table_size = htsize; - memset(table, 0, htsize * sizeof(*table)); - return table; -} -} // end namespace internal - -// For 0 <= offset <= 4, GetUint32AtOffset(UNALIGNED_LOAD64(p), offset) will -// equal UNALIGNED_LOAD32(p + offset). Motivation: On x86-64 hardware we have -// empirically found that overlapping loads such as -// UNALIGNED_LOAD32(p) ... UNALIGNED_LOAD32(p+1) ... UNALIGNED_LOAD32(p+2) -// are slower than UNALIGNED_LOAD64(p) followed by shifts and casts to uint32. -static inline uint32 GetUint32AtOffset(uint64 v, int offset) { - DCHECK(0 <= offset && offset <= 4) << offset; - return v >> (LittleEndian::IsLittleEndian() ? 8 * offset : 32 - 8 * offset); -} - -// Flat array compression that does not emit the "uncompressed length" -// prefix. Compresses "input" string to the "*op" buffer. -// -// REQUIRES: "input" is at most "kBlockSize" bytes long. -// REQUIRES: "op" points to an array of memory that is at least -// "MaxCompressedLength(input.size())" in size. -// REQUIRES: All elements in "table[0..table_size-1]" are initialized to zero. -// REQUIRES: "table_size" is a power of two -// -// Returns an "end" pointer into "op" buffer. -// "end - op" is the compressed size of "input". -namespace internal { -char* CompressFragment(const char* input, - size_t input_size, - char* op, - uint16* table, - const int table_size) { - // "ip" is the input pointer, and "op" is the output pointer. - const char* ip = input; - CHECK_LE(input_size, kBlockSize); - CHECK_EQ(table_size & (table_size - 1), 0) << ": table must be power of two"; - const int shift = 32 - Bits::Log2Floor(table_size); - DCHECK_EQ(kuint32max >> shift, table_size - 1); - const char* ip_end = input + input_size; - const char* base_ip = ip; - // Bytes in [next_emit, ip) will be emitted as literal bytes. Or - // [next_emit, ip_end) after the main loop. - const char* next_emit = ip; - - const int kInputMarginBytes = 15; - if (PREDICT_TRUE(input_size >= kInputMarginBytes)) { - const char* ip_limit = input + input_size - kInputMarginBytes; - - for (uint32 next_hash = Hash(++ip, shift); ; ) { - DCHECK_LT(next_emit, ip); - // The body of this loop calls EmitLiteral once and then EmitCopy one or - // more times. (The exception is that when we're close to exhausting - // the input we goto emit_remainder.) - // - // In the first iteration of this loop we're just starting, so - // there's nothing to copy, so calling EmitLiteral once is - // necessary. And we only start a new iteration when the - // current iteration has determined that a call to EmitLiteral will - // precede the next call to EmitCopy (if any). - // - // Step 1: Scan forward in the input looking for a 4-byte-long match. - // If we get close to exhausting the input then goto emit_remainder. - // - // Heuristic match skipping: If 32 bytes are scanned with no matches - // found, start looking only at every other byte. If 32 more bytes are - // scanned, look at every third byte, etc.. When a match is found, - // immediately go back to looking at every byte. This is a small loss - // (~5% performance, ~0.1% density) for compressible data due to more - // bookkeeping, but for non-compressible data (such as JPEG) it's a huge - // win since the compressor quickly "realizes" the data is incompressible - // and doesn't bother looking for matches everywhere. - // - // The "skip" variable keeps track of how many bytes there are since the - // last match; dividing it by 32 (ie. right-shifting by five) gives the - // number of bytes to move ahead for each iteration. - uint32 skip = 32; - - const char* next_ip = ip; - const char* candidate; - do { - ip = next_ip; - uint32 hash = next_hash; - DCHECK_EQ(hash, Hash(ip, shift)); - uint32 bytes_between_hash_lookups = skip++ >> 5; - next_ip = ip + bytes_between_hash_lookups; - if (PREDICT_FALSE(next_ip > ip_limit)) { - goto emit_remainder; - } - next_hash = Hash(next_ip, shift); - candidate = base_ip + table[hash]; - DCHECK_GE(candidate, base_ip); - DCHECK_LT(candidate, ip); - - table[hash] = ip - base_ip; - } while (PREDICT_TRUE(UNALIGNED_LOAD32(ip) != - UNALIGNED_LOAD32(candidate))); - - // Step 2: A 4-byte match has been found. We'll later see if more - // than 4 bytes match. But, prior to the match, input - // bytes [next_emit, ip) are unmatched. Emit them as "literal bytes." - DCHECK_LE(next_emit + 16, ip_end); - op = EmitLiteral(op, next_emit, ip - next_emit, true); - - // Step 3: Call EmitCopy, and then see if another EmitCopy could - // be our next move. Repeat until we find no match for the - // input immediately after what was consumed by the last EmitCopy call. - // - // If we exit this loop normally then we need to call EmitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can exit - // this loop via goto if we get close to exhausting the input. - uint64 input_bytes = 0; - uint32 candidate_bytes = 0; - - do { - // We have a 4-byte match at ip, and no need to emit any - // "literal bytes" prior to ip. - const char* base = ip; - int matched = 4 + FindMatchLength(candidate + 4, ip + 4, ip_end); - ip += matched; - int offset = base - candidate; - DCHECK_EQ(0, memcmp(base, candidate, matched)); - op = EmitCopy(op, offset, matched); - // We could immediately start working at ip now, but to improve - // compression we first update table[Hash(ip - 1, ...)]. - const char* insert_tail = ip - 1; - next_emit = ip; - if (PREDICT_FALSE(ip >= ip_limit)) { - goto emit_remainder; - } - input_bytes = UNALIGNED_LOAD64(insert_tail); - uint32 prev_hash = HashBytes(GetUint32AtOffset(input_bytes, 0), shift); - table[prev_hash] = ip - base_ip - 1; - uint32 cur_hash = HashBytes(GetUint32AtOffset(input_bytes, 1), shift); - candidate = base_ip + table[cur_hash]; - candidate_bytes = UNALIGNED_LOAD32(candidate); - table[cur_hash] = ip - base_ip; - } while (GetUint32AtOffset(input_bytes, 1) == candidate_bytes); - - next_hash = HashBytes(GetUint32AtOffset(input_bytes, 2), shift); - ++ip; - } - } - - emit_remainder: - // Emit the remaining bytes as a literal - if (next_emit < ip_end) { - op = EmitLiteral(op, next_emit, ip_end - next_emit, false); - } - - return op; -} -} // end namespace internal - -// Signature of output types needed by decompression code. -// The decompression code is templatized on a type that obeys this -// signature so that we do not pay virtual function call overhead in -// the middle of a tight decompression loop. -// -// class DecompressionWriter { -// public: -// // Called before decompression -// void SetExpectedLength(size_t length); -// -// // Called after decompression -// bool CheckLength() const; -// -// // Called repeatedly during decompression -// bool Append(const char* ip, uint32 length, bool allow_fast_path); -// bool AppendFromSelf(uint32 offset, uint32 length); -// }; -// -// "allow_fast_path" is a parameter that says if there is at least 16 -// readable bytes in "ip". It is currently only used by SnappyArrayWriter. - -// ----------------------------------------------------------------------- -// Lookup table for decompression code. Generated by ComputeTable() below. -// ----------------------------------------------------------------------- - -// Mapping from i in range [0,4] to a mask to extract the bottom 8*i bits -static const uint32 wordmask[] = { - 0u, 0xffu, 0xffffu, 0xffffffu, 0xffffffffu -}; - -// Data stored per entry in lookup table: -// Range Bits-used Description -// ------------------------------------ -// 1..64 0..7 Literal/copy length encoded in opcode byte -// 0..7 8..10 Copy offset encoded in opcode byte / 256 -// 0..4 11..13 Extra bytes after opcode -// -// We use eight bits for the length even though 7 would have sufficed -// because of efficiency reasons: -// (1) Extracting a byte is faster than a bit-field -// (2) It properly aligns copy offset so we do not need a <<8 -static const uint16 char_table[256] = { - 0x0001, 0x0804, 0x1001, 0x2001, 0x0002, 0x0805, 0x1002, 0x2002, - 0x0003, 0x0806, 0x1003, 0x2003, 0x0004, 0x0807, 0x1004, 0x2004, - 0x0005, 0x0808, 0x1005, 0x2005, 0x0006, 0x0809, 0x1006, 0x2006, - 0x0007, 0x080a, 0x1007, 0x2007, 0x0008, 0x080b, 0x1008, 0x2008, - 0x0009, 0x0904, 0x1009, 0x2009, 0x000a, 0x0905, 0x100a, 0x200a, - 0x000b, 0x0906, 0x100b, 0x200b, 0x000c, 0x0907, 0x100c, 0x200c, - 0x000d, 0x0908, 0x100d, 0x200d, 0x000e, 0x0909, 0x100e, 0x200e, - 0x000f, 0x090a, 0x100f, 0x200f, 0x0010, 0x090b, 0x1010, 0x2010, - 0x0011, 0x0a04, 0x1011, 0x2011, 0x0012, 0x0a05, 0x1012, 0x2012, - 0x0013, 0x0a06, 0x1013, 0x2013, 0x0014, 0x0a07, 0x1014, 0x2014, - 0x0015, 0x0a08, 0x1015, 0x2015, 0x0016, 0x0a09, 0x1016, 0x2016, - 0x0017, 0x0a0a, 0x1017, 0x2017, 0x0018, 0x0a0b, 0x1018, 0x2018, - 0x0019, 0x0b04, 0x1019, 0x2019, 0x001a, 0x0b05, 0x101a, 0x201a, - 0x001b, 0x0b06, 0x101b, 0x201b, 0x001c, 0x0b07, 0x101c, 0x201c, - 0x001d, 0x0b08, 0x101d, 0x201d, 0x001e, 0x0b09, 0x101e, 0x201e, - 0x001f, 0x0b0a, 0x101f, 0x201f, 0x0020, 0x0b0b, 0x1020, 0x2020, - 0x0021, 0x0c04, 0x1021, 0x2021, 0x0022, 0x0c05, 0x1022, 0x2022, - 0x0023, 0x0c06, 0x1023, 0x2023, 0x0024, 0x0c07, 0x1024, 0x2024, - 0x0025, 0x0c08, 0x1025, 0x2025, 0x0026, 0x0c09, 0x1026, 0x2026, - 0x0027, 0x0c0a, 0x1027, 0x2027, 0x0028, 0x0c0b, 0x1028, 0x2028, - 0x0029, 0x0d04, 0x1029, 0x2029, 0x002a, 0x0d05, 0x102a, 0x202a, - 0x002b, 0x0d06, 0x102b, 0x202b, 0x002c, 0x0d07, 0x102c, 0x202c, - 0x002d, 0x0d08, 0x102d, 0x202d, 0x002e, 0x0d09, 0x102e, 0x202e, - 0x002f, 0x0d0a, 0x102f, 0x202f, 0x0030, 0x0d0b, 0x1030, 0x2030, - 0x0031, 0x0e04, 0x1031, 0x2031, 0x0032, 0x0e05, 0x1032, 0x2032, - 0x0033, 0x0e06, 0x1033, 0x2033, 0x0034, 0x0e07, 0x1034, 0x2034, - 0x0035, 0x0e08, 0x1035, 0x2035, 0x0036, 0x0e09, 0x1036, 0x2036, - 0x0037, 0x0e0a, 0x1037, 0x2037, 0x0038, 0x0e0b, 0x1038, 0x2038, - 0x0039, 0x0f04, 0x1039, 0x2039, 0x003a, 0x0f05, 0x103a, 0x203a, - 0x003b, 0x0f06, 0x103b, 0x203b, 0x003c, 0x0f07, 0x103c, 0x203c, - 0x0801, 0x0f08, 0x103d, 0x203d, 0x1001, 0x0f09, 0x103e, 0x203e, - 0x1801, 0x0f0a, 0x103f, 0x203f, 0x2001, 0x0f0b, 0x1040, 0x2040 -}; - -// In debug mode, allow optional computation of the table at startup. -// Also, check that the decompression table is correct. -#ifndef NDEBUG -DEFINE_bool(snappy_dump_decompression_table, false, - "If true, we print the decompression table at startup."); - -static uint16 MakeEntry(unsigned int extra, - unsigned int len, - unsigned int copy_offset) { - // Check that all of the fields fit within the allocated space - DCHECK_EQ(extra, extra & 0x7); // At most 3 bits - DCHECK_EQ(copy_offset, copy_offset & 0x7); // At most 3 bits - DCHECK_EQ(len, len & 0x7f); // At most 7 bits - return len | (copy_offset << 8) | (extra << 11); -} - -static void ComputeTable() { - uint16 dst[256]; - - // Place invalid entries in all places to detect missing initialization - int assigned = 0; - for (int i = 0; i < 256; i++) { - dst[i] = 0xffff; - } - - // Small LITERAL entries. We store (len-1) in the top 6 bits. - for (unsigned int len = 1; len <= 60; len++) { - dst[LITERAL | ((len-1) << 2)] = MakeEntry(0, len, 0); - assigned++; - } - - // Large LITERAL entries. We use 60..63 in the high 6 bits to - // encode the number of bytes of length info that follow the opcode. - for (unsigned int extra_bytes = 1; extra_bytes <= 4; extra_bytes++) { - // We set the length field in the lookup table to 1 because extra - // bytes encode len-1. - dst[LITERAL | ((extra_bytes+59) << 2)] = MakeEntry(extra_bytes, 1, 0); - assigned++; - } - - // COPY_1_BYTE_OFFSET. - // - // The tag byte in the compressed data stores len-4 in 3 bits, and - // offset/256 in 5 bits. offset%256 is stored in the next byte. - // - // This format is used for length in range [4..11] and offset in - // range [0..2047] - for (unsigned int len = 4; len < 12; len++) { - for (unsigned int offset = 0; offset < 2048; offset += 256) { - dst[COPY_1_BYTE_OFFSET | ((len-4)<<2) | ((offset>>8)<<5)] = - MakeEntry(1, len, offset>>8); - assigned++; - } - } - - // COPY_2_BYTE_OFFSET. - // Tag contains len-1 in top 6 bits, and offset in next two bytes. - for (unsigned int len = 1; len <= 64; len++) { - dst[COPY_2_BYTE_OFFSET | ((len-1)<<2)] = MakeEntry(2, len, 0); - assigned++; - } - - // COPY_4_BYTE_OFFSET. - // Tag contents len-1 in top 6 bits, and offset in next four bytes. - for (unsigned int len = 1; len <= 64; len++) { - dst[COPY_4_BYTE_OFFSET | ((len-1)<<2)] = MakeEntry(4, len, 0); - assigned++; - } - - // Check that each entry was initialized exactly once. - CHECK_EQ(assigned, 256); - for (int i = 0; i < 256; i++) { - CHECK_NE(dst[i], 0xffff); - } - - if (FLAGS_snappy_dump_decompression_table) { - printf("static const uint16 char_table[256] = {\n "); - for (int i = 0; i < 256; i++) { - printf("0x%04x%s", - dst[i], - ((i == 255) ? "\n" : (((i%8) == 7) ? ",\n " : ", "))); - } - printf("};\n"); - } - - // Check that computed table matched recorded table - for (int i = 0; i < 256; i++) { - CHECK_EQ(dst[i], char_table[i]); - } -} -REGISTER_MODULE_INITIALIZER(snappy, ComputeTable()); -#endif /* !NDEBUG */ - -// Helper class for decompression -class SnappyDecompressor { - private: - Source* reader_; // Underlying source of bytes to decompress - const char* ip_; // Points to next buffered byte - const char* ip_limit_; // Points just past buffered bytes - uint32 peeked_; // Bytes peeked from reader (need to skip) - bool eof_; // Hit end of input without an error? - char scratch_[5]; // Temporary buffer for PeekFast() boundaries - - // Ensure that all of the tag metadata for the next tag is available - // in [ip_..ip_limit_-1]. Also ensures that [ip,ip+4] is readable even - // if (ip_limit_ - ip_ < 5). - // - // Returns true on success, false on error or end of input. - bool RefillTag(); - - public: - explicit SnappyDecompressor(Source* reader) - : reader_(reader), - ip_(NULL), - ip_limit_(NULL), - peeked_(0), - eof_(false) { - } - - ~SnappyDecompressor() { - // Advance past any bytes we peeked at from the reader - reader_->Skip(peeked_); - } - - // Returns true iff we have hit the end of the input without an error. - bool eof() const { - return eof_; - } - - // Read the uncompressed length stored at the start of the compressed data. - // On succcess, stores the length in *result and returns true. - // On failure, returns false. - bool ReadUncompressedLength(uint32* result) { - DCHECK(ip_ == NULL); // Must not have read anything yet - // Length is encoded in 1..5 bytes - *result = 0; - uint32 shift = 0; - while (true) { - if (shift >= 32) return false; - size_t n; - const char* ip = reader_->Peek(&n); - if (n == 0) return false; - const unsigned char c = *(reinterpret_cast(ip)); - reader_->Skip(1); - *result |= static_cast(c & 0x7f) << shift; - if (c < 128) { - break; - } - shift += 7; - } - return true; - } - - // Process the next item found in the input. - // Returns true if successful, false on error or end of input. - template - void DecompressAllTags(Writer* writer) { - const char* ip = ip_; - for ( ;; ) { - if (ip_limit_ - ip < 5) { - ip_ = ip; - if (!RefillTag()) return; - ip = ip_; - } - - const unsigned char c = *(reinterpret_cast(ip++)); - - if ((c & 0x3) == LITERAL) { - uint32 literal_length = c >> 2; - if (PREDICT_FALSE(literal_length >= 60)) { - // Long literal. - const uint32 literal_length_length = literal_length - 59; - literal_length = - LittleEndian::Load32(ip) & wordmask[literal_length_length]; - ip += literal_length_length; - } - ++literal_length; - - uint32 avail = ip_limit_ - ip; - while (avail < literal_length) { - if (!writer->Append(ip, avail, false)) return; - literal_length -= avail; - reader_->Skip(peeked_); - size_t n; - ip = reader_->Peek(&n); - avail = n; - peeked_ = avail; - if (avail == 0) return; // Premature end of input - ip_limit_ = ip + avail; - } - bool allow_fast_path = (avail >= 16); - if (!writer->Append(ip, literal_length, allow_fast_path)) { - return; - } - ip += literal_length; - } else { - const uint32 entry = char_table[c]; - const uint32 trailer = LittleEndian::Load32(ip) & wordmask[entry >> 11]; - const uint32 length = entry & 0xff; - ip += entry >> 11; - - // copy_offset/256 is encoded in bits 8..10. By just fetching - // those bits, we get copy_offset (since the bit-field starts at - // bit 8). - const uint32 copy_offset = entry & 0x700; - if (!writer->AppendFromSelf(copy_offset + trailer, length)) { - return; - } - } - } - } -}; - -bool SnappyDecompressor::RefillTag() { - const char* ip = ip_; - if (ip == ip_limit_) { - // Fetch a new fragment from the reader - reader_->Skip(peeked_); // All peeked bytes are used up - size_t n; - ip = reader_->Peek(&n); - peeked_ = n; - if (n == 0) { - eof_ = true; - return false; - } - ip_limit_ = ip + n; - } - - // Read the tag character - DCHECK_LT(ip, ip_limit_); - const unsigned char c = *(reinterpret_cast(ip)); - const uint32 entry = char_table[c]; - const uint32 needed = (entry >> 11) + 1; // +1 byte for 'c' - DCHECK_LE(needed, sizeof(scratch_)); - - // Read more bytes from reader if needed - uint32 nbuf = ip_limit_ - ip; - if (nbuf < needed) { - // Stitch together bytes from ip and reader to form the word - // contents. We store the needed bytes in "scratch_". They - // will be consumed immediately by the caller since we do not - // read more than we need. - memmove(scratch_, ip, nbuf); - reader_->Skip(peeked_); // All peeked bytes are used up - peeked_ = 0; - while (nbuf < needed) { - size_t length; - const char* src = reader_->Peek(&length); - if (length == 0) return false; - uint32 to_add = min(needed - nbuf, length); - memcpy(scratch_ + nbuf, src, to_add); - nbuf += to_add; - reader_->Skip(to_add); - } - DCHECK_EQ(nbuf, needed); - ip_ = scratch_; - ip_limit_ = scratch_ + needed; - } else if (nbuf < 5) { - // Have enough bytes, but move into scratch_ so that we do not - // read past end of input - memmove(scratch_, ip, nbuf); - reader_->Skip(peeked_); // All peeked bytes are used up - peeked_ = 0; - ip_ = scratch_; - ip_limit_ = scratch_ + nbuf; - } else { - // Pass pointer to buffer returned by reader_. - ip_ = ip; - } - return true; -} - -template -static bool InternalUncompress(Source* r, - Writer* writer, - uint32 max_len) { - // Read the uncompressed length from the front of the compressed input - SnappyDecompressor decompressor(r); - uint32 uncompressed_len = 0; - if (!decompressor.ReadUncompressedLength(&uncompressed_len)) return false; - // Protect against possible DoS attack - if (static_cast(uncompressed_len) > max_len) { - return false; - } - - writer->SetExpectedLength(uncompressed_len); - - // Process the entire input - decompressor.DecompressAllTags(writer); - return (decompressor.eof() && writer->CheckLength()); -} - -bool GetUncompressedLength(Source* source, uint32* result) { - SnappyDecompressor decompressor(source); - return decompressor.ReadUncompressedLength(result); -} - -size_t Compress(Source* reader, Sink* writer) { - size_t written = 0; - int N = reader->Available(); - char ulength[Varint::kMax32]; - char* p = Varint::Encode32(ulength, N); - writer->Append(ulength, p-ulength); - written += (p - ulength); - - internal::WorkingMemory wmem; - char* scratch = NULL; - char* scratch_output = NULL; - - while (N > 0) { - // Get next block to compress (without copying if possible) - size_t fragment_size; - const char* fragment = reader->Peek(&fragment_size); - DCHECK_NE(fragment_size, 0) << ": premature end of input"; - const int num_to_read = min(N, kBlockSize); - size_t bytes_read = fragment_size; - - int pending_advance = 0; - if (bytes_read >= num_to_read) { - // Buffer returned by reader is large enough - pending_advance = num_to_read; - fragment_size = num_to_read; - } else { - // Read into scratch buffer - if (scratch == NULL) { - // If this is the last iteration, we want to allocate N bytes - // of space, otherwise the max possible kBlockSize space. - // num_to_read contains exactly the correct value - scratch = new char[num_to_read]; - } - memcpy(scratch, fragment, bytes_read); - reader->Skip(bytes_read); - - while (bytes_read < num_to_read) { - fragment = reader->Peek(&fragment_size); - size_t n = min(fragment_size, num_to_read - bytes_read); - memcpy(scratch + bytes_read, fragment, n); - bytes_read += n; - reader->Skip(n); - } - DCHECK_EQ(bytes_read, num_to_read); - fragment = scratch; - fragment_size = num_to_read; - } - DCHECK_EQ(fragment_size, num_to_read); - - // Get encoding table for compression - int table_size; - uint16* table = wmem.GetHashTable(num_to_read, &table_size); - - // Compress input_fragment and append to dest - const int max_output = MaxCompressedLength(num_to_read); - - // Need a scratch buffer for the output, in case the byte sink doesn't - // have room for us directly. - if (scratch_output == NULL) { - scratch_output = new char[max_output]; - } else { - // Since we encode kBlockSize regions followed by a region - // which is <= kBlockSize in length, a previously allocated - // scratch_output[] region is big enough for this iteration. - } - char* dest = writer->GetAppendBuffer(max_output, scratch_output); - char* end = internal::CompressFragment(fragment, fragment_size, - dest, table, table_size); - writer->Append(dest, end - dest); - written += (end - dest); - - N -= num_to_read; - reader->Skip(pending_advance); - } - - delete[] scratch; - delete[] scratch_output; - - return written; -} - -// ----------------------------------------------------------------------- -// Flat array interfaces -// ----------------------------------------------------------------------- - -// A type that writes to a flat array. -// Note that this is not a "ByteSink", but a type that matches the -// Writer template argument to SnappyDecompressor::DecompressAllTags(). -class SnappyArrayWriter { - private: - char* base_; - char* op_; - char* op_limit_; - - public: - inline explicit SnappyArrayWriter(char* dst) - : base_(dst), - op_(dst) { - } - - inline void SetExpectedLength(size_t len) { - op_limit_ = op_ + len; - } - - inline bool CheckLength() const { - return op_ == op_limit_; - } - - inline bool Append(const char* ip, uint32 len, bool allow_fast_path) { - char* op = op_; - const int space_left = op_limit_ - op; - if (allow_fast_path && len <= 16 && space_left >= 16) { - // Fast path, used for the majority (about 90%) of dynamic invocations. - UNALIGNED_STORE64(op, UNALIGNED_LOAD64(ip)); - UNALIGNED_STORE64(op + 8, UNALIGNED_LOAD64(ip + 8)); - } else { - if (space_left < len) { - return false; - } - memcpy(op, ip, len); - } - op_ = op + len; - return true; - } - - inline bool AppendFromSelf(uint32 offset, uint32 len) { - char* op = op_; - const int space_left = op_limit_ - op; - - if (op - base_ <= offset - 1u) { // -1u catches offset==0 - return false; - } - if (len <= 16 && offset >= 8 && space_left >= 16) { - // Fast path, used for the majority (70-80%) of dynamic invocations. - UNALIGNED_STORE64(op, UNALIGNED_LOAD64(op - offset)); - UNALIGNED_STORE64(op + 8, UNALIGNED_LOAD64(op - offset + 8)); - } else { - if (space_left >= len + kMaxIncrementCopyOverflow) { - IncrementalCopyFastPath(op - offset, op, len); - } else { - if (space_left < len) { - return false; - } - IncrementalCopy(op - offset, op, len); - } - } - - op_ = op + len; - return true; - } -}; - -bool RawUncompress(const char* compressed, size_t n, char* uncompressed) { - ByteArraySource reader(compressed, n); - return RawUncompress(&reader, uncompressed); -} - -bool RawUncompress(Source* compressed, char* uncompressed) { - SnappyArrayWriter output(uncompressed); - return InternalUncompress(compressed, &output, kuint32max); -} - -bool Uncompress(const char* compressed, size_t n, string* uncompressed) { - size_t ulength; - if (!GetUncompressedLength(compressed, n, &ulength)) { - return false; - } - // Protect against possible DoS attack - if ((static_cast(ulength) + uncompressed->size()) > - uncompressed->max_size()) { - return false; - } - STLStringResizeUninitialized(uncompressed, ulength); - return RawUncompress(compressed, n, string_as_array(uncompressed)); -} - - -// A Writer that drops everything on the floor and just does validation -class SnappyDecompressionValidator { - private: - size_t expected_; - size_t produced_; - - public: - inline SnappyDecompressionValidator() : produced_(0) { } - inline void SetExpectedLength(size_t len) { - expected_ = len; - } - inline bool CheckLength() const { - return expected_ == produced_; - } - inline bool Append(const char* ip, uint32 len, bool allow_fast_path) { - produced_ += len; - return produced_ <= expected_; - } - inline bool AppendFromSelf(uint32 offset, uint32 len) { - if (produced_ <= offset - 1u) return false; // -1u catches offset==0 - produced_ += len; - return produced_ <= expected_; - } -}; - -bool IsValidCompressedBuffer(const char* compressed, size_t n) { - ByteArraySource reader(compressed, n); - SnappyDecompressionValidator writer; - return InternalUncompress(&reader, &writer, kuint32max); -} - -void RawCompress(const char* input, - size_t input_length, - char* compressed, - size_t* compressed_length) { - ByteArraySource reader(input, input_length); - UncheckedByteArraySink writer(compressed); - Compress(&reader, &writer); - - // Compute how many bytes were added - *compressed_length = (writer.CurrentDestination() - compressed); -} - -size_t Compress(const char* input, size_t input_length, string* compressed) { - // Pre-grow the buffer to the max length of the compressed output - compressed->resize(MaxCompressedLength(input_length)); - - size_t compressed_length; - RawCompress(input, input_length, string_as_array(compressed), - &compressed_length); - compressed->resize(compressed_length); - return compressed_length; -} - - -} // end namespace snappy - diff --git a/cppForSwig/leveldbwin/snappy_src/snappy.h b/cppForSwig/leveldbwin/snappy_src/snappy.h deleted file mode 100644 index 8d6ef2294..000000000 --- a/cppForSwig/leveldbwin/snappy_src/snappy.h +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2005 and onwards Google Inc. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// A light-weight compression algorithm. It is designed for speed of -// compression and decompression, rather than for the utmost in space -// savings. -// -// For getting better compression ratios when you are compressing data -// with long repeated sequences or compressing data that is similar to -// other data, while still compressing fast, you might look at first -// using BMDiff and then compressing the output of BMDiff with -// Snappy. - -#ifndef UTIL_SNAPPY_SNAPPY_H__ -#define UTIL_SNAPPY_SNAPPY_H__ - -#include -#include - -#include "snappy-stubs-public.h" - -namespace snappy { - class Source; - class Sink; - - // ------------------------------------------------------------------------ - // Generic compression/decompression routines. - // ------------------------------------------------------------------------ - - // Compress the bytes read from "*source" and append to "*sink". Return the - // number of bytes written. - size_t Compress(Source* source, Sink* sink); - - bool GetUncompressedLength(Source* source, uint32* result); - - // ------------------------------------------------------------------------ - // Higher-level string based routines (should be sufficient for most users) - // ------------------------------------------------------------------------ - - // Sets "*output" to the compressed version of "input[0,input_length-1]". - // Original contents of *output are lost. - // - // REQUIRES: "input[]" is not an alias of "*output". - size_t Compress(const char* input, size_t input_length, string* output); - - // Decompresses "compressed[0,compressed_length-1]" to "*uncompressed". - // Original contents of "*uncompressed" are lost. - // - // REQUIRES: "compressed[]" is not an alias of "*uncompressed". - // - // returns false if the message is corrupted and could not be decompressed - bool Uncompress(const char* compressed, size_t compressed_length, - string* uncompressed); - - - // ------------------------------------------------------------------------ - // Lower-level character array based routines. May be useful for - // efficiency reasons in certain circumstances. - // ------------------------------------------------------------------------ - - // REQUIRES: "compressed" must point to an area of memory that is at - // least "MaxCompressedLength(input_length)" bytes in length. - // - // Takes the data stored in "input[0..input_length]" and stores - // it in the array pointed to by "compressed". - // - // "*compressed_length" is set to the length of the compressed output. - // - // Example: - // char* output = new char[snappy::MaxCompressedLength(input_length)]; - // size_t output_length; - // RawCompress(input, input_length, output, &output_length); - // ... Process(output, output_length) ... - // delete [] output; - void RawCompress(const char* input, - size_t input_length, - char* compressed, - size_t* compressed_length); - - // Given data in "compressed[0..compressed_length-1]" generated by - // calling the Snappy::Compress routine, this routine - // stores the uncompressed data to - // uncompressed[0..GetUncompressedLength(compressed)-1] - // returns false if the message is corrupted and could not be decrypted - bool RawUncompress(const char* compressed, size_t compressed_length, - char* uncompressed); - - // Given data from the byte source 'compressed' generated by calling - // the Snappy::Compress routine, this routine stores the uncompressed - // data to - // uncompressed[0..GetUncompressedLength(compressed,compressed_length)-1] - // returns false if the message is corrupted and could not be decrypted - bool RawUncompress(Source* compressed, char* uncompressed); - - // Returns the maximal size of the compressed representation of - // input data that is "source_bytes" bytes in length; - size_t MaxCompressedLength(size_t source_bytes); - - // REQUIRES: "compressed[]" was produced by RawCompress() or Compress() - // Returns true and stores the length of the uncompressed data in - // *result normally. Returns false on parsing error. - // This operation takes O(1) time. - bool GetUncompressedLength(const char* compressed, size_t compressed_length, - size_t* result); - - // Returns true iff the contents of "compressed[]" can be uncompressed - // successfully. Does not return the uncompressed data. Takes - // time proportional to compressed_length, but is usually at least - // a factor of four faster than actual decompression. - bool IsValidCompressedBuffer(const char* compressed, - size_t compressed_length); - - // *** DO NOT CHANGE THE VALUE OF kBlockSize *** - // - // New Compression code chops up the input into blocks of at most - // the following size. This ensures that back-references in the - // output never cross kBlockSize block boundaries. This can be - // helpful in implementing blocked decompression. However the - // decompression code should not rely on this guarantee since older - // compression code may not obey it. - static const int kBlockLog = 15; - static const int kBlockSize = 1 << kBlockLog; - - static const int kMaxHashTableBits = 14; - static const int kMaxHashTableSize = 1 << kMaxHashTableBits; - -} // end namespace snappy - - -#endif // UTIL_SNAPPY_SNAPPY_H__ From ad7589c1c06c993c4d6ae15167627387d47e3284 Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 28 Jan 2022 17:13:10 +0100 Subject: [PATCH 06/47] fix max button --- cppForSwig/BridgeAPI/CppBridge.cpp | 66 ++++++++++++++++++- cppForSwig/BridgeAPI/CppBridge.h | 2 + .../BridgeAPI/ProtobufCommandParser.cpp | 12 ++++ cppForSwig/BridgeAPI/ProtobufConversions.h | 2 +- cppForSwig/CoinSelection.cpp | 2 +- cppForSwig/Signer/ScriptRecipient.h | 2 +- cppForSwig/protobuf/ClientProto.proto | 2 + ui/TxFrames.py | 31 +++++---- 8 files changed, 101 insertions(+), 18 deletions(-) diff --git a/cppForSwig/BridgeAPI/CppBridge.cpp b/cppForSwig/BridgeAPI/CppBridge.cpp index 0634da091..1030ddeae 100755 --- a/cppForSwig/BridgeAPI/CppBridge.cpp +++ b/cppForSwig/BridgeAPI/CppBridge.cpp @@ -1406,7 +1406,7 @@ bool CppBridge::cs_ProcessCustomUtxoList(const ClientCommand& msg) } try - { + { iter->second->processCustomUtxoList(utxos, flatFee, feeByte, flags); return true; } @@ -1416,6 +1416,68 @@ bool CppBridge::cs_ProcessCustomUtxoList(const ClientCommand& msg) return false; } +//////////////////////////////////////////////////////////////////////////////// +BridgeReply CppBridge::cs_getFeeForMaxVal(const ClientCommand& msg) +{ + if (msg.stringargs_size() != 1 || + msg.floatargs_size() != 1) + { + throw runtime_error("invalid command cs_getFeeForMaxVal"); + } + + auto iter = csMap_.find(msg.stringargs(0)); + if (iter == csMap_.end()) + throw runtime_error("invalid cs id"); + + auto feeByte = msg.floatargs(0); + auto flatFee = iter->second->getFeeForMaxVal(feeByte); + + auto response = make_unique(); + response->add_longs(flatFee); + return response; + +} + +//////////////////////////////////////////////////////////////////////////////// +BridgeReply CppBridge::cs_getFeeForMaxValUtxoVector(const ClientCommand& msg) +{ + if (msg.stringargs_size() != 1 || + msg.floatargs_size() != 1) + { + throw runtime_error("invalid command cs_getFeeForMaxValUtxoVector"); + } + + auto iter = csMap_.find(msg.stringargs(0)); + if (iter == csMap_.end()) + throw runtime_error("invalid cs id"); + + auto feeByte = msg.floatargs(0); + + vector serUtxos; + for (int i=0; isecond->getFeeForMaxValUtxoVector(serUtxos, feeByte); + + auto response = make_unique(); + response->add_longs(flatFee); + return response; + +} + //////////////////////////////////////////////////////////////////////////////// void CppBridge::createAddressBook(const string& id, unsigned msgId) { @@ -1636,7 +1698,7 @@ BridgeReply CppBridge::signer_getSerializedState(const string& id) const auto iter = signerMap_.find(id); if (iter == signerMap_.end()) throw runtime_error("invalid signer id"); - + auto signerState = iter->second->signer_.serializeState(); string signerStateStr; if (!signerState.SerializeToString(&signerStateStr)) diff --git a/cppForSwig/BridgeAPI/CppBridge.h b/cppForSwig/BridgeAPI/CppBridge.h index 5c9a6a60a..46737b7e0 100755 --- a/cppForSwig/BridgeAPI/CppBridge.h +++ b/cppForSwig/BridgeAPI/CppBridge.h @@ -222,6 +222,8 @@ namespace Armory BridgeReply cs_getFeeByte(const std::string&); BridgeReply cs_getSizeEstimate(const std::string&); bool cs_ProcessCustomUtxoList(const Codec_ClientProto::ClientCommand&); + BridgeReply cs_getFeeForMaxVal(const Codec_ClientProto::ClientCommand&); + BridgeReply cs_getFeeForMaxValUtxoVector(const Codec_ClientProto::ClientCommand&); //signer BridgeReply initNewSigner(void); diff --git a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp index c88f94626..96babafff 100644 --- a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp +++ b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp @@ -445,6 +445,18 @@ bool ProtobufCommandParser::processData( break; } + case Methods::cs_getFeeForMaxVal: + { + response = bridge->cs_getFeeForMaxVal(msg); + break; + } + + case Methods::cs_getFeeForMaxValUtxoVector: + { + response = bridge->cs_getFeeForMaxValUtxoVector(msg); + break; + } + case Methods::generateRandomHex: { if (msg.intargs_size() != 1) diff --git a/cppForSwig/BridgeAPI/ProtobufConversions.h b/cppForSwig/BridgeAPI/ProtobufConversions.h index e5717a28c..c139cba5f 100644 --- a/cppForSwig/BridgeAPI/ProtobufConversions.h +++ b/cppForSwig/BridgeAPI/ProtobufConversions.h @@ -16,7 +16,7 @@ #define PROTO_ASSETID_PREFIX 0xAFu //forward declarations -class UTXO; +struct UTXO; class AddressEntry; class BinaryData; diff --git a/cppForSwig/CoinSelection.cpp b/cppForSwig/CoinSelection.cpp index 6e4ade23f..4b503e8fa 100644 --- a/cppForSwig/CoinSelection.cpp +++ b/cppForSwig/CoinSelection.cpp @@ -1470,7 +1470,7 @@ void CoinSelectionInstance::checkSpendVal(uint64_t spendableBalance) const void CoinSelectionInstance::processCustomUtxoList( vector& utxos, uint64_t fee, float fee_byte, unsigned flags) { - if (utxos.size() == 0) + if (utxos.empty()) throw CoinSelectionException("empty custom utxo list!"); selectUTXOs(utxos, fee, fee_byte, flags); diff --git a/cppForSwig/Signer/ScriptRecipient.h b/cppForSwig/Signer/ScriptRecipient.h index 7b0dbe0d0..e15ea999c 100644 --- a/cppForSwig/Signer/ScriptRecipient.h +++ b/cppForSwig/Signer/ScriptRecipient.h @@ -63,7 +63,7 @@ namespace Armory //virtuals virtual const BinaryData& getSerializedScript(void) const { - if (script_.getSize() == 0) + if (script_.empty()) serialize(); return script_; diff --git a/cppForSwig/protobuf/ClientProto.proto b/cppForSwig/protobuf/ClientProto.proto index 5207d96c4..a0b3ada2c 100755 --- a/cppForSwig/protobuf/ClientProto.proto +++ b/cppForSwig/protobuf/ClientProto.proto @@ -64,6 +64,8 @@ enum Methods cs_getFeeByte = 117; cs_getSizeEstimate = 118; cs_ProcessCustomUtxoList = 119; + cs_getFeeForMaxVal = 120; + cs_getFeeForMaxValUtxoVector = 121; initNewSigner = 130; destroySigner = 131; diff --git a/ui/TxFrames.py b/ui/TxFrames.py index 611b70a4e..c0939c27e 100755 --- a/ui/TxFrames.py +++ b/ui/TxFrames.py @@ -28,7 +28,7 @@ LOGEXCEPT, LOGERROR, LOGINFO, NegativeValueError, TooMuchPrecisionError, \ str2coin, CPP_TXOUT_STDSINGLESIG, CPP_TXOUT_P2SH, \ coin2str, MIN_FEE_BYTE, getNameForAddrType, addrTypeInSet, \ - getAddressTypeForOutputType + getAddressTypeForOutputType, binary_to_hex from ui.FeeSelectUI import FeeSelectionDialog @@ -130,6 +130,20 @@ def getSizeEstimate(self): return TheBridge.cs_getSizeEstimate(self.id) + ############################################################################# + def getFeeForMaxVal(self, feePerByte): + if self.id == None: + raise Exception("uninitialized coin selection instance") + + return TheBridge.cs_getFeeForMaxVal(self.id, feePerByte) + + ############################################################################# + def getFeeForMaxValUtxoVector(self, utxoList, feePerByte): + if self.id == None: + raise Exception("uninitialized coin selection instance") + + return TheBridge.cs_getFeeForMaxValUtxoVector(self.id, utxoList, feePerByte) + ################################################################################ class SendBitcoinsFrame(ArmoryFrame): @@ -585,16 +599,7 @@ def updateCoinSelectionRecipient(self, uid): def serializeUtxoList(self, utxoList): serializedUtxoList = [] for utxo in utxoList: - bp = BinaryPacker() - bp.put(UINT64, utxo.getValue()) - bp.put(UINT32, utxo.getTxHeight()) - bp.put(UINT16, utxo.getTxIndex()) - bp.put(UINT16, utxo.getTxOutIndex()) - bp.put(VAR_STR, utxo.getTxHash()) - bp.put(VAR_STR, utxo.getScript()) - bp.put(UINT32, utxo.sequence) - - serializedUtxoList.append(bp.getBinaryString()) + serializedUtxoList.append(utxo) return serializedUtxoList @@ -1028,9 +1033,9 @@ def signTxLastStep(success, signerObj): self.sendCallback() self.main.signalExecution.executeMethod(\ - signTxLastStep, success, signerObj) + [signTxLastStep, [success, signerObj]]) - self.wlt.signUnsignedTx(ustx, finalizeSignTx) + ustx.signTx(self.wlt.uniqueIDB58, finalizeSignTx) except: LOGEXCEPT('Problem sending transaction!') From 87258e5610d9e4c835d3508e5c32d59d7016194e Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 28 Jan 2022 19:27:18 +0100 Subject: [PATCH 07/47] add missing definition in CppBridge.py --- armoryengine/CppBridge.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/armoryengine/CppBridge.py b/armoryengine/CppBridge.py index c633697f9..3750bdf1f 100755 --- a/armoryengine/CppBridge.py +++ b/armoryengine/CppBridge.py @@ -808,6 +808,40 @@ def cs_ProcessCustomUtxoList(self, csId, \ if response.ints[0] == 0: raise BridgeError("ProcessCustomUtxoList failed") + ############################################################################# + def cs_getFeeForMaxVal(self, csId, feePerByte): + packet = ClientProto_pb2.ClientCommand() + packet.method = ClientProto_pb2.cs_getFeeForMaxVal + packet.stringArgs.append(csId) + packet.floatArgs.append(feePerByte) + + fut = self.sendToBridgeProto(packet) + socketResponse = fut.getVal() + + response = ClientProto_pb2.ReplyNumbers() + response.ParseFromString(socketResponse) + + return response.longs[0] + + ############################################################################# + def cs_getFeeForMaxValUtxoVector(self, csId, utxoList, feePerByte): + packet = ClientProto_pb2.ClientCommand() + packet.method = ClientProto_pb2.cs_getFeeForMaxValUtxoVector + packet.stringArgs.append(csId) + packet.floatArgs.append(feePerByte) + + for utxo in utxoList: + bridgeUtxo = utxo.toBridgeUtxo() + packet.byteArgs.append(bridgeUtxo.SerializeToString()) + + fut = self.sendToBridgeProto(packet) + socketResponse = fut.getVal() + + response = ClientProto_pb2.ReplyNumbers() + response.ParseFromString(socketResponse) + + return response.longs[0] + ############################################################################# def generateRandomHex(self, size): packet = ClientProto_pb2.ClientCommand() @@ -1128,7 +1162,7 @@ def getNameForAddrType(self, addrType): ############################################################################# def setAddressTypeFor(self, walletId, assetId, addrType): packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getAddressStrFor + packet.method = ClientProto_pb2.setAddressTypeFor packet.stringArgs.append(walletId) packet.byteArgs.append(assetId) From 1863ed081c8f1e9e00f40b3d38abaa5bae80955e Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 28 Jan 2022 19:27:52 +0100 Subject: [PATCH 08/47] display datadir on DB startup --- cppForSwig/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cppForSwig/main.cpp b/cppForSwig/main.cpp index 883903a95..825c88a02 100644 --- a/cppForSwig/main.cpp +++ b/cppForSwig/main.cpp @@ -80,6 +80,7 @@ int main(int argc, char* argv[]) { //setup remote peers db, this will block the init process until //peers db is unlocked + LOGINFO << "datadir: " << Armory::Config::getDataDir(); auto&& passLbd = TerminalPassphrasePrompt::getLambda("peers db"); WebSocketServer::initAuthPeers(passLbd); } From 2f9e540ab67f0d8f7b679573f2d17606fe631bc7 Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 28 Jan 2022 19:34:01 +0100 Subject: [PATCH 09/47] link to system protobuf --- cppForSwig/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cppForSwig/Makefile.am b/cppForSwig/Makefile.am index 868f4183b..3a900b0b1 100644 --- a/cppForSwig/Makefile.am +++ b/cppForSwig/Makefile.am @@ -18,7 +18,7 @@ LIBLMDBPP = liblmdbpp.la LIBHKDF = hkdf/libhkdf.la LIBSECP256K1 = $(LIBBTC_LIBDIR)/src/secp256k1/libsecp256k1.la LIBWEBSOCKETS_STATIC = $(WEBSOCKETS_LIBDIR)/libwebsockets.a -LIBPROTOBUF_STATIC = $(PROTOBUF_LIBDIR)/libprotobuf.so +LIBPROTOBUF_STATIC = -lprotobuf if BUILD_OPENSSL_SUPPORT LIBCRYPTO_STATIC = $(CRYPTO_LIBDIR)/libcrypto.a LIBSSL_STATIC = $(SSL_LIBDIR)/libssl.a From eedff0987002a0a91e1de553a0a823169fac89d8 Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 28 Jan 2022 19:42:09 +0100 Subject: [PATCH 10/47] add missing import --- qtdialogs/DlgWalletDetails.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qtdialogs/DlgWalletDetails.py b/qtdialogs/DlgWalletDetails.py index 91068b889..829d53e27 100644 --- a/qtdialogs/DlgWalletDetails.py +++ b/qtdialogs/DlgWalletDetails.py @@ -25,6 +25,7 @@ from qtdialogs.DlgNewAddress import \ DlgNewAddressDisp, ShowRecvCoinsWarningIfNecessary from qtdialogs.DlgKeypoolSettings import DlgKeypoolSettings +from qtdialogs.DlgSendBitcoins import DlgSendBitcoins ################################################################################ From 9fcdbd5246a08ec0f0b3a0411d1cff9418bd6659 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 2 Feb 2022 20:03:49 +0100 Subject: [PATCH 11/47] fix DlgDispTxInfo --- qtdialogs/DlgDispTxInfo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qtdialogs/DlgDispTxInfo.py b/qtdialogs/DlgDispTxInfo.py index 0889921d9..194acb77d 100644 --- a/qtdialogs/DlgDispTxInfo.py +++ b/qtdialogs/DlgDispTxInfo.py @@ -1022,3 +1022,4 @@ def extractTxInfo(pytx, rcvTime=None): sumTxIn = None return [txHash, txOutToList, sumTxOut, txinFromList, sumTxIn, \ + txTime, txBlk, txIdx, txSize, txWeight] From f47badaca799dbeddba510f61325aac82b7e3842 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 2 Feb 2022 20:15:27 +0100 Subject: [PATCH 12/47] fix error verbose in when resolving config paths --- cppForSwig/ArmoryConfig.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cppForSwig/ArmoryConfig.cpp b/cppForSwig/ArmoryConfig.cpp index 94addc3e2..fd6c02d22 100755 --- a/cppForSwig/ArmoryConfig.cpp +++ b/cppForSwig/ArmoryConfig.cpp @@ -1052,7 +1052,7 @@ void Pathing::processArgs(const map& args, ProcessType procType) return; } - //create dbdir if was set automatically + //create dbdir if set automatically if (autoDbDir) { if (!testPath(dbDir_, 0)) @@ -1068,8 +1068,7 @@ void Pathing::processArgs(const map& args, ProcessType procType) //now for the regular test, let it throw if it fails if (!testPath(dbDir_, 6)) { - string errMsg = Armory::Config::getDataDir() + - " is not a valid db path"; + string errMsg = dbDir_ + " is not a valid db path"; throw DbErrorMsg(errMsg); } @@ -1082,8 +1081,7 @@ void Pathing::processArgs(const map& args, ProcessType procType) { if (!testPath(blkFilePath_, 2)) { - string errMsg = Armory::Config::getDataDir() + - " is not a valid blockchain data path"; + string errMsg = blkFilePath_ + " is not a valid blockchain data path"; throw DbErrorMsg(errMsg); } } From 7b4f1536a4825677f51e03c068183af273f3af78 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 2 Feb 2022 20:25:15 +0100 Subject: [PATCH 13/47] fix licensing on refactor dlg files --- qtdialogs/DlgAddressBook.py | 18 +++++++++------ qtdialogs/DlgBrowserWarn.py | 18 +++++++++------ qtdialogs/DlgChangePassphrase.py | 18 +++++++++------ qtdialogs/DlgConfirmSend.py | 18 +++++++++------ qtdialogs/DlgCorruptWallet.py | 18 +++++++++------ qtdialogs/DlgDispTxInfo.py | 18 +++++++++------ qtdialogs/DlgEULA.py | 18 +++++++++------ qtdialogs/DlgExportTxHistory.py | 18 +++++++++------ qtdialogs/DlgHelpAbout.py | 18 +++++++++------ qtdialogs/DlgInflatedQR.py | 18 +++++++++------ qtdialogs/DlgIntroMessage.py | 18 +++++++++------ qtdialogs/DlgKeypoolSettings.py | 18 +++++++++------ qtdialogs/DlgMigrateWallet.py | 18 +++++++++------ qtdialogs/DlgNewAddress.py | 18 +++++++++------ qtdialogs/DlgOfflineTx.py | 18 +++++++++------ qtdialogs/DlgPasswd3.py | 18 +++++++++------ qtdialogs/DlgProgress.py | 18 +++++++++------ qtdialogs/DlgQRCodeDisplay.py | 18 +++++++++------ qtdialogs/DlgReplaceWallet.py | 18 +++++++++------ qtdialogs/DlgRequestPayment.py | 18 +++++++++------ qtdialogs/DlgRestoreFragged.py | 27 +++++++++++++--------- qtdialogs/DlgRestoreSingle.py | 27 ++++++++++++++-------- qtdialogs/DlgRestoreWOData.py | 32 ++++++++++++++++---------- qtdialogs/DlgSendBitcoins.py | 18 +++++++++------ qtdialogs/DlgSetComment.py | 18 +++++++++------ qtdialogs/DlgSettings.py | 19 ++++++++------- qtdialogs/DlgShowKeyList.py | 18 +++++++++------ qtdialogs/DlgUniversalRestoreSelect.py | 19 ++++++++------- qtdialogs/DlgUnlockWallet.py | 18 +++++++++------ qtdialogs/DlgUriCopyAndPaste.py | 18 +++++++++------ qtdialogs/DlgWalletDetails.py | 22 ++++++++++-------- qtdialogs/DlgWalletSelect.py | 18 +++++++++------ qtdialogs/DlgWltRecoverWallet.py | 25 ++++++++++++-------- qtdialogs/MsgBoxCustom.py | 18 +++++++++------ qtdialogs/MsgBoxWithDNAA.py | 4 +--- 35 files changed, 401 insertions(+), 260 deletions(-) diff --git a/qtdialogs/DlgAddressBook.py b/qtdialogs/DlgAddressBook.py index 5c5425a4d..1db6c518c 100644 --- a/qtdialogs/DlgAddressBook.py +++ b/qtdialogs/DlgAddressBook.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt, QSize, QByteArray from PySide2.QtGui import QIcon, QPixmap diff --git a/qtdialogs/DlgBrowserWarn.py b/qtdialogs/DlgBrowserWarn.py index 0b424be2e..97aa40615 100644 --- a/qtdialogs/DlgBrowserWarn.py +++ b/qtdialogs/DlgBrowserWarn.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from qtdialogs.ArmoryDialog import ArmoryDialog diff --git a/qtdialogs/DlgChangePassphrase.py b/qtdialogs/DlgChangePassphrase.py index 5c7b5a801..5da7e933f 100644 --- a/qtdialogs/DlgChangePassphrase.py +++ b/qtdialogs/DlgChangePassphrase.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtWidgets import QCheckBox, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QMessageBox, QPushButton from PySide2.QtGui import QIcon diff --git a/qtdialogs/DlgConfirmSend.py b/qtdialogs/DlgConfirmSend.py index 9b3a8edf8..9de75c0a7 100644 --- a/qtdialogs/DlgConfirmSend.py +++ b/qtdialogs/DlgConfirmSend.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt from PySide2.QtGui import QPixmap, QFont diff --git a/qtdialogs/DlgCorruptWallet.py b/qtdialogs/DlgCorruptWallet.py index e36bc1785..2919322d0 100644 --- a/qtdialogs/DlgCorruptWallet.py +++ b/qtdialogs/DlgCorruptWallet.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## import threading diff --git a/qtdialogs/DlgDispTxInfo.py b/qtdialogs/DlgDispTxInfo.py index 194acb77d..3d55361c4 100644 --- a/qtdialogs/DlgDispTxInfo.py +++ b/qtdialogs/DlgDispTxInfo.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt from PySide2.QtGui import QFont diff --git a/qtdialogs/DlgEULA.py b/qtdialogs/DlgEULA.py index 4521b603e..31d5c6d4a 100644 --- a/qtdialogs/DlgEULA.py +++ b/qtdialogs/DlgEULA.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import QSize from PySide2.QtGui import QIcon diff --git a/qtdialogs/DlgExportTxHistory.py b/qtdialogs/DlgExportTxHistory.py index f9b76bf31..b75c7fce7 100644 --- a/qtdialogs/DlgExportTxHistory.py +++ b/qtdialogs/DlgExportTxHistory.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import SIGNAL from PySide2.QtWidgets import QComboBox, QLineEdit, QPushButton, QGridLayout, \ diff --git a/qtdialogs/DlgHelpAbout.py b/qtdialogs/DlgHelpAbout.py index 5348a12ee..37582d2fa 100644 --- a/qtdialogs/DlgHelpAbout.py +++ b/qtdialogs/DlgHelpAbout.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt from PySide2.QtGui import QPixmap diff --git a/qtdialogs/DlgInflatedQR.py b/qtdialogs/DlgInflatedQR.py index 3835be8ed..9248ba437 100644 --- a/qtdialogs/DlgInflatedQR.py +++ b/qtdialogs/DlgInflatedQR.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtWidgets import QVBoxLayout diff --git a/qtdialogs/DlgIntroMessage.py b/qtdialogs/DlgIntroMessage.py index dca893fc3..4011715e7 100644 --- a/qtdialogs/DlgIntroMessage.py +++ b/qtdialogs/DlgIntroMessage.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt, QSize from PySide2.QtGui import QIcon, QPixmap diff --git a/qtdialogs/DlgKeypoolSettings.py b/qtdialogs/DlgKeypoolSettings.py index 1500ad622..860463fbe 100644 --- a/qtdialogs/DlgKeypoolSettings.py +++ b/qtdialogs/DlgKeypoolSettings.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtWidgets import QLineEdit, QPushButton, QDialogButtonBox, \ QVBoxLayout, QMessageBox diff --git a/qtdialogs/DlgMigrateWallet.py b/qtdialogs/DlgMigrateWallet.py index 7c31279af..276141757 100644 --- a/qtdialogs/DlgMigrateWallet.py +++ b/qtdialogs/DlgMigrateWallet.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import QObject from PySide2.QtGui import QIcon diff --git a/qtdialogs/DlgNewAddress.py b/qtdialogs/DlgNewAddress.py index baeb8d14e..7b796b7d4 100644 --- a/qtdialogs/DlgNewAddress.py +++ b/qtdialogs/DlgNewAddress.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt from PySide2.QtGui import QPalette, QFont diff --git a/qtdialogs/DlgOfflineTx.py b/qtdialogs/DlgOfflineTx.py index 0c731f2d0..9766d8cc1 100644 --- a/qtdialogs/DlgOfflineTx.py +++ b/qtdialogs/DlgOfflineTx.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt from PySide2.QtGui import QIcon diff --git a/qtdialogs/DlgPasswd3.py b/qtdialogs/DlgPasswd3.py index 756bec17f..f34409d43 100644 --- a/qtdialogs/DlgPasswd3.py +++ b/qtdialogs/DlgPasswd3.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtWidgets import QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QPushButton from PySide2.QtGui import QPixmap diff --git a/qtdialogs/DlgProgress.py b/qtdialogs/DlgProgress.py index 8fe80758f..c06a9f2c5 100644 --- a/qtdialogs/DlgProgress.py +++ b/qtdialogs/DlgProgress.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## import threading import time diff --git a/qtdialogs/DlgQRCodeDisplay.py b/qtdialogs/DlgQRCodeDisplay.py index ac32849d5..a945fa18c 100644 --- a/qtdialogs/DlgQRCodeDisplay.py +++ b/qtdialogs/DlgQRCodeDisplay.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from qtdialogs.ArmoryDialog import ArmoryDialog diff --git a/qtdialogs/DlgReplaceWallet.py b/qtdialogs/DlgReplaceWallet.py index b3cc38843..defbee121 100644 --- a/qtdialogs/DlgReplaceWallet.py +++ b/qtdialogs/DlgReplaceWallet.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## import os diff --git a/qtdialogs/DlgRequestPayment.py b/qtdialogs/DlgRequestPayment.py index c3f3ae2a0..9735808a5 100644 --- a/qtdialogs/DlgRequestPayment.py +++ b/qtdialogs/DlgRequestPayment.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from qtdialogs.ArmoryDialog import ArmoryDialog diff --git a/qtdialogs/DlgRestoreFragged.py b/qtdialogs/DlgRestoreFragged.py index c858f9d0c..1e647eef8 100644 --- a/qtdialogs/DlgRestoreFragged.py +++ b/qtdialogs/DlgRestoreFragged.py @@ -1,18 +1,23 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ - -from PySide2.QtWidgets import QCheckBox, QFrame, QGridLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QScrollArea, QTabWidget, QVBoxLayout +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## + +from PySide2.QtWidgets import QCheckBox, QFrame, QGridLayout, QLabel, \ + QLineEdit, QMessageBox, QPushButton, QScrollArea, QTabWidget, QVBoxLayout from PySide2.QtGui import QPixmap from PySide2.QtCore import QSize, Qt, SIGNAL from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, STYLE_SUNKEN, makeHorizFrame, makeVertFrame, relaxedSizeNChar -#from CppBlockUtils import SecureBinaryData +from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, \ + STYLE_SUNKEN, makeHorizFrame, makeVertFrame, relaxedSizeNChar ################################################################################ class DlgRestoreFragged(ArmoryDialog): diff --git a/qtdialogs/DlgRestoreSingle.py b/qtdialogs/DlgRestoreSingle.py index 2a9c1d6dc..7e9f04c81 100644 --- a/qtdialogs/DlgRestoreSingle.py +++ b/qtdialogs/DlgRestoreSingle.py @@ -1,12 +1,18 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ - -from PySide2.QtWidgets import QButtonGroup, QCheckBox, QDialogButtonBox, QFrame, QGridLayout, QLabel, QLayout, QLineEdit, QMessageBox, QPushButton, QRadioButton, QTabWidget, QVBoxLayout +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## + +from PySide2.QtWidgets import QButtonGroup, QCheckBox, QDialogButtonBox, \ + QFrame, QGridLayout, QLabel, QLayout, QLineEdit, QMessageBox, \ + QPushButton, QRadioButton, QTabWidget, QVBoxLayout from PySide2.QtCore import SIGNAL from armoryengine import ClientProto_pb2 @@ -17,7 +23,8 @@ from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.DlgChangePassphrase import DlgChangePassphrase from qtdialogs.DlgReplaceWallet import DlgReplaceWallet -from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, makeHorizFrame, makeVertFrame +from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, \ + makeHorizFrame, makeVertFrame from qtdialogs.qtdialogs import MaskedInputLineEdit, verifyRecoveryTestID from ui.WalletFrames import AdvancedOptionsFrame diff --git a/qtdialogs/DlgRestoreWOData.py b/qtdialogs/DlgRestoreWOData.py index f313ac4ed..813c71ce9 100644 --- a/qtdialogs/DlgRestoreWOData.py +++ b/qtdialogs/DlgRestoreWOData.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## # Class that will create the watch-only wallet data (root public key & chain # code) restoration window. @@ -13,17 +17,21 @@ import os import sys -from PySide2.QtWidgets import QDialogButtonBox, QFrame, QGridLayout, QLabel, QLayout, QMessageBox, QPushButton, QVBoxLayout +from PySide2.QtWidgets import QDialogButtonBox, QFrame, QGridLayout, \ + QLabel, QLayout, QMessageBox, QPushButton, QVBoxLayout from PySide2.QtCore import SIGNAL -from armoryengine.ArmoryUtils import LOGERROR, binary_to_base58, binary_to_int, easyType16_to_binary, int_to_binary, readSixteenEasyBytes, verifyChecksum -from armoryengine.PyBtcWallet import PYROOTPKCCSIGNMASK, PYROOTPKCCVERMASK, PyBtcWallet +from armoryengine.ArmoryUtils import LOGERROR, binary_to_base58, \ + binary_to_int, easyType16_to_binary, int_to_binary, \ + readSixteenEasyBytes, verifyChecksum +from armoryengine.PyBtcWallet import PYROOTPKCCSIGNMASK, \ + PYROOTPKCCVERMASK, PyBtcWallet from armoryengine.PyBtcAddress import PyBtcAddress -#from CppBlockUtils import CryptoECDSA, SecureBinaryData from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.DlgProgress import DlgProgress -from qtdialogs.qtdefines import GETFONT, HLINE, QRichLabel, STRETCH, STYLE_RAISED, makeHorizFrame +from qtdialogs.qtdefines import GETFONT, HLINE, QRichLabel, STRETCH, \ + STYLE_RAISED, makeHorizFrame from qtdialogs.qtdialogs import MaskedInputLineEdit, verifyRecoveryTestID class DlgRestoreWOData(ArmoryDialog): diff --git a/qtdialogs/DlgSendBitcoins.py b/qtdialogs/DlgSendBitcoins.py index 3f26af4be..8297e99ff 100644 --- a/qtdialogs/DlgSendBitcoins.py +++ b/qtdialogs/DlgSendBitcoins.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import QSize from PySide2.QtWidgets import QVBoxLayout diff --git a/qtdialogs/DlgSetComment.py b/qtdialogs/DlgSetComment.py index 2f389abed..5f00c2b15 100644 --- a/qtdialogs/DlgSetComment.py +++ b/qtdialogs/DlgSetComment.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import QObject from PySide2.QtGui import QIcon diff --git a/qtdialogs/DlgSettings.py b/qtdialogs/DlgSettings.py index 546700207..d3ad301cd 100644 --- a/qtdialogs/DlgSettings.py +++ b/qtdialogs/DlgSettings.py @@ -2,14 +2,17 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -############################################################################### -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -############################################################################### - +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## import time import os diff --git a/qtdialogs/DlgShowKeyList.py b/qtdialogs/DlgShowKeyList.py index 0381789ed..d85d6f539 100644 --- a/qtdialogs/DlgShowKeyList.py +++ b/qtdialogs/DlgShowKeyList.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from qtdialogs.ArmoryDialog import ArmoryDialog diff --git a/qtdialogs/DlgUniversalRestoreSelect.py b/qtdialogs/DlgUniversalRestoreSelect.py index f0f126c45..d2a0c9f25 100644 --- a/qtdialogs/DlgUniversalRestoreSelect.py +++ b/qtdialogs/DlgUniversalRestoreSelect.py @@ -1,11 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ - +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.qtdefines import HLINE, QRichLabel from armoryengine.ArmoryUtils import LOGINFO diff --git a/qtdialogs/DlgUnlockWallet.py b/qtdialogs/DlgUnlockWallet.py index b97270b44..40dfa3a7c 100644 --- a/qtdialogs/DlgUnlockWallet.py +++ b/qtdialogs/DlgUnlockWallet.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from armoryengine.CppBridge import TheBridge diff --git a/qtdialogs/DlgUriCopyAndPaste.py b/qtdialogs/DlgUriCopyAndPaste.py index 1480476b2..fa56b8c70 100644 --- a/qtdialogs/DlgUriCopyAndPaste.py +++ b/qtdialogs/DlgUriCopyAndPaste.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from qtdialogs.qtdefines import ArmoryDialog diff --git a/qtdialogs/DlgWalletDetails.py b/qtdialogs/DlgWalletDetails.py index 787d53556..d3ef2ff75 100644 --- a/qtdialogs/DlgWalletDetails.py +++ b/qtdialogs/DlgWalletDetails.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt, QByteArray from PySide2.QtWidgets import QFrame, QVBoxLayout, QGridLayout, QPushButton, \ @@ -29,6 +33,7 @@ DlgNewAddressDisp, ShowRecvCoinsWarningIfNecessary from qtdialogs.DlgKeypoolSettings import DlgKeypoolSettings from qtdialogs.DlgSendBitcoins import DlgSendBitcoins +from qtdialogs.DlgBackupCenter import DlgBackupCenter ################################################################################ @@ -65,7 +70,7 @@ def __init__(self, wlt, usermode=USERMODE.Standard, parent=None, main=None): # Now add all the options buttons, dependent on the type of wallet. - lbtnChangeLabels = QLabelButton(self.tr('Change Wallet Labels')); + lbtnChangeLabels = QLabelButton(self.tr('Change Wallet Labels')) lbtnChangeLabels.linkActivated.connect(self.changeLabels) if not self.wlt.watchingOnly: @@ -559,7 +564,6 @@ def changeKdf(self): """ pass - def execBackupDlg(self): if self.main.usermode == USERMODE.Expert: DlgBackupCenter(self, self.main, self.wlt).exec_() diff --git a/qtdialogs/DlgWalletSelect.py b/qtdialogs/DlgWalletSelect.py index 81b6e11b2..7aa89aaa1 100644 --- a/qtdialogs/DlgWalletSelect.py +++ b/qtdialogs/DlgWalletSelect.py @@ -1,10 +1,14 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtWidgets import QMessageBox, QPushButton, QDialogButtonBox, \ QVBoxLayout diff --git a/qtdialogs/DlgWltRecoverWallet.py b/qtdialogs/DlgWltRecoverWallet.py index 25c269a4b..af96fdb17 100644 --- a/qtdialogs/DlgWltRecoverWallet.py +++ b/qtdialogs/DlgWltRecoverWallet.py @@ -1,15 +1,19 @@ -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ - +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## import os from PySide2.QtCore import SIGNAL -from PySide2.QtWidgets import QFileDialog, QFrame, QGridLayout, QHBoxLayout, QLabel, QLayout, QLineEdit, QPushButton, QRadioButton +from PySide2.QtWidgets import QFileDialog, QFrame, QGridLayout, \ + QHBoxLayout, QLabel, QLayout, QLineEdit, QPushButton, QRadioButton from armorycolors import htmlColor from armoryengine.ArmoryUtils import ARMORY_HOME_DIR, LOGINFO, OS_MACOSX @@ -17,7 +21,8 @@ from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.DlgCorruptWallet import DlgCorruptWallet from qtdialogs.DlgWalletSelect import DlgWalletSelect -from qtdialogs.qtdefines import GETFONT, QRichLabel, STYLE_SUNKEN, makeHorizFrame, tightSizeNChar +from qtdialogs.qtdefines import GETFONT, QRichLabel, STYLE_SUNKEN, \ + makeHorizFrame, tightSizeNChar ############################################################################### class DlgWltRecoverWallet(ArmoryDialog): diff --git a/qtdialogs/MsgBoxCustom.py b/qtdialogs/MsgBoxCustom.py index c2760206a..1030f0649 100644 --- a/qtdialogs/MsgBoxCustom.py +++ b/qtdialogs/MsgBoxCustom.py @@ -1,10 +1,14 @@ -############################################################################### -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -############################################################################### +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## from PySide2.QtCore import Qt, SIGNAL from PySide2.QtWidgets import QLabel, QDialogButtonBox, QPushButton, \ diff --git a/qtdialogs/MsgBoxWithDNAA.py b/qtdialogs/MsgBoxWithDNAA.py index 11f5cd3b5..79b6a5b98 100644 --- a/qtdialogs/MsgBoxWithDNAA.py +++ b/qtdialogs/MsgBoxWithDNAA.py @@ -1,12 +1,10 @@ -#! /usr/bin/python -# -*- coding: UTF-8 -*- ############################################################################## # # # Copyright (C) 2011-2015, Armory Technologies, Inc. # # Distributed under the GNU Affero General Public License (AGPL v3) # # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # -# Copyright (C) 2016-17, goatpig # +# Copyright (C) 2016-2022, goatpig # # Distributed under the MIT license # # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # From cfa9089c106b78488f1798715d32b4bf04aa22fc Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 16 Feb 2022 15:32:51 +0100 Subject: [PATCH 14/47] implement botched Armory python hmac in cpp --- cppForSwig/BtcUtils.cpp | 48 +++++++++++++++--- cppForSwig/BtcUtils.h | 8 +-- cppForSwig/gtest/UtilsTests.cpp | 90 +++++++++------------------------ 3 files changed, 68 insertions(+), 78 deletions(-) diff --git a/cppForSwig/BtcUtils.cpp b/cppForSwig/BtcUtils.cpp index a25005993..936cd1553 100644 --- a/cppForSwig/BtcUtils.cpp +++ b/cppForSwig/BtcUtils.cpp @@ -188,6 +188,40 @@ void BtcUtils::getHMAC512(const void* keyptr, size_t keylen, CryptoSHA2::getHMAC512(key_bdr, msg_bdr, (uint8_t*)digest); } +//////////////////////////////////////////////////////////////////////////////// +BinaryData BtcUtils::getBotchedArmoryHMAC256( + const BinaryData& key, const BinaryData& msg) +{ + BinaryData hmacKey; + if (key.getSize() > 32) + { + hmacKey = BtcUtils::getSha256(key); + } + else if (key.getSize() <= 32) + { + hmacKey.resize(32); + memcpy(hmacKey.getPtr(), key.getPtr(), key.getSize()); + memset(hmacKey.getPtr() + key.getSize(), 0, 32 - key.getSize()); + } + + BinaryData oxor(32), ixor(32); + for (int i=0; i<32; i++) + { + oxor.getPtr()[i] = hmacKey.getPtr()[i] ^ 0x5c; + ixor.getPtr()[i] = hmacKey.getPtr()[i] ^ 0x36; + } + + ixor.append(msg); + auto iHash = BtcUtils::getSha256(ixor); + + BinaryWriter bw; + bw.put_BinaryData(oxor); + bw.put_BinaryData(iHash); + + return BtcUtils::getSha256(bw.getData()); + +} + //////////////////////////////////////////////////////////////////////////////// SecureBinaryData BtcUtils::computeChainCode_Armory135( const SecureBinaryData& privateRoot) @@ -197,18 +231,16 @@ SecureBinaryData BtcUtils::computeChainCode_Armory135( key: double SHA256 of the root key message: 'Derive Chaincode from Root Key' - TODO: The Armory Python code uses a botched self implemented HMAC256, + TODO: The Armory Python code uses a botched self implemented HMAC256, reproduce it here. */ - auto&& hmacKey = BtcUtils::hash256(privateRoot); - string hmacMsg("Derive Chaincode from Root Key"); - SecureBinaryData chainCode(32); - - getHMAC256(hmacKey.getPtr(), hmacKey.getSize(), - hmacMsg.c_str(), hmacMsg.size(), chainCode.getPtr()); + auto hmacKey = BtcUtils::hash256(privateRoot); + auto hmacMsg = BinaryData::fromString("Derive Chaincode from Root Key"); - return chainCode; + //use key as is for invalid armory hmac256: armory erroneously uses the + //output size for sha256 (32 bytes) instead of the block size (64 bytes) + return getBotchedArmoryHMAC256(hmacKey, hmacMsg); } //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/BtcUtils.h b/cppForSwig/BtcUtils.h index d1ad69a1f..c556fed57 100644 --- a/cppForSwig/BtcUtils.h +++ b/cppForSwig/BtcUtils.h @@ -1857,6 +1857,7 @@ class BtcUtils static std::string computeID(const SecureBinaryData& pubkey); + //HMAC static BinaryData getHMAC256( const SecureBinaryData& key, const SecureBinaryData& message); @@ -1864,7 +1865,6 @@ class BtcUtils const SecureBinaryData& key, const SecureBinaryData& message); - static BinaryData getHMAC256( const BinaryData& key, const std::string& message); @@ -1876,13 +1876,15 @@ class BtcUtils const std::string& key, const SecureBinaryData& message); - - static void getHMAC256(const uint8_t* keyptr, size_t keylen, const char* msg, size_t msglen, uint8_t* digest); static void getHMAC512(const void* keyptr, size_t keylen, const void* msg, size_t msglen, void* digest); + static BinaryData getBotchedArmoryHMAC256( + const BinaryData& key, const BinaryData& msg); + + static SecureBinaryData computeChainCode_Armory135( const SecureBinaryData& privateRoot); diff --git a/cppForSwig/gtest/UtilsTests.cpp b/cppForSwig/gtest/UtilsTests.cpp index eb1ec871d..a0e7ae08d 100644 --- a/cppForSwig/gtest/UtilsTests.cpp +++ b/cppForSwig/gtest/UtilsTests.cpp @@ -2839,9 +2839,7 @@ class BtcUtilsTest : public ::testing::Test string homedir_; }; - - - +//////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, ReadVarInt) { BinaryData vi0 = READHEX("00"); @@ -2898,7 +2896,7 @@ TEST_F(BtcUtilsTest, ReadVarInt) EXPECT_EQ(BtcUtils::calcVarIntSize(z), 9ULL); } - +//////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, Num2Str) { EXPECT_EQ(BtcUtils::numToStrWCommas(0), string("0")); @@ -2911,8 +2909,7 @@ TEST_F(BtcUtilsTest, Num2Str) EXPECT_EQ(BtcUtils::numToStrWCommas(-12345678), string("-12,345,678")); } - - +//////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, PackBits) { list::iterator iter, iter2; @@ -3000,14 +2997,12 @@ TEST_F(BtcUtilsTest, PackBits) EXPECT_EQ(packed, READHEX("0170")); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, SimpleHash) { - BinaryData hashOut; + BinaryData hashOut; - // sha256(sha256(X)); + // sha256(sha256(X)) BtcUtils::getHash256(rawHead_.getPtr(), rawHead_.getSize(), hashOut); EXPECT_EQ(hashOut, headHashLE_); EXPECT_EQ(hashOut, headHashBE_.copySwapEndian()); @@ -3028,8 +3023,7 @@ TEST_F(BtcUtilsTest, SimpleHash) hashOut = BtcUtils::getHash256(rawHead_); EXPECT_EQ(hashOut, headHashLE_); - - // ripemd160(sha256(X)); + // ripemd160(sha256(X)) BtcUtils::getHash160(satoshiPubKey_.getPtr(), satoshiPubKey_.getSize(), hashOut); EXPECT_EQ(hashOut, satoshiHash160_); @@ -3049,7 +3043,20 @@ TEST_F(BtcUtilsTest, SimpleHash) EXPECT_EQ(hashOut, satoshiHash160_); } +//////////////////////////////////////////////////////////////////////////////// +TEST_F(BtcUtilsTest, BotchedArmoryHMAC) +{ + auto sha_abcd = READHEX("88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589"); + auto sha_efgh = READHEX("e5e088a0b66163a0a26a5e053d2a4496dc16ab6e0e3dd1adf2d16aa84a078c9d"); + auto hmac_1 = READHEX("edd2c945dc57a5eecdb4dbb2db8ae4f33f9669046e47acb517c8f6bcdf6ee591"); + auto abcd = BinaryData::fromString("abcd"); + auto efgh = BinaryData::fromString("efgh"); + + EXPECT_EQ(BtcUtils::getSha256(abcd), sha_abcd); + EXPECT_EQ(BtcUtils::getSha256(efgh), sha_efgh); + EXPECT_EQ(BtcUtils::getBotchedArmoryHMAC256(abcd, efgh), hmac_1); +} //////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, TxOutScriptID_Hash160) @@ -3202,18 +3209,6 @@ TEST_F(BtcUtilsTest, TxOutScriptID_MultiList) EXPECT_EQ(pkList[1], pub1); } - -//TEST_F(BtcUtilsTest, TxInScriptID) -//{ - //TXIN_SCRIPT_STDUNCOMPR, - //TXIN_SCRIPT_STDCOMPR, - //TXIN_SCRIPT_COINBASE, - //TXIN_SCRIPT_SPENDPUBKEY, - //TXIN_SCRIPT_SPENDMULTI, - //TXIN_SCRIPT_SPENDP2SH, - //TXIN_SCRIPT_NONSTANDARD -//} - //////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, TxInScriptID_StdUncompr) { @@ -3285,8 +3280,6 @@ TEST_F(BtcUtilsTest, TxInScriptID_SpendPubKey) //txInHash160s.push_back( READHEX("957efec6af757ccbbcf9a436f0083c5ddaa3bf1d")); // this one can't be determined } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, TxInScriptID_SpendMultisig) { @@ -3350,8 +3343,6 @@ TEST_F(BtcUtilsTest, TxInScriptID_SpendP2SH) EXPECT_EQ(BtcUtils::getTxInAddrFromType(script, scrType), a160); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, BitsToDifficulty) { @@ -3359,13 +3350,12 @@ TEST_F(BtcUtilsTest, BitsToDifficulty) double a = BtcUtils::convertDiffBitsToDouble(READHEX("ffff001d")); double b = BtcUtils::convertDiffBitsToDouble(READHEX("be2f021a")); double c = BtcUtils::convertDiffBitsToDouble(READHEX("3daa011a")); - + EXPECT_DOUBLE_EQ(a, 1.0); EXPECT_DOUBLE_EQ(b, 7672999.920164138); EXPECT_DOUBLE_EQ(c, 10076292.883418716); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(BtcUtilsTest, ScriptToOpCodes) { @@ -3423,8 +3413,6 @@ TEST_F(BtcUtilsTest, ScriptToOpCodes) EXPECT_EQ(output[i], opstr[i]); } - - //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// class BlockObjTest : public ::testing::Test @@ -3558,8 +3546,6 @@ class BlockObjTest : public ::testing::Test Tx tx2_; }; - - //////////////////////////////////////////////////////////////////////////////// TEST_F(BlockObjTest, HeaderNoInit) { @@ -3569,7 +3555,6 @@ TEST_F(BlockObjTest, HeaderNoInit) EXPECT_EQ(bh.getBlockSize(), UINT32_MAX); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(BlockObjTest, HeaderUnserialize) { @@ -3609,8 +3594,6 @@ TEST_F(BlockObjTest, HeaderProperties) EXPECT_EQ(BlockHeader(rawHead_).serialize(), rawHead_); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(BlockObjTest, OutPointProperties) { @@ -3639,7 +3622,6 @@ TEST_F(BlockObjTest, OutPointProperties) EXPECT_EQ(op.getTxHashRef(), prevHash.getRef()); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(BlockObjTest, OutPointSerialize) { @@ -3728,7 +3710,6 @@ TEST_F(BlockObjTest, DISABLED_FullBlock) BinaryRefReader brr(rawBlock_); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(BlockObjTest, DISABLED_TxIOPairStuff) { @@ -3741,8 +3722,6 @@ TEST_F(BlockObjTest, DISABLED_RegisteredTxStuff) EXPECT_TRUE(false); } - - //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// class StoredBlockObjTest : public ::testing::Test @@ -3938,7 +3917,6 @@ class StoredBlockObjTest : public ::testing::Test StoredHeader sbh_; }; - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, StoredObjNoInit) { @@ -4023,8 +4001,6 @@ TEST_F(StoredBlockObjTest, GetDBKeys) EXPECT_EQ(sths.getDBKey( false ), key); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, LengthUnfrag) { @@ -4047,8 +4023,6 @@ TEST_F(StoredBlockObjTest, LengthUnfrag) EXPECT_EQ(offout[2], 434ULL); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, LengthFragged) { @@ -4236,7 +4210,7 @@ TEST_F(StoredBlockObjTest, ReadBlkKeyData) EXPECT_EQ( txi, 7); EXPECT_EQ( brr.getSizeRemaining(), 0ULL); EXPECT_EQ( bdtype, BLKDATA_TX); - + brr.setNewData(key7); bdtype = DBUtils::readBlkDataKeyNoPrefix(brr, hgt, dup, txi, txo); EXPECT_EQ( hgt, 123000ULL); @@ -4263,7 +4237,7 @@ TEST_F(StoredBlockObjTest, ReadBlkKeyData) EXPECT_EQ( txi, 7); EXPECT_EQ( brr.getSizeRemaining(), 0ULL); EXPECT_EQ( bdtype, BLKDATA_TXOUT); - + brr.setNewData(key9); bdtype = DBUtils::readBlkDataKeyNoPrefix(brr, hgt, dup, txi, txo); EXPECT_EQ( hgt, 123000ULL); @@ -4309,7 +4283,6 @@ TEST_F(StoredBlockObjTest, SHeaderDBSerFull_H) EXPECT_EQ(serializeDBValue(sbh_, HEADERS, ARMORY_DB_FULL), rawHead_ + last4); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, SHeaderDBSerFull_B1) { @@ -4352,7 +4325,6 @@ TEST_F(StoredBlockObjTest, SHeaderDBUnserFull_H) EXPECT_EQ(sbh_.duplicateID_, 1); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, SHeaderDBUnserFull_B1) { @@ -4376,7 +4348,6 @@ TEST_F(StoredBlockObjTest, SHeaderDBUnserFull_B1) EXPECT_EQ(sbh_.unserMkType_, MERKLE_SER_NONE); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, SHeaderDBUnserFull_B2) { @@ -4477,8 +4448,6 @@ TEST_F(StoredBlockObjTest, STxUnserFragged) EXPECT_EQ( stx.stxoMap_[1].txOutIndex_, 1); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, STxReconstruct) { @@ -4607,8 +4576,6 @@ TEST_F(StoredBlockObjTest, STxUnserDBValue_2) EXPECT_EQ( stx.fragBytes_, 370ULL); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, STxOutUnserialize) { @@ -4657,7 +4624,6 @@ TEST_F(StoredBlockObjTest, STxOutSerDBValue_1) EXPECT_EQ(serializeDBValue(stxo0), READHEX("1400") + rawTxOut0_); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, STxOutSerDBValue_2) @@ -4681,8 +4647,6 @@ TEST_F(StoredBlockObjTest, STxOutSerDBValue_2) ); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, STxOutSerDBValue_3) { @@ -4705,7 +4669,6 @@ TEST_F(StoredBlockObjTest, STxOutSerDBValue_3) ); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, STxOutUnserDBValue_1) { @@ -4726,6 +4689,7 @@ TEST_F(StoredBlockObjTest, STxOutUnserDBValue_1) EXPECT_FALSE(stxo.isCoinbase_); EXPECT_EQ( stxo.unserArmVer_, 0ULL); } + //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, STxOutUnserDBValue_2) { @@ -4747,7 +4711,6 @@ TEST_F(StoredBlockObjTest, STxOutUnserDBValue_2) EXPECT_EQ( stxo.unserArmVer_, 0ULL); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, STxOutUnserDBValue_3) { @@ -4769,7 +4732,6 @@ TEST_F(StoredBlockObjTest, STxOutUnserDBValue_3) EXPECT_EQ( stxo.unserArmVer_, 0ULL); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, SHeaderFullBlock) { @@ -4783,7 +4745,6 @@ TEST_F(StoredBlockObjTest, SHeaderFullBlock) EXPECT_EQ(bw.getDataRef(), rawBlock_.getRef()); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, SUndoDataSer) { @@ -4845,8 +4806,6 @@ TEST_F(StoredBlockObjTest, SUndoDataSer) EXPECT_EQ(serializeDBValue(sud), answer); } - - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, SUndoDataUnser) { @@ -4905,7 +4864,6 @@ TEST_F(StoredBlockObjTest, SUndoDataUnser) EXPECT_EQ(sud.stxOutsRemovedByBlock_[1].txIndex_, 17ULL); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, STxHintsSer) { @@ -5082,7 +5040,6 @@ TEST_F(StoredBlockObjTest, SHeadHgtListSer) EXPECT_EQ(testHHL.serializeDBValue(), expectOut.getData()); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(StoredBlockObjTest, SHeadHgtListUnser) { @@ -5355,7 +5312,6 @@ TEST_F(StoredBlockObjTest, SScriptHistoryUnser) EXPECT_EQ( subref.txioMap_[txio1key].getValue(), val1); EXPECT_EQ( subref.txioMap_[txio0key].getDBKeyOfOutput(), txio0key); EXPECT_EQ( subref.txioMap_[txio1key].getDBKeyOfOutput(), txio1key); - ///////////////////////////////////////////////////////////////////////////// From d428b998e4e83a26e48c75e15d36ce80cf0341e0 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 16 Feb 2022 15:35:18 +0100 Subject: [PATCH 15/47] Refactor BackupCenter into its own file Fix wallet restore from paper backup --- ArmoryQt.py | 4 +- qtdialogs/DlgBackupCenter.py | 1419 ++++++++++++++++++ qtdialogs/DlgChangePassphrase.py | 6 +- qtdialogs/DlgReplaceWallet.py | 19 +- qtdialogs/DlgRestoreFragged.py | 1 + qtdialogs/DlgRestoreSingle.py | 85 +- qtdialogs/DlgUniversalRestoreSelect.py | 24 +- qtdialogs/qtdefines.py | 20 +- qtdialogs/qtdialogs.py | 1881 ++++-------------------- ui/MultiSigDialogs.py | 12 +- ui/WalletFrames.py | 3 +- 11 files changed, 1770 insertions(+), 1704 deletions(-) create mode 100644 qtdialogs/DlgBackupCenter.py diff --git a/ArmoryQt.py b/ArmoryQt.py index 68ab37bf9..c01b58b36 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -84,8 +84,8 @@ restoreTableView, determineWalletType, WLTTYPES, tightSizeStr, \ QLabelButton, MSGBOX, saveTableView -from qtdialogs.qtdialogs import URLHandler, ArmorySplashScreen, LoadingDisp from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.qtdialogs import URLHandler, ArmorySplashScreen, LoadingDisp from qtdialogs.DlgMigrateWallet import DlgMigrateWallet from qtdialogs.DlgSendBitcoins import DlgSendBitcoins from qtdialogs.DlgAddressBook import DlgAddressBook, createAddrBookButton @@ -105,6 +105,8 @@ from qtdialogs.DlgHelpAbout import DlgHelpAbout from qtdialogs.MsgBoxCustom import MsgBoxCustom from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA +from qtdialogs.DlgUniversalRestoreSelect import DlgUniversalRestoreSelect + from ui.QtExecuteSignal import QtExecuteSignal diff --git a/qtdialogs/DlgBackupCenter.py b/qtdialogs/DlgBackupCenter.py new file mode 100644 index 000000000..a0c3fd5f3 --- /dev/null +++ b/qtdialogs/DlgBackupCenter.py @@ -0,0 +1,1419 @@ +################################################################################ +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +################################################################################ + +from PySide2.QtCore import Qt, Signal, QPointF, QRectF +from PySide2.QtGui import QColor, QPen, QPainter, QBrush, QPixmap, \ + QMatrix, QIcon +from PySide2.QtWidgets import QRadioButton, QPushButton, QVBoxLayout, \ + QFrame, QMessageBox, QGraphicsTextItem, QGraphicsRectItem, \ + QGraphicsScene, QGraphicsView, QCheckBox, QComboBox, QGraphicsPixmapItem, \ + QGraphicsLineItem, QGraphicsItem + +from ui.WalletFrames import WalletBackupFrame +from ui.QrCodeMatrix import CreateQRMatrix + +from qtdialogs.qtdefines import makeHorizFrame, QRichLabel, \ + makeVertFrame, QImageLabel, HLINE, GETFONT, STYLE_RAISED, tightSizeStr, \ + setLayoutStretch +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.qtdialogs import STRETCH +from qtdialogs.DlgUnlockWallet import DlgUnlockWallet + +from armoryengine.ArmoryUtils import toUnicode, USE_TESTNET, USE_REGTEST +from armorycolors import htmlColor + +################################################################################ +class DlgBackupCenter(ArmoryDialog): + + ############################################################################# + def __init__(self, parent, main, wlt): + super(DlgBackupCenter, self).__init__(parent, main) + + self.wlt = wlt + wltID = wlt.uniqueIDB58 + wltName = wlt.labelName + + self.walletBackupFrame = WalletBackupFrame(parent, main) + self.walletBackupFrame.setWallet(wlt) + self.btnDone = QPushButton(self.tr('Done')) + self.btnDone.clicked.connect(self.reject) + frmBottomBtns = makeHorizFrame([STRETCH, self.btnDone]) + + layoutDialog = QVBoxLayout() + + layoutDialog.addWidget(self.walletBackupFrame) + + layoutDialog.addWidget(frmBottomBtns) + + self.setLayout(layoutDialog) + self.setWindowTitle(self.tr("Backup Center")) + self.setMinimumSize(640, 350) + +################################################################################ +class DlgSimpleBackup(ArmoryDialog): + + ############################################################################# + def __init__(self, parent, main, wlt): + super(DlgSimpleBackup, self).__init__(parent, main) + + self.wlt = wlt + + lblDescrTitle = QRichLabel(self.tr( + 'Protect Your Bitcoins -- Make a Wallet Backup!')) + + lblDescr = QRichLabel(self.tr( + 'A failed hard-drive or forgotten passphrase will lead to ' + 'permanent loss of bitcoins! Luckily, Armory wallets only ' + 'need to be backed up one time, and protect you in both ' + 'of these events. If you\'ve ever forgotten a password or had ' + 'a hardware failure, make a backup!')) + + # ## Paper + lblPaper = QRichLabel(self.tr( + 'Use a printer or pen-and-paper to write down your wallet "seed."')) + btnPaper = QPushButton(self.tr('Make Paper Backup')) + + # ## Digital + lblDigital = QRichLabel(self.tr( + 'Create an unencrypted copy of your wallet file, including imported ' + 'addresses.')) + btnDigital = QPushButton(self.tr('Make Digital Backup')) + + # ## Other + btnOther = QPushButton(self.tr('See Other Backup Options')) + + def backupDigital(): + if self.main.digitalBackupWarning(): + self.main.makeWalletCopy(self, self.wlt, 'Decrypt', 'decrypt') + self.accept() + + def backupPaper(): + OpenPaperBackupWindow('Single', self, self.main, self.wlt) + self.accept() + + def backupOther(): + self.accept() + DlgBackupCenter(self, self.main, self.wlt).exec_() + + btnPaper.connect.clicked(backupPaper) + btnDigital.connect.clicked(backupDigital) + btnOther.connect.clicked(backupOther) + + layout = QGridLayout() + + layout.addWidget(lblPaper, 0, 0) + layout.addWidget(btnPaper, 0, 2) + + layout.addWidget(HLINE(), 1, 0, 1, 3) + + layout.addWidget(lblDigital, 2, 0) + layout.addWidget(btnDigital, 2, 2) + + layout.addWidget(HLINE(), 3, 0, 1, 3) + + layout.addWidget(makeHorizFrame([STRETCH, btnOther, STRETCH]), 4, 0, 1, 3) + + # layout.addWidget( VLINE(), 0,1, 5,1) + + layout.setContentsMargins(10, 5, 10, 5) + setLayoutStretchRows(layout, 1, 0, 1, 0, 0) + setLayoutStretchCols(layout, 1, 0, 0) + + frmGrid = QFrame() + frmGrid.setFrameStyle(STYLE_PLAIN) + frmGrid.setLayout(layout) + + btnClose = QPushButton(self.tr('Done')) + btnClose.connect.clicked(self.accept) + frmClose = makeHorizFrame([STRETCH, btnClose]) + + frmAll = makeVertFrame([lblDescrTitle, lblDescr, frmGrid, frmClose]) + layoutAll = QVBoxLayout() + layoutAll.addWidget(frmAll) + self.setLayout(layoutAll) + self.sizeHint = lambda: QSize(400, 250) + + self.setWindowTitle(self.tr('Backup Options')) + +################################################################################ +class SimplePrintableGraphicsScene(object): + + ############################################################################# + def __init__(self, parent, main): + """ + We use the following coordinates: + + -----> +x + | + | + V +y + + """ + self.parent = parent + self.main = main + + self.INCH = 72 + self.PAPER_A4_WIDTH = 8.5 * self.INCH + self.PAPER_A4_HEIGHT = 11.0 * self.INCH + self.MARGIN_PIXELS = 0.6 * self.INCH + + self.PAGE_BKGD_COLOR = QColor(255, 255, 255) + self.PAGE_TEXT_COLOR = QColor(0, 0, 0) + + self.fontFix = GETFONT('Courier', 9) + self.fontVar = GETFONT('Times', 10) + + self.gfxScene = QGraphicsScene(self.parent) + self.gfxScene.setSceneRect(0, 0, self.PAPER_A4_WIDTH, self.PAPER_A4_HEIGHT) + self.gfxScene.setBackgroundBrush(self.PAGE_BKGD_COLOR) + + # For when it eventually makes it to the printer + # self.printer = QPrinter(QPrinter.HighResolution) + # self.printer.setPageSize(QPrinter.Letter) + # self.gfxPainter = QPainter(self.printer) + # self.gfxPainter.setRenderHint(QPainter.TextAntialiasing) + # self.gfxPainter.setPen(Qt.NoPen) + # self.gfxPainter.setBrush(QBrush(self.PAGE_TEXT_COLOR)) + + self.cursorPos = QPointF(self.MARGIN_PIXELS, self.MARGIN_PIXELS) + self.lastCursorMove = (0, 0) + + + def getCursorXY(self): + return (self.cursorPos.x(), self.cursorPos.y()) + + def getScene(self): + return self.gfxScene + + def pageRect(self): + marg = self.MARGIN_PIXELS + return QRectF(marg, marg, self.PAPER_A4_WIDTH - marg, self.PAPER_A4_HEIGHT - marg) + + def insidePageRect(self, pt=None): + if pt == None: + pt = self.cursorPos + + return self.pageRect.contains(pt) + + def moveCursor(self, dx, dy, absolute=False): + xOld, yOld = self.getCursorXY() + if absolute: + self.cursorPos = QPointF(dx, dy) + self.lastCursorMove = (dx - xOld, dy - yOld) + else: + self.cursorPos = QPointF(xOld + dx, yOld + dy) + self.lastCursorMove = (dx, dy) + + + def resetScene(self): + self.gfxScene.clear() + self.resetCursor() + + def resetCursor(self): + self.cursorPos = QPointF(self.MARGIN_PIXELS, self.MARGIN_PIXELS) + + + def newLine(self, extra_dy=0): + xOld, yOld = self.getCursorXY() + xNew = self.MARGIN_PIXELS + yNew = self.cursorPos.y() + self.lastItemSize[1] + extra_dy - 5 + self.moveCursor(xNew - xOld, yNew - yOld) + + + def drawHLine(self, width=None, penWidth=1): + if width == None: + width = 3 * self.INCH + currX, currY = self.getCursorXY() + lineItem = QGraphicsLineItem(currX, currY, currX + width, currY) + pen = QPen() + pen.setWidth(penWidth) + lineItem.setPen(pen) + self.gfxScene.addItem(lineItem) + rect = lineItem.boundingRect() + self.lastItemSize = (rect.width(), rect.height()) + self.moveCursor(rect.width(), 0) + return self.lastItemSize + + def drawRect(self, w, h, edgeColor=QColor(0, 0, 0), fillColor=None, penWidth=1): + rectItem = QGraphicsRectItem(self.cursorPos.x(), self.cursorPos.y(), w, h) + if edgeColor == None: + rectItem.setPen(QPen(Qt.NoPen)) + else: + pen = QPen(edgeColor) + pen.setWidth(penWidth) + rectItem.setPen(pen) + + if fillColor == None: + rectItem.setBrush(QBrush(Qt.NoBrush)) + else: + rectItem.setBrush(QBrush(fillColor)) + + self.gfxScene.addItem(rectItem) + rect = rectItem.boundingRect() + self.lastItemSize = (rect.width(), rect.height()) + self.moveCursor(rect.width(), 0) + return self.lastItemSize + + + def drawText(self, txt, font=None, wrapWidth=None, useHtml=True): + if font == None: + font = GETFONT('Var', 9) + txtItem = QGraphicsTextItem('') + if useHtml: + txtItem.setHtml(toUnicode(txt)) + else: + txtItem.setPlainText(toUnicode(txt)) + txtItem.setDefaultTextColor(self.PAGE_TEXT_COLOR) + txtItem.setPos(self.cursorPos) + txtItem.setFont(font) + if not wrapWidth == None: + txtItem.setTextWidth(wrapWidth) + self.gfxScene.addItem(txtItem) + rect = txtItem.boundingRect() + self.lastItemSize = (rect.width(), rect.height()) + self.moveCursor(rect.width(), 0) + return self.lastItemSize + + def drawPixmapFile(self, pixFn, sizePx=None): + pix = QPixmap(pixFn) + if not sizePx == None: + pix = pix.scaled(sizePx, sizePx) + pixItem = QGraphicsPixmapItem(pix) + pixItem.setPos(self.cursorPos) + pixItem.setMatrix(QMatrix()) + self.gfxScene.addItem(pixItem) + rect = pixItem.boundingRect() + self.lastItemSize = (rect.width(), rect.height()) + self.moveCursor(rect.width(), 0) + return self.lastItemSize + + def drawQR(self, qrdata, size=150): + objQR = GfxItemQRCode(qrdata, size) + objQR.setPos(self.cursorPos) + objQR.setMatrix(QMatrix()) + self.gfxScene.addItem(objQR) + rect = objQR.boundingRect() + self.lastItemSize = (rect.width(), rect.height()) + self.moveCursor(rect.width(), 0) + return self.lastItemSize + + + def drawColumn(self, strList, rowHeight=None, font=None, useHtml=True): + """ + This draws a bunch of left-justified strings in a column. It returns + a tight bounding box around all elements in the column, which can easily + be used to start the next column. The rowHeight is returned, and also + an available input, in case you are drawing text/font that has a different + height in each column, and want to make sure they stay aligned. + + Just like the other methods, this leaves the cursor sitting at the + original y-value, but shifted to the right by the width of the column. + """ + origX, origY = self.getCursorXY() + maxColWidth = 0 + cumulativeY = 0 + for r in strList: + szX, szY = self.drawText(r, font=font, useHtml=useHtml) + prevY = self.cursorPos.y() + if rowHeight == None: + self.newLine() + szY = self.cursorPos.y() - prevY + self.moveCursor(origX - self.MARGIN_PIXELS, 0) + else: + self.moveCursor(-szX, rowHeight) + maxColWidth = max(maxColWidth, szX) + cumulativeY += szY + + if rowHeight == None: + rowHeight = float(cumulativeY) / len(strList) + + self.moveCursor(origX + maxColWidth, origY, absolute=True) + + return [QRectF(origX, origY, maxColWidth, cumulativeY), rowHeight] + +################################################################################ +class GfxItemQRCode(QGraphicsItem): + """ + Converts binary data to base58, and encodes the Base58 characters in + the QR-code. It seems weird to use Base58 instead of binary, but the + QR-code has no problem with the size, instead, we want the data in the + QR-code to match exactly what is human-readable on the page, which is + in Base58. + + You must supply exactly one of "totalSize" or "modSize". TotalSize + guarantees that the QR code will fit insides box of a given size. + ModSize is how big each module/pixel of the QR code is, which means + that a bigger QR block results in a bigger physical size on paper. + """ + def __init__(self, rawDataToEncode, maxSize=None): + super(GfxItemQRCode, self).__init__() + self.maxSize = maxSize + self.updateQRData(rawDataToEncode) + + def boundingRect(self): + return self.Rect + + def updateQRData(self, toEncode, maxSize=None): + if maxSize == None: + maxSize = self.maxSize + else: + self.maxSize = maxSize + + self.qrmtrx, self.modCt = CreateQRMatrix(toEncode, 'H') + self.modSz = round(float(self.maxSize) / float(self.modCt) - 0.5) + totalSize = self.modCt * self.modSz + self.Rect = QRectF(0, 0, totalSize, totalSize) + + def paint(self, painter, option, widget=None): + painter.setPen(Qt.NoPen) + painter.setBrush(QBrush(QColor(0, 0, 0))) + + for r in range(self.modCt): + for c in range(self.modCt): + if self.qrmtrx[r][c] > 0: + painter.drawRect(*[self.modSz * a for a in [r, c, 1, 1]]) + +################################################################################ +class DlgPrintBackup(ArmoryDialog): + backupSetupSignal = Signal() + + """ + Open up a "Make Paper Backup" dialog, so the user can print out a hard + copy of whatever data they need to recover their wallet should they lose + it. + + This method is kind of a mess, because it ended up having to support + printing of single-sheet, imported keys, single fragments, multiple + fragments, with-or-without SecurePrint. + """ + def __init__(self, parent, main, wlt, printType='SingleSheet', \ + fragMtrx=[], fragMtrxCrypt=[], fragData=[], + privKey=None, chaincode=None): + super(DlgPrintBackup, self).__init__(parent, main) + + + self.wlt = wlt + self.binImport = [] + self.fragMtrx = fragMtrx + + self.doPrintFrag = printType.lower().startswith('frag') + self.fragMtrx = fragMtrx + self.fragMtrxCrypt = fragMtrxCrypt + self.fragData = fragData + if self.doPrintFrag: + self.doMultiFrag = len(fragData['Range']) > 1 + + self.backupData = None + def resumeSetup(rootData): + self.backupData = rootData + self.backupSetupSignal.emit() + + self.backupSetupSignal.connect(self.setup) + rootData = self.wlt.createBackupString(resumeSetup) + + ### + def setup(self): + # This badBackup stuff was implemented to avoid making backups if there is + # an inconsistency in the data. Yes, this is like a goto! + if self.backupData == None: + LOGEXCEPT("Problem with private key and/or chaincode. Aborting.") + QMessageBox.critical(self, self.tr("Error Creating Backup"), self.tr( + 'There was an error with the backup creator. The operation is being ' + 'canceled to avoid making bad backups!'), QMessageBox.Ok) + return + + # A self-evident check of whether we need to print the chaincode. + # If we derive the chaincode from the private key, and it matches + # what's already in the wallet, we obviously don't need to print it! + self.noNeedChaincode = (len(self.backupData.chainclear) == 0) + + # Save off imported addresses in case they need to be printed, too + for a160, addr in self.wlt.addrMap.items(): + if addr.chainIndex == -2: + if addr.binPrivKey32_Plain.getSize() == 33 or addr.isCompressed(): + prv = addr.binPrivKey32_Plain.toBinStr()[:32] + self.binImport.append([a160, SecureBinaryData(prv), 1]) + prv = None + else: + self.binImport.append([a160, addr.binPrivKey32_Plain.copy(), 0]) + + + tempTxtItem = QGraphicsTextItem('') + tempTxtItem.setPlainText(toUnicode('0123QAZjqlmYy')) + tempTxtItem.setFont(GETFONT('Fix', 7)) + self.importHgt = tempTxtItem.boundingRect().height() - 5 + + + # Create the scene and the view. + self.scene = SimplePrintableGraphicsScene(self, self.main) + self.view = QGraphicsView() + self.view.setRenderHint(QPainter.TextAntialiasing) + self.view.setScene(self.scene.getScene()) + + + self.chkImportPrint = QCheckBox(self.tr('Print imported keys')) + self.chkImportPrint.clicked.connect(self.clickImportChk) + + self.lblPageStr = QRichLabel(self.tr('Page:')) + self.comboPageNum = QComboBox() + self.lblPageMaxStr = QRichLabel('') + self.comboPageNum.activated.connect(self.redrawBackup) + + # We enable printing of imported addresses but not frag'ing them.... way + # too much work for everyone (developer and user) to deal with 2x or 3x + # the amount of data to type + self.chkImportPrint.setVisible(len(self.binImport) > 0 and not self.doPrintFrag) + self.lblPageStr.setVisible(False) + self.comboPageNum.setVisible(False) + self.lblPageMaxStr.setVisible(False) + + self.chkSecurePrint = QCheckBox(self.tr(u'Use SecurePrint\u200b\u2122 to prevent exposing keys to printer or other ' + 'network devices')) + + if(self.doPrintFrag): + self.chkSecurePrint.setChecked(self.fragData['Secure']) + + self.ttipSecurePrint = self.main.createToolTipWidget(self.tr( + u'SecurePrint\u200b\u2122 encrypts your backup with a code displayed on ' + 'the screen, so that no other devices on your network see the sensitive ' + 'data when you send it to the printer. If you turn on ' + u'SecurePrint\u200b\u2122 you must write the code on the page after ' + 'it is done printing! There is no point in using this feature if ' + 'you copy the data by hand.')) + + self.lblSecurePrint = QRichLabel(self.tr( + u'IMPORTANT: You must write the SecurePrint\u200b\u2122 ' + u'encryption code on each printed backup page! Your SecurePrint\u200b\u2122 code is ' + '%s. Your backup will not work ' + 'if this code is lost!' % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ + self.backupData.sppass, htmlColor('TextWarn')))) + + self.chkSecurePrint.clicked.connect(self.redrawBackup) + + + self.btnPrint = QPushButton('&Print...') + self.btnPrint.setMinimumWidth(3 * tightSizeStr(self.btnPrint, 'Print...')[0]) + self.btnCancel = QPushButton('&Cancel') + self.btnPrint.clicked.connect(self.print_) + self.btnCancel.clicked.connect(self.accept) + + if self.doPrintFrag: + M, N = self.fragData['M'], self.fragData['N'] + lblDescr = QRichLabel(self.tr( + 'Print Wallet Backup Fragments

' + 'When any %s of these fragments are combined, all previous ' + 'and future addresses generated by this wallet will be ' + 'restored, giving you complete access to your bitcoins. The ' + 'data can be copied by hand if a working printer is not ' + 'available. Please make sure that all data lines contain ' + '9 columns ' + 'of 4 characters each (excluding "ID" lines).' % M)) + else: + withChain = '' if self.noNeedChaincode else 'and "Chaincode"' + lblDescr = QRichLabel(self.tr( + 'Print a Forever-Backup

' + 'Printing this sheet protects all previous and future addresses ' + 'generated by this wallet! You can copy the "Root Key" %s ' + 'by hand if a working printer is not available. Please make sure that ' + 'all data lines contain 9 columns ' + 'of 4 characters each.' % withChain)) + + lblDescr.setContentsMargins(5, 5, 5, 5) + frmDescr = makeHorizFrame([lblDescr], STYLE_RAISED) + + self.redrawBackup() + frmChkImport = makeHorizFrame([self.chkImportPrint, \ + STRETCH, \ + self.lblPageStr, \ + self.comboPageNum, \ + self.lblPageMaxStr]) + + frmSecurePrint = makeHorizFrame([self.chkSecurePrint, + self.ttipSecurePrint, + STRETCH]) + + frmButtons = makeHorizFrame([self.btnCancel, STRETCH, self.btnPrint]) + + layout = QVBoxLayout() + layout.addWidget(frmDescr) + layout.addWidget(frmChkImport) + layout.addWidget(self.view) + layout.addWidget(frmSecurePrint) + layout.addWidget(self.lblSecurePrint) + layout.addWidget(frmButtons) + setLayoutStretch(layout, 0, 1, 0, 0, 0) + + self.setLayout(layout) + + self.setWindowIcon(QIcon('./img/printer_icon.png')) + self.setWindowTitle('Print Wallet Backup') + + + # Apparently I can't programmatically scroll until after it's painted + def scrollTop(): + vbar = self.view.verticalScrollBar() + vbar.setValue(vbar.minimum()) + self.callLater(0.01, scrollTop) + + def redrawBackup(self): + cmbPage = 1 + if self.comboPageNum.count() > 0: + cmbPage = int(str(self.comboPageNum.currentText())) + + if self.doPrintFrag: + cmbPage -= 1 + if not self.doMultiFrag: + cmbPage = self.fragData['Range'][0] + elif self.comboPageNum.count() > 0: + cmbPage = int(str(self.comboPageNum.currentText())) - 1 + + self.createPrintScene('Fragmented Backup', cmbPage) + else: + pgSelect = cmbPage if self.chkImportPrint.isChecked() else 1 + if pgSelect == 1: + self.createPrintScene('SingleSheetFirstPage', '') + else: + pg = pgSelect - 2 + nKey = self.maxKeysPerPage + self.createPrintScene('SingleSheetImported', [pg * nKey, (pg + 1) * nKey]) + + + showPageCombo = self.chkImportPrint.isChecked() or \ + (self.doPrintFrag and self.doMultiFrag) + self.showPageSelect(showPageCombo) + self.view.update() + + + + + def clickImportChk(self): + if self.numImportPages > 1 and self.chkImportPrint.isChecked(): + ans = QMessageBox.warning(self, self.tr('Lots to Print!'), self.tr( + 'This wallet contains %d imported keys, which will require ' + '%d pages to print. Not only will this use a lot of paper, ' + 'it will be a lot of work to manually type in these keys in the ' + 'event that you need to restore this backup. It is recommended ' + 'that you do not print your imported keys and instead make ' + 'a digital backup, which can be restored instantly if needed. ' + '

Do you want to print the imported keys, anyway?' % (len(self.binImport), self.numImportPages)), \ + QMessageBox.Yes | QMessageBox.No) + if not ans == QMessageBox.Yes: + self.chkImportPrint.setChecked(False) + + showPageCombo = self.chkImportPrint.isChecked() or \ + (self.doPrintFrag and self.doMultiFrag) + self.showPageSelect(showPageCombo) + self.comboPageNum.setCurrentIndex(0) + self.redrawBackup() + + + def showPageSelect(self, doShow=True): + MARGIN = self.scene.MARGIN_PIXELS + bottomOfPage = self.scene.pageRect().height() + MARGIN + totalHgt = bottomOfPage - self.bottomOfSceneHeader + self.maxKeysPerPage = int(totalHgt / (self.importHgt)) + self.numImportPages = int((len(self.binImport) - 1) / self.maxKeysPerPage) + 1 + if self.comboPageNum.count() == 0: + if self.doPrintFrag: + numFrag = len(self.fragData['Range']) + for i in range(numFrag): + self.comboPageNum.addItem(str(i + 1)) + self.lblPageMaxStr.setText(self.tr('of %d' % numFrag)) + else: + for i in range(self.numImportPages + 1): + self.comboPageNum.addItem(str(i + 1)) + self.lblPageMaxStr.setText(self.tr('of %d' % (self.numImportPages + 1))) + + + self.lblPageStr.setVisible(doShow) + self.comboPageNum.setVisible(doShow) + self.lblPageMaxStr.setVisible(doShow) + + + + + def print_(self): + LOGINFO('Printing!') + self.printer = QPrinter(QPrinter.HighResolution) + self.printer.setPageSize(QPrinter.Letter) + + if QPrintDialog(self.printer).exec_(): + painter = QPainter(self.printer) + painter.setRenderHint(QPainter.TextAntialiasing) + + if self.doPrintFrag: + for i in self.fragData['Range']: + self.createPrintScene('Fragment', i) + self.scene.getScene().render(painter) + if not i == len(self.fragData['Range']) - 1: + self.printer.newPage() + + else: + self.createPrintScene('SingleSheetFirstPage', '') + self.scene.getScene().render(painter) + + if len(self.binImport) > 0 and self.chkImportPrint.isChecked(): + nKey = self.maxKeysPerPage + for i in range(self.numImportPages): + self.printer.newPage() + self.createPrintScene('SingleSheetImported', [i * nKey, (i + 1) * nKey]) + self.scene.getScene().render(painter) + + painter.end() + + # The last scene printed is what's displayed now. Set the combo box + self.comboPageNum.setCurrentIndex(self.comboPageNum.count() - 1) + + if self.chkSecurePrint.isChecked(): + QMessageBox.warning(self, self.tr('SecurePrint Code'), self.tr( + u'
You must write your SecurePrint\u200b\u2122 ' + 'code on each sheet of paper you just printed! ' + 'Write it in the red box in upper-right corner ' + u'of each printed page.

SecurePrint\u200b\u2122 code: ' + '%s

' + 'NOTE: the above code is case-sensitive!' % (htmlColor('TextBlue'), self.randpass.toBinStr())), \ + QMessageBox.Ok) + if self.chkSecurePrint.isChecked(): + self.btnCancel.setText('Done') + else: + self.accept() + + + def cleanup(self): + self.backupData = None + + for x, y in self.fragMtrxCrypt: + x.destroy() + y.destroy() + + def accept(self): + self.cleanup() + super(DlgPrintBackup, self).accept() + + def reject(self): + self.cleanup() + super(DlgPrintBackup, self).reject() + + + ############################################################################# + ############################################################################# + def createPrintScene(self, printType, printData): + self.scene.gfxScene.clear() + self.scene.resetCursor() + + pr = self.scene.pageRect() + self.scene.drawRect(pr.width(), pr.height(), edgeColor=None, fillColor=QColor(255, 255, 255)) + self.scene.resetCursor() + + + INCH = self.scene.INCH + MARGIN = self.scene.MARGIN_PIXELS + + doMask = self.chkSecurePrint.isChecked() + + if USE_TESTNET or USE_REGTEST: + self.scene.drawPixmapFile('./img/armory_logo_green_h56.png') + else: + self.scene.drawPixmapFile('./img/armory_logo_h36.png') + self.scene.newLine() + + self.scene.drawText('Paper Backup for Armory Wallet', GETFONT('Var', 11)) + self.scene.newLine() + + self.scene.newLine(extra_dy=20) + self.scene.drawHLine() + self.scene.newLine(extra_dy=20) + + + ssType = self.tr(u' (SecurePrint\u200b\u2122)') if doMask else self.tr(' (Unencrypted)') + if printType == 'SingleSheetFirstPage': + bType = self.tr('Single-Sheet') # %s' % ssType) + elif printType == 'SingleSheetImported': + bType = self.tr('Imported Keys %s' % ssType) + elif printType.lower().startswith('frag'): + m_count = str(self.fragData['M']) + n_count = str(self.fragData['N']) + bstr = self.tr('Fragmented Backup (%s-of-%s)' % (m_count, n_count)) + bType = bstr + ' ' + ssType + + if printType.startswith('SingleSheet'): + colRect, rowHgt = self.scene.drawColumn(['Wallet Version:', 'Wallet ID:', \ + 'Wallet Name:', 'Backup Type:']) + self.scene.moveCursor(15, 0) + suf = 'c' if self.noNeedChaincode else 'a' + colRect, rowHgt = self.scene.drawColumn(['1.35' + suf, self.wlt.uniqueIDB58, \ + self.wlt.labelName, bType]) + self.scene.moveCursor(15, colRect.y() + colRect.height(), absolute=True) + else: + colRect, rowHgt = self.scene.drawColumn(['Wallet Version:', 'Wallet ID:', \ + 'Wallet Name:', 'Backup Type:', \ + 'Fragment:']) + baseID = self.fragData['FragIDStr'] + fragNum = printData + 1 + fragID = '%s-#%d' % (baseID, htmlColor('TextBlue'), fragNum) + self.scene.moveCursor(15, 0) + suf = 'c' if self.noNeedChaincode else 'a' + colRect, rowHgt = self.scene.drawColumn(['1.35' + suf, self.wlt.uniqueIDB58, \ + self.wlt.labelName, bType, fragID]) + self.scene.moveCursor(15, colRect.y() + colRect.height(), absolute=True) + + + # Display warning about unprotected key data + wrap = 0.9 * self.scene.pageRect().width() + + if self.doPrintFrag: + warnMsg = self.tr( + 'Any subset of %s fragments with this ' + 'ID (%s) are sufficient to recover all the ' + 'coins contained in this wallet. To optimize the physical security of ' + 'your wallet, please store the fragments in different locations.' % (htmlColor('TextBlue'), \ + str(self.fragData['M']), htmlColor('TextBlue'), self.fragData['FragIDStr'])) + else: + container = 'this wallet' if printType == 'SingleSheetFirstPage' else 'these addresses' + warnMsg = self.tr( + 'WARNING: Anyone who has access to this ' + 'page has access to all the bitcoins in %s! Please keep this ' + 'page in a safe place.' % container) + + self.scene.newLine() + self.scene.drawText(warnMsg, GETFONT('Var', 9), wrapWidth=wrap) + + self.scene.newLine(extra_dy=20) + self.scene.drawHLine() + self.scene.newLine(extra_dy=20) + + if self.doPrintFrag: + numLine = 'three' if self.noNeedChaincode else 'five' + else: + numLine = 'two' if self.noNeedChaincode else 'four' + + if printType == 'SingleSheetFirstPage': + descrMsg = self.tr( + 'The following %s lines backup all addresses ' + 'ever generated by this wallet (previous and future). ' + 'This can be used to recover your wallet if you forget your passphrase or ' + 'suffer hardware failure and lose your wallet files.' % numLine) + elif printType == 'SingleSheetImported': + if self.chkSecurePrint.isChecked(): + descrMsg = self.tr( + 'The following is a list of all private keys imported into your ' + 'wallet before this backup was made. These keys are encrypted ' + u'with the SecurePrint\u200b\u2122 code and can only be restored ' + 'by entering them into Armory. Print a copy of this backup without ' + u'the SecurePrint\u200b\u2122 option if you want to be able to import ' + 'them into another application.') + else: + descrMsg = self.tr( + 'The following is a list of all private keys imported into your ' + 'wallet before this backup was made. Each one must be copied ' + 'manually into the application where you wish to import them.') + elif printType.lower().startswith('frag'): + fragNum = printData + 1 + descrMsg = self.tr( + 'The following is fragment #%s for this ' + 'wallet.' % (htmlColor('TextBlue'), str(printData + 1))) + + + self.scene.drawText(descrMsg, GETFONT('var', 8), wrapWidth=wrap) + self.scene.newLine(extra_dy=10) + + ########################################################################### + # Draw the SecurePrint box if needed, frag pie, then return cursor + prevCursor = self.scene.getCursorXY() + + self.lblSecurePrint.setVisible(doMask) + if doMask: + self.scene.resetCursor() + self.scene.moveCursor(4.0 * INCH, 0) + spWid, spHgt = 2.75 * INCH, 1.5 * INCH, + if doMask: + self.scene.drawRect(spWid, spHgt, edgeColor=QColor(180, 0, 0), penWidth=3) + + self.scene.resetCursor() + self.scene.moveCursor(4.07 * INCH, 0.07 * INCH) + + self.scene.drawText(self.tr( + 'CRITICAL: This backup will not ' + u'work without the SecurePrint\u200b\u2122 ' + 'code displayed on the screen during printing. ' + 'Copy it here in ink:'), wrapWidth=spWid * 0.93, font=GETFONT('Var', 7)) + + self.scene.newLine(extra_dy=8) + self.scene.moveCursor(4.07 * INCH, 0) + codeWid, codeHgt = self.scene.drawText('Code:') + self.scene.moveCursor(0, codeHgt - 3) + wid = spWid - codeWid + w, h = self.scene.drawHLine(width=wid * 0.9, penWidth=2) + + + + # Done drawing other stuff, so return to the original drawing location + self.scene.moveCursor(*prevCursor, absolute=True) + ########################################################################### + + + ########################################################################### + # Finally, draw the backup information. + + # If this page is only imported addresses, draw them then bail + self.bottomOfSceneHeader = self.scene.cursorPos.y() + if printType == 'SingleSheetImported': + self.scene.moveCursor(0, 0.1 * INCH) + importList = self.binImport + if self.chkSecurePrint.isChecked(): + importList = self.binImportCrypt + + for a160, priv, isCompr in importList[printData[0]:printData[1]]: + comprByte = ('\x01' if isCompr == 1 else '') + prprv = encodePrivKeyBase58(priv.toBinStr() + comprByte) + toPrint = [prprv[i * 6:(i + 1) * 6] for i in range((len(prprv) + 5) / 6)] + addrHint = ' (%s...)' % hash160_to_addrStr(a160)[:12] + self.scene.drawText(' '.join(toPrint), GETFONT('Fix', 7)) + self.scene.moveCursor(0.02 * INCH, 0) + self.scene.drawText(addrHint, GETFONT('Var', 7)) + self.scene.newLine(extra_dy=-3) + prprv = None + return + + + if self.doPrintFrag: + M = self.fragData['M'] + Lines = [] + Prefix = [] + fmtrx = self.fragMtrxCrypt if doMask else self.fragMtrx + + try: + yBin = fmtrx[printData][1] + binID = base58_to_binary(self.fragData['fragSetID']) + IDLine = ComputeFragIDLineHex(M, printData, binID, doMask, addSpaces=True) + if len(yBin) == 32: + Prefix.append('ID:'); Lines.append(IDLine) + Prefix.append('F1:'); Lines.append(makeSixteenBytesEasy(yBin[:16 ])) + Prefix.append('F2:'); Lines.append(makeSixteenBytesEasy(yBin[ 16:])) + elif len(yBin) == 64: + Prefix.append('ID:'); Lines.append(IDLine) + Prefix.append('F1:'); Lines.append(makeSixteenBytesEasy(yBin[:16 ])) + Prefix.append('F2:'); Lines.append(makeSixteenBytesEasy(yBin[ 16:32 ])) + Prefix.append('F3:'); Lines.append(makeSixteenBytesEasy(yBin[ 32:48 ])) + Prefix.append('F4:'); Lines.append(makeSixteenBytesEasy(yBin[ 48:])) + else: + LOGERROR('yBin is not 32 or 64 bytes! It is %s bytes', len(yBin)) + finally: + yBin = None + + else: + # Single-sheet backup + if doMask: + code12 = self.backupData.rootencr + code34 = self.backupData.chainencr + else: + code12 = self.backupData.rootclear + code34 = self.backupData.chainclear + + + Lines = [] + Prefix = [] + Prefix.append('Root Key:'); Lines.append(code12[0]) + Prefix.append(''); Lines.append(code12[1]) + if not self.noNeedChaincode: + Prefix.append('Chaincode:'); Lines.append(code34[0]) + Prefix.append(''); Lines.append(code34[1]) + + # Draw the prefix + origX, origY = self.scene.getCursorXY() + self.scene.moveCursor(20, 0) + colRect, rowHgt = self.scene.drawColumn(['' + l + '' for l in Prefix]) + + nudgeDown = 2 # because the differing font size makes it look unaligned + self.scene.moveCursor(20, nudgeDown) + self.scene.drawColumn(Lines, + font=GETFONT('Fixed', 8, bold=True), \ + rowHeight=rowHgt, + useHtml=False) + + self.scene.moveCursor(MARGIN, colRect.y() - 2, absolute=True) + width = self.scene.pageRect().width() - 2 * MARGIN + self.scene.drawRect(width, colRect.height() + 7, edgeColor=QColor(0, 0, 0), fillColor=None) + + self.scene.newLine(extra_dy=30) + self.scene.drawText(self.tr( + 'The following QR code is for convenience only. It contains the ' + 'exact same data as the %s lines above. If you copy this backup ' + 'by hand, you can safely ignore this QR code.' % numLine), wrapWidth=4 * INCH) + + self.scene.moveCursor(20, 0) + x, y = self.scene.getCursorXY() + edgeRgt = self.scene.pageRect().width() - MARGIN + edgeBot = self.scene.pageRect().height() - MARGIN + + qrSize = max(1.5 * INCH, min(edgeRgt - x, edgeBot - y, 2.0 * INCH)) + self.scene.drawQR('\n'.join(Lines), qrSize) + self.scene.newLine(extra_dy=25) + + Lines = None + + # Finally, draw some pie slices at the bottom + if self.doPrintFrag: + M, N = self.fragData['M'], self.fragData['N'] + bottomOfPage = self.scene.pageRect().height() + MARGIN + maxPieHeight = bottomOfPage - self.scene.getCursorXY()[1] - 8 + maxPieWidth = int((self.scene.pageRect().width() - 2 * MARGIN) / N) - 10 + pieSize = min(72., maxPieHeight, maxPieWidth) + for i in range(N): + startX, startY = self.scene.getCursorXY() + drawSize = self.scene.drawPixmapFile('./img/frag%df.png' % M, sizePx=pieSize) + self.scene.moveCursor(10, 0) + if i == printData: + returnX, returnY = self.scene.getCursorXY() + self.scene.moveCursor(startX, startY, absolute=True) + self.scene.moveCursor(-5, -5) + self.scene.drawRect(drawSize[0] + 10, \ + drawSize[1] + 10, \ + edgeColor=Colors.TextBlue, \ + penWidth=3) + self.scene.newLine() + self.scene.moveCursor(startX - MARGIN, 0) + self.scene.drawText('#%d' % \ + (htmlColor('TextBlue'), fragNum), GETFONT('Var', 10)) + self.scene.moveCursor(returnX, returnY, absolute=True) + + + + vbar = self.view.verticalScrollBar() + vbar.setValue(vbar.minimum()) + self.view.update() + +################################################################################ +class DlgFragBackup(ArmoryDialog): + + ############################################################################# + def __init__(self, parent, main, wlt): + super(DlgFragBackup, self).__init__(parent, main) + + self.wlt = wlt + + lblDescrTitle = QRichLabel(self.tr( + 'Create M-of-N Fragmented Backup of "%s" (%s)' % (wlt.labelName, wlt.uniqueIDB58)), doWrap=False) + lblDescrTitle.setContentsMargins(5, 5, 5, 5) + + self.lblAboveFrags = QRichLabel('') + self.lblAboveFrags.setContentsMargins(10, 0, 10, 0) + + frmDescr = makeVertFrame([lblDescrTitle, self.lblAboveFrags], \ + STYLE_RAISED) + + self.fragDisplayLastN = 0 + self.fragDisplayLastM = 0 + + self.maxM = 5 if not self.main.usermode == USERMODE.Expert else 8 + self.maxN = 6 if not self.main.usermode == USERMODE.Expert else 12 + self.currMinN = 2 + self.maxmaxN = 12 + + self.comboM = QComboBox() + self.comboN = QComboBox() + + for M in range(2, self.maxM + 1): + self.comboM.addItem(str(M)) + + for N in range(self.currMinN, self.maxN + 1): + self.comboN.addItem(str(N)) + + self.comboM.setCurrentIndex(1) + self.comboN.setCurrentIndex(2) + + def updateM(): + self.updateComboN() + self.createFragDisplay() + + updateN = self.createFragDisplay + + self.connect(self.comboM, SIGNAL('activated(int)'), updateM) + self.connect(self.comboN, SIGNAL('activated(int)'), updateN) + self.comboM.setMinimumWidth(30) + self.comboN.setMinimumWidth(30) + + btnAccept = QPushButton(self.tr('Close')) + self.connect(btnAccept, SIGNAL(CLICKED), self.accept) + frmBottomBtn = makeHorizFrame([STRETCH, btnAccept]) + + # We will hold all fragments here, in SBD objects. Destroy all of them + # before the dialog exits + self.secureRoot = self.wlt.addrMap['ROOT'].binPrivKey32_Plain.copy() + self.secureChain = self.wlt.addrMap['ROOT'].chaincode.copy() + self.secureMtrx = [] + + testChain = DeriveChaincodeFromRootKey(self.secureRoot) + if testChain == self.secureChain: + self.noNeedChaincode = True + self.securePrint = self.secureRoot + else: + self.securePrint = self.secureRoot + self.secureChain + + self.chkSecurePrint = QCheckBox(self.tr(u'Use SecurePrint\u200b\u2122 ' + 'to prevent exposing keys to printer or other devices')) + + self.scrollArea = QScrollArea() + self.createFragDisplay() + self.scrollArea.setWidgetResizable(True) + + self.ttipSecurePrint = self.main.createToolTipWidget(self.tr( + u'SecurePrint\u200b\u2122 encrypts your backup with a code displayed on ' + 'the screen, so that no other devices or processes has access to the ' + 'unencrypted private keys (either network devices when printing, or ' + 'other applications if you save a fragment to disk or USB device). ' + u'You must keep the SecurePrint\u200b\u2122 code with the backup!')) + self.lblSecurePrint = QRichLabel(self.tr( + 'IMPORTANT: You must keep the ' + u'SecurePrint\u200b\u2122 encryption code with your backup! ' + u'Your SecurePrint\u200b\u2122 code is ' + '%s. ' + 'All fragments for a given wallet use the ' + 'same code.' % (htmlColor('TextWarn'), htmlColor('TextBlue'), self.backupData.sppass, \ + htmlColor('TextWarn')))) + self.connect(self.chkSecurePrint, SIGNAL(CLICKED), self.clickChkSP) + self.chkSecurePrint.setChecked(False) + self.lblSecurePrint.setVisible(False) + frmChkSP = makeHorizFrame([self.chkSecurePrint, self.ttipSecurePrint, STRETCH]) + + dlgLayout = QVBoxLayout() + dlgLayout.addWidget(frmDescr) + dlgLayout.addWidget(self.scrollArea) + dlgLayout.addWidget(frmChkSP) + dlgLayout.addWidget(self.lblSecurePrint) + dlgLayout.addWidget(frmBottomBtn) + setLayoutStretch(dlgLayout, 0, 1, 0, 0, 0) + + self.setLayout(dlgLayout) + self.setMinimumWidth(650) + self.setMinimumHeight(450) + self.setWindowTitle('Create Backup Fragments') + + + ############################################################################# + def clickChkSP(self): + self.lblSecurePrint.setVisible(self.chkSecurePrint.isChecked()) + self.createFragDisplay() + + + ############################################################################# + def updateComboN(self): + M = int(str(self.comboM.currentText())) + oldN = int(str(self.comboN.currentText())) + self.currMinN = M + self.comboN.clear() + + for i, N in enumerate(range(self.currMinN, self.maxN + 1)): + self.comboN.addItem(str(N)) + + if M > oldN: + self.comboN.setCurrentIndex(0) + else: + for i, N in enumerate(range(self.currMinN, self.maxN + 1)): + if N == oldN: + self.comboN.setCurrentIndex(i) + + + + ############################################################################# + def createFragDisplay(self): + M = int(str(self.comboM.currentText())) + N = int(str(self.comboN.currentText())) + + #only recompute fragments if M or N changed + if self.fragDisplayLastN != N or \ + self.fragDisplayLastM != M: + self.recomputeFragData() + + self.fragDisplayLastN = N + self.fragDisplayLastM = M + + lblAboveM = QRichLabel(self.tr('Required Fragments '), hAlign=Qt.AlignHCenter, doWrap=False) + lblAboveN = QRichLabel(self.tr('Total Fragments '), hAlign=Qt.AlignHCenter) + frmComboM = makeHorizFrame([STRETCH, QLabel('M:'), self.comboM, STRETCH]) + frmComboN = makeHorizFrame([STRETCH, QLabel('N:'), self.comboN, STRETCH]) + + btnPrintAll = QPushButton(self.tr('Print All Fragments')) + btnPrintAll.connect.clicked(self.clickPrintAll) + leftFrame = makeVertFrame([STRETCH, \ + lblAboveM, \ + frmComboM, \ + lblAboveN, \ + frmComboN, \ + STRETCH, \ + HLINE(), \ + btnPrintAll, \ + STRETCH], STYLE_STYLED) + + layout = QHBoxLayout() + layout.addWidget(leftFrame) + + for f in range(N): + layout.addWidget(self.createFragFrm(f)) + + + frmScroll = QFrame() + frmScroll.setFrameStyle(STYLE_SUNKEN) + frmScroll.setStyleSheet('QFrame { background-color : %s }' % \ + htmlColor('SlightBkgdDark')) + frmScroll.setLayout(layout) + self.scrollArea.setWidget(frmScroll) + + BLUE = htmlColor('TextBlue') + self.lblAboveFrags.setText(self.tr( + 'Any %d of these ' + '%d' + 'fragments are sufficient to restore your wallet, and each fragment ' + 'has the ID, %s. All fragments with the ' + 'same fragment ID are compatible with each other!' % (BLUE, M, BLUE, N, BLUE, self.fragPrefixStr))) + + + ############################################################################# + def createFragFrm(self, idx): + + doMask = self.chkSecurePrint.isChecked() + M = int(str(self.comboM.currentText())) + N = int(str(self.comboN.currentText())) + + lblFragID = QRichLabel(self.tr('Fragment ID:
%s-%s
' % \ + (str(self.fragPrefixStr), str(idx + 1)))) + # lblWltID = QRichLabel('(%s)' % self.wlt.uniqueIDB58) + lblFragPix = QImageLabel(self.fragPixmapFn, size=(72, 72)) + if doMask: + ys = self.secureMtrxCrypt[idx][1].toBinStr()[:42] + else: + ys = self.secureMtrx[idx][1].toBinStr()[:42] + + easyYs1 = makeSixteenBytesEasy(ys[:16 ]) + easyYs2 = makeSixteenBytesEasy(ys[ 16:32]) + + binID = base58_to_binary(self.uniqueFragSetID) + ID = ComputeFragIDLineHex(M, idx, binID, doMask, addSpaces=True) + + fragPreview = 'ID: %s...
' % ID[:12] + fragPreview += 'F1: %s...
' % easyYs1[:12] + fragPreview += 'F2: %s... ' % easyYs2[:12] + lblPreview = QRichLabel(fragPreview) + lblPreview.setFont(GETFONT('Fixed', 9)) + + lblFragIdx = QRichLabel('#%d' % (idx + 1), size=4, color='TextBlue', \ + hAlign=Qt.AlignHCenter) + + frmTopLeft = makeVertFrame([lblFragID, lblFragIdx, STRETCH]) + frmTopRight = makeVertFrame([lblFragPix, STRETCH]) + + frmPaper = makeVertFrame([lblPreview]) + frmPaper.setStyleSheet('QFrame { background-color : #ffffff }') + + fnPrint = lambda: self.clickPrintFrag(idx) + fnSave = lambda: self.clickSaveFrag(idx) + + btnPrintFrag = QPushButton(self.tr('View/Print')) + btnSaveFrag = QPushButton(self.tr('Save to File')) + self.connect(btnPrintFrag, SIGNAL(CLICKED), fnPrint) + self.connect(btnSaveFrag, SIGNAL(CLICKED), fnSave) + frmButtons = makeHorizFrame([btnPrintFrag, btnSaveFrag]) + + + layout = QGridLayout() + layout.addWidget(frmTopLeft, 0, 0, 1, 1) + layout.addWidget(frmTopRight, 0, 1, 1, 1) + layout.addWidget(frmPaper, 1, 0, 1, 2) + layout.addWidget(frmButtons, 2, 0, 1, 2) + layout.setSizeConstraint(QLayout.SetFixedSize) + + outFrame = QFrame() + outFrame.setFrameStyle(STYLE_STYLED) + outFrame.setLayout(layout) + return outFrame + + + ############################################################################# + def clickPrintAll(self): + self.clickPrintFrag(range(int(str(self.comboN.currentText())))) + + ############################################################################# + def clickPrintFrag(self, zindex): + if not isinstance(zindex, (list, tuple)): + zindex = [zindex] + fragData = {} + fragData['M'] = int(str(self.comboM.currentText())) + fragData['N'] = int(str(self.comboN.currentText())) + fragData['FragIDStr'] = self.fragPrefixStr + fragData['FragPixmap'] = self.fragPixmapFn + fragData['Range'] = zindex + fragData['Secure'] = self.chkSecurePrint.isChecked() + fragData['fragSetID'] = self.uniqueFragSetID + dlg = DlgPrintBackup(self, self.main, self.wlt, 'Fragments', \ + self.secureMtrx, self.secureMtrxCrypt, fragData, \ + self.secureRoot, self.secureChain) + dlg.exec_() + + ############################################################################# + def clickSaveFrag(self, zindex): + saveMtrx = self.secureMtrx + doMask = False + if self.chkSecurePrint.isChecked(): + response = QMessageBox.question(self, self.tr('Secure Backup?'), self.tr( + u'You have selected to use SecurePrint\u200b\u2122 for the printed ' + 'backups, which can also be applied to fragments saved to file. ' + u'Doing so will require you store the SecurePrint\u200b\u2122 ' + 'code with the backup, but it will prevent unencrypted key data from ' + 'touching any disks.

Do you want to encrypt the fragment ' + u'file with the same SecurePrint\u200b\u2122 code?'), \ + QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) + + if response == QMessageBox.Yes: + saveMtrx = self.secureMtrxCrypt + doMask = True + elif response == QMessageBox.No: + pass + else: + return + + + wid = self.wlt.uniqueIDB58 + pref = self.fragPrefixStr + fnum = zindex + 1 + M = self.M + sec = 'secure.' if doMask else '' + defaultFn = 'wallet_%s_%s_num%d_need%d.%sfrag' % (wid, pref, fnum, M, sec) + #print 'FragFN:', defaultFn + savepath = self.main.getFileSave('Save Fragment', \ + ['Wallet Fragments (*.frag)'], \ + defaultFn) + + if len(toUnicode(savepath)) == 0: + return + + fout = open(savepath, 'w') + fout.write('Wallet ID: %s\n' % wid) + fout.write('Create Date: %s\n' % unixTimeToFormatStr(RightNow())) + fout.write('Fragment ID: %s-#%d\n' % (pref, fnum)) + fout.write('Frag Needed: %d\n' % M) + fout.write('\n\n') + + try: + yBin = saveMtrx[zindex][1].toBinStr() + binID = base58_to_binary(self.uniqueFragSetID) + IDLine = ComputeFragIDLineHex(M, zindex, binID, doMask, addSpaces=True) + if len(yBin) == 32: + fout.write('ID: ' + IDLine + '\n') + fout.write('F1: ' + makeSixteenBytesEasy(yBin[:16 ]) + '\n') + fout.write('F2: ' + makeSixteenBytesEasy(yBin[ 16:]) + '\n') + elif len(yBin) == 64: + fout.write('ID: ' + IDLine + '\n') + fout.write('F1: ' + makeSixteenBytesEasy(yBin[:16 ]) + '\n') + fout.write('F2: ' + makeSixteenBytesEasy(yBin[ 16:32 ]) + '\n') + fout.write('F3: ' + makeSixteenBytesEasy(yBin[ 32:48 ]) + '\n') + fout.write('F4: ' + makeSixteenBytesEasy(yBin[ 48:]) + '\n') + else: + LOGERROR('yBin is not 32 or 64 bytes! It is %s bytes', len(yBin)) + finally: + yBin = None + + fout.close() + + qmsg = self.tr( + 'The fragment was successfully saved to the following location: ' + '

%s

' % savepath) + + if doMask: + qmsg += self.tr( + 'Important: ' + 'The fragment was encrypted with the ' + u'SecurePrint\u200b\u2122 encryption code. You must keep this ' + 'code with the backup in order to use it! The code is ' + 'case-sensitive! ' + '

%s' + '

' + 'The above code is case-sensitive!' \ + % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ + self.backupData.sppass)) + + QMessageBox.information(self, self.tr('Success'), qmsg, QMessageBox.Ok) + + + + ############################################################################# + def destroyFrags(self): + if len(self.secureMtrx) == 0: + return + + if isinstance(self.secureMtrx[0], (list, tuple)): + for sbdList in self.secureMtrx: + for sbd in sbdList: + sbd.destroy() + for sbdList in self.secureMtrxCrypt: + for sbd in sbdList: + sbd.destroy() + else: + for sbd in self.secureMtrx: + sbd.destroy() + for sbd in self.secureMtrxCrypt: + sbd.destroy() + + self.secureMtrx = [] + self.secureMtrxCrypt = [] + + + ############################################################################# + def destroyEverything(self): + self.secureRoot.destroy() + self.secureChain.destroy() + self.securePrint.destroy() + self.destroyFrags() + + ############################################################################# + def recomputeFragData(self): + """ + Only M is needed, since N doesn't change + """ + + M = int(str(self.comboM.currentText())) + N = int(str(self.comboN.currentText())) + # Make sure only local variables contain non-SBD data + self.destroyFrags() + self.uniqueFragSetID = \ + binary_to_base58(SecureBinaryData().GenerateRandom(6).toBinStr()) + insecureData = SplitSecret(self.securePrint, M, self.maxmaxN) + for x, y in insecureData: + self.secureMtrx.append([SecureBinaryData(x), SecureBinaryData(y)]) + insecureData, x, y = None, None, None + + ##### + # Now we compute the SecurePrint(TM) versions of the fragments + SECPRINT = HardcodedKeyMaskParams() + MASK = lambda x: SECPRINT['FUNC_MASK'](x, ekey=self.binCrypt32) + if not self.randpass or not self.binCrypt32: + self.randpass = SECPRINT['FUNC_PWD'](self.secureRoot + self.secureChain) + self.binCrypt32 = SECPRINT['FUNC_KDF'](self.randpass) + self.secureMtrxCrypt = [] + for sbdX, sbdY in self.secureMtrx: + self.secureMtrxCrypt.append([sbdX.copy(), MASK(sbdY)]) + ##### + + self.M, self.N = M, N + self.fragPrefixStr = ComputeFragIDBase58(self.M, \ + base58_to_binary(self.uniqueFragSetID)) + self.fragPixmapFn = './img/frag%df.png' % M + + + ############################################################################# + def accept(self): + self.destroyEverything() + super(DlgFragBackup, self).accept() + + ############################################################################# + def reject(self): + self.destroyEverything() + super(DlgFragBackup, self).reject() diff --git a/qtdialogs/DlgChangePassphrase.py b/qtdialogs/DlgChangePassphrase.py index 5da7e933f..de20a64df 100644 --- a/qtdialogs/DlgChangePassphrase.py +++ b/qtdialogs/DlgChangePassphrase.py @@ -10,7 +10,8 @@ # # ############################################################################## -from PySide2.QtWidgets import QCheckBox, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QMessageBox, QPushButton +from PySide2.QtWidgets import QCheckBox, QDialogButtonBox, QGridLayout, \ + QLabel, QLineEdit, QMessageBox, QPushButton from PySide2.QtGui import QIcon from PySide2.QtCore import Qt, SIGNAL, SLOT @@ -114,8 +115,7 @@ def checkPassphrase(self): p2 = self.edtPasswd2.text() goodColor = htmlColor('TextGreen') badColor = htmlColor('TextRed') - if not isASCII(p1) or \ - ot isASCII(p2): + if not isASCII(p1) or not isASCII(p2): self.lblMatches.setText(self.tr('Passphrase is non-ASCII!' % badColor)) return False if not p1 == p2: diff --git a/qtdialogs/DlgReplaceWallet.py b/qtdialogs/DlgReplaceWallet.py index defbee121..3149cca4c 100644 --- a/qtdialogs/DlgReplaceWallet.py +++ b/qtdialogs/DlgReplaceWallet.py @@ -12,11 +12,9 @@ import os -from PySide2.QtCore import SIGNAL from PySide2.QtWidgets import QGridLayout, QLabel, QPushButton from armoryengine.ArmoryUtils import LOGEXCEPT, RightNowStr -from armoryengine.PyBtcWalletRecovery import RECOVERMODE from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.DlgProgress import DlgProgress @@ -24,7 +22,7 @@ ################################################################################ class DlgReplaceWallet(ArmoryDialog): - ############################################################################# + ############################################################################# def __init__(self, WalletID, parent, main): super(DlgReplaceWallet, self).__init__(parent, main) @@ -46,9 +44,9 @@ def __init__(self, WalletID, parent, main): self.btnReplace = QPushButton(self.tr('Overwrite')) self.btnSaveMeta = QPushButton(self.tr('Merge')) - self.connect(self.btnAbort, SIGNAL('clicked()'), self.reject) - self.connect(self.btnReplace, SIGNAL('clicked()'), self.Replace) - self.connect(self.btnSaveMeta, SIGNAL('clicked()'), self.SaveMeta) + self.btnAbort.clicked.connect(self.reject) + self.btnReplace.clicked.connect(self.Replace) + self.btnSaveMeta.clicked.connect(self.SaveMeta) layoutDlg = QGridLayout() @@ -60,7 +58,7 @@ def __init__(self, WalletID, parent, main): self.setLayout(layoutDlg) self.setWindowTitle('Wallet already exists') - ######### + ######### def Replace(self): self.main.removeWalletFromApplication(self.WalletID) @@ -88,8 +86,10 @@ def Replace(self): self.output =1 self.accept() - ######### + ######### def SaveMeta(self): + raise Exception("regression, fix me") + ''' from armoryengine.PyBtcWalletRecovery import PyBtcWalletRecovery metaProgress = DlgProgress(self, self.main, Title=self.tr('Ripping Meta Data')) @@ -98,4 +98,5 @@ def SaveMeta(self): WalletPath=self.wltPath, Mode=RECOVERMODE.Meta, Progress=metaProgress.UpdateText) - self.Replace() \ No newline at end of file + self.Replace() + ''' \ No newline at end of file diff --git a/qtdialogs/DlgRestoreFragged.py b/qtdialogs/DlgRestoreFragged.py index 1e647eef8..140106ee1 100644 --- a/qtdialogs/DlgRestoreFragged.py +++ b/qtdialogs/DlgRestoreFragged.py @@ -585,3 +585,4 @@ def privAndChainFromRow(secret): subsAndIDs = [(row[0], calcWalletIDFromRoot(*row[1])) for row in results] DlgShowTestResults(self, isRandom, subsAndIDs, \ + M, len(fragMtrx), self.testWltID).exec_() diff --git a/qtdialogs/DlgRestoreSingle.py b/qtdialogs/DlgRestoreSingle.py index 7e9f04c81..0baacc7a0 100644 --- a/qtdialogs/DlgRestoreSingle.py +++ b/qtdialogs/DlgRestoreSingle.py @@ -13,9 +13,9 @@ from PySide2.QtWidgets import QButtonGroup, QCheckBox, QDialogButtonBox, \ QFrame, QGridLayout, QLabel, QLayout, QLineEdit, QMessageBox, \ QPushButton, QRadioButton, QTabWidget, QVBoxLayout -from PySide2.QtCore import SIGNAL from armoryengine import ClientProto_pb2 +from armoryengine.CppBridge import TheBridge from armoryengine.ArmoryUtils import LOGERROR, UINT32_MAX, UINT8_MAX from armoryengine.BDM import TheBDM from armoryengine.PyBtcWallet import PyBtcWallet @@ -31,7 +31,7 @@ ################################################################################ class DlgRestoreSingle(ArmoryDialog): - ############################################################################# + ############################################################################# def __init__(self, parent, main, thisIsATest=False, expectWltID=None): super(DlgRestoreSingle, self).__init__(parent, main) @@ -61,9 +61,9 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): self.version135Button = QRadioButton(self.tr('Version 1.35 (4 lines)'), self) self.version135aButton = QRadioButton(self.tr('Version 1.35a (4 lines Unencrypted)'), self) - self.version135aSPButton = QRadioButton(self.trUtf8(u'Version 1.35a (4 lines + SecurePrint\u200b\u2122)'), self) + self.version135aSPButton = QRadioButton(self.tr(u'Version 1.35a (4 lines + SecurePrint\u200b\u2122)'), self) self.version135cButton = QRadioButton(self.tr('Version 1.35c (2 lines Unencrypted)'), self) - self.version135cSPButton = QRadioButton(self.trUtf8(u'Version 1.35c (2 lines + SecurePrint\u200b\u2122)'), self) + self.version135cSPButton = QRadioButton(self.tr(u'Version 1.35c (2 lines + SecurePrint\u200b\u2122)'), self) self.backupTypeButtonGroup = QButtonGroup(self) self.backupTypeButtonGroup.addButton(self.version135Button) self.backupTypeButtonGroup.addButton(self.version135aButton) @@ -86,7 +86,7 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): frmBackupType = makeVertFrame([lblType, radioButtonFrame]) - self.lblSP = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code:'), doWrap=False) + self.lblSP = QRichLabel(self.tr(u'SecurePrint\u200b\u2122 Code:'), doWrap=False) self.editSecurePrint = QLineEdit() self.prfxList = [QLabel(self.tr('Root Key:')), QLabel(''), QLabel(self.tr('Chaincode:')), QLabel('')] @@ -109,8 +109,8 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): self.btnAccept = QPushButton(doItText) self.btnCancel = QPushButton(self.tr("Cancel")) - self.connect(self.btnAccept, SIGNAL("clicked()"), self.verifyUserInput) - self.connect(self.btnCancel, SIGNAL("clicked()"), self.reject) + self.btnAccept.clicked.connect(self.verifyUserInput) + self.btnCancel.clicked.connect(self.reject) buttonBox = QDialogButtonBox() buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) @@ -146,22 +146,22 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): self.layout().setSizeConstraint(QLayout.SetFixedSize) self.changeType(self.backupTypeButtonGroup.checkedId()) - ############################################################################# - # Hide advanced options whenver the restored wallet is unencrypted + ############################################################################# + # Hide advanced options whenver the restored wallet is unencrypted def onEncryptCheckboxChange(self): self.advancedOptionsTab.setEnabled(self.chkEncrypt.isChecked()) - ############################################################################# + ############################################################################# def accept(self): TheBDM.unregisterCustomPrompt(self.callbackId) super(ArmoryDialog, self).accept() - ############################################################################# + ############################################################################# def reject(self): TheBDM.unregisterCustomPrompt(self.callbackId) super(ArmoryDialog, self).reject() - ############################################################################# + ############################################################################# def changeType(self, sel): if sel == self.backupTypeButtonGroup.id(self.version135Button): visList = [0, 1, 1, 1, 1] @@ -185,7 +185,7 @@ def changeType(self, sel): self.isLongForm = (visList[-1] == 1) - ############################################################################# + ############################################################################# def processCallback(self, payload, callerId): if callerId == UINT32_MAX: @@ -217,9 +217,9 @@ def processCallback(self, payload, callerId): if extra != None: reply.extra = bytes(extra, 'utf-8') -# TheBridge.callbackFollowUp(reply, self.callbackId, callerId) + TheBridge.callbackFollowUp(reply, self.callbackId, callerId) - ############################################################################# + ############################################################################# def processCallbackPayload(self, payload): msg = ClientProto_pb2.RestorePrompt() msg.ParseFromString(payload) @@ -231,19 +231,19 @@ def processCallbackPayload(self, payload): newWltID = msg.extra if len(newWltID) > 0: if self.thisIsATest: - # Stop here if this was just a test + # Stop here if this was just a test verifyRecoveryTestID(self, newWltID, self.testWltID) #return false to caller to end the restore process return False, None - # return result of id comparison + # return result of id comparison dlgOwnWlt = None if newWltID in self.main.walletMap: dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) if (dlgOwnWlt.exec_()): - #TODO: deal with replacement code + #TODO: deal with replacement code if dlgOwnWlt.output == 0: return False, None else: @@ -256,11 +256,11 @@ def processCallbackPayload(self, payload): 'key and chain-code data again.' % newWltID), \ QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: - #return true to caller to proceed with restore operation + #return true to caller to proceed with restore operation self.newWltID = newWltID return True, None - #reconstructed wallet id is invalid if we get this far + #reconstructed wallet id is invalid if we get this far lineNumber = -1 canBeSalvaged = True if len(msg.checksums) != self.lineCount: @@ -273,8 +273,8 @@ def processCallbackPayload(self, payload): if lineNumber == -1 or canBeSalvaged == False: QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( - 'Encountered an unkonwn error when restoring this backup. Aborting.', \ - QMessageBox.Ok)) + 'Encountered an unkonwn error when restoring this backup. Aborting.'), \ + QMessageBox.Ok) self.reject() return False, None @@ -292,7 +292,7 @@ def processCallbackPayload(self, payload): return False, None if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Passphrase"): - #return new wallet's private keys password + #return new wallet's private keys password passwd = [] if self.chkEncrypt.isChecked(): dlgPasswd = DlgChangePassphrase(self, self.main) @@ -308,7 +308,7 @@ def processCallbackPayload(self, payload): return False, None if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Control"): - #TODO: need UI to input control passphrase + #TODO: need UI to input control passphrase return True, None if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Success"): @@ -333,15 +333,15 @@ def processCallbackPayload(self, payload): return False, None if msg.promptType == ClientProto_pb2.RestorePromptType.Value("DecryptError"): - #TODO: notify of invalid SP pass + #TODO: notify of invalid SP pass pass if msg.promptType == ClientProto_pb2.RestorePromptType.Value("TypeError"): - #TODO: wallet type conveyed by backup is unknown + #TODO: wallet type conveyed by backup is unknown pass else: - #TODO: unknown error + #TODO: unknown error return False, None @@ -361,7 +361,7 @@ def verifyUserInput(self): spPass = "" if self.doMask: - #add secureprint passphrase if this backup is encrypted + #add secureprint passphrase if this backup is encrypted spPass = str(self.editSecurePrint.text()).strip() ''' @@ -379,12 +379,12 @@ def verifyUserInput(self): ''' def callback(payload, callerId): self.main.signalExecution.executeMethod(\ - self.processCallback, payload, callerId) + [self.processCallback, [payload, callerId]]) self.callbackId = TheBDM.registerCustomPrompt(callback) -# TheBridge.restoreWallet(root, chaincode, spPass, self.callbackId) - return + TheBridge.restoreWallet(root, chaincode, spPass, self.callbackId) + ''' if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) @@ -393,14 +393,15 @@ def callback(payload, callerId): QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) return -# if nError > 0: -# pluralStr = 'error' if nError == 1 else 'errors' -# -# msg = self.tr( -# 'Detected errors in the data you entered. ' -# 'Armory attempted to fix the errors but it is not ' -# 'always right. Be sure to verify the "Wallet Unique ID" ' -# 'closely on the next window.') -# -# QMessageBox.question(self, self.tr('Errors Corrected'), msg, \ -# QMessageBox.Ok) \ No newline at end of file + if nError > 0: + pluralStr = 'error' if nError == 1 else 'errors' + + msg = self.tr( + 'Detected errors in the data you entered. ' + 'Armory attempted to fix the errors but it is not ' + 'always right. Be sure to verify the "Wallet Unique ID" ' + 'closely on the next window.') + + QMessageBox.question(self, self.tr('Errors Corrected'), msg, \ + QMessageBox.Ok) + ''' \ No newline at end of file diff --git a/qtdialogs/DlgUniversalRestoreSelect.py b/qtdialogs/DlgUniversalRestoreSelect.py index d2a0c9f25..6759db3db 100644 --- a/qtdialogs/DlgUniversalRestoreSelect.py +++ b/qtdialogs/DlgUniversalRestoreSelect.py @@ -9,11 +9,15 @@ # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # ############################################################################## -from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.qtdefines import HLINE, QRichLabel +from PySide2.QtWidgets import QCheckBox, QDialogButtonBox, QPushButton, \ + QVBoxLayout, QRadioButton, QButtonGroup + from armoryengine.ArmoryUtils import LOGINFO -from PySide2.QtWidgets import QCheckBox, QDialogButtonBox, QPushButton, QVBoxLayout -from PySide2.QtCore import SIGNAL +from qtdialogs.qtdefines import HLINE, QRichLabel +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.DlgRestoreSingle import DlgRestoreSingle +from qtdialogs.DlgRestoreFragged import DlgRestoreFragged +from qtdialogs.DlgRestoreWOData import DlgRestoreWOData ################################################################################ class DlgUniversalRestoreSelect(ArmoryDialog): @@ -42,18 +46,18 @@ def __init__(self, parent, main): btngrp.setExclusive(True) self.rdoSingle.setChecked(True) - self.connect(self.rdoSingle, SIGNAL("clicked()"), self.clickedRadio) - self.connect(self.rdoFragged, SIGNAL("clicked()"), self.clickedRadio) - self.connect(self.rdoDigital, SIGNAL("clicked()"), self.clickedRadio) - self.connect(self.rdoWOData, SIGNAL("clicked()"), self.clickedRadio) + self.rdoSingle.clicked.connect(self.clickedRadio) + self.rdoFragged.clicked.connect(self.clickedRadio) + self.rdoDigital.clicked.connect(self.clickedRadio) + self.rdoWOData.clicked.connect(self.clickedRadio) self.btnOkay = QPushButton(self.tr('Continue')) self.btnCancel = QPushButton(self.tr('Cancel')) buttonBox = QDialogButtonBox() buttonBox.addButton(self.btnOkay, QDialogButtonBox.AcceptRole) buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) - self.connect(self.btnOkay, SIGNAL("clicked()"), self.clickedOkay) - self.connect(self.btnCancel, SIGNAL("clicked()"), self.reject) + self.btnOkay.clicked.connect(self.clickedOkay) + self.btnCancel.clicked.connect(self.reject) layout = QVBoxLayout() diff --git a/qtdialogs/qtdefines.py b/qtdialogs/qtdefines.py index 2b62cd3a9..2f26ca43c 100755 --- a/qtdialogs/qtdefines.py +++ b/qtdialogs/qtdefines.py @@ -22,7 +22,7 @@ from PySide2.QtWidgets import QWidget, QDialog, QFrame, QLabel, \ QStyledItemDelegate, QTableView, QHBoxLayout, QLayoutItem, \ QVBoxLayout, QCheckBox, QDialogButtonBox, QPushButton, \ - QSpacerItem, QSizePolicy, QGridLayout, QApplication + QSpacerItem, QSizePolicy, QGridLayout, QApplication, QRadioButton import urllib @@ -415,6 +415,7 @@ def leaveEvent(self, ev): ssStr = "QLabel { background-color : %s }" % htmlColor('LBtnNormalBG') self.setStyleSheet(ssStr) +################################################################################ def makeLayoutFrame(dirStr, widgetList, style=QFrame.NoFrame, condenseMargins=False): frm = QFrame() frm.setFrameStyle(style) @@ -526,7 +527,24 @@ def saveTableView(qtbl): return 'ff' + first + ''.join(rest) +################################################################################ +class QRadioButtonBackupCtr(QRadioButton): + def __init__(self, parent, txt, index): + super(QRadioButtonBackupCtr, self).__init__(txt) + self.parent = parent + self.index = index + def enterEvent(self, ev): + pass + # self.parent.setDispFrame(self.index) + # self.setStyleSheet('QRadioButton { background-color : %s }' % \ + # htmlColor('SlightBkgdDark')) + + def leaveEvent(self, ev): + pass + # self.parent.setDispFrame(-1) + # self.setStyleSheet('QRadioButton { background-color : %s }' % \ + # htmlColor('Background')) ################################################################################ diff --git a/qtdialogs/qtdialogs.py b/qtdialogs/qtdialogs.py index c4ec5fdd1..168c71103 100755 --- a/qtdialogs/qtdialogs.py +++ b/qtdialogs/qtdialogs.py @@ -2,13 +2,17 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2022, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## import functools import shutil @@ -36,10 +40,11 @@ from armoryengine.Block import PyBlockHeader from armoryengine import ClientProto_pb2 -from qtdialogs.qtdefines import USERMODE, GETFONT, \ - tightSizeStr, determineWalletType, WLTTYPES +from qtdialogs.qtdefines import USERMODE, GETFONT, tightSizeStr, \ + determineWalletType, WLTTYPES, MSGBOX, QRichLabel from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.MsgBoxCustom import MsgBoxCustom NO_CHANGE = 'NoChange' MIN_PASSWD_WIDTH = lambda obj: tightSizeStr(obj, '*' * 16)[0] @@ -1897,6 +1902,64 @@ def fillAddrPoolAndAccept(): # Will pop up a little "please wait..." window while filling addr pool DlgExecLongProcess(fillAddrPoolAndAccept, self.tr("Recovering wallet..."), self, self.main).exec_() +################################################################################ +def OpenPaperBackupWindow(backupType, parent, main, wlt, unlockTitle=None): + + if wlt.useEncryption and wlt.isLocked: + if unlockTitle == None: + unlockTitle = parent.tr("Unlock Paper Backup") + dlg = DlgUnlockWallet(wlt, parent, main, unlockTitle) + if not dlg.exec_(): + QMessageBox.warning(parent, parent.tr('Unlock Failed'), parent.tr( + 'The wallet could not be unlocked. Please try again with ' + 'the correct unlock passphrase.'), QMessageBox.Ok) + return False + + result = True + verifyText = '' + if backupType == 'Single': + from qtdialogs.DlgBackupCenter import DlgPrintBackup + result = DlgPrintBackup(parent, main, wlt).exec_() + verifyText = parent.tr( + u'If the backup was printed with SecurePrint\u200b\u2122, please ' + u'make sure you wrote the SecurePrint\u200b\u2122 code on the ' + 'printed sheet of paper. Note that the code is ' + 'case-sensitive!') + elif backupType == 'Frag': + result = DlgFragBackup(parent, main, wlt).exec_() + verifyText = parent.tr( + u'If the backup was created with SecurePrint\u200b\u2122, please ' + u'make sure you wrote the SecurePrint\u200b\u2122 code on each ' + 'fragment (or stored with each file fragment). The code is the ' + 'same for all fragments.') + + doTest = MsgBoxCustom(MSGBOX.Warning, parent.tr('Verify Your Backup!'), parent.tr( + 'Verify your backup! ' + '

' + 'If you just made a backup, make sure that it is correct! ' + 'The following steps are recommended to verify its integrity: ' + '
' + '
    ' + '
  • Verify each line of the backup data contains 9 columns ' + 'of 4 letters each (excluding any "ID" lines).
  • ' + '
  • %s
  • ' + '
  • Use Armory\'s backup tester to test the backup before you ' + 'physiclly secure it.
  • ' + '
' + '
' + 'Armory has a backup tester that uses the exact same ' + 'process as restoring your wallet, but stops before it writes any ' + 'data to disk. Would you like to test your backup now? ' + % verifyText), yesStr="Test Backup", noStr="Cancel") + + if doTest: + if backupType == 'Single': + DlgRestoreSingle(parent, main, True, wlt.uniqueIDB58).exec_() + elif backupType == 'Frag': + DlgRestoreFragged(parent, main, True, wlt.uniqueIDB58).exec_() + + return result + ################################################################################ class DlgRemoveWallet(ArmoryDialog): @@ -2332,1124 +2395,219 @@ def boundingRect(self): return QRectF(0, 0, w, nLine * (1.5 * h)) -class GfxItemQRCode(QGraphicsItem): - """ - Converts binary data to base58, and encodes the Base58 characters in - the QR-code. It seems weird to use Base58 instead of binary, but the - QR-code has no problem with the size, instead, we want the data in the - QR-code to match exactly what is human-readable on the page, which is - in Base58. - - You must supply exactly one of "totalSize" or "modSize". TotalSize - guarantees that the QR code will fit insides box of a given size. - ModSize is how big each module/pixel of the QR code is, which means - that a bigger QR block results in a bigger physical size on paper. - """ - def __init__(self, rawDataToEncode, maxSize=None): - super(GfxItemQRCode, self).__init__() - self.maxSize = maxSize - self.updateQRData(rawDataToEncode) - - def boundingRect(self): - return self.Rect - - def updateQRData(self, toEncode, maxSize=None): - if maxSize == None: - maxSize = self.maxSize - else: - self.maxSize = maxSize +################################################################################ +class DlgBadConnection(ArmoryDialog): + def __init__(self, haveInternet, haveSatoshi, parent=None, main=None): + super(DlgBadConnection, self).__init__(parent, main) - self.qrmtrx, self.modCt = CreateQRMatrix(toEncode, 'H') - self.modSz = round(float(self.maxSize) / float(self.modCt) - 0.5) - totalSize = self.modCt * self.modSz - self.Rect = QRectF(0, 0, totalSize, totalSize) - def paint(self, painter, option, widget=None): - painter.setPen(Qt.NoPen) - painter.setBrush(QBrush(QColor(0, 0, 0))) + layout = QGridLayout() + lblWarnImg = QLabel() + lblWarnImg.setPixmap(QPixmap('./img/MsgBox_warning48.png')) + lblWarnImg.setAlignment(Qt.AlignHCenter | Qt.AlignTop) - for r in range(self.modCt): - for c in range(self.modCt): - if self.qrmtrx[r][c] > 0: - painter.drawRect(*[self.modSz * a for a in [r, c, 1, 1]]) + lblDescr = QLabel() + if not haveInternet and not CLI_OPTIONS.offline: + lblDescr = QRichLabel(self.tr( + 'Armory was not able to detect an internet connection, so Armory ' + 'will operate in "Offline" mode. In this mode, only wallet ' + '-management and unsigned-transaction functionality will be available. ' + '

' + 'If this is an error, please check your internet connection and ' + 'restart Armory.

Would you like to continue in "Offline" mode?')) + elif haveInternet and not haveSatoshi: + lblDescr = QRichLabel(self.tr( + 'Armory was not able to detect the presence of Bitcoin Core or bitcoind ' + 'client software (available at https://bitcoin.org). Please make sure that ' + 'the one of those programs is...
' + '
(1) ...open and connected to the network ' + '
(2) ...on the same network as Armory (main-network or test-network) ' + '
(3) ...synchronized with the blockchain before ' + 'starting Armory

Without the Bitcoin Core or bitcoind open, you will only ' + 'be able to run Armory in "Offline" mode, which will not have access ' + 'to new blockchain data, and you will not be able to send outgoing ' + 'transactions

If you do not want to be in "Offline" mode, please ' + 'restart Armory after one of these programs is open and synchronized with ' + 'the network')) + else: + # Nothing to do -- we shouldn't have even gotten here + # self.reject() + pass -class SimplePrintableGraphicsScene(object): + self.main.abortLoad = False + def abortLoad(): + self.main.abortLoad = True + self.reject() + lblDescr.setMinimumWidth(500) + self.btnAccept = QPushButton(self.tr("Continue in Offline Mode")) + self.btnCancel = QPushButton(self.tr("Close Armory")) + self.connect(self.btnAccept, SIGNAL(CLICKED), self.accept) + self.connect(self.btnCancel, SIGNAL(CLICKED), abortLoad) + buttonBox = QDialogButtonBox() + buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) + buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) - def __init__(self, parent, main): - """ - We use the following coordinates: + layout.addWidget(lblWarnImg, 0, 1, 2, 1) + layout.addWidget(lblDescr, 0, 2, 1, 1) + layout.addWidget(buttonBox, 1, 2, 1, 1) - -----> +x - | - | - V +y + self.setLayout(layout) + self.setWindowTitle(self.tr('Network not available')) - """ - self.parent = parent - self.main = main - self.INCH = 72 - self.PAPER_A4_WIDTH = 8.5 * self.INCH - self.PAPER_A4_HEIGHT = 11.0 * self.INCH - self.MARGIN_PIXELS = 0.6 * self.INCH +################################################################################ +def readSigBlock(parent, fullPacket): + addrB58, messageStr, pubkey, sig = '', '', '', '' + lines = fullPacket.split('\n') + readingMessage, readingPub, readingSig = False, False, False + for i in range(len(lines)): + s = lines[i].strip() - self.PAGE_BKGD_COLOR = QColor(255, 255, 255) - self.PAGE_TEXT_COLOR = QColor(0, 0, 0) + # ADDRESS + if s.startswith('Addr'): + addrB58 = s.split(':')[-1].strip() - self.fontFix = GETFONT('Courier', 9) - self.fontVar = GETFONT('Times', 10) + # MESSAGE STRING + if s.startswith('Message') or readingMessage: + readingMessage = True + if s.startswith('Pub') or s.startswith('Sig') or ('END-CHAL' in s): + readingMessage = False + else: + # Message string needs to be exact, grab what's between the + # double quotes, no newlines + iq1 = s.index('"') + 1 + iq2 = s.index('"', iq1) + messageStr += s[iq1:iq2] - self.gfxScene = QGraphicsScene(self.parent) - self.gfxScene.setSceneRect(0, 0, self.PAPER_A4_WIDTH, self.PAPER_A4_HEIGHT) - self.gfxScene.setBackgroundBrush(self.PAGE_BKGD_COLOR) + # PUBLIC KEY + if s.startswith('Pub') or readingPub: + readingPub = True + if s.startswith('Sig') or ('END-SIGNATURE-BLOCK' in s): + readingPub = False + else: + pubkey += s.split(':')[-1].strip().replace(' ', '') - # For when it eventually makes it to the printer - # self.printer = QPrinter(QPrinter.HighResolution) - # self.printer.setPageSize(QPrinter.Letter) - # self.gfxPainter = QPainter(self.printer) - # self.gfxPainter.setRenderHint(QPainter.TextAntialiasing) - # self.gfxPainter.setPen(Qt.NoPen) - # self.gfxPainter.setBrush(QBrush(self.PAGE_TEXT_COLOR)) + # SIGNATURE + if s.startswith('Sig') or readingSig: + readingSig = True + if 'END-SIGNATURE-BLOCK' in s: + readingSig = False + else: + sig += s.split(':')[-1].strip().replace(' ', '') - self.cursorPos = QPointF(self.MARGIN_PIXELS, self.MARGIN_PIXELS) - self.lastCursorMove = (0, 0) + if len(pubkey) > 0: + try: + pubkey = hex_to_binary(pubkey) + if len(pubkey) not in (32, 33, 64, 65): raise + except: + QMessageBox.critical(parent, parent.tr('Bad Public Key'), \ + parent.tr('Public key data was not recognized'), QMessageBox.Ok) + pubkey = '' - def getCursorXY(self): - return (self.cursorPos.x(), self.cursorPos.y()) + if len(sig) > 0: + try: + sig = hex_to_binary(sig) + except: + QMessageBox.critical(parent, parent.tr('Bad Signature'), \ + parent.tr('Signature data is malformed!'), QMessageBox.Ok) + sig = '' - def getScene(self): - return self.gfxScene - def pageRect(self): - marg = self.MARGIN_PIXELS - return QRectF(marg, marg, self.PAPER_A4_WIDTH - marg, self.PAPER_A4_HEIGHT - marg) + pubkeyhash = hash160(pubkey) + if not pubkeyhash == addrStr_to_hash160(addrB58)[1]: + QMessageBox.critical(parent, parent.tr('Address Mismatch'), \ + parent.tr('!!! The address included in the signature block does not ' + 'match the supplied public key! This should never happen, ' + 'and may in fact be an attempt to mislead you !!!'), QMessageBox.Ok) + sig = '' - def insidePageRect(self, pt=None): - if pt == None: - pt = self.cursorPos - return self.pageRect.contains(pt) - def moveCursor(self, dx, dy, absolute=False): - xOld, yOld = self.getCursorXY() - if absolute: - self.cursorPos = QPointF(dx, dy) - self.lastCursorMove = (dx - xOld, dy - yOld) - else: - self.cursorPos = QPointF(xOld + dx, yOld + dy) - self.lastCursorMove = (dx, dy) - - - def resetScene(self): - self.gfxScene.clear() - self.resetCursor() - - def resetCursor(self): - self.cursorPos = QPointF(self.MARGIN_PIXELS, self.MARGIN_PIXELS) - - - def newLine(self, extra_dy=0): - xOld, yOld = self.getCursorXY() - xNew = self.MARGIN_PIXELS - yNew = self.cursorPos.y() + self.lastItemSize[1] + extra_dy - 5 - self.moveCursor(xNew - xOld, yNew - yOld) - - - def drawHLine(self, width=None, penWidth=1): - if width == None: - width = 3 * self.INCH - currX, currY = self.getCursorXY() - lineItem = QGraphicsLineItem(currX, currY, currX + width, currY) - pen = QPen() - pen.setWidth(penWidth) - lineItem.setPen(pen) - self.gfxScene.addItem(lineItem) - rect = lineItem.boundingRect() - self.lastItemSize = (rect.width(), rect.height()) - self.moveCursor(rect.width(), 0) - return self.lastItemSize - - def drawRect(self, w, h, edgeColor=QColor(0, 0, 0), fillColor=None, penWidth=1): - rectItem = QGraphicsRectItem(self.cursorPos.x(), self.cursorPos.y(), w, h) - if edgeColor == None: - rectItem.setPen(QPen(Qt.NoPen)) - else: - pen = QPen(edgeColor) - pen.setWidth(penWidth) - rectItem.setPen(pen) + return addrB58, messageStr, pubkey, sig - if fillColor == None: - rectItem.setBrush(QBrush(Qt.NoBrush)) - else: - rectItem.setBrush(QBrush(fillColor)) - self.gfxScene.addItem(rectItem) - rect = rectItem.boundingRect() - self.lastItemSize = (rect.width(), rect.height()) - self.moveCursor(rect.width(), 0) - return self.lastItemSize +################################################################################ +def makeSigBlock(addrB58, MessageStr, binPubkey='', binSig=''): + lineWid = 50 + s = '-----BEGIN-SIGNATURE-BLOCK'.ljust(lineWid + 13, '-') + '\n' + ### Address ### + s += 'Address: %s\n' % addrB58 - def drawText(self, txt, font=None, wrapWidth=None, useHtml=True): - if font == None: - font = GETFONT('Var', 9) - txtItem = QGraphicsTextItem('') - if useHtml: - txtItem.setHtml(toUnicode(txt)) - else: - txtItem.setPlainText(toUnicode(txt)) - txtItem.setDefaultTextColor(self.PAGE_TEXT_COLOR) - txtItem.setPos(self.cursorPos) - txtItem.setFont(font) - if not wrapWidth == None: - txtItem.setTextWidth(wrapWidth) - self.gfxScene.addItem(txtItem) - rect = txtItem.boundingRect() - self.lastItemSize = (rect.width(), rect.height()) - self.moveCursor(rect.width(), 0) - return self.lastItemSize - - def drawPixmapFile(self, pixFn, sizePx=None): - pix = QPixmap(pixFn) - if not sizePx == None: - pix = pix.scaled(sizePx, sizePx) - pixItem = QGraphicsPixmapItem(pix) - pixItem.setPos(self.cursorPos) - pixItem.setMatrix(QMatrix()) - self.gfxScene.addItem(pixItem) - rect = pixItem.boundingRect() - self.lastItemSize = (rect.width(), rect.height()) - self.moveCursor(rect.width(), 0) - return self.lastItemSize - - def drawQR(self, qrdata, size=150): - objQR = GfxItemQRCode(qrdata, size) - objQR.setPos(self.cursorPos) - objQR.setMatrix(QMatrix()) - self.gfxScene.addItem(objQR) - rect = objQR.boundingRect() - self.lastItemSize = (rect.width(), rect.height()) - self.moveCursor(rect.width(), 0) - return self.lastItemSize - - - def drawColumn(self, strList, rowHeight=None, font=None, useHtml=True): - """ - This draws a bunch of left-justified strings in a column. It returns - a tight bounding box around all elements in the column, which can easily - be used to start the next column. The rowHeight is returned, and also - an available input, in case you are drawing text/font that has a different - height in each column, and want to make sure they stay aligned. - - Just like the other methods, this leaves the cursor sitting at the - original y-value, but shifted to the right by the width of the column. - """ - origX, origY = self.getCursorXY() - maxColWidth = 0 - cumulativeY = 0 - for r in strList: - szX, szY = self.drawText(r, font=font, useHtml=useHtml) - prevY = self.cursorPos.y() - if rowHeight == None: - self.newLine() - szY = self.cursorPos.y() - prevY - self.moveCursor(origX - self.MARGIN_PIXELS, 0) - else: - self.moveCursor(-szX, rowHeight) - maxColWidth = max(maxColWidth, szX) - cumulativeY += szY + ### Message ### + chPerLine = lineWid - 2 + nMessageLines = (len(MessageStr) - 1) / chPerLine + 1 + for i in range(nMessageLines): + cLine = 'Message: "%s"\n' if i == 0 else ' "%s"\n' + s += cLine % MessageStr[i * chPerLine:(i + 1) * chPerLine] - if rowHeight == None: - rowHeight = float(cumulativeY) / len(strList) + ### Public Key ### + if len(binPubkey) > 0: + hexPub = binary_to_hex(binPubkey) + nPubLines = (len(hexPub) - 1) / lineWid + 1 + for i in range(nPubLines): + pLine = 'PublicKey: %s\n' if i == 0 else ' %s\n' + s += pLine % hexPub[i * lineWid:(i + 1) * lineWid] - self.moveCursor(origX + maxColWidth, origY, absolute=True) + ### Signature ### + if len(binSig) > 0: + hexSig = binary_to_hex(binSig) + nSigLines = (len(hexSig) - 1) / lineWid + 1 + for i in range(nSigLines): + sLine = 'Signature: %s\n' if i == 0 else ' %s\n' + s += sLine % hexSig[i * lineWid:(i + 1) * lineWid] - return [QRectF(origX, origY, maxColWidth, cumulativeY), rowHeight] + s += '-----END-SIGNATURE-BLOCK'.ljust(lineWid + 13, '-') + '\n' + return s -class DlgPrintBackup(ArmoryDialog): +class DlgExecLongProcess(ArmoryDialog): """ - Open up a "Make Paper Backup" dialog, so the user can print out a hard - copy of whatever data they need to recover their wallet should they lose - it. + Execute a processing that may require having the user to wait a while. + Should appear like a splash screen, and will automatically close when + the processing is done. As such, you should have very little text, just + in case it finishes immediately, the user won't have time to read it. - This method is kind of a mess, because it ended up having to support - printing of single-sheet, imported keys, single fragments, multiple - fragments, with-or-without SecurePrint. + DlgExecLongProcess(execFunc, 'Short Description', self, self.main).exec_() """ - def __init__(self, parent, main, wlt, printType='SingleSheet', \ - fragMtrx=[], fragMtrxCrypt=[], fragData=[], - privKey=None, chaincode=None): - super(DlgPrintBackup, self).__init__(parent, main) - - - self.wlt = wlt - self.binImport = [] - self.fragMtrx = fragMtrx + def __init__(self, funcExec, msg='', parent=None, main=None): + super(DlgExecLongProcess, self).__init__(parent, main) - self.doPrintFrag = printType.lower().startswith('frag') - self.fragMtrx = fragMtrx - self.fragMtrxCrypt = fragMtrxCrypt - self.fragData = fragData - if self.doPrintFrag: - self.doMultiFrag = len(fragData['Range']) > 1 + self.func = funcExec - self.backupData = None - def resumeSetup(rootData): - self.backupData = rootData - self.emit(SIGNAL('backupSetupSignal')) + waitFont = GETFONT('Var', 14) + descrFont = GETFONT('Var', 12) + palette = QPalette() + palette.setColor(QPalette.Window, QColor(235, 235, 255)) + self.setPalette(palette); + self.setAutoFillBackground(True) - self.connect(self, SIGNAL('backupSetupSignal'), self.setup) - rootData = self.wlt.createBackupString(resumeSetup) + if parent: + qr = parent.geometry() + x, y, w, h = qr.left(), qr.top(), qr.width(), qr.height() + dlgW = relaxedSizeStr(waitFont, msg)[0] + dlgW = min(dlgW, 400) + dlgH = 150 + self.setGeometry(int(x + w / 2 - dlgW / 2), int(y + h / 2 - dlgH / 2), dlgW, dlgH) - ### - def setup(self): - # This badBackup stuff was implemented to avoid making backups if there is - # an inconsistency in the data. Yes, this is like a goto! - if self.backupData == None: - LOGEXCEPT("Problem with private key and/or chaincode. Aborting.") - QMessageBox.critical(self, self.tr("Error Creating Backup"), self.tr( - 'There was an error with the backup creator. The operation is being ' - 'canceled to avoid making bad backups!'), QMessageBox.Ok) - return + lblWaitMsg = QRichLabel(self.tr('Please Wait...')) + lblWaitMsg.setFont(waitFont) + lblWaitMsg.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter) - # A self-evident check of whether we need to print the chaincode. - # If we derive the chaincode from the private key, and it matches - # what's already in the wallet, we obviously don't need to print it! - self.noNeedChaincode = (len(self.backupData.chainclear) == 0) - - # Save off imported addresses in case they need to be printed, too - for a160, addr in self.wlt.addrMap.items(): - if addr.chainIndex == -2: - if addr.binPrivKey32_Plain.getSize() == 33 or addr.isCompressed(): - prv = addr.binPrivKey32_Plain.toBinStr()[:32] - self.binImport.append([a160, SecureBinaryData(prv), 1]) - prv = None - else: - self.binImport.append([a160, addr.binPrivKey32_Plain.copy(), 0]) + lblDescrMsg = QRichLabel(msg) + lblDescrMsg.setFont(descrFont) + lblDescrMsg.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter) + self.setWindowFlags(Qt.SplashScreen) - tempTxtItem = QGraphicsTextItem('') - tempTxtItem.setPlainText(toUnicode('0123QAZjqlmYy')) - tempTxtItem.setFont(GETFONT('Fix', 7)) - self.importHgt = tempTxtItem.boundingRect().height() - 5 - - - # Create the scene and the view. - self.scene = SimplePrintableGraphicsScene(self, self.main) - self.view = QGraphicsView() - self.view.setRenderHint(QPainter.TextAntialiasing) - self.view.setScene(self.scene.getScene()) - - - self.chkImportPrint = QCheckBox(self.tr('Print imported keys')) - self.connect(self.chkImportPrint, SIGNAL(CLICKED), self.clickImportChk) - - self.lblPageStr = QRichLabel(self.tr('Page:')) - self.comboPageNum = QComboBox() - self.lblPageMaxStr = QRichLabel('') - self.connect(self.comboPageNum, SIGNAL('activated(int)'), self.redrawBackup) - - # We enable printing of imported addresses but not frag'ing them.... way - # too much work for everyone (developer and user) to deal with 2x or 3x - # the amount of data to type - self.chkImportPrint.setVisible(len(self.binImport) > 0 and not self.doPrintFrag) - self.lblPageStr.setVisible(False) - self.comboPageNum.setVisible(False) - self.lblPageMaxStr.setVisible(False) - - self.chkSecurePrint = QCheckBox(self.trUtf8(u'Use SecurePrint\u200b\u2122 to prevent exposing keys to printer or other ' - 'network devices')) - - if(self.doPrintFrag): - self.chkSecurePrint.setChecked(self.fragData['Secure']) - - self.ttipSecurePrint = self.main.createToolTipWidget(self.trUtf8( - u'SecurePrint\u200b\u2122 encrypts your backup with a code displayed on ' - 'the screen, so that no other devices on your network see the sensitive ' - 'data when you send it to the printer. If you turn on ' - u'SecurePrint\u200b\u2122 you must write the code on the page after ' - 'it is done printing! There is no point in using this feature if ' - 'you copy the data by hand.')) - - self.lblSecurePrint = QRichLabel(self.trUtf8( - u'IMPORTANT: You must write the SecurePrint\u200b\u2122 ' - u'encryption code on each printed backup page! Your SecurePrint\u200b\u2122 code is ' - '%s. Your backup will not work ' - 'if this code is lost!' % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ - self.backupData.sppass, htmlColor('TextWarn')))) - - self.connect(self.chkSecurePrint, SIGNAL("clicked()"), self.redrawBackup) - - - self.btnPrint = QPushButton('&Print...') - self.btnPrint.setMinimumWidth(3 * tightSizeStr(self.btnPrint, 'Print...')[0]) - self.btnCancel = QPushButton('&Cancel') - self.connect(self.btnPrint, SIGNAL(CLICKED), self.print_) - self.connect(self.btnCancel, SIGNAL(CLICKED), self.accept) - - if self.doPrintFrag: - M, N = self.fragData['M'], self.fragData['N'] - lblDescr = QRichLabel(self.tr( - 'Print Wallet Backup Fragments

' - 'When any %s of these fragments are combined, all previous ' - 'and future addresses generated by this wallet will be ' - 'restored, giving you complete access to your bitcoins. The ' - 'data can be copied by hand if a working printer is not ' - 'available. Please make sure that all data lines contain ' - '9 columns ' - 'of 4 characters each (excluding "ID" lines).' % M)) - else: - withChain = '' if self.noNeedChaincode else 'and "Chaincode"' - lblDescr = QRichLabel(self.tr( - 'Print a Forever-Backup

' - 'Printing this sheet protects all previous and future addresses ' - 'generated by this wallet! You can copy the "Root Key" %s ' - 'by hand if a working printer is not available. Please make sure that ' - 'all data lines contain 9 columns ' - 'of 4 characters each.' % withChain)) - - lblDescr.setContentsMargins(5, 5, 5, 5) - frmDescr = makeHorizFrame([lblDescr], STYLE_RAISED) - - self.redrawBackup() - frmChkImport = makeHorizFrame([self.chkImportPrint, \ - STRETCH, \ - self.lblPageStr, \ - self.comboPageNum, \ - self.lblPageMaxStr]) - - frmSecurePrint = makeHorizFrame([self.chkSecurePrint, - self.ttipSecurePrint, - STRETCH]) - - frmButtons = makeHorizFrame([self.btnCancel, STRETCH, self.btnPrint]) - - layout = QVBoxLayout() - layout.addWidget(frmDescr) - layout.addWidget(frmChkImport) - layout.addWidget(self.view) - layout.addWidget(frmSecurePrint) - layout.addWidget(self.lblSecurePrint) - layout.addWidget(frmButtons) - setLayoutStretch(layout, 0, 1, 0, 0, 0) - - self.setLayout(layout) - - self.setWindowIcon(QIcon('./img/printer_icon.png')) - self.setWindowTitle('Print Wallet Backup') - - - # Apparently I can't programmatically scroll until after it's painted - def scrollTop(): - vbar = self.view.verticalScrollBar() - vbar.setValue(vbar.minimum()) - self.callLater(0.01, scrollTop) - - def redrawBackup(self): - cmbPage = 1 - if self.comboPageNum.count() > 0: - cmbPage = int(str(self.comboPageNum.currentText())) - - if self.doPrintFrag: - cmbPage -= 1 - if not self.doMultiFrag: - cmbPage = self.fragData['Range'][0] - elif self.comboPageNum.count() > 0: - cmbPage = int(str(self.comboPageNum.currentText())) - 1 - - self.createPrintScene('Fragmented Backup', cmbPage) - else: - pgSelect = cmbPage if self.chkImportPrint.isChecked() else 1 - if pgSelect == 1: - self.createPrintScene('SingleSheetFirstPage', '') - else: - pg = pgSelect - 2 - nKey = self.maxKeysPerPage - self.createPrintScene('SingleSheetImported', [pg * nKey, (pg + 1) * nKey]) - - - showPageCombo = self.chkImportPrint.isChecked() or \ - (self.doPrintFrag and self.doMultiFrag) - self.showPageSelect(showPageCombo) - self.view.update() - - - - - def clickImportChk(self): - if self.numImportPages > 1 and self.chkImportPrint.isChecked(): - ans = QMessageBox.warning(self, self.tr('Lots to Print!'), self.tr( - 'This wallet contains %d imported keys, which will require ' - '%d pages to print. Not only will this use a lot of paper, ' - 'it will be a lot of work to manually type in these keys in the ' - 'event that you need to restore this backup. It is recommended ' - 'that you do not print your imported keys and instead make ' - 'a digital backup, which can be restored instantly if needed. ' - '

Do you want to print the imported keys, anyway?' % (len(self.binImport), self.numImportPages)), \ - QMessageBox.Yes | QMessageBox.No) - if not ans == QMessageBox.Yes: - self.chkImportPrint.setChecked(False) - - showPageCombo = self.chkImportPrint.isChecked() or \ - (self.doPrintFrag and self.doMultiFrag) - self.showPageSelect(showPageCombo) - self.comboPageNum.setCurrentIndex(0) - self.redrawBackup() - - - def showPageSelect(self, doShow=True): - MARGIN = self.scene.MARGIN_PIXELS - bottomOfPage = self.scene.pageRect().height() + MARGIN - totalHgt = bottomOfPage - self.bottomOfSceneHeader - self.maxKeysPerPage = int(totalHgt / (self.importHgt)) - self.numImportPages = int((len(self.binImport) - 1) / self.maxKeysPerPage) + 1 - if self.comboPageNum.count() == 0: - if self.doPrintFrag: - numFrag = len(self.fragData['Range']) - for i in range(numFrag): - self.comboPageNum.addItem(str(i + 1)) - self.lblPageMaxStr.setText(self.tr('of %d' % numFrag)) - else: - for i in range(self.numImportPages + 1): - self.comboPageNum.addItem(str(i + 1)) - self.lblPageMaxStr.setText(self.tr('of %d' % (self.numImportPages + 1))) - - - self.lblPageStr.setVisible(doShow) - self.comboPageNum.setVisible(doShow) - self.lblPageMaxStr.setVisible(doShow) - - - - - def print_(self): - LOGINFO('Printing!') - self.printer = QPrinter(QPrinter.HighResolution) - self.printer.setPageSize(QPrinter.Letter) - - if QPrintDialog(self.printer).exec_(): - painter = QPainter(self.printer) - painter.setRenderHint(QPainter.TextAntialiasing) - - if self.doPrintFrag: - for i in self.fragData['Range']: - self.createPrintScene('Fragment', i) - self.scene.getScene().render(painter) - if not i == len(self.fragData['Range']) - 1: - self.printer.newPage() - - else: - self.createPrintScene('SingleSheetFirstPage', '') - self.scene.getScene().render(painter) - - if len(self.binImport) > 0 and self.chkImportPrint.isChecked(): - nKey = self.maxKeysPerPage - for i in range(self.numImportPages): - self.printer.newPage() - self.createPrintScene('SingleSheetImported', [i * nKey, (i + 1) * nKey]) - self.scene.getScene().render(painter) - - painter.end() - - # The last scene printed is what's displayed now. Set the combo box - self.comboPageNum.setCurrentIndex(self.comboPageNum.count() - 1) - - if self.chkSecurePrint.isChecked(): - QMessageBox.warning(self, self.tr('SecurePrint Code'), self.trUtf8( - u'
You must write your SecurePrint\u200b\u2122 ' - 'code on each sheet of paper you just printed! ' - 'Write it in the red box in upper-right corner ' - u'of each printed page.

SecurePrint\u200b\u2122 code: ' - '%s

' - 'NOTE: the above code is case-sensitive!' % (htmlColor('TextBlue'), self.randpass.toBinStr())), \ - QMessageBox.Ok) - if self.chkSecurePrint.isChecked(): - self.btnCancel.setText('Done') - else: - self.accept() - - - def cleanup(self): - self.backupData = None - - for x, y in self.fragMtrxCrypt: - x.destroy() - y.destroy() - - def accept(self): - self.cleanup() - super(DlgPrintBackup, self).accept() - - def reject(self): - self.cleanup() - super(DlgPrintBackup, self).reject() - - - ############################################################################# - ############################################################################# - def createPrintScene(self, printType, printData): - self.scene.gfxScene.clear() - self.scene.resetCursor() - - pr = self.scene.pageRect() - self.scene.drawRect(pr.width(), pr.height(), edgeColor=None, fillColor=QColor(255, 255, 255)) - self.scene.resetCursor() - - - INCH = self.scene.INCH - MARGIN = self.scene.MARGIN_PIXELS - - doMask = self.chkSecurePrint.isChecked() - - if USE_TESTNET or USE_REGTEST: - self.scene.drawPixmapFile('./img/armory_logo_green_h56.png') - else: - self.scene.drawPixmapFile('./img/armory_logo_h36.png') - self.scene.newLine() - - self.scene.drawText('Paper Backup for Armory Wallet', GETFONT('Var', 11)) - self.scene.newLine() - - self.scene.newLine(extra_dy=20) - self.scene.drawHLine() - self.scene.newLine(extra_dy=20) - - - ssType = self.trUtf8(u' (SecurePrint\u200b\u2122)') if doMask else self.tr(' (Unencrypted)') - if printType == 'SingleSheetFirstPage': - bType = self.tr('Single-Sheet') # %s' % ssType) - elif printType == 'SingleSheetImported': - bType = self.tr('Imported Keys %s' % ssType) - elif printType.lower().startswith('frag'): - m_count = str(self.fragData['M']) - n_count = str(self.fragData['N']) - bstr = self.tr('Fragmented Backup (%s-of-%s)' % (m_count, n_count)) - bType = bstr + ' ' + ssType - - if printType.startswith('SingleSheet'): - colRect, rowHgt = self.scene.drawColumn(['Wallet Version:', 'Wallet ID:', \ - 'Wallet Name:', 'Backup Type:']) - self.scene.moveCursor(15, 0) - suf = 'c' if self.noNeedChaincode else 'a' - colRect, rowHgt = self.scene.drawColumn(['1.35' + suf, self.wlt.uniqueIDB58, \ - self.wlt.labelName, bType]) - self.scene.moveCursor(15, colRect.y() + colRect.height(), absolute=True) - else: - colRect, rowHgt = self.scene.drawColumn(['Wallet Version:', 'Wallet ID:', \ - 'Wallet Name:', 'Backup Type:', \ - 'Fragment:']) - baseID = self.fragData['FragIDStr'] - fragNum = printData + 1 - fragID = '%s-#%d' % (baseID, htmlColor('TextBlue'), fragNum) - self.scene.moveCursor(15, 0) - suf = 'c' if self.noNeedChaincode else 'a' - colRect, rowHgt = self.scene.drawColumn(['1.35' + suf, self.wlt.uniqueIDB58, \ - self.wlt.labelName, bType, fragID]) - self.scene.moveCursor(15, colRect.y() + colRect.height(), absolute=True) - - - # Display warning about unprotected key data - wrap = 0.9 * self.scene.pageRect().width() - - if self.doPrintFrag: - warnMsg = self.tr( - 'Any subset of %s fragments with this ' - 'ID (%s) are sufficient to recover all the ' - 'coins contained in this wallet. To optimize the physical security of ' - 'your wallet, please store the fragments in different locations.' % (htmlColor('TextBlue'), \ - str(self.fragData['M']), htmlColor('TextBlue'), self.fragData['FragIDStr'])) - else: - container = 'this wallet' if printType == 'SingleSheetFirstPage' else 'these addresses' - warnMsg = self.tr( - 'WARNING: Anyone who has access to this ' - 'page has access to all the bitcoins in %s! Please keep this ' - 'page in a safe place.' % container) - - self.scene.newLine() - self.scene.drawText(warnMsg, GETFONT('Var', 9), wrapWidth=wrap) - - self.scene.newLine(extra_dy=20) - self.scene.drawHLine() - self.scene.newLine(extra_dy=20) - - if self.doPrintFrag: - numLine = 'three' if self.noNeedChaincode else 'five' - else: - numLine = 'two' if self.noNeedChaincode else 'four' - - if printType == 'SingleSheetFirstPage': - descrMsg = self.tr( - 'The following %s lines backup all addresses ' - 'ever generated by this wallet (previous and future). ' - 'This can be used to recover your wallet if you forget your passphrase or ' - 'suffer hardware failure and lose your wallet files.' % numLine) - elif printType == 'SingleSheetImported': - if self.chkSecurePrint.isChecked(): - descrMsg = self.trUtf8( - 'The following is a list of all private keys imported into your ' - 'wallet before this backup was made. These keys are encrypted ' - u'with the SecurePrint\u200b\u2122 code and can only be restored ' - 'by entering them into Armory. Print a copy of this backup without ' - u'the SecurePrint\u200b\u2122 option if you want to be able to import ' - 'them into another application.') - else: - descrMsg = self.tr( - 'The following is a list of all private keys imported into your ' - 'wallet before this backup was made. Each one must be copied ' - 'manually into the application where you wish to import them.') - elif printType.lower().startswith('frag'): - fragNum = printData + 1 - descrMsg = self.tr( - 'The following is fragment #%s for this ' - 'wallet.' % (htmlColor('TextBlue'), str(printData + 1))) - - - self.scene.drawText(descrMsg, GETFONT('var', 8), wrapWidth=wrap) - self.scene.newLine(extra_dy=10) - - ########################################################################### - # Draw the SecurePrint box if needed, frag pie, then return cursor - prevCursor = self.scene.getCursorXY() - - self.lblSecurePrint.setVisible(doMask) - if doMask: - self.scene.resetCursor() - self.scene.moveCursor(4.0 * INCH, 0) - spWid, spHgt = 2.75 * INCH, 1.5 * INCH, - if doMask: - self.scene.drawRect(spWid, spHgt, edgeColor=QColor(180, 0, 0), penWidth=3) - - self.scene.resetCursor() - self.scene.moveCursor(4.07 * INCH, 0.07 * INCH) - - self.scene.drawText(self.trUtf8( - 'CRITICAL: This backup will not ' - u'work without the SecurePrint\u200b\u2122 ' - 'code displayed on the screen during printing. ' - 'Copy it here in ink:'), wrapWidth=spWid * 0.93, font=GETFONT('Var', 7)) - - self.scene.newLine(extra_dy=8) - self.scene.moveCursor(4.07 * INCH, 0) - codeWid, codeHgt = self.scene.drawText('Code:') - self.scene.moveCursor(0, codeHgt - 3) - wid = spWid - codeWid - w, h = self.scene.drawHLine(width=wid * 0.9, penWidth=2) - - - - # Done drawing other stuff, so return to the original drawing location - self.scene.moveCursor(*prevCursor, absolute=True) - ########################################################################### - - - ########################################################################### - # Finally, draw the backup information. - - # If this page is only imported addresses, draw them then bail - self.bottomOfSceneHeader = self.scene.cursorPos.y() - if printType == 'SingleSheetImported': - self.scene.moveCursor(0, 0.1 * INCH) - importList = self.binImport - if self.chkSecurePrint.isChecked(): - importList = self.binImportCrypt - - for a160, priv, isCompr in importList[printData[0]:printData[1]]: - comprByte = ('\x01' if isCompr == 1 else '') - prprv = encodePrivKeyBase58(priv.toBinStr() + comprByte) - toPrint = [prprv[i * 6:(i + 1) * 6] for i in range((len(prprv) + 5) / 6)] - addrHint = ' (%s...)' % hash160_to_addrStr(a160)[:12] - self.scene.drawText(' '.join(toPrint), GETFONT('Fix', 7)) - self.scene.moveCursor(0.02 * INCH, 0) - self.scene.drawText(addrHint, GETFONT('Var', 7)) - self.scene.newLine(extra_dy=-3) - prprv = None - return - - - if self.doPrintFrag: - M = self.fragData['M'] - Lines = [] - Prefix = [] - fmtrx = self.fragMtrxCrypt if doMask else self.fragMtrx - - try: - yBin = fmtrx[printData][1] - binID = base58_to_binary(self.fragData['fragSetID']) - IDLine = ComputeFragIDLineHex(M, printData, binID, doMask, addSpaces=True) - if len(yBin) == 32: - Prefix.append('ID:'); Lines.append(IDLine) - Prefix.append('F1:'); Lines.append(makeSixteenBytesEasy(yBin[:16 ])) - Prefix.append('F2:'); Lines.append(makeSixteenBytesEasy(yBin[ 16:])) - elif len(yBin) == 64: - Prefix.append('ID:'); Lines.append(IDLine) - Prefix.append('F1:'); Lines.append(makeSixteenBytesEasy(yBin[:16 ])) - Prefix.append('F2:'); Lines.append(makeSixteenBytesEasy(yBin[ 16:32 ])) - Prefix.append('F3:'); Lines.append(makeSixteenBytesEasy(yBin[ 32:48 ])) - Prefix.append('F4:'); Lines.append(makeSixteenBytesEasy(yBin[ 48:])) - else: - LOGERROR('yBin is not 32 or 64 bytes! It is %s bytes', len(yBin)) - finally: - yBin = None - - else: - # Single-sheet backup - if doMask: - code12 = self.backupData.rootencr - code34 = self.backupData.chainencr - else: - code12 = self.backupData.rootclear - code34 = self.backupData.chainclear - - - Lines = [] - Prefix = [] - Prefix.append('Root Key:'); Lines.append(code12[0]) - Prefix.append(''); Lines.append(code12[1]) - if not self.noNeedChaincode: - Prefix.append('Chaincode:'); Lines.append(code34[0]) - Prefix.append(''); Lines.append(code34[1]) - - # Draw the prefix - origX, origY = self.scene.getCursorXY() - self.scene.moveCursor(20, 0) - colRect, rowHgt = self.scene.drawColumn(['' + l + '' for l in Prefix]) - - nudgeDown = 2 # because the differing font size makes it look unaligned - self.scene.moveCursor(20, nudgeDown) - self.scene.drawColumn(Lines, - font=GETFONT('Fixed', 8, bold=True), \ - rowHeight=rowHgt, - useHtml=False) - - self.scene.moveCursor(MARGIN, colRect.y() - 2, absolute=True) - width = self.scene.pageRect().width() - 2 * MARGIN - self.scene.drawRect(width, colRect.height() + 7, edgeColor=QColor(0, 0, 0), fillColor=None) - - self.scene.newLine(extra_dy=30) - self.scene.drawText(self.tr( - 'The following QR code is for convenience only. It contains the ' - 'exact same data as the %s lines above. If you copy this backup ' - 'by hand, you can safely ignore this QR code.' % numLine), wrapWidth=4 * INCH) - - self.scene.moveCursor(20, 0) - x, y = self.scene.getCursorXY() - edgeRgt = self.scene.pageRect().width() - MARGIN - edgeBot = self.scene.pageRect().height() - MARGIN - - qrSize = max(1.5 * INCH, min(edgeRgt - x, edgeBot - y, 2.0 * INCH)) - self.scene.drawQR('\n'.join(Lines), qrSize) - self.scene.newLine(extra_dy=25) - - Lines = None - - # Finally, draw some pie slices at the bottom - if self.doPrintFrag: - M, N = self.fragData['M'], self.fragData['N'] - bottomOfPage = self.scene.pageRect().height() + MARGIN - maxPieHeight = bottomOfPage - self.scene.getCursorXY()[1] - 8 - maxPieWidth = int((self.scene.pageRect().width() - 2 * MARGIN) / N) - 10 - pieSize = min(72., maxPieHeight, maxPieWidth) - for i in range(N): - startX, startY = self.scene.getCursorXY() - drawSize = self.scene.drawPixmapFile('./img/frag%df.png' % M, sizePx=pieSize) - self.scene.moveCursor(10, 0) - if i == printData: - returnX, returnY = self.scene.getCursorXY() - self.scene.moveCursor(startX, startY, absolute=True) - self.scene.moveCursor(-5, -5) - self.scene.drawRect(drawSize[0] + 10, \ - drawSize[1] + 10, \ - edgeColor=Colors.TextBlue, \ - penWidth=3) - self.scene.newLine() - self.scene.moveCursor(startX - MARGIN, 0) - self.scene.drawText('#%d' % \ - (htmlColor('TextBlue'), fragNum), GETFONT('Var', 10)) - self.scene.moveCursor(returnX, returnY, absolute=True) - - - - vbar = self.view.verticalScrollBar() - vbar.setValue(vbar.minimum()) - self.view.update() - - - -################################################################################ -def OpenPaperBackupWindow(backupType, parent, main, wlt, unlockTitle=None): - - if wlt.useEncryption and wlt.isLocked: - if unlockTitle == None: - unlockTitle = parent.tr("Unlock Paper Backup") - dlg = DlgUnlockWallet(wlt, parent, main, unlockTitle) - if not dlg.exec_(): - QMessageBox.warning(parent, parent.tr('Unlock Failed'), parent.tr( - 'The wallet could not be unlocked. Please try again with ' - 'the correct unlock passphrase.'), QMessageBox.Ok) - return False - - result = True - verifyText = '' - if backupType == 'Single': - result = DlgPrintBackup(parent, main, wlt).exec_() - verifyText = parent.trUtf8( - u'If the backup was printed with SecurePrint\u200b\u2122, please ' - u'make sure you wrote the SecurePrint\u200b\u2122 code on the ' - 'printed sheet of paper. Note that the code is ' - 'case-sensitive!') - elif backupType == 'Frag': - result = DlgFragBackup(parent, main, wlt).exec_() - verifyText = parent.trUtf8( - u'If the backup was created with SecurePrint\u200b\u2122, please ' - u'make sure you wrote the SecurePrint\u200b\u2122 code on each ' - 'fragment (or stored with each file fragment). The code is the ' - 'same for all fragments.') - - doTest = MsgBoxCustom(MSGBOX.Warning, parent.tr('Verify Your Backup!'), parent.trUtf8( - 'Verify your backup! ' - '

' - 'If you just made a backup, make sure that it is correct! ' - 'The following steps are recommended to verify its integrity: ' - '
' - '
    ' - '
  • Verify each line of the backup data contains 9 columns ' - 'of 4 letters each (excluding any "ID" lines).
  • ' - '
  • %s
  • ' - '
  • Use Armory\'s backup tester to test the backup before you ' - 'physiclly secure it.
  • ' - '
' - '
' - 'Armory has a backup tester that uses the exact same ' - 'process as restoring your wallet, but stops before it writes any ' - 'data to disk. Would you like to test your backup now? ' - % verifyText), yesStr="Test Backup", noStr="Cancel") - - if doTest: - if backupType == 'Single': - DlgRestoreSingle(parent, main, True, wlt.uniqueIDB58).exec_() - elif backupType == 'Frag': - DlgRestoreFragged(parent, main, True, wlt.uniqueIDB58).exec_() - - return result - -################################################################################ -class DlgBadConnection(ArmoryDialog): - def __init__(self, haveInternet, haveSatoshi, parent=None, main=None): - super(DlgBadConnection, self).__init__(parent, main) - - - layout = QGridLayout() - lblWarnImg = QLabel() - lblWarnImg.setPixmap(QPixmap('./img/MsgBox_warning48.png')) - lblWarnImg.setAlignment(Qt.AlignHCenter | Qt.AlignTop) - - lblDescr = QLabel() - if not haveInternet and not CLI_OPTIONS.offline: - lblDescr = QRichLabel(self.tr( - 'Armory was not able to detect an internet connection, so Armory ' - 'will operate in "Offline" mode. In this mode, only wallet ' - '-management and unsigned-transaction functionality will be available. ' - '

' - 'If this is an error, please check your internet connection and ' - 'restart Armory.

Would you like to continue in "Offline" mode?')) - elif haveInternet and not haveSatoshi: - lblDescr = QRichLabel(self.tr( - 'Armory was not able to detect the presence of Bitcoin Core or bitcoind ' - 'client software (available at https://bitcoin.org). Please make sure that ' - 'the one of those programs is...
' - '
(1) ...open and connected to the network ' - '
(2) ...on the same network as Armory (main-network or test-network) ' - '
(3) ...synchronized with the blockchain before ' - 'starting Armory

Without the Bitcoin Core or bitcoind open, you will only ' - 'be able to run Armory in "Offline" mode, which will not have access ' - 'to new blockchain data, and you will not be able to send outgoing ' - 'transactions

If you do not want to be in "Offline" mode, please ' - 'restart Armory after one of these programs is open and synchronized with ' - 'the network')) - else: - # Nothing to do -- we shouldn't have even gotten here - # self.reject() - pass - - - self.main.abortLoad = False - def abortLoad(): - self.main.abortLoad = True - self.reject() - - lblDescr.setMinimumWidth(500) - self.btnAccept = QPushButton(self.tr("Continue in Offline Mode")) - self.btnCancel = QPushButton(self.tr("Close Armory")) - self.connect(self.btnAccept, SIGNAL(CLICKED), self.accept) - self.connect(self.btnCancel, SIGNAL(CLICKED), abortLoad) - buttonBox = QDialogButtonBox() - buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) - buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) - - layout.addWidget(lblWarnImg, 0, 1, 2, 1) - layout.addWidget(lblDescr, 0, 2, 1, 1) - layout.addWidget(buttonBox, 1, 2, 1, 1) - - self.setLayout(layout) - self.setWindowTitle(self.tr('Network not available')) - - -################################################################################ -def readSigBlock(parent, fullPacket): - addrB58, messageStr, pubkey, sig = '', '', '', '' - lines = fullPacket.split('\n') - readingMessage, readingPub, readingSig = False, False, False - for i in range(len(lines)): - s = lines[i].strip() - - # ADDRESS - if s.startswith('Addr'): - addrB58 = s.split(':')[-1].strip() - - # MESSAGE STRING - if s.startswith('Message') or readingMessage: - readingMessage = True - if s.startswith('Pub') or s.startswith('Sig') or ('END-CHAL' in s): - readingMessage = False - else: - # Message string needs to be exact, grab what's between the - # double quotes, no newlines - iq1 = s.index('"') + 1 - iq2 = s.index('"', iq1) - messageStr += s[iq1:iq2] - - # PUBLIC KEY - if s.startswith('Pub') or readingPub: - readingPub = True - if s.startswith('Sig') or ('END-SIGNATURE-BLOCK' in s): - readingPub = False - else: - pubkey += s.split(':')[-1].strip().replace(' ', '') - - # SIGNATURE - if s.startswith('Sig') or readingSig: - readingSig = True - if 'END-SIGNATURE-BLOCK' in s: - readingSig = False - else: - sig += s.split(':')[-1].strip().replace(' ', '') - - - if len(pubkey) > 0: - try: - pubkey = hex_to_binary(pubkey) - if len(pubkey) not in (32, 33, 64, 65): raise - except: - QMessageBox.critical(parent, parent.tr('Bad Public Key'), \ - parent.tr('Public key data was not recognized'), QMessageBox.Ok) - pubkey = '' - - if len(sig) > 0: - try: - sig = hex_to_binary(sig) - except: - QMessageBox.critical(parent, parent.tr('Bad Signature'), \ - parent.tr('Signature data is malformed!'), QMessageBox.Ok) - sig = '' - - - pubkeyhash = hash160(pubkey) - if not pubkeyhash == addrStr_to_hash160(addrB58)[1]: - QMessageBox.critical(parent, parent.tr('Address Mismatch'), \ - parent.tr('!!! The address included in the signature block does not ' - 'match the supplied public key! This should never happen, ' - 'and may in fact be an attempt to mislead you !!!'), QMessageBox.Ok) - sig = '' - - - - return addrB58, messageStr, pubkey, sig - - -################################################################################ -def makeSigBlock(addrB58, MessageStr, binPubkey='', binSig=''): - lineWid = 50 - s = '-----BEGIN-SIGNATURE-BLOCK'.ljust(lineWid + 13, '-') + '\n' - - ### Address ### - s += 'Address: %s\n' % addrB58 - - ### Message ### - chPerLine = lineWid - 2 - nMessageLines = (len(MessageStr) - 1) / chPerLine + 1 - for i in range(nMessageLines): - cLine = 'Message: "%s"\n' if i == 0 else ' "%s"\n' - s += cLine % MessageStr[i * chPerLine:(i + 1) * chPerLine] - - ### Public Key ### - if len(binPubkey) > 0: - hexPub = binary_to_hex(binPubkey) - nPubLines = (len(hexPub) - 1) / lineWid + 1 - for i in range(nPubLines): - pLine = 'PublicKey: %s\n' if i == 0 else ' %s\n' - s += pLine % hexPub[i * lineWid:(i + 1) * lineWid] - - ### Signature ### - if len(binSig) > 0: - hexSig = binary_to_hex(binSig) - nSigLines = (len(hexSig) - 1) / lineWid + 1 - for i in range(nSigLines): - sLine = 'Signature: %s\n' if i == 0 else ' %s\n' - s += sLine % hexSig[i * lineWid:(i + 1) * lineWid] - - s += '-----END-SIGNATURE-BLOCK'.ljust(lineWid + 13, '-') + '\n' - return s - - - -class DlgExecLongProcess(ArmoryDialog): - """ - Execute a processing that may require having the user to wait a while. - Should appear like a splash screen, and will automatically close when - the processing is done. As such, you should have very little text, just - in case it finishes immediately, the user won't have time to read it. - - DlgExecLongProcess(execFunc, 'Short Description', self, self.main).exec_() - """ - def __init__(self, funcExec, msg='', parent=None, main=None): - super(DlgExecLongProcess, self).__init__(parent, main) - - self.func = funcExec - - waitFont = GETFONT('Var', 14) - descrFont = GETFONT('Var', 12) - palette = QPalette() - palette.setColor(QPalette.Window, QColor(235, 235, 255)) - self.setPalette(palette); - self.setAutoFillBackground(True) - - if parent: - qr = parent.geometry() - x, y, w, h = qr.left(), qr.top(), qr.width(), qr.height() - dlgW = relaxedSizeStr(waitFont, msg)[0] - dlgW = min(dlgW, 400) - dlgH = 150 - self.setGeometry(int(x + w / 2 - dlgW / 2), int(y + h / 2 - dlgH / 2), dlgW, dlgH) - - lblWaitMsg = QRichLabel(self.tr('Please Wait...')) - lblWaitMsg.setFont(waitFont) - lblWaitMsg.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter) - - lblDescrMsg = QRichLabel(msg) - lblDescrMsg.setFont(descrFont) - lblDescrMsg.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter) - - self.setWindowFlags(Qt.SplashScreen) - - layout = QVBoxLayout() - layout.addWidget(lblWaitMsg) - layout.addWidget(lblDescrMsg) - self.setLayout(layout) + layout = QVBoxLayout() + layout.addWidget(lblWaitMsg) + layout.addWidget(lblDescrMsg) + self.setLayout(layout) def exec_(self): @@ -3832,118 +2990,6 @@ def leaveEvent(self, ev): # htmlColor('Background')) -################################################################################ -class DlgBackupCenter(ArmoryDialog): - - ############################################################################# - def __init__(self, parent, main, wlt): - super(DlgBackupCenter, self).__init__(parent, main) - - self.wlt = wlt - wltID = wlt.uniqueIDB58 - wltName = wlt.labelName - - self.walletBackupFrame = WalletBackupFrame(parent, main) - self.walletBackupFrame.setWallet(wlt) - self.btnDone = QPushButton(self.tr('Done')) - self.connect(self.btnDone, SIGNAL(CLICKED), self.reject) - frmBottomBtns = makeHorizFrame([STRETCH, self.btnDone]) - - layoutDialog = QVBoxLayout() - - layoutDialog.addWidget(self.walletBackupFrame) - - layoutDialog.addWidget(frmBottomBtns) - - self.setLayout(layoutDialog) - self.setWindowTitle(self.tr("Backup Center")) - self.setMinimumSize(640, 350) - -################################################################################ -class DlgSimpleBackup(ArmoryDialog): - def __init__(self, parent, main, wlt): - super(DlgSimpleBackup, self).__init__(parent, main) - - self.wlt = wlt - - lblDescrTitle = QRichLabel(self.tr( - 'Protect Your Bitcoins -- Make a Wallet Backup!')) - - lblDescr = QRichLabel(self.tr( - 'A failed hard-drive or forgotten passphrase will lead to ' - 'permanent loss of bitcoins! Luckily, Armory wallets only ' - 'need to be backed up one time, and protect you in both ' - 'of these events. If you\'ve ever forgotten a password or had ' - 'a hardware failure, make a backup!')) - - # ## Paper - lblPaper = QRichLabel(self.tr( - 'Use a printer or pen-and-paper to write down your wallet "seed."')) - btnPaper = QPushButton(self.tr('Make Paper Backup')) - - # ## Digital - lblDigital = QRichLabel(self.tr( - 'Create an unencrypted copy of your wallet file, including imported ' - 'addresses.')) - btnDigital = QPushButton(self.tr('Make Digital Backup')) - - # ## Other - btnOther = QPushButton(self.tr('See Other Backup Options')) - - def backupDigital(): - if self.main.digitalBackupWarning(): - self.main.makeWalletCopy(self, self.wlt, 'Decrypt', 'decrypt') - self.accept() - - def backupPaper(): - OpenPaperBackupWindow('Single', self, self.main, self.wlt) - self.accept() - - def backupOther(): - self.accept() - DlgBackupCenter(self, self.main, self.wlt).exec_() - - self.connect(btnPaper, SIGNAL(CLICKED), backupPaper) - self.connect(btnDigital, SIGNAL(CLICKED), backupDigital) - self.connect(btnOther, SIGNAL(CLICKED), backupOther) - - layout = QGridLayout() - - layout.addWidget(lblPaper, 0, 0) - layout.addWidget(btnPaper, 0, 2) - - layout.addWidget(HLINE(), 1, 0, 1, 3) - - layout.addWidget(lblDigital, 2, 0) - layout.addWidget(btnDigital, 2, 2) - - layout.addWidget(HLINE(), 3, 0, 1, 3) - - layout.addWidget(makeHorizFrame([STRETCH, btnOther, STRETCH]), 4, 0, 1, 3) - - # layout.addWidget( VLINE(), 0,1, 5,1) - - layout.setContentsMargins(10, 5, 10, 5) - setLayoutStretchRows(layout, 1, 0, 1, 0, 0) - setLayoutStretchCols(layout, 1, 0, 0) - - frmGrid = QFrame() - frmGrid.setFrameStyle(STYLE_PLAIN) - frmGrid.setLayout(layout) - - btnClose = QPushButton(self.tr('Done')) - self.connect(btnClose, SIGNAL(CLICKED), self.accept) - frmClose = makeHorizFrame([STRETCH, btnClose]) - - frmAll = makeVertFrame([lblDescrTitle, lblDescr, frmGrid, frmClose]) - layoutAll = QVBoxLayout() - layoutAll.addWidget(frmAll) - self.setLayout(layoutAll) - self.sizeHint = lambda: QSize(400, 250) - - self.setWindowTitle(self.tr('Backup Options')) - - ################################################################################ # Class that acts as a center where the user can decide what to do with the # watch-only wallet. The data can be displayed, printed, or saved to a file as a @@ -4305,433 +3351,6 @@ def createPrintScene(self): self.view.update() -################################################################################ -class DlgFragBackup(ArmoryDialog): - - ############################################################################# - def __init__(self, parent, main, wlt): - super(DlgFragBackup, self).__init__(parent, main) - - self.wlt = wlt - - lblDescrTitle = QRichLabel(self.tr( - 'Create M-of-N Fragmented Backup of "%s" (%s)' % (wlt.labelName, wlt.uniqueIDB58)), doWrap=False) - lblDescrTitle.setContentsMargins(5, 5, 5, 5) - - self.lblAboveFrags = QRichLabel('') - self.lblAboveFrags.setContentsMargins(10, 0, 10, 0) - - frmDescr = makeVertFrame([lblDescrTitle, self.lblAboveFrags], \ - STYLE_RAISED) - - self.fragDisplayLastN = 0 - self.fragDisplayLastM = 0 - - self.maxM = 5 if not self.main.usermode == USERMODE.Expert else 8 - self.maxN = 6 if not self.main.usermode == USERMODE.Expert else 12 - self.currMinN = 2 - self.maxmaxN = 12 - - self.comboM = QComboBox() - self.comboN = QComboBox() - - for M in range(2, self.maxM + 1): - self.comboM.addItem(str(M)) - - for N in range(self.currMinN, self.maxN + 1): - self.comboN.addItem(str(N)) - - self.comboM.setCurrentIndex(1) - self.comboN.setCurrentIndex(2) - - def updateM(): - self.updateComboN() - self.createFragDisplay() - - updateN = self.createFragDisplay - - self.connect(self.comboM, SIGNAL('activated(int)'), updateM) - self.connect(self.comboN, SIGNAL('activated(int)'), updateN) - self.comboM.setMinimumWidth(30) - self.comboN.setMinimumWidth(30) - - btnAccept = QPushButton(self.tr('Close')) - self.connect(btnAccept, SIGNAL(CLICKED), self.accept) - frmBottomBtn = makeHorizFrame([STRETCH, btnAccept]) - - # We will hold all fragments here, in SBD objects. Destroy all of them - # before the dialog exits - self.secureRoot = self.wlt.addrMap['ROOT'].binPrivKey32_Plain.copy() - self.secureChain = self.wlt.addrMap['ROOT'].chaincode.copy() - self.secureMtrx = [] - - testChain = DeriveChaincodeFromRootKey(self.secureRoot) - if testChain == self.secureChain: - self.noNeedChaincode = True - self.securePrint = self.secureRoot - else: - self.securePrint = self.secureRoot + self.secureChain - - self.chkSecurePrint = QCheckBox(self.trUtf8(u'Use SecurePrint\u200b\u2122 ' - 'to prevent exposing keys to printer or other devices')) - - self.scrollArea = QScrollArea() - self.createFragDisplay() - self.scrollArea.setWidgetResizable(True) - - self.ttipSecurePrint = self.main.createToolTipWidget(self.trUtf8( - u'SecurePrint\u200b\u2122 encrypts your backup with a code displayed on ' - 'the screen, so that no other devices or processes has access to the ' - 'unencrypted private keys (either network devices when printing, or ' - 'other applications if you save a fragment to disk or USB device). ' - u'You must keep the SecurePrint\u200b\u2122 code with the backup!')) - self.lblSecurePrint = QRichLabel(self.trUtf8( - 'IMPORTANT: You must keep the ' - u'SecurePrint\u200b\u2122 encryption code with your backup! ' - u'Your SecurePrint\u200b\u2122 code is ' - '%s. ' - 'All fragments for a given wallet use the ' - 'same code.' % (htmlColor('TextWarn'), htmlColor('TextBlue'), self.backupData.sppass, \ - htmlColor('TextWarn')))) - self.connect(self.chkSecurePrint, SIGNAL(CLICKED), self.clickChkSP) - self.chkSecurePrint.setChecked(False) - self.lblSecurePrint.setVisible(False) - frmChkSP = makeHorizFrame([self.chkSecurePrint, self.ttipSecurePrint, STRETCH]) - - dlgLayout = QVBoxLayout() - dlgLayout.addWidget(frmDescr) - dlgLayout.addWidget(self.scrollArea) - dlgLayout.addWidget(frmChkSP) - dlgLayout.addWidget(self.lblSecurePrint) - dlgLayout.addWidget(frmBottomBtn) - setLayoutStretch(dlgLayout, 0, 1, 0, 0, 0) - - self.setLayout(dlgLayout) - self.setMinimumWidth(650) - self.setMinimumHeight(450) - self.setWindowTitle('Create Backup Fragments') - - - ############################################################################# - def clickChkSP(self): - self.lblSecurePrint.setVisible(self.chkSecurePrint.isChecked()) - self.createFragDisplay() - - - ############################################################################# - def updateComboN(self): - M = int(str(self.comboM.currentText())) - oldN = int(str(self.comboN.currentText())) - self.currMinN = M - self.comboN.clear() - - for i, N in enumerate(range(self.currMinN, self.maxN + 1)): - self.comboN.addItem(str(N)) - - if M > oldN: - self.comboN.setCurrentIndex(0) - else: - for i, N in enumerate(range(self.currMinN, self.maxN + 1)): - if N == oldN: - self.comboN.setCurrentIndex(i) - - - - ############################################################################# - def createFragDisplay(self): - M = int(str(self.comboM.currentText())) - N = int(str(self.comboN.currentText())) - - #only recompute fragments if M or N changed - if self.fragDisplayLastN != N or \ - self.fragDisplayLastM != M: - self.recomputeFragData() - - self.fragDisplayLastN = N - self.fragDisplayLastM = M - - lblAboveM = QRichLabel(self.tr('Required Fragments '), hAlign=Qt.AlignHCenter, doWrap=False) - lblAboveN = QRichLabel(self.tr('Total Fragments '), hAlign=Qt.AlignHCenter) - frmComboM = makeHorizFrame([STRETCH, QLabel('M:'), self.comboM, STRETCH]) - frmComboN = makeHorizFrame([STRETCH, QLabel('N:'), self.comboN, STRETCH]) - - btnPrintAll = QPushButton(self.tr('Print All Fragments')) - self.connect(btnPrintAll, SIGNAL(CLICKED), self.clickPrintAll) - leftFrame = makeVertFrame([STRETCH, \ - lblAboveM, \ - frmComboM, \ - lblAboveN, \ - frmComboN, \ - STRETCH, \ - HLINE(), \ - btnPrintAll, \ - STRETCH], STYLE_STYLED) - - layout = QHBoxLayout() - layout.addWidget(leftFrame) - - for f in range(N): - layout.addWidget(self.createFragFrm(f)) - - - frmScroll = QFrame() - frmScroll.setFrameStyle(STYLE_SUNKEN) - frmScroll.setStyleSheet('QFrame { background-color : %s }' % \ - htmlColor('SlightBkgdDark')) - frmScroll.setLayout(layout) - self.scrollArea.setWidget(frmScroll) - - BLUE = htmlColor('TextBlue') - self.lblAboveFrags.setText(self.tr( - 'Any %d of these ' - '%d' - 'fragments are sufficient to restore your wallet, and each fragment ' - 'has the ID, %s. All fragments with the ' - 'same fragment ID are compatible with each other!' % (BLUE, M, BLUE, N, BLUE, self.fragPrefixStr))) - - - ############################################################################# - def createFragFrm(self, idx): - - doMask = self.chkSecurePrint.isChecked() - M = int(str(self.comboM.currentText())) - N = int(str(self.comboN.currentText())) - - lblFragID = QRichLabel(self.tr('Fragment ID:
%s-%s
' % \ - (str(self.fragPrefixStr), str(idx + 1)))) - # lblWltID = QRichLabel('(%s)' % self.wlt.uniqueIDB58) - lblFragPix = QImageLabel(self.fragPixmapFn, size=(72, 72)) - if doMask: - ys = self.secureMtrxCrypt[idx][1].toBinStr()[:42] - else: - ys = self.secureMtrx[idx][1].toBinStr()[:42] - - easyYs1 = makeSixteenBytesEasy(ys[:16 ]) - easyYs2 = makeSixteenBytesEasy(ys[ 16:32]) - - binID = base58_to_binary(self.uniqueFragSetID) - ID = ComputeFragIDLineHex(M, idx, binID, doMask, addSpaces=True) - - fragPreview = 'ID: %s...
' % ID[:12] - fragPreview += 'F1: %s...
' % easyYs1[:12] - fragPreview += 'F2: %s... ' % easyYs2[:12] - lblPreview = QRichLabel(fragPreview) - lblPreview.setFont(GETFONT('Fixed', 9)) - - lblFragIdx = QRichLabel('#%d' % (idx + 1), size=4, color='TextBlue', \ - hAlign=Qt.AlignHCenter) - - frmTopLeft = makeVertFrame([lblFragID, lblFragIdx, STRETCH]) - frmTopRight = makeVertFrame([lblFragPix, STRETCH]) - - frmPaper = makeVertFrame([lblPreview]) - frmPaper.setStyleSheet('QFrame { background-color : #ffffff }') - - fnPrint = lambda: self.clickPrintFrag(idx) - fnSave = lambda: self.clickSaveFrag(idx) - - btnPrintFrag = QPushButton(self.tr('View/Print')) - btnSaveFrag = QPushButton(self.tr('Save to File')) - self.connect(btnPrintFrag, SIGNAL(CLICKED), fnPrint) - self.connect(btnSaveFrag, SIGNAL(CLICKED), fnSave) - frmButtons = makeHorizFrame([btnPrintFrag, btnSaveFrag]) - - - layout = QGridLayout() - layout.addWidget(frmTopLeft, 0, 0, 1, 1) - layout.addWidget(frmTopRight, 0, 1, 1, 1) - layout.addWidget(frmPaper, 1, 0, 1, 2) - layout.addWidget(frmButtons, 2, 0, 1, 2) - layout.setSizeConstraint(QLayout.SetFixedSize) - - outFrame = QFrame() - outFrame.setFrameStyle(STYLE_STYLED) - outFrame.setLayout(layout) - return outFrame - - - ############################################################################# - def clickPrintAll(self): - self.clickPrintFrag(range(int(str(self.comboN.currentText())))) - - ############################################################################# - def clickPrintFrag(self, zindex): - if not isinstance(zindex, (list, tuple)): - zindex = [zindex] - fragData = {} - fragData['M'] = int(str(self.comboM.currentText())) - fragData['N'] = int(str(self.comboN.currentText())) - fragData['FragIDStr'] = self.fragPrefixStr - fragData['FragPixmap'] = self.fragPixmapFn - fragData['Range'] = zindex - fragData['Secure'] = self.chkSecurePrint.isChecked() - fragData['fragSetID'] = self.uniqueFragSetID - dlg = DlgPrintBackup(self, self.main, self.wlt, 'Fragments', \ - self.secureMtrx, self.secureMtrxCrypt, fragData, \ - self.secureRoot, self.secureChain) - dlg.exec_() - - ############################################################################# - def clickSaveFrag(self, zindex): - saveMtrx = self.secureMtrx - doMask = False - if self.chkSecurePrint.isChecked(): - response = QMessageBox.question(self, self.tr('Secure Backup?'), self.trUtf8( - u'You have selected to use SecurePrint\u200b\u2122 for the printed ' - 'backups, which can also be applied to fragments saved to file. ' - u'Doing so will require you store the SecurePrint\u200b\u2122 ' - 'code with the backup, but it will prevent unencrypted key data from ' - 'touching any disks.

Do you want to encrypt the fragment ' - u'file with the same SecurePrint\u200b\u2122 code?'), \ - QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) - - if response == QMessageBox.Yes: - saveMtrx = self.secureMtrxCrypt - doMask = True - elif response == QMessageBox.No: - pass - else: - return - - - wid = self.wlt.uniqueIDB58 - pref = self.fragPrefixStr - fnum = zindex + 1 - M = self.M - sec = 'secure.' if doMask else '' - defaultFn = 'wallet_%s_%s_num%d_need%d.%sfrag' % (wid, pref, fnum, M, sec) - #print 'FragFN:', defaultFn - savepath = self.main.getFileSave('Save Fragment', \ - ['Wallet Fragments (*.frag)'], \ - defaultFn) - - if len(toUnicode(savepath)) == 0: - return - - fout = open(savepath, 'w') - fout.write('Wallet ID: %s\n' % wid) - fout.write('Create Date: %s\n' % unixTimeToFormatStr(RightNow())) - fout.write('Fragment ID: %s-#%d\n' % (pref, fnum)) - fout.write('Frag Needed: %d\n' % M) - fout.write('\n\n') - - try: - yBin = saveMtrx[zindex][1].toBinStr() - binID = base58_to_binary(self.uniqueFragSetID) - IDLine = ComputeFragIDLineHex(M, zindex, binID, doMask, addSpaces=True) - if len(yBin) == 32: - fout.write('ID: ' + IDLine + '\n') - fout.write('F1: ' + makeSixteenBytesEasy(yBin[:16 ]) + '\n') - fout.write('F2: ' + makeSixteenBytesEasy(yBin[ 16:]) + '\n') - elif len(yBin) == 64: - fout.write('ID: ' + IDLine + '\n') - fout.write('F1: ' + makeSixteenBytesEasy(yBin[:16 ]) + '\n') - fout.write('F2: ' + makeSixteenBytesEasy(yBin[ 16:32 ]) + '\n') - fout.write('F3: ' + makeSixteenBytesEasy(yBin[ 32:48 ]) + '\n') - fout.write('F4: ' + makeSixteenBytesEasy(yBin[ 48:]) + '\n') - else: - LOGERROR('yBin is not 32 or 64 bytes! It is %s bytes', len(yBin)) - finally: - yBin = None - - fout.close() - - qmsg = self.tr( - 'The fragment was successfully saved to the following location: ' - '

%s

' % savepath) - - if doMask: - qmsg += self.trUtf8( - 'Important: ' - 'The fragment was encrypted with the ' - u'SecurePrint\u200b\u2122 encryption code. You must keep this ' - 'code with the backup in order to use it! The code is ' - 'case-sensitive! ' - '

%s' - '

' - 'The above code is case-sensitive!' \ - % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ - self.backupData.sppass)) - - QMessageBox.information(self, self.tr('Success'), qmsg, QMessageBox.Ok) - - - - ############################################################################# - def destroyFrags(self): - if len(self.secureMtrx) == 0: - return - - if isinstance(self.secureMtrx[0], (list, tuple)): - for sbdList in self.secureMtrx: - for sbd in sbdList: - sbd.destroy() - for sbdList in self.secureMtrxCrypt: - for sbd in sbdList: - sbd.destroy() - else: - for sbd in self.secureMtrx: - sbd.destroy() - for sbd in self.secureMtrxCrypt: - sbd.destroy() - - self.secureMtrx = [] - self.secureMtrxCrypt = [] - - - ############################################################################# - def destroyEverything(self): - self.secureRoot.destroy() - self.secureChain.destroy() - self.securePrint.destroy() - self.destroyFrags() - - ############################################################################# - def recomputeFragData(self): - """ - Only M is needed, since N doesn't change - """ - - M = int(str(self.comboM.currentText())) - N = int(str(self.comboN.currentText())) - # Make sure only local variables contain non-SBD data - self.destroyFrags() - self.uniqueFragSetID = \ - binary_to_base58(SecureBinaryData().GenerateRandom(6).toBinStr()) - insecureData = SplitSecret(self.securePrint, M, self.maxmaxN) - for x, y in insecureData: - self.secureMtrx.append([SecureBinaryData(x), SecureBinaryData(y)]) - insecureData, x, y = None, None, None - - ##### - # Now we compute the SecurePrint(TM) versions of the fragments - SECPRINT = HardcodedKeyMaskParams() - MASK = lambda x: SECPRINT['FUNC_MASK'](x, ekey=self.binCrypt32) - if not self.randpass or not self.binCrypt32: - self.randpass = SECPRINT['FUNC_PWD'](self.secureRoot + self.secureChain) - self.binCrypt32 = SECPRINT['FUNC_KDF'](self.randpass) - self.secureMtrxCrypt = [] - for sbdX, sbdY in self.secureMtrx: - self.secureMtrxCrypt.append([sbdX.copy(), MASK(sbdY)]) - ##### - - self.M, self.N = M, N - self.fragPrefixStr = ComputeFragIDBase58(self.M, \ - base58_to_binary(self.uniqueFragSetID)) - self.fragPixmapFn = './img/frag%df.png' % M - - - ############################################################################# - def accept(self): - self.destroyEverything() - super(DlgFragBackup, self).accept() - - ############################################################################# - def reject(self): - self.destroyEverything() - super(DlgFragBackup, self).reject() - - ################################################################################ # Create a special QLineEdit with a masked input # Forces the cursor to start at position 0 whenever there is no input @@ -4743,7 +3362,7 @@ def __init__(self, inputMask): fixFont = GETFONT('Fix', 9) self.setFont(fixFont) self.setMinimumWidth(tightSizeStr(fixFont, inputMask)[0] + 10) - self.connect(self, SIGNAL('cursorPositionChanged(int,int)'), self.controlCursor) + self.cursorPositionChanged.connect(self.controlCursor) def controlCursor(self, oldpos, newpos): if newpos != 0 and len(str(self.text()).strip()) == 0: @@ -4864,12 +3483,12 @@ class DlgEnterSecurePrintCode(ArmoryDialog): def __init__(self, parent, main): super(DlgEnterSecurePrintCode, self).__init__(parent, main) - lblSecurePrintCodeDescr = QRichLabel(self.trUtf8( + lblSecurePrintCodeDescr = QRichLabel(self.tr( u'This fragment file requires a SecurePrint\u200b\u2122 code. ' 'You will only have to enter this code once since it is the same ' 'on all fragments.')) lblSecurePrintCodeDescr.setMinimumWidth(440) - self.lblSP = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code: '), doWrap=False) + self.lblSP = QRichLabel(self.tr(u'SecurePrint\u200b\u2122 Code: '), doWrap=False) self.editSecurePrint = QLineEdit() spFrame = makeHorizFrame([self.lblSP, self.editSecurePrint, STRETCH]) @@ -4911,7 +3530,7 @@ def __init__(self, parent, main, fragList=[], wltType=UNKNOWN, securePrintCode=N replStr = '[' + ','.join(strList[:]) + ']' already = self.tr('You have entered fragments %s, so far.' % replStr) - lblDescr = QRichLabel(self.trUtf8( + lblDescr = QRichLabel(self.tr( 'Enter Another Fragment...

%s ' 'The fragments can be entered in any order, as long as you provide ' 'enough of them to restore the wallet. If any fragments use a ' @@ -4921,9 +3540,9 @@ def __init__(self, parent, main, fragList=[], wltType=UNKNOWN, securePrintCode=N self.version0Button = QRadioButton(self.tr( BACKUP_TYPE_0_TEXT), self) self.version135aButton = QRadioButton(self.tr( BACKUP_TYPE_135a_TEXT), self) - self.version135aSPButton = QRadioButton(self.trUtf8( BACKUP_TYPE_135a_SP_TEXT), self) + self.version135aSPButton = QRadioButton(self.tr( BACKUP_TYPE_135a_SP_TEXT), self) self.version135cButton = QRadioButton(self.tr( BACKUP_TYPE_135c_TEXT), self) - self.version135cSPButton = QRadioButton(self.trUtf8( BACKUP_TYPE_135c_SP_TEXT), self) + self.version135cSPButton = QRadioButton(self.tr( BACKUP_TYPE_135c_SP_TEXT), self) self.backupTypeButtonGroup = QButtonGroup(self) self.backupTypeButtonGroup.addButton(self.version0Button) self.backupTypeButtonGroup.addButton(self.version135aButton) @@ -4993,7 +3612,7 @@ def __init__(self, parent, main, fragList=[], wltType=UNKNOWN, securePrintCode=N # Add Secure Print row - Use supplied securePrintCode and # disable text entry if it is not None - self.lblSP = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code:'), doWrap=False) + self.lblSP = QRichLabel(self.tr(u'SecurePrint\u200b\u2122 Code:'), doWrap=False) self.editSecurePrint = QLineEdit() self.editSecurePrint.setEnabled(not securePrintCode) if (securePrintCode): diff --git a/ui/MultiSigDialogs.py b/ui/MultiSigDialogs.py index e90383990..1d426a382 100644 --- a/ui/MultiSigDialogs.py +++ b/ui/MultiSigDialogs.py @@ -3528,12 +3528,12 @@ def doContinue(self): cppTx = TheBDM.getTxByHash(txHash) if not cppTx.isInitialized(): LOGERROR('UTXO was supplied for which we could not find prev Tx') - QMessageBox.warning(self, self.tr('Transaction Not Found'), self.trUtf8( - 'There was an error creating the promissory note -- the selected ' - 'coins were not found in the blockchain. Please go to ' - '"Help"\xe2\x86\x92"Submit Bug Report" from ' - 'the main window and submit your log files so the Armory team ' - 'can review this error.'), QMessageBox.Ok) + QMessageBox.warning(self, self.tr('Transaction Not Found'), self.tr( + 'There was an error creating the promissory note -- the selected ' + 'coins were not found in the blockchain. Please go to ' + '"Help"\xe2\x86\x92"Submit Bug Report" from ' + 'the main window and submit your log files so the Armory team ' + 'can review this error.'), QMessageBox.Ok) rawTx = cppTx.serialize() utxoScrAddr = utxo.getRecipientScrAddr() diff --git a/ui/WalletFrames.py b/ui/WalletFrames.py index 4bc4fd380..3b8038563 100755 --- a/ui/WalletFrames.py +++ b/ui/WalletFrames.py @@ -37,7 +37,8 @@ # Need to put circular imports at the end of the script to avoid an import deadlock from qtdialogs.qtdialogs import STRETCH, MIN_PASSWD_WIDTH, \ - QRadioButtonBackupCtr, OpenPaperBackupWindow + OpenPaperBackupWindow +from qtdialogs.qtdefines import QRadioButtonBackupCtr from ui.CoinControlUI import CoinControlDlg, RBFDlg From 87e9a549c7589ae1b28db578f4703a4b6b24f349 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 13 Mar 2022 11:48:34 +0100 Subject: [PATCH 16/47] Implement client side addSupportingTx for Signer object Implement client side getHistoryForWalletSelection Fix wallet filtering in GUI Fix str2coin Fix tx history export to CSV --- ArmoryQt.py | 11 ++- armoryengine/ArmoryUtils.py | 41 ++++----- armoryengine/BDM.py | 2 +- armoryengine/CppBridge.py | 35 +++++++- cppForSwig/BridgeAPI/CppBridge.cpp | 85 ++++++++++++++----- cppForSwig/BridgeAPI/CppBridge.h | 3 + .../BridgeAPI/ProtobufCommandParser.cpp | 29 +++++++ cppForSwig/protobuf/ClientProto.proto | 6 +- qtdialogs/DlgExportTxHistory.py | 23 ++--- 9 files changed, 174 insertions(+), 61 deletions(-) mode change 100755 => 100644 cppForSwig/protobuf/ClientProto.proto diff --git a/ArmoryQt.py b/ArmoryQt.py index c01b58b36..27cb4b145 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -2356,6 +2356,9 @@ def createCombinedLedger(self, resetMainLedger=False): Create a ledger to display on the main screen, that consists of ledger entries of any SUBSET of available wallets. """ + if self.ledgerView == None: + return + bdmState = TheBDM.getState() @@ -2397,7 +2400,7 @@ def createCombinedLedger(self, resetMainLedger=False): if resetMainLedger == False: self.ledgerModel.reset() else: - self.ledgerView.goToTop() + self.ledgerView.scrollToTop() except AttributeError: raise @@ -2439,7 +2442,7 @@ def convertLedgerToTable(self, ledgerProto, showSentToSelfAmt=True, wltIDIn=None if wlt: isWatch = (determineWalletType(wlt, self)[0] == WLTTYPES.WatchOnly) - wltName = wlt.labelName + wltName = wlt.getDisplayStr(pref="Wlt") dispComment = self.getCommentForLE(le, wltID) else: lboxId = wltID @@ -4794,6 +4797,10 @@ def handleCppNotification(self, action, args): self.updateStatusBarText() elif action == REFRESH_ACTION: + #ignore refresh notification until the bdm_ready notification + if TheBDM.getState() != BDM_BLOCKCHAIN_READY: + return + #The wallet ledgers have been updated from an event outside of new ZC #or new blocks (usually a wallet or address was imported, or the #wallet filter was modified) diff --git a/armoryengine/ArmoryUtils.py b/armoryengine/ArmoryUtils.py index 70d5bc119..88a28abbb 100755 --- a/armoryengine/ArmoryUtils.py +++ b/armoryengine/ArmoryUtils.py @@ -1260,22 +1260,20 @@ def str2coin(theStr, negAllowed=True, maxDec=8, roundHighPrec=True): if len(coinStr.strip())==0: raise ValueError - isNeg = ('-' in coinStr) - coinStrPos = coinStr.replace('-','') - if not '.' in coinStrPos: - if not negAllowed and isNeg: - raise NegativeValueError - return (int(coinStrPos)*ONE_BTC)*(-1 if isNeg else 1) + + if not '.' in coinStr: + result = int(coinStr)*ONE_BTC else: - lhs,rhs = coinStrPos.strip().split('.') - if len(lhs.strip('-'))==0: - lhs='0' - if len(rhs)>maxDec and not roundHighPrec: - raise TooMuchPrecisionError - if not negAllowed and isNeg: - raise NegativeValueError - fullInt = int(float(coinStr) * ONE_BTC) - return fullInt*(-1 if isNeg else 1) + isNeg = ('-' in coinStr) + coinStrPos = coinStr.replace('-', '') + lhs,rhs = coinStrPos.strip(' ').split('.') + result = int(lhs) * ONE_BTC + int(rhs.ljust(8, "0")) + if isNeg: + result = -result + + if not negAllowed and result < 0: + raise NegativeValueError + return result ################################################################################ @@ -1404,8 +1402,7 @@ def hash160_to_p2pkhash_script(binStr20): if not len(binStr20)==20: raise InvalidHashError('Tried to convert non-20-byte str to p2pkh script') - from armoryengine.Transaction import getOpCode - from armoryengine.Script import scriptPushData + from armoryengine.Script import scriptPushData, getOpCode outScript = ''.join([ getOpCode('OP_DUP' ), \ getOpCode('OP_HASH160' ), \ scriptPushData(binStr20), @@ -1421,8 +1418,7 @@ def hash160_to_p2sh_script(binStr20): if not len(binStr20)==20: raise InvalidHashError('Tried to convert non-20-byte str to p2sh script') - from armoryengine.Transaction import getOpCode - from armoryengine.Script import scriptPushData + from armoryengine.Script import scriptPushData, getOpCode outScript = ''.join([ getOpCode('OP_HASH160'), scriptPushData(binStr20), getOpCode('OP_EQUAL')]) @@ -1443,8 +1439,7 @@ def pubkey_to_p2pk_script(binStr33or65): if not len(binStr33or65) in [33, 65]: raise KeyDataError('Invalid public key supplied to p2pk script') - from armoryengine.Transaction import getOpCode - from armoryengine.Script import scriptPushData + from armoryengine.Script import scriptPushData, getOpCode serPubKey = scriptPushData(binStr33or65) outScript = serPubKey + getOpCode('OP_CHECKSIG') return outScript @@ -1465,7 +1460,7 @@ def pubkeylist_to_multisig_script(pkList, M, withSort=True): if sum([ (0 if len(pk) in [33,65] else 1) for pk in pkList]) > 0: raise KeyDataError('Not all strings in pkList are 33 or 65 bytes!') - from armoryengine.Transaction import getOpCode + from armoryengine.Script import getOpCode opM = getOpCode('OP_%d' % M) opN = getOpCode('OP_%d' % len(pkList)) @@ -2192,7 +2187,7 @@ def unixTimeToFormatStr(unixTime, formatStr=DEFAULT_DATE_FORMAT): pleasant, human-readable format """ dtobj = datetime.fromtimestamp(unixTime) - return dtobj + return dtobj.strftime(formatStr) def secondsToHumanTime(nSec): strPieces = [] diff --git a/armoryengine/BDM.py b/armoryengine/BDM.py index 2a940cbae..5ccd9a4c4 100644 --- a/armoryengine/BDM.py +++ b/armoryengine/BDM.py @@ -267,7 +267,7 @@ def pushNotification(self, notifProto): elif action == BDMAction_Refresh: act = REFRESH_ACTION - arglist = arg + arglist = notifProto.ids elif action == BDMAction_Exited: act = STOPPED_ACTION diff --git a/armoryengine/CppBridge.py b/armoryengine/CppBridge.py index 3750bdf1f..16529b14c 100755 --- a/armoryengine/CppBridge.py +++ b/armoryengine/CppBridge.py @@ -320,7 +320,7 @@ def readBridgeSocket(self): ############################################################################# def pushNotification(self, data): - payload = ClientProto_pb2.CppBridgeCallback() + payload = ClientProto_pb2.CppBridgeCallbackMsg() payload.ParseFromString(data) notifThread = threading.Thread(\ @@ -330,7 +330,7 @@ def pushNotification(self, data): ############################################################################# def pushProgressNotification(self, data): - payload = ClientProto_pb2.CppProgressCallback() + payload = ClientProto_pb2.CppProgressCallbackMsg() payload.ParseFromString(data) TheBDM.reportProgress(payload) @@ -1201,6 +1201,21 @@ def estimateFee(self, blocks, strat): return response + ############################################################################# + def getHistoryForWalletSelection(self, wltIDList, order): + packet = ClientProto_pb2.ClientCommand() + packet.method = ClientProto_pb2.getHistoryForWalletSelection + packet.stringArgs.append(order) + for wltID in wltIDList: + packet.stringArgs.append(wltID) + + fut = self.sendToBridgeProto(packet) + socketResponse = fut.getVal() + + response = ClientProto_pb2.BridgeLedgers() + response.ParseFromString(socketResponse) + + return response ################################################################################ class BridgeSigner(object): @@ -1308,6 +1323,22 @@ def populateUtxo(self, hashVal, txoutid, value, script): if response.ints[0] == 0: raise BridgeSignerError("addSpenderByOutpoint") + ############################################################################# + def addSupportingTx(self, rawTxData): + packet = ClientProto_pb2.ClientCommand() + packet.method = ClientProto_pb2.signer_addSupportingTx + packet.stringArgs.append(self.signerId) + packet.byteArgs.append(rawTxData) + + fut = TheBridge.sendToBridgeProto(packet) + socketResponse = fut.getVal() + + response = ClientProto_pb2.ReplyNumbers() + response.ParseFromString(socketResponse) + + if response.ints[0] == 0: + raise BridgeSignerError("addSpenderByOutpoint") + ############################################################################# def addRecipient(self, value, script): packet = ClientProto_pb2.ClientCommand() diff --git a/cppForSwig/BridgeAPI/CppBridge.cpp b/cppForSwig/BridgeAPI/CppBridge.cpp index 1030ddeae..431fa98b7 100755 --- a/cppForSwig/BridgeAPI/CppBridge.cpp +++ b/cppForSwig/BridgeAPI/CppBridge.cpp @@ -669,7 +669,7 @@ const string& CppBridge::getLedgerDelegateIdForScrAddr( //////////////////////////////////////////////////////////////////////////////// void CppBridge::getHistoryPageForDelegate( - const std::string& id, unsigned pageId, unsigned msgId) + const string& id, unsigned pageId, unsigned msgId) { auto iter = delegateMap_.find(id); if (iter == delegateMap_.end()) @@ -692,6 +692,27 @@ void CppBridge::getHistoryPageForDelegate( iter->second.getHistoryPage(pageId, lbd); } +//////////////////////////////////////////////////////////////////////////////// +void CppBridge::getHistoryForWalletSelection( + const string& order, vector wltIds, unsigned msgId) +{ + auto lbd = [this, msgId]( + ReturnMessage> result)->void + { + auto&& leVec = result.get(); + auto msgProto = make_unique(); + for (auto& le : leVec) + { + auto leProto = msgProto->add_le(); + CppToProto::ledger(leProto, le); + } + + this->writeToClient(move(msgProto), msgId); + }; + + bdvPtr_->getHistoryForWalletSelection(wltIds, order, lbd); +} + //////////////////////////////////////////////////////////////////////////////// BridgeReply CppBridge::getNodeStatus() { @@ -828,7 +849,7 @@ void CppBridge::extendAddressPool(const string& wltId, reportedTicks = eventCount; float progressFloat = float(tickCount) / float(tickTotal); - auto msg = make_unique(); + auto msg = make_unique(); msg->set_progress(progressFloat); msg->set_progressnumeric(tickCount); msg->add_ids(callbackId); @@ -840,7 +861,7 @@ void CppBridge::extendAddressPool(const string& wltId, accPtr->extendPublicChain(wltPtr->getIface(), count, updateProgress); //shutdown progress dialog - auto msgProgress = make_unique(); + auto msgProgress = make_unique(); msgProgress->set_progress(0); msgProgress->set_progressnumeric(0); msgProgress->set_phase(BDMPhase_Completed); @@ -1637,7 +1658,7 @@ bool CppBridge::signer_SetLockTime(const string& id, unsigned locktime) //////////////////////////////////////////////////////////////////////////////// bool CppBridge::signer_addSpenderByOutpoint( - const string& id, const BinaryDataRef& hash, + const string& id, const BinaryDataRef& hash, unsigned txOutId, unsigned sequence) { auto iter = signerMap_.find(id); @@ -1650,7 +1671,7 @@ bool CppBridge::signer_addSpenderByOutpoint( //////////////////////////////////////////////////////////////////////////////// bool CppBridge::signer_populateUtxo( - const string& id, const BinaryDataRef& hash, + const string& id, const BinaryDataRef& hash, unsigned txOutId, uint64_t value, const BinaryDataRef& script) { auto iter = signerMap_.find(id); @@ -1670,6 +1691,26 @@ bool CppBridge::signer_populateUtxo( return true; } +//////////////////////////////////////////////////////////////////////////////// +bool CppBridge::signer_addSupportingTx( + const string& id, const BinaryDataRef& rawTxData) +{ + auto iter = signerMap_.find(id); + if (iter == signerMap_.end()) + return false; + + try + { + iter->second->signer_.addSupportingTx(rawTxData); + } + catch(exception&) + { + return false; + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////// bool CppBridge::signer_addRecipient( const std::string& id, const BinaryDataRef& script, uint64_t value) @@ -1688,7 +1729,7 @@ bool CppBridge::signer_addRecipient( { return false; } - + return true; } @@ -2002,7 +2043,7 @@ void BridgeCallback::run(BdmNotification notif) vector payloadVec(payload.ByteSizeLong()); payload.SerializeToArray(&payloadVec[0], payloadVec.size()); - auto msg = make_unique(); + auto msg = make_unique(); msg->set_type(BDMAction_ZC); msg->add_opaque(&payloadVec[0], payloadVec.size()); @@ -2018,17 +2059,21 @@ void BridgeCallback::run(BdmNotification notif) case BDMAction_Refresh: { + auto msg = make_unique(); + msg->set_type(BDMAction_Refresh); + for (auto& id : notif.ids_) { string idStr(id.toCharPtr(), id.getSize()); - if (idStr == FILTER_CHANGE_FLAG) - { - //notify filter change - } + msg->add_ids(idStr); - idQueue_.push_back(move(idStr)); + //TODO: dumb way to watch over the pre bdm_ready wallet + //registration, fix this crap + if (idStr != FILTER_CHANGE_FLAG) + idQueue_.push_back(move(idStr)); } + pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); break; } @@ -2053,7 +2098,7 @@ void BridgeCallback::run(BdmNotification notif) nodeStatusMsg.SerializeToArray( &serializedNodeStatus[0], serializedNodeStatus.size()); - auto msg = make_unique(); + auto msg = make_unique(); msg->set_type(BDMAction_NodeStatus); msg->add_opaque( &serializedNodeStatus[0], serializedNodeStatus.size()); @@ -2084,7 +2129,7 @@ void BridgeCallback::progress( float progress, unsigned secondsRem, unsigned progressNumeric) { - auto msg = make_unique(); + auto msg = make_unique(); msg->set_phase((uint32_t)phase); msg->set_progress(progress); msg->set_etasec(secondsRem); @@ -2099,7 +2144,7 @@ void BridgeCallback::progress( //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_SetupDone() { - auto msg = make_unique(); + auto msg = make_unique(); msg->set_type(CppBridgeState::CppBridge_Ready); pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); @@ -2108,7 +2153,7 @@ void BridgeCallback::notify_SetupDone() //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_SetupRegistrationDone(const set& ids) { - auto msg = make_unique(); + auto msg = make_unique(); msg->set_type(CppBridgeState::CppBridge_Registered); for (auto& id : ids) msg->add_ids(id); @@ -2119,7 +2164,7 @@ void BridgeCallback::notify_SetupRegistrationDone(const set& ids) //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_RegistrationDone(const set& ids) { - auto msg = make_unique(); + auto msg = make_unique(); msg->set_type(BDMAction_Refresh); for (auto& id : ids) msg->add_ids(id); @@ -2130,7 +2175,7 @@ void BridgeCallback::notify_RegistrationDone(const set& ids) //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_NewBlock(unsigned height) { - auto msg = make_unique(); + auto msg = make_unique(); msg->set_type(BDMAction_NewBlock); msg->set_height(height); @@ -2140,7 +2185,7 @@ void BridgeCallback::notify_NewBlock(unsigned height) //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_Ready(unsigned height) { - auto msg = make_unique(); + auto msg = make_unique(); msg->set_type(BDMAction_Ready); msg->set_height(height); @@ -2150,7 +2195,7 @@ void BridgeCallback::notify_Ready(unsigned height) //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::disconnected() { - auto msg = make_unique(); + auto msg = make_unique(); msg->set_type(DISCONNECTED_CALLBACK_ID); pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); diff --git a/cppForSwig/BridgeAPI/CppBridge.h b/cppForSwig/BridgeAPI/CppBridge.h index 46737b7e0..506d9b510 100755 --- a/cppForSwig/BridgeAPI/CppBridge.h +++ b/cppForSwig/BridgeAPI/CppBridge.h @@ -197,6 +197,8 @@ namespace Armory const std::string& getLedgerDelegateIdForScrAddr( const std::string&, const BinaryDataRef&); void getHistoryPageForDelegate(const std::string&, unsigned, unsigned); + void getHistoryForWalletSelection(const std::string&, + std::vector, unsigned); void createAddressBook(const std::string&, unsigned); void setComment(const Codec_ClientProto::ClientCommand&); @@ -236,6 +238,7 @@ namespace Armory bool signer_populateUtxo( const std::string&, const BinaryDataRef&, unsigned, uint64_t, const BinaryDataRef&); + bool signer_addSupportingTx(const std::string&, const BinaryDataRef&); bool signer_addRecipient( const std::string&, const BinaryDataRef&, uint64_t); diff --git a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp index 96babafff..fe2de1334 100644 --- a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp +++ b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp @@ -146,6 +146,19 @@ bool ProtobufCommandParser::processData( break; } + case Methods::getHistoryForWalletSelection: + { + if (msg.stringargs_size() < 1) + throw runtime_error("invalid command: getHistoryForWalletSelection"); + + vector wltIdVec; + for (unsigned i=1; igetHistoryForWalletSelection(msg.stringargs(0), wltIdVec, id); + break; + } + case Methods::getNodeStatus: { response = move(bridge->getNodeStatus()); @@ -611,6 +624,22 @@ bool ProtobufCommandParser::processData( break; } + case Methods::signer_addSupportingTx: + { + if (msg.stringargs_size() != 1 || msg.byteargs_size() != 1) + throw runtime_error("invalid command: signer_addSupportingTx"); + + BinaryDataRef rawTxData; rawTxData.setRef(msg.byteargs(0)); + + auto result = bridge->signer_addSupportingTx( + msg.stringargs(0), rawTxData); + + auto resultProto = make_unique(); + resultProto->add_ints(result); + response = move(resultProto); + break; + } + case Methods::signer_addRecipient: { if (msg.stringargs_size() != 1 || diff --git a/cppForSwig/protobuf/ClientProto.proto b/cppForSwig/protobuf/ClientProto.proto old mode 100755 new mode 100644 index a0b3ada2c..a29f40c52 --- a/cppForSwig/protobuf/ClientProto.proto +++ b/cppForSwig/protobuf/ClientProto.proto @@ -23,6 +23,7 @@ enum Methods getHistoryPageForDelegate = 40; getBalanceAndCount = 41; getAddrCombinedList = 42; + getHistoryForWalletSelection = 43; getTxByHash = 70; getHeaderByHeight = 71; @@ -81,6 +82,7 @@ enum Methods signer_getUnsignedTx = 141; signer_getSignedStateForInput = 142; signer_resolve = 143; + signer_addSupportingTx = 144; getHash160 = 200; getBlockTimeByHeight = 201; @@ -158,7 +160,7 @@ message WalletPayload //////////////////////////////////////////////////////////////////////////////// // BDV callback -message CppBridgeCallback +message CppBridgeCallbackMsg { required uint32 type = 1; @@ -168,7 +170,7 @@ message CppBridgeCallback repeated bytes opaque = 20; } -message CppProgressCallback +message CppProgressCallbackMsg { optional uint32 phase = 1; required float progress = 2; diff --git a/qtdialogs/DlgExportTxHistory.py b/qtdialogs/DlgExportTxHistory.py index b75c7fce7..5090561a6 100644 --- a/qtdialogs/DlgExportTxHistory.py +++ b/qtdialogs/DlgExportTxHistory.py @@ -23,6 +23,7 @@ from armorymodels import LEDGERCOLS from armoryengine.Transaction import getFeeForTx +from armoryengine.CppBridge import TheBridge ####################################################################### @@ -45,7 +46,7 @@ def __init__(self, parent=None, main=None): self.cmbWltSelect.insertSeparator(8) for wltID in self.main.walletIDList: - self.cmbWltSelect.addItem(self.main.walletMap[wltID].labelName) + self.cmbWltSelect.addItem(self.main.walletMap[wltID].getDisplayStr()) self.cmbWltSelect.insertSeparator(8 + len(self.main.walletIDList)) for idx in self.reversedLBdict: @@ -219,7 +220,7 @@ def createFile_CSV(self): wltBalances[wltID] = wlt.getBalance('Total') else: - #lockbox + #lockbox cppwlt = self.main.cppLockboxWltMap[wltID] totalFunds += cppwlt.getFullBalance() spendFunds += cppwlt.getSpendableBalance(TheBDM.getTopBlockHeight(), IGNOREZC) @@ -235,7 +236,7 @@ def createFile_CSV(self): allBalances = totalFunds - #prepare csv file + #prepare csv file wltSelectStr = str(self.cmbWltSelect.currentText()).replace(' ', '_') timestampStr = unixTimeToFormatStr(RightNow(), '%Y%m%d_%H%M') filenamePrefix = 'ArmoryTxHistory_%s_%s' % (wltSelectStr, timestampStr) @@ -276,22 +277,22 @@ def createFile_CSV(self): f.write(','.join(str(header) for header in headerRow) + '\n') - #get history - historyLedger = TheBDM.bdv().getHistoryForWalletSelection(wltIDList, order) + #get history + historyLedger = TheBridge.getHistoryForWalletSelection(wltIDList, order) - # Each value in COL.Amount will be exactly how much the wallet balance - # increased or decreased as a result of this transaction. + # Each value in COL.Amount will be exactly how much the wallet balance + # increased or decreased as a result of this transaction. ledgerTable = self.main.convertLedgerToTable(historyLedger, showSentToSelfAmt=True) - # Sort the data chronologically first, compute the running balance for - # each row, then sort it the way that was requested by the user. + # Sort the data chronologically first, compute the running balance for + # each row, then sort it the way that was requested by the user. for row in ledgerTable: if row[COL.toSelf] == False: rawAmt = str2coin(row[COL.Amount]) else: - #if SentToSelf, balance and total rolling balance should only - #take fee in account + #if SentToSelf, balance and total rolling balance should only + #take fee in account rawAmt, fee_byte = getFeeForTx(hex_to_binary(row[COL.TxHash])) rawAmt = -1 * rawAmt From 9c29992c6792015ccfcdf4bf94aed65665c0ec5a Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 13 Mar 2022 11:52:03 +0100 Subject: [PATCH 17/47] [WIP] dont handle p2sh preimage image in py unsigned tx ser/deser, use cpp Signer class instead Move opcodes from Transaction.py to Script.py Fix getFeeForTx --- armoryengine/Script.py | 285 ++++++++++++++++++++++++++++- armoryengine/Transaction.py | 352 ++++-------------------------------- ui/TxFrames.py | 4 +- 3 files changed, 322 insertions(+), 319 deletions(-) diff --git a/armoryengine/Script.py b/armoryengine/Script.py index dc1af4a6f..62fffa58a 100644 --- a/armoryengine/Script.py +++ b/armoryengine/Script.py @@ -12,14 +12,292 @@ # SCRIPTING! # ################################################################################ -from armoryengine.ArmoryUtils import * from armoryengine.BinaryPacker import UINT8, BINARY_CHUNK, UINT16, UINT32 from armoryengine.BinaryUnpacker import BinaryUnpacker from armoryengine.Timer import TimeThisFunction -from armoryengine.Transaction import * +################################################################################ +# Identify all the codes/strings that are needed for dealing with scripts +################################################################################ +# Start list of OP codes +opnames = ['']*256 +opnames[0] = 'OP_0' +for i in range(1,76): + opnames[i] ='OP_PUSHDATA' +opnames[76] = 'OP_PUSHDATA1' +opnames[77] = 'OP_PUSHDATA2' +opnames[78] = 'OP_PUSHDATA4' +opnames[79] = 'OP_1NEGATE' +opnames[81] = 'OP_1' +opnames[81] = 'OP_TRUE' +for i in range(1,17): + opnames[80+i] = 'OP_' + str(i) +opnames[97] = 'OP_NOP' +opnames[99] = 'OP_IF' +opnames[100] = 'OP_NOTIF' +opnames[103] = 'OP_ELSE' +opnames[104] = 'OP_ENDIF' +opnames[105] = 'OP_VERIFY' +opnames[106] = 'OP_RETURN' +opnames[107] = 'OP_TOALTSTACK' +opnames[108] = 'OP_FROMALTSTACK' +opnames[115] = 'OP_IFDUP' +opnames[116] = 'OP_DEPTH' +opnames[117] = 'OP_DROP' +opnames[118] = 'OP_DUP' +opnames[119] = 'OP_NIP' +opnames[120] = 'OP_OVER' +opnames[121] = 'OP_PICK' +opnames[122] = 'OP_ROLL' +opnames[123] = 'OP_ROT' +opnames[124] = 'OP_SWAP' +opnames[125] = 'OP_TUCK' +opnames[109] = 'OP_2DROP' +opnames[110] = 'OP_2DUP' +opnames[111] = 'OP_3DUP' +opnames[112] = 'OP_2OVER' +opnames[113] = 'OP_2ROT' +opnames[114] = 'OP_2SWAP' +opnames[126] = 'OP_CAT' +opnames[127] = 'OP_SUBSTR' +opnames[128] = 'OP_LEFT' +opnames[129] = 'OP_RIGHT' +opnames[130] = 'OP_SIZE' +opnames[131] = 'OP_INVERT' +opnames[132] = 'OP_AND' +opnames[133] = 'OP_OR' +opnames[134] = 'OP_XOR' +opnames[135] = 'OP_EQUAL' +opnames[136] = 'OP_EQUALVERIFY' +opnames[139] = 'OP_1ADD' +opnames[140] = 'OP_1SUB' +opnames[141] = 'OP_2MUL' +opnames[142] = 'OP_2DIV' +opnames[143] = 'OP_NEGATE' +opnames[144] = 'OP_ABS' +opnames[145] = 'OP_NOT' +opnames[146] = 'OP_0NOTEQUAL' +opnames[147] = 'OP_ADD' +opnames[148] = 'OP_SUB' +opnames[149] = 'OP_MUL' +opnames[150] = 'OP_DIV' +opnames[151] = 'OP_MOD' +opnames[152] = 'OP_LSHIFT' +opnames[153] = 'OP_RSHIFT' +opnames[154] = 'OP_BOOLAND' +opnames[155] = 'OP_BOOLOR' +opnames[156] = 'OP_NUMEQUAL' +opnames[157] = 'OP_NUMEQUALVERIFY' +opnames[158] = 'OP_NUMNOTEQUAL' +opnames[159] = 'OP_LESSTHAN' +opnames[160] = 'OP_GREATERTHAN' +opnames[161] = 'OP_LESSTHANOREQUAL' +opnames[162] = 'OP_GREATERTHANOREQUAL' +opnames[163] = 'OP_MIN' +opnames[164] = 'OP_MAX' +opnames[165] = 'OP_WITHIN' +opnames[166] = 'OP_RIPEMD160' +opnames[167] = 'OP_SHA1' +opnames[168] = 'OP_SHA256' +opnames[169] = 'OP_HASH160' +opnames[170] = 'OP_HASH256' +opnames[171] = 'OP_CODESEPARATOR' +opnames[172] = 'OP_CHECKSIG' +opnames[173] = 'OP_CHECKSIGVERIFY' +opnames[174] = 'OP_CHECKMULTISIG' +opnames[175] = 'OP_CHECKMULTISIGVERIFY' + +OP_0 = 0 +OP_FALSE = 0 +OP_PUSHDATA1 = 76 +OP_PUSHDATA2 = 77 +OP_PUSHDATA4 = 78 +OP_1NEGATE = 79 +OP_1 = 81 +OP_TRUE = 81 +OP_2 = 82 +OP_3 = 83 +OP_4 = 84 +OP_5 = 85 +OP_6 = 86 +OP_7 = 87 +OP_8 = 88 +OP_9 = 89 +OP_10 = 90 +OP_11 = 91 +OP_12 = 92 +OP_13 = 93 +OP_14 = 94 +OP_15 = 95 +OP_16 = 96 +OP_NOP = 97 +OP_IF = 99 +OP_NOTIF = 100 +OP_ELSE = 103 +OP_ENDIF = 104 +OP_VERIFY = 105 +OP_RETURN = 106 +OP_TOALTSTACK = 107 +OP_FROMALTSTACK = 108 +OP_IFDUP = 115 +OP_DEPTH = 116 +OP_DROP = 117 +OP_DUP = 118 +OP_NIP = 119 +OP_OVER = 120 +OP_PICK = 121 +OP_ROLL = 122 +OP_ROT = 123 +OP_SWAP = 124 +OP_TUCK = 125 +OP_2DROP = 109 +OP_2DUP = 110 +OP_3DUP = 111 +OP_2OVER = 112 +OP_2ROT = 113 +OP_2SWAP = 114 +OP_CAT = 126 +OP_SUBSTR = 127 +OP_LEFT = 128 +OP_RIGHT = 129 +OP_SIZE = 130 +OP_INVERT = 131 +OP_AND = 132 +OP_OR = 133 +OP_XOR = 134 +OP_EQUAL = 135 +OP_EQUALVERIFY = 136 +OP_1ADD = 139 +OP_1SUB = 140 +OP_2MUL = 141 +OP_2DIV = 142 +OP_NEGATE = 143 +OP_ABS = 144 +OP_NOT = 145 +OP_0NOTEQUAL = 146 +OP_ADD = 147 +OP_SUB = 148 +OP_MUL = 149 +OP_DIV = 150 +OP_MOD = 151 +OP_LSHIFT = 152 +OP_RSHIFT = 153 +OP_BOOLAND = 154 +OP_BOOLOR = 155 +OP_NUMEQUAL = 156 +OP_NUMEQUALVERIFY = 157 +OP_NUMNOTEQUAL = 158 +OP_LESSTHAN = 159 +OP_GREATERTHAN = 160 +OP_LESSTHANOREQUAL = 161 +OP_GREATERTHANOREQUAL = 162 +OP_MIN = 163 +OP_MAX = 164 +OP_WITHIN = 165 +OP_RIPEMD160 = 166 +OP_SHA1 = 167 +OP_SHA256 = 168 +OP_HASH160 = 169 +OP_HASH256 = 170 +OP_CODESEPARATOR = 171 +OP_CHECKSIG = 172 +OP_CHECKSIGVERIFY = 173 +OP_CHECKMULTISIG = 174 +OP_CHECKMULTISIGVERIFY = 175 + +opCodeLookup = {} +opCodeLookup['OP_FALSE'] = 0 +opCodeLookup['OP_0'] = 0 +opCodeLookup['OP_PUSHDATA1'] = 76 +opCodeLookup['OP_PUSHDATA2'] = 77 +opCodeLookup['OP_PUSHDATA4'] = 78 +opCodeLookup['OP_1NEGATE'] = 79 +opCodeLookup['OP_1'] = 81 +for i in range(1,17): + opCodeLookup['OP_'+str(i)] = 80+i +opCodeLookup['OP_TRUE'] = 81 +opCodeLookup['OP_NOP'] = 97 +opCodeLookup['OP_IF'] = 99 +opCodeLookup['OP_NOTIF'] = 100 +opCodeLookup['OP_ELSE'] = 103 +opCodeLookup['OP_ENDIF'] = 104 +opCodeLookup['OP_VERIFY'] = 105 +opCodeLookup['OP_RETURN'] = 106 +opCodeLookup['OP_TOALTSTACK'] = 107 +opCodeLookup['OP_FROMALTSTACK'] = 108 +opCodeLookup['OP_IFDUP'] = 115 +opCodeLookup['OP_DEPTH'] = 116 +opCodeLookup['OP_DROP'] = 117 +opCodeLookup['OP_DUP'] = 118 +opCodeLookup['OP_NIP'] = 119 +opCodeLookup['OP_OVER'] = 120 +opCodeLookup['OP_PICK'] = 121 +opCodeLookup['OP_ROLL'] = 122 +opCodeLookup['OP_ROT'] = 123 +opCodeLookup['OP_SWAP'] = 124 +opCodeLookup['OP_TUCK'] = 125 +opCodeLookup['OP_2DROP'] = 109 +opCodeLookup['OP_2DUP'] = 110 +opCodeLookup['OP_3DUP'] = 111 +opCodeLookup['OP_2OVER'] = 112 +opCodeLookup['OP_2ROT'] = 113 +opCodeLookup['OP_2SWAP'] = 114 +opCodeLookup['OP_CAT'] = 126 +opCodeLookup['OP_SUBSTR'] = 127 +opCodeLookup['OP_LEFT'] = 128 +opCodeLookup['OP_RIGHT'] = 129 +opCodeLookup['OP_SIZE'] = 130 +opCodeLookup['OP_INVERT'] = 131 +opCodeLookup['OP_AND'] = 132 +opCodeLookup['OP_OR'] = 133 +opCodeLookup['OP_XOR'] = 134 +opCodeLookup['OP_EQUAL'] = 135 +opCodeLookup['OP_EQUALVERIFY'] = 136 +opCodeLookup['OP_1ADD'] = 139 +opCodeLookup['OP_1SUB'] = 140 +opCodeLookup['OP_2MUL'] = 141 +opCodeLookup['OP_2DIV'] = 142 +opCodeLookup['OP_NEGATE'] = 143 +opCodeLookup['OP_ABS'] = 144 +opCodeLookup['OP_NOT'] = 145 +opCodeLookup['OP_0NOTEQUAL'] = 146 +opCodeLookup['OP_ADD'] = 147 +opCodeLookup['OP_SUB'] = 148 +opCodeLookup['OP_MUL'] = 149 +opCodeLookup['OP_DIV'] = 150 +opCodeLookup['OP_MOD'] = 151 +opCodeLookup['OP_LSHIFT'] = 152 +opCodeLookup['OP_RSHIFT'] = 153 +opCodeLookup['OP_BOOLAND'] = 154 +opCodeLookup['OP_BOOLOR'] = 155 +opCodeLookup['OP_NUMEQUAL'] = 156 +opCodeLookup['OP_NUMEQUALVERIFY'] = 157 +opCodeLookup['OP_NUMNOTEQUAL'] = 158 +opCodeLookup['OP_LESSTHAN'] = 159 +opCodeLookup['OP_GREATERTHAN'] = 160 +opCodeLookup['OP_LESSTHANOREQUAL'] = 161 +opCodeLookup['OP_GREATERTHANOREQUAL'] = 162 +opCodeLookup['OP_MIN'] = 163 +opCodeLookup['OP_MAX'] = 164 +opCodeLookup['OP_WITHIN'] = 165 +opCodeLookup['OP_RIPEMD160'] = 166 +opCodeLookup['OP_SHA1'] = 167 +opCodeLookup['OP_SHA256'] = 168 +opCodeLookup['OP_HASH160'] = 169 +opCodeLookup['OP_HASH256'] = 170 +opCodeLookup['OP_CODESEPARATOR'] = 171 +opCodeLookup['OP_CHECKSIG'] = 172 +opCodeLookup['OP_CHECKSIGVERIFY'] = 173 +opCodeLookup['OP_CHECKMULTISIG'] = 174 +opCodeLookup['OP_CHECKMULTISIGVERIFY'] = 175 +################################################################################ +def getOpCode(name): + return int_to_binary(opCodeLookup[name], widthBytes=1) + +################################################################################ def convertScriptToOpStrings(binScript): + from armoryengine.ArmoryUtils import binary_to_hex opList = [] i = 0 @@ -112,6 +390,7 @@ def getBinaryScript(self): return ''.join(self.opList) def getHexScript(self): + from armoryengine.ArmoryUtils import binary_to_hex return binary_to_hex(''.join(self.opList)) def getHumanScript(self): @@ -303,7 +582,7 @@ def executeOpCode(self, opcode, scriptUnpacker, stack, stackAlt): """ # TODO: Gavin clarified the effects of OP_0, and OP_1-OP_16. - # OP_0 puts an empty string onto the stack, which evaluateses to + # OP_0 puts an empty string onto the stack, which evaluates to # false and is plugged into HASH160 as '' # OP_X puts a single byte onto the stack, 0x01 to 0x10 # diff --git a/armoryengine/Transaction.py b/armoryengine/Transaction.py index ec9067a0d..f8b67b1dd 100644 --- a/armoryengine/Transaction.py +++ b/armoryengine/Transaction.py @@ -35,301 +35,6 @@ BASE_SCRIPT = 'base_script' -################################################################################ -# Identify all the codes/strings that are needed for dealing with scripts -################################################################################ -# Start list of OP codes -OP_0 = 0 -OP_FALSE = 0 -OP_PUSHDATA1 = 76 -OP_PUSHDATA2 = 77 -OP_PUSHDATA4 = 78 -OP_1NEGATE = 79 -OP_1 = 81 -OP_TRUE = 81 -OP_2 = 82 -OP_3 = 83 -OP_4 = 84 -OP_5 = 85 -OP_6 = 86 -OP_7 = 87 -OP_8 = 88 -OP_9 = 89 -OP_10 = 90 -OP_11 = 91 -OP_12 = 92 -OP_13 = 93 -OP_14 = 94 -OP_15 = 95 -OP_16 = 96 -OP_NOP = 97 -OP_IF = 99 -OP_NOTIF = 100 -OP_ELSE = 103 -OP_ENDIF = 104 -OP_VERIFY = 105 -OP_RETURN = 106 -OP_TOALTSTACK = 107 -OP_FROMALTSTACK = 108 -OP_IFDUP = 115 -OP_DEPTH = 116 -OP_DROP = 117 -OP_DUP = 118 -OP_NIP = 119 -OP_OVER = 120 -OP_PICK = 121 -OP_ROLL = 122 -OP_ROT = 123 -OP_SWAP = 124 -OP_TUCK = 125 -OP_2DROP = 109 -OP_2DUP = 110 -OP_3DUP = 111 -OP_2OVER = 112 -OP_2ROT = 113 -OP_2SWAP = 114 -OP_CAT = 126 -OP_SUBSTR = 127 -OP_LEFT = 128 -OP_RIGHT = 129 -OP_SIZE = 130 -OP_INVERT = 131 -OP_AND = 132 -OP_OR = 133 -OP_XOR = 134 -OP_EQUAL = 135 -OP_EQUALVERIFY = 136 -OP_1ADD = 139 -OP_1SUB = 140 -OP_2MUL = 141 -OP_2DIV = 142 -OP_NEGATE = 143 -OP_ABS = 144 -OP_NOT = 145 -OP_0NOTEQUAL = 146 -OP_ADD = 147 -OP_SUB = 148 -OP_MUL = 149 -OP_DIV = 150 -OP_MOD = 151 -OP_LSHIFT = 152 -OP_RSHIFT = 153 -OP_BOOLAND = 154 -OP_BOOLOR = 155 -OP_NUMEQUAL = 156 -OP_NUMEQUALVERIFY = 157 -OP_NUMNOTEQUAL = 158 -OP_LESSTHAN = 159 -OP_GREATERTHAN = 160 -OP_LESSTHANOREQUAL = 161 -OP_GREATERTHANOREQUAL = 162 -OP_MIN = 163 -OP_MAX = 164 -OP_WITHIN = 165 -OP_RIPEMD160 = 166 -OP_SHA1 = 167 -OP_SHA256 = 168 -OP_HASH160 = 169 -OP_HASH256 = 170 -OP_CODESEPARATOR = 171 -OP_CHECKSIG = 172 -OP_CHECKSIGVERIFY = 173 -OP_CHECKMULTISIG = 174 -OP_CHECKMULTISIGVERIFY = 175 - -opnames = ['']*256 -opnames[0] = 'OP_0' -for i in range(1,76): - opnames[i] ='OP_PUSHDATA' -opnames[76] = 'OP_PUSHDATA1' -opnames[77] = 'OP_PUSHDATA2' -opnames[78] = 'OP_PUSHDATA4' -opnames[79] = 'OP_1NEGATE' -opnames[81] = 'OP_1' -opnames[81] = 'OP_TRUE' -for i in range(1,17): - opnames[80+i] = 'OP_' + str(i) -opnames[97] = 'OP_NOP' -opnames[99] = 'OP_IF' -opnames[100] = 'OP_NOTIF' -opnames[103] = 'OP_ELSE' -opnames[104] = 'OP_ENDIF' -opnames[105] = 'OP_VERIFY' -opnames[106] = 'OP_RETURN' -opnames[107] = 'OP_TOALTSTACK' -opnames[108] = 'OP_FROMALTSTACK' -opnames[115] = 'OP_IFDUP' -opnames[116] = 'OP_DEPTH' -opnames[117] = 'OP_DROP' -opnames[118] = 'OP_DUP' -opnames[119] = 'OP_NIP' -opnames[120] = 'OP_OVER' -opnames[121] = 'OP_PICK' -opnames[122] = 'OP_ROLL' -opnames[123] = 'OP_ROT' -opnames[124] = 'OP_SWAP' -opnames[125] = 'OP_TUCK' -opnames[109] = 'OP_2DROP' -opnames[110] = 'OP_2DUP' -opnames[111] = 'OP_3DUP' -opnames[112] = 'OP_2OVER' -opnames[113] = 'OP_2ROT' -opnames[114] = 'OP_2SWAP' -opnames[126] = 'OP_CAT' -opnames[127] = 'OP_SUBSTR' -opnames[128] = 'OP_LEFT' -opnames[129] = 'OP_RIGHT' -opnames[130] = 'OP_SIZE' -opnames[131] = 'OP_INVERT' -opnames[132] = 'OP_AND' -opnames[133] = 'OP_OR' -opnames[134] = 'OP_XOR' -opnames[135] = 'OP_EQUAL' -opnames[136] = 'OP_EQUALVERIFY' -opnames[139] = 'OP_1ADD' -opnames[140] = 'OP_1SUB' -opnames[141] = 'OP_2MUL' -opnames[142] = 'OP_2DIV' -opnames[143] = 'OP_NEGATE' -opnames[144] = 'OP_ABS' -opnames[145] = 'OP_NOT' -opnames[146] = 'OP_0NOTEQUAL' -opnames[147] = 'OP_ADD' -opnames[148] = 'OP_SUB' -opnames[149] = 'OP_MUL' -opnames[150] = 'OP_DIV' -opnames[151] = 'OP_MOD' -opnames[152] = 'OP_LSHIFT' -opnames[153] = 'OP_RSHIFT' -opnames[154] = 'OP_BOOLAND' -opnames[155] = 'OP_BOOLOR' -opnames[156] = 'OP_NUMEQUAL' -opnames[157] = 'OP_NUMEQUALVERIFY' -opnames[158] = 'OP_NUMNOTEQUAL' -opnames[159] = 'OP_LESSTHAN' -opnames[160] = 'OP_GREATERTHAN' -opnames[161] = 'OP_LESSTHANOREQUAL' -opnames[162] = 'OP_GREATERTHANOREQUAL' -opnames[163] = 'OP_MIN' -opnames[164] = 'OP_MAX' -opnames[165] = 'OP_WITHIN' -opnames[166] = 'OP_RIPEMD160' -opnames[167] = 'OP_SHA1' -opnames[168] = 'OP_SHA256' -opnames[169] = 'OP_HASH160' -opnames[170] = 'OP_HASH256' -opnames[171] = 'OP_CODESEPARATOR' -opnames[172] = 'OP_CHECKSIG' -opnames[173] = 'OP_CHECKSIGVERIFY' -opnames[174] = 'OP_CHECKMULTISIG' -opnames[175] = 'OP_CHECKMULTISIGVERIFY' - - -opCodeLookup = {} -opCodeLookup['OP_FALSE'] = 0 -opCodeLookup['OP_0'] = 0 -opCodeLookup['OP_PUSHDATA1'] = 76 -opCodeLookup['OP_PUSHDATA2'] = 77 -opCodeLookup['OP_PUSHDATA4'] = 78 -opCodeLookup['OP_1NEGATE'] = 79 -opCodeLookup['OP_1'] = 81 -for i in range(1,17): - opCodeLookup['OP_'+str(i)] = 80+i -opCodeLookup['OP_TRUE'] = 81 -opCodeLookup['OP_NOP'] = 97 -opCodeLookup['OP_IF'] = 99 -opCodeLookup['OP_NOTIF'] = 100 -opCodeLookup['OP_ELSE'] = 103 -opCodeLookup['OP_ENDIF'] = 104 -opCodeLookup['OP_VERIFY'] = 105 -opCodeLookup['OP_RETURN'] = 106 -opCodeLookup['OP_TOALTSTACK'] = 107 -opCodeLookup['OP_FROMALTSTACK'] = 108 -opCodeLookup['OP_IFDUP'] = 115 -opCodeLookup['OP_DEPTH'] = 116 -opCodeLookup['OP_DROP'] = 117 -opCodeLookup['OP_DUP'] = 118 -opCodeLookup['OP_NIP'] = 119 -opCodeLookup['OP_OVER'] = 120 -opCodeLookup['OP_PICK'] = 121 -opCodeLookup['OP_ROLL'] = 122 -opCodeLookup['OP_ROT'] = 123 -opCodeLookup['OP_SWAP'] = 124 -opCodeLookup['OP_TUCK'] = 125 -opCodeLookup['OP_2DROP'] = 109 -opCodeLookup['OP_2DUP'] = 110 -opCodeLookup['OP_3DUP'] = 111 -opCodeLookup['OP_2OVER'] = 112 -opCodeLookup['OP_2ROT'] = 113 -opCodeLookup['OP_2SWAP'] = 114 -opCodeLookup['OP_CAT'] = 126 -opCodeLookup['OP_SUBSTR'] = 127 -opCodeLookup['OP_LEFT'] = 128 -opCodeLookup['OP_RIGHT'] = 129 -opCodeLookup['OP_SIZE'] = 130 -opCodeLookup['OP_INVERT'] = 131 -opCodeLookup['OP_AND'] = 132 -opCodeLookup['OP_OR'] = 133 -opCodeLookup['OP_XOR'] = 134 -opCodeLookup['OP_EQUAL'] = 135 -opCodeLookup['OP_EQUALVERIFY'] = 136 -opCodeLookup['OP_1ADD'] = 139 -opCodeLookup['OP_1SUB'] = 140 -opCodeLookup['OP_2MUL'] = 141 -opCodeLookup['OP_2DIV'] = 142 -opCodeLookup['OP_NEGATE'] = 143 -opCodeLookup['OP_ABS'] = 144 -opCodeLookup['OP_NOT'] = 145 -opCodeLookup['OP_0NOTEQUAL'] = 146 -opCodeLookup['OP_ADD'] = 147 -opCodeLookup['OP_SUB'] = 148 -opCodeLookup['OP_MUL'] = 149 -opCodeLookup['OP_DIV'] = 150 -opCodeLookup['OP_MOD'] = 151 -opCodeLookup['OP_LSHIFT'] = 152 -opCodeLookup['OP_RSHIFT'] = 153 -opCodeLookup['OP_BOOLAND'] = 154 -opCodeLookup['OP_BOOLOR'] = 155 -opCodeLookup['OP_NUMEQUAL'] = 156 -opCodeLookup['OP_NUMEQUALVERIFY'] = 157 -opCodeLookup['OP_NUMNOTEQUAL'] = 158 -opCodeLookup['OP_LESSTHAN'] = 159 -opCodeLookup['OP_GREATERTHAN'] = 160 -opCodeLookup['OP_LESSTHANOREQUAL'] = 161 -opCodeLookup['OP_GREATERTHANOREQUAL'] = 162 -opCodeLookup['OP_MIN'] = 163 -opCodeLookup['OP_MAX'] = 164 -opCodeLookup['OP_WITHIN'] = 165 -opCodeLookup['OP_RIPEMD160'] = 166 -opCodeLookup['OP_SHA1'] = 167 -opCodeLookup['OP_SHA256'] = 168 -opCodeLookup['OP_HASH160'] = 169 -opCodeLookup['OP_HASH256'] = 170 -opCodeLookup['OP_CODESEPARATOR'] = 171 -opCodeLookup['OP_CHECKSIG'] = 172 -opCodeLookup['OP_CHECKSIGVERIFY'] = 173 -opCodeLookup['OP_CHECKMULTISIG'] = 174 -opCodeLookup['OP_CHECKMULTISIGVERIFY'] = 175 -#Word Opcode Description -#OP_PUBKEYHASH = 253 Represents a public key hashed with OP_HASH160. -#OP_PUBKEY = 254 Represents a public key compatible with OP_CHECKSIG. -#OP_INVALIDOPCODE = 255 Matches any opcode that is not yet assigned. -#[edit] Reserved words -#Any opcode not assigned is also reserved. Using an unassigned opcode makes the transaction invalid. -#Word Opcode When used... -#OP_RESERVED = 80 Transaction is invalid -#OP_VER = 98 Transaction is invalid -#OP_VERIF = 101 Transaction is invalid -#OP_VERNOTIF = 102 Transaction is invalid -#OP_RESERVED1 = 137 Transaction is invalid -#OP_RESERVED2 = 138 Transaction is invalid -#OP_NOP1 = OP_NOP10 176-185 The word is ignored. - - -################################################################################ -def getOpCode(name): - return int_to_binary(opCodeLookup[name], widthBytes=1) - ################################################################################ class InputSignedStatusObject(object): def __init__(self, protoData): @@ -358,7 +63,7 @@ def __init__(self): ############################################################################# def __del__(self): - TheBridge.destroySigner(self.id) + self.signer.cleanup() ############################################################################# def setup(self): @@ -380,6 +85,10 @@ def addSpenderByOutpoint(self, hashVal, index, seq, value): def populateUtxo(self, hashVal, index, value, script): self.signer.populateUtxo(hashVal, index, value, script) + ############################################################################# + def addSupportingTx(self, rawTxData): + self.signer.addSupportingTx(rawTxData) + ############################################################################# def addRecipient(self, value, script): self.signer.addRecipient(value, script) @@ -612,6 +321,7 @@ def __init__(self): self.binScript = UNINITIALIZED self.intSeq = 2**32-1 self.outpointValue = 2**64-1 + self.supportTx = None def unserialize(self, toUnpack): if isinstance(toUnpack, BinaryUnpacker): @@ -1245,6 +955,9 @@ def __init__(self, rawSupportTx='', nested = False baseScript = self.txoScript if self.scriptType==CPP_TXOUT_P2SH: + nested = True + + ''' # If we're here, we should've passed in a P2SH script if len(self.p2shMap) == 0: self.isInitialized = False @@ -1264,10 +977,10 @@ def __init__(self, rawSupportTx='', # original script baseScript = self.p2shMap[BASE_SCRIPT] self.scriptType = getTxOutScriptType(baseScript) - nested = True - + ''' ##### + ''' # Fill some of the other fields with info needed to spend the script if self.scriptType==CPP_TXOUT_MULTISIG: #nested or raw MS scripts for lockboxes @@ -1311,7 +1024,7 @@ def __init__(self, rawSupportTx='', self.scrAddrs = [scrAddr] self.pubKeys = [pubKeyMap[scrAddr]] self.signatures = [''] - self.wltLocators = [''] + self.wltLocators = [''] self.isLegacyScript = False if scrType == CPP_TXOUT_P2WPKH: @@ -1336,7 +1049,8 @@ def __init__(self, rawSupportTx='', else: LOGWARN("Unexpected nested type for TxIn %d" % i) - pass + pass + ''' # "insert*s" can either be a single values, or a list @@ -2175,6 +1889,9 @@ def setupSigner(self): self.signer.populateUtxo(txin.outpoint.txHash, txin.outpoint.txOutIndex, \ txin.outpointValue, txin.binScript) + if txin.supportTx != None: + self.signer.addSupportingTx(txin.supportTx) + for txout in self.pytxObj.outputs: self.signer.addRecipient(txout.value, txout.binScript) @@ -2201,6 +1918,7 @@ def createFromUnsignedTxIO(self, ustxinList, dtxoList, lockTime=0): self.pytxObj.inputs[iin].binScript = ustxi.txoScript self.pytxObj.inputs[iin].intSeq = ustxi.sequence self.pytxObj.inputs[iin].outpointValue = ustxi.value + self.pytxObj.inputs[iin].supportTx = ustxi.supportTx for iout,dtxo in enumerate(dtxoList): self.pytxObj.outputs[iout] = PyTxOut() @@ -2287,6 +2005,7 @@ def createFromPyTx(self, pytx, pubKeyMap=None, txMap=None, p2shMap=None): txoScrAddr = script_to_scrAddr(txoScript) txoType = getTxOutScriptType(txoScript) + ''' p2shMap_copy = {} if txoType==CPP_TXOUT_P2SH: p2sh = p2shMap.get(binary_to_hex(txoScrAddr)) @@ -2298,10 +2017,11 @@ def createFromPyTx(self, pytx, pubKeyMap=None, txMap=None, p2shMap=None): val = p2shMap[script_key] p2shMap_copy[script_key] = val script_key = val + ''' ustxiList.append(UnsignedTxInput(pyPrevTx.serializeWithoutWitness(), txoIdx, - p2shMap_copy, + {}, pubKeyMap, sequence=txin.intSeq, inputID=count)) count = count + 1 @@ -2906,18 +2626,21 @@ def PyCreateAndSignTx_old(srcTxOuts, dstAddrsVals): def getFeeForTx(txHash): if TheBDM.getState()==BDM_BLOCKCHAIN_READY: try: - tx = TheBDM.getTxByHash(txHash) + tx = PyTx().unserialize(TheBridge.getTxByHash(txHash).raw) if not tx.isInitialized(): LOGERROR('Attempted to get fee for tx we don\'t have...? %s', \ binary_to_hex(txHash,BIGENDIAN)) - return 0 + return 0, 0 valIn, valOut = 0,0 - for i in range(tx.getNumTxIn()): - outpoint = tx.getTxInCopy(i).getOutPoint() - valIn += TheBDM.bdv().getValueForTxOut( - outpoint.getTxHash(), outpoint.getTxOutIndex()) - for i in range(tx.getNumTxOut()): - valOut += tx.getTxOutCopy(i).getValue() + for i in range(len(tx.inputs)): + outpoint = tx.inputs[i].getOutPoint() + parentTx = PyTx().unserialize(TheBridge.getTxByHash(outpoint.txHash).raw) + if not parentTx.isInitialized(): + LOGERROR('[getFeeForTx] Couldnt resolve parent') + return 0, 0 + valIn += parentTx.outputs[outpoint.txOutIndex].getValue() + for i in range(len(tx.outputs)): + valOut += tx.outputs[i].getValue() fee = valIn - valOut txWeight = tx.getTxWeight() fee_byte = fee / float(txWeight) @@ -2936,23 +2659,22 @@ def determineSentToSelfAmt(le, wlt): """ amt = 0 if le.isSentToSelf: - txProto = TheBridge.getTxByHash(txHashBin) + txProto = TheBridge.getTxByHash(le.hash) pytx = PyTx().unserialize(txProto.raw) if not pytx.isInitialized(): return (0, 0) if pytx.getNumTxOut()==1: - return (pytx.getTxOutCopy(0).getValue(), -1) + return (pytx.outputs[0].getValue(), -1) maxChainIndex = -5 txOutChangeVal = 0 changeIndex = -1 valSum = 0 for i in range(pytx.getNumTxOut()): - valSum += pytx.getTxOutCopy(i).getValue() - addr160 = CheckHash160(pytx.getTxOutCopy(i).getScrAddressStr()) - addr = wlt.getAddrByHash160(addr160) + valSum += pytx.outputs[i].getValue() + addr = wlt.getAddrByString(pytx.outputs[i].getScrAddressStr()) if addr and addr.chainIndex > maxChainIndex: maxChainIndex = addr.chainIndex - txOutChangeVal = pytx.getTxOutCopy(i).getValue() + txOutChangeVal = pytx.outputs[i].getValue() changeIndex = i amt = valSum - txOutChangeVal diff --git a/ui/TxFrames.py b/ui/TxFrames.py index c0939c27e..59766b79c 100755 --- a/ui/TxFrames.py +++ b/ui/TxFrames.py @@ -942,6 +942,7 @@ def validateInputsGetUSTX(self, peek=False): # If this has nothing to do with lockboxes, we need to make sure # we're providing a key map for the inputs + ''' for utxo in utxoSelect: scrType = getTxOutScriptType(utxo.getScript()) scrAddr = utxo.getRecipientScrAddr() @@ -965,6 +966,7 @@ def validateInputsGetUSTX(self, peek=False): raise Exception("invalid address index") pubKeyMap[scrAddr] = addrObj.getPubKey() + ''' ''' If we are consuming any number of SegWit utxos, pass the utxo selection @@ -974,7 +976,7 @@ def validateInputsGetUSTX(self, peek=False): # Now create the unsigned USTX ustx = UnsignedTransaction().createFromTxOutSelection(\ - utxoSelect, scriptValPairs, pubKeyMap, p2shMap=p2shMap, \ + utxoSelect, scriptValPairs, {}, {}, \ lockTime=TheBDM.getTopBlockHeight()) for msg in opreturn_list: From fd2d310f5bc8ba82432d83260058938e60374c6c Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 13 Mar 2022 11:54:03 +0100 Subject: [PATCH 18/47] Fix some whitespace/style infractions --- cppForSwig/ArmoryBackups.cpp | 2 +- cppForSwig/AsyncClient.h | 4 ++-- cppForSwig/Wallets/BIP32_Node.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cppForSwig/ArmoryBackups.cpp b/cppForSwig/ArmoryBackups.cpp index ce90549ea..7e186b7eb 100644 --- a/cppForSwig/ArmoryBackups.cpp +++ b/cppForSwig/ArmoryBackups.cpp @@ -1002,7 +1002,7 @@ shared_ptr Helpers::restoreFromBackup( SecureBinaryData dummy; callerPrompt( RestorePromptType::ChecksumError, checksumIndexes, dummy); - throw RestoreUserException("checksum error"); + throw RestoreUserException("checksum error"); }; vector repairedIndexes; diff --git a/cppForSwig/AsyncClient.h b/cppForSwig/AsyncClient.h index 4d97a5409..2e0adbf2a 100755 --- a/cppForSwig/AsyncClient.h +++ b/cppForSwig/AsyncClient.h @@ -97,11 +97,11 @@ template class ReturnMessage error_ = std::make_shared(err); } - U get(void) + U get(void) { if (error_ != nullptr) throw *error_; - + return std::move(value_); } }; diff --git a/cppForSwig/Wallets/BIP32_Node.cpp b/cppForSwig/Wallets/BIP32_Node.cpp index a8f1d4447..7cee9ecf1 100644 --- a/cppForSwig/Wallets/BIP32_Node.cpp +++ b/cppForSwig/Wallets/BIP32_Node.cpp @@ -101,8 +101,8 @@ void BIP32_Node::decodeBase58(const char* str) { btc_hdnode node; - //b58 decode - if(!btc_hdnode_deserialize( + //b58 decode + if (!btc_hdnode_deserialize( str, Armory::Config::BitcoinSettings::get_chain_params(), &node)) throw std::runtime_error("invalid bip32 serialized string"); From 2042e5cd2d67b62043bb8305b801565895fcabbe Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 13 Mar 2022 11:54:24 +0100 Subject: [PATCH 19/47] const a bunch of getters in BinaryData --- cppForSwig/BinaryData.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cppForSwig/BinaryData.h b/cppForSwig/BinaryData.h index df3f0b9b4..610bccaf3 100644 --- a/cppForSwig/BinaryData.h +++ b/cppForSwig/BinaryData.h @@ -1460,13 +1460,13 @@ class BinaryWriter void put_BitPacker(BitPacker & bp) { put_BinaryData(bp.getBinaryData()); } ///////////////////////////////////////////////////////////////////////////// - BinaryData const & getData(void) + BinaryData const & getData(void) const { return theString_; } ///////////////////////////////////////////////////////////////////////////// - size_t getSize(void) + size_t getSize(void) const { return theString_.getSize(); } @@ -1478,13 +1478,13 @@ class BinaryWriter } ///////////////////////////////////////////////////////////////////////////// - std::string toString(void) + std::string toString(void) const { return theString_.toBinStr(); } ///////////////////////////////////////////////////////////////////////////// - std::string toHex(void) + std::string toHex(void) const { return theString_.toHexStr(); } From 5da73fc618e8362610379e4d246a3aa9e370f731 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 13 Mar 2022 11:54:51 +0100 Subject: [PATCH 20/47] increase size limits for supernode tables --- cppForSwig/lmdb_wrapper.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cppForSwig/lmdb_wrapper.cpp b/cppForSwig/lmdb_wrapper.cpp index 9bf7e62ed..fef6a47c8 100644 --- a/cppForSwig/lmdb_wrapper.cpp +++ b/cppForSwig/lmdb_wrapper.cpp @@ -39,17 +39,17 @@ using namespace Armory::Config; const set LMDBBlockDatabase::supernodeDBs_({}); const map LMDBBlockDatabase::mapSizes_ = { - {"headers", 4 * 1024 * 1024 * 1024ULL}, - {"blkdata", 1024 * 1024ULL}, - {"history", 1024 * 1024ULL}, + {"headers", 50 * 1024 * 1024 * 1024ULL}, + {"blkdata", 50 * 1024 * 1024ULL}, + {"history", 50 * 1024 * 1024ULL}, {"txhints", 20 * 1024 * 1024 * 1024ULL}, - {"ssh", 500 * 1024 * 1024 * 1024ULL}, + {"ssh", 2000 * 1024 * 1024 * 1024ULL}, {"subssh", 2000 * 1024 * 1024 * 1024ULL}, - {"subssh_meta", 100 * 1024 * 1024ULL}, + {"subssh_meta", 1024 * 1024 * 1024ULL}, {"stxo", 2000 * 1024 * 1024 * 1024ULL}, {"zeroconf", 10 * 1024 * 1024 * 1024ULL}, - {"txfilters", 10 * 1024 * 1024 * 1024ULL}, - {"spentness", 500 * 1024 * 1024 * 1024ULL}, + {"txfilters", 100 * 1024 * 1024 * 1024ULL}, + {"spentness", 2000 * 1024 * 1024 * 1024ULL}, }; //////////////////////////////////////////////////////////////////////////////// From b2fdfdcb4e1acea0fe55dea17efeb6f52b0a8a46 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 13 Mar 2022 11:56:57 +0100 Subject: [PATCH 21/47] Fix locktime ser/deser in Signer [WIP] Add legacy Armory ustx codec in Signer --- cppForSwig/Signer/Signer.cpp | 213 ++++++++++++++++++++++++++++++- cppForSwig/Signer/Signer.h | 7 +- cppForSwig/gtest/SignerTests.cpp | 42 +++++- 3 files changed, 254 insertions(+), 8 deletions(-) diff --git a/cppForSwig/Signer/Signer.cpp b/cppForSwig/Signer/Signer.cpp index 11df99c15..986d3ac3e 100644 --- a/cppForSwig/Signer/Signer.cpp +++ b/cppForSwig/Signer/Signer.cpp @@ -16,6 +16,7 @@ #include "Addresses.h" #include "../TxOutScrRef.h" +#include "../BitcoinSettings.h" using namespace std; using namespace Armory::Signer; @@ -744,7 +745,7 @@ void ScriptSpender::serializeStateUtxo( else { auto outpoint = protoMsg.mutable_outpoint(); - + auto outputHashRef = getOutputHash(); outpoint->set_txhash(outputHashRef.getPtr(), outputHashRef.getSize()); outpoint->set_txoutindex(getOutputIndex()); @@ -1614,11 +1615,11 @@ void ScriptSpender::injectSignature(SecureBinaryData& sig, unsigned sigId) BinaryDataRef ScriptSpender::getRedeemScriptFromStack( const map>* stackPtr) const { - if (stackPtr == nullptr) + if (stackPtr == nullptr) return BinaryDataRef(); shared_ptr firstPushData; - + //look for redeem script from sig stack items for (auto stackPair : *stackPtr) { @@ -1801,7 +1802,7 @@ void ScriptSpender::toPSBT(BinaryWriter& bw) const //partial sigs { /* - This section only applies to MS or exotic scripts that can be + This section only applies to MS or exotic scripts that can be partially signed. Single sig scripts go to the finalized section right away. */ @@ -2805,8 +2806,11 @@ BinaryDataRef Signer::serializeUnsignedTx(bool loose) bw.put_BinaryData(recipient->getSerializedScript()); //no witness data for unsigned transactions - for (unsigned i=0; i < spenders_.size(); i++) - bw.put_uint8_t(0); + if (isSW) + { + for (unsigned i=0; i < spenders_.size(); i++) + bw.put_uint8_t(0); + } //lock time bw.put_uint32_t(lockTime_); @@ -3216,6 +3220,203 @@ void Signer::merge(const Signer& rhs) matchAssetPathsWithRoots(); } +//////////////////////////////////////////////////////////////////////////////// +BinaryData Signer::serializeState_Legacy() const +{ + if (isSegWit()) + throw runtime_error("SW txs cannot be serialized to legacy format"); + + BinaryWriter bw; + bw.put_uint32_t(1); + auto magicBytes = Armory::Config::BitcoinSettings::getMagicBytes(); + bw.put_BinaryData(magicBytes); + bw.put_uint32_t(0); //4 empty bytes + + //inputs + bw.put_var_int(spenders_.size()); + for (const auto& spender : spenders_) + { + BinaryWriter bwTxIn; + bwTxIn.put_uint32_t(1); //unsigned_tx_version + bwTxIn.put_BinaryData(magicBytes); + bwTxIn.put_BinaryData(spender->getOutpoint()); + + //supporting tx + try + { + const auto& tx = spender->getSupportingTx(); + bwTxIn.put_var_int(tx.getSize()); + bwTxIn.put_BinaryData(tx.serialize()); + } + catch (const runtime_error&) + { + bwTxIn.put_var_int(0); + } + + //p2sh map BASE_SCRIPT + if (!spender->isP2SH()) + { + bwTxIn.put_var_int(0); + } + else + { + //we assume the spender is resolved since it's flagged as p2sh + if (spender->isSigned()) + { + //if the spender is signed then the stack is empty, we'll have + //to retrieve the base script from the finalized stack. Let's + //keep it simple for now and look at it later. + throw runtime_error( + "Legacy signing across multiple wallets not supported yet"); + } + + auto script = spender->getRedeemScriptFromStack( + &spender->legacyStack_); + bwTxIn.put_var_int(script.getSize()); + bwTxIn.put_BinaryData(script); + } + + //contribID & label (lockbox fields, leaving them empty) + bwTxIn.put_var_int(0); + bwTxIn.put_var_int(0); + + //sequence + bwTxIn.put_uint32_t(spender->getSequence()); + + //key & sig list + auto pubkeys = spender->getRelevantPubkeys(); + bwTxIn.put_var_int(pubkeys.size()); + + for (const auto& pubkeyIt : pubkeys) + { + //pubkey + bwTxIn.put_var_int(pubkeyIt.second.getSize()); + bwTxIn.put_BinaryData(pubkeyIt.second); + + //sig, skipping for now + bwTxIn.put_var_int(0); + + //wallet locator, skipping for now + bwTxIn.put_var_int(0); + } + + + //rest of p2sh map, for nested SW + //we'll ignore this as we dont allow legacy ser for SW txs + + //finalize + bw.put_var_int(bwTxIn.getSize()); + bw.put_BinaryData(bwTxIn.getData()); + } + + //outputs + list serializedRecipients; + for (const auto& recipientList : recipients_) + { + BinaryWriter bwTxOut; + for (const auto& recipient : recipientList.second) + { + bwTxOut.put_uint32_t(1); //unsigned_tx_version + bwTxOut.put_BinaryData(magicBytes); + + auto output = recipient->getSerializedScript(); + auto script = output.getSliceRef(8, output.getSize()-8); + + bwTxOut.put_BinaryData(script); + bwTxOut.put_uint64_t(recipient->getValue()); + + //p2sh script (ignore for now) + bwTxOut.put_var_int(0); + + //wltLocator + bwTxOut.put_var_int(0); + + //auth method & data, ignore + bwTxOut.put_var_int(0); + bwTxOut.put_var_int(0); + + //contrib id & label (lockbox stuff, ignore) + bwTxOut.put_var_int(0); + bwTxOut.put_var_int(0); + + //add to list + serializedRecipients.emplace_back(move(bwTxOut)); + } + } + + //finalize outputs + bw.put_var_int(serializedRecipients.size()); + for (const auto& rec : serializedRecipients) + { + bw.put_var_int(rec.getSize()); + bw.put_BinaryData(rec.getData()); + } + + //locktime + bw.put_uint32_t(lockTime_); + + //done + return bw.getData(); +} + +//////////////////////////////////////////////////////////////////////////////// +string Signer::getLegacyB58ID() +{ + //legacy unsigned serialization with hardcoded version + BinaryWriter bw; + bw.put_uint32_t(1); //version + + //inputs + bw.put_var_int(spenders_.size()); + for (const auto& spender : spenders_) + { + //outpoint + bw.put_BinaryData(spender->getOutpoint()); + + //empty scriptsig + bw.put_uint8_t(0); + + //sequence + bw.put_uint32_t(spender->getSequence()); + } + + //outputs + list serializedRecipients; + for (const auto& recipientList : recipients_) + { + BinaryWriter bwTxOut; + for (const auto& recipient : recipientList.second) + { + auto output = recipient->getSerializedScript(); + auto script = output.getSliceRef(8, output.getSize()-8); + + //value + bwTxOut.put_uint64_t(recipient->getValue()); + + //script + bwTxOut.put_BinaryData(script); + + //add to list + serializedRecipients.emplace_back(move(bwTxOut)); + } + } + + //finalize outputs + bw.put_var_int(serializedRecipients.size()); + for (const auto& rec : serializedRecipients) + bw.put_BinaryData(rec.getData()); + + //locktime + bw.put_uint32_t(0); + + auto serializedTx = bw.getData(); + if (serializedTx.getSize() < 4) + throw runtime_error("invalid serialized tx"); + + auto hashedTxPrefix = BtcUtils::getHash256(serializedTx); + return BtcUtils::base58_encode(hashedTxPrefix).substr(0, 8); +} + //////////////////////////////////////////////////////////////////////////////// bool Signer::isResolved() const { diff --git a/cppForSwig/Signer/Signer.h b/cppForSwig/Signer/Signer.h index e93befd45..bd28047e0 100644 --- a/cppForSwig/Signer/Signer.h +++ b/cppForSwig/Signer/Signer.h @@ -371,7 +371,6 @@ namespace Armory BinaryDataRef&, std::shared_ptr); /*spender data getters*/ - std::shared_ptr getSpender(unsigned) const; uint64_t getOutpointValue(unsigned) const override; unsigned getTxInSequence(unsigned) const override; @@ -393,6 +392,12 @@ namespace Armory void deserializeState(const Codec_SignerState::SignerState&); void merge(const Signer& rhs); + BinaryData serializeState_Legacy(void) const; + std::string getLegacyB58ID(void); + + std::string getTxSigCollect(void) const; + void fromTxSigCollect(const std::string&); + //PSBT BinaryData toPSBT(void) const; static Signer fromPSBT(BinaryDataRef); diff --git a/cppForSwig/gtest/SignerTests.cpp b/cppForSwig/gtest/SignerTests.cpp index e1000e267..d47ecd21b 100644 --- a/cppForSwig/gtest/SignerTests.cpp +++ b/cppForSwig/gtest/SignerTests.cpp @@ -3395,6 +3395,7 @@ TEST_F(SignerTest, SpendTest_MultipleSigners_ParallelSigning_GetUnsignedTx_Neste //////////////////////////////////////////////////////////////////////////////// TEST_F(SignerTest, GetUnsignedTxId) { + auto locktime = 1946132849; TestUtils::setBlocks({ "0", "1", "2", "3" }, blk0dat_); initBDM(); @@ -3540,9 +3541,23 @@ TEST_F(SignerTest, GetUnsignedTxId) signer.getTxId(); EXPECT_TRUE(false); } - catch (exception&) + catch (const exception&) {} + try + { + //set a lock time, check it's encoded correctly + signer.setLockTime(locktime); + auto unsignedTx = signer.serializeUnsignedTx(); + + Tx tx(unsignedTx); + EXPECT_EQ(tx.getLockTime(), locktime); + } + catch (const exception&) + { + EXPECT_TRUE(false); + } + //sign, verify then broadcast signer.sign(); EXPECT_TRUE(signer.verify()); @@ -3610,6 +3625,14 @@ TEST_F(SignerTest, GetUnsignedTxId) signer2.addRecipient(addrVec_1[1]->getRecipient(total - spendVal)); } + //locktime deser test + { + signer2.setLockTime(++locktime); + auto unsignedTx = signer2.serializeUnsignedTx(); + + Tx tx(unsignedTx); + EXPECT_EQ(tx.getLockTime(), locktime); + } serializedSignerState = move(signer2.serializeState()); } @@ -3636,6 +3659,13 @@ TEST_F(SignerTest, GetUnsignedTxId) signer3.addRecipient(addrVec_2[1]->getRecipient(total - spendVal)); } + //locktime deser test + { + auto unsignedTx = signer3.serializeUnsignedTx(); + + Tx tx(unsignedTx); + EXPECT_EQ(tx.getLockTime(), locktime); + } serializedSignerState = move(signer3.serializeState()); } @@ -3649,6 +3679,11 @@ TEST_F(SignerTest, GetUnsignedTxId) signer4.setFeed(assetFeed2); { + auto unsignedTx = signer4.serializeUnsignedTx(); + + Tx tx(unsignedTx); + EXPECT_EQ(tx.getLockTime(), locktime); + auto lock = assetWlt_1->lockDecryptedContainer(); signer4.sign(); } @@ -3714,6 +3749,11 @@ TEST_F(SignerTest, GetUnsignedTxId) BinaryData txid; try { + auto unsignedTx = signer5.serializeUnsignedTx(); + + Tx tx(unsignedTx); + EXPECT_EQ(tx.getLockTime(), locktime); + txid = signer5.getTxId(); } catch (...) From 799d7ec456b4686eac1fc9e661cdc6e823ff64a9 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 30 Mar 2022 15:40:08 +0200 Subject: [PATCH 22/47] USTX serialization type selector Add legacy txsig collect codec --- armoryengine/CppBridge.py | 50 +- armoryengine/Transaction.py | 46 +- cppForSwig/BridgeAPI/CppBridge.cpp | 48 +- cppForSwig/BridgeAPI/CppBridge.h | 7 +- .../BridgeAPI/ProtobufCommandParser.cpp | 51 +- cppForSwig/BtcUtils.cpp | 20 +- cppForSwig/BtcUtils.h | 11 +- cppForSwig/Signer/Signer.cpp | 500 +++++++++++++++++- cppForSwig/Signer/Signer.h | 23 +- cppForSwig/protobuf/ClientProto.proto | 6 +- qtdialogs/DlgOfflineTx.py | 13 +- ui/TxFrames.py | 9 +- ui/TxFramesOffline.py | 127 ++++- 13 files changed, 800 insertions(+), 111 deletions(-) diff --git a/armoryengine/CppBridge.py b/armoryengine/CppBridge.py index 16529b14c..9d42cd90a 100755 --- a/armoryengine/CppBridge.py +++ b/armoryengine/CppBridge.py @@ -1357,25 +1357,26 @@ def addRecipient(self, value, script): raise BridgeSignerError("addRecipient") ############################################################################# - def getSerializedState(self): + def toTxSigCollect(self, ustxType): packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_getSerializedState + packet.method = ClientProto_pb2.signer_toTxSigCollect packet.stringArgs.append(self.signerId) + packet.intArgs.append(ustxType) fut = TheBridge.sendToBridgeProto(packet) socketResponse = fut.getVal() - response = ClientProto_pb2.ReplyBinary() + response = ClientProto_pb2.ReplyStrings() response.ParseFromString(socketResponse) return response.reply[0] ############################################################################# - def unserializeState(self, state): + def fromTxSigCollect(self, txSigCollect): packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_unserializeState + packet.method = ClientProto_pb2.signer_fromTxSigCollect packet.stringArgs.append(self.signerId) - packet.byteArgs.append(state) + packet.stringArgs.append(txSigCollect) fut = TheBridge.sendToBridgeProto(packet) socketResponse = fut.getVal() @@ -1384,7 +1385,7 @@ def unserializeState(self, state): response.ParseFromString(socketResponse) if response.ints[0] == 0: - raise BridgeSignerError("unserializeState") + raise BridgeSignerError("fromTxSigCollect") ############################################################################# def resolve(self, wltId): @@ -1403,14 +1404,13 @@ def resolve(self, wltId): raise BridgeSignerError("resolve") ############################################################################# - def signTx(self, wltId, callback, args): + def signTx(self, wltId, callback): packet = ClientProto_pb2.ClientCommand() packet.method = ClientProto_pb2.signer_signTx packet.stringArgs.append(self.signerId) packet.stringArgs.append(wltId) callbackArgs = [callback] - callbackArgs.extend(args) TheBridge.sendToBridgeProto( packet, False, self.signTxCallback, callbackArgs) @@ -1419,11 +1419,9 @@ def signTxCallback(self, socketResponse, args): response = ClientProto_pb2.ReplyNumbers() response.ParseFromString(socketResponse) - callbackArgs = [response.ints[0]] - callbackArgs.extend(args[1:]) callbackThread = threading.Thread(\ group=None, target=args[0], \ - name=None, args=callbackArgs, kwargs={}) + name=None, args=[], kwargs={}) callbackThread.start() ############################################################################# @@ -1470,5 +1468,33 @@ def getSignedStateForInput(self, inputId): return response + ############################################################################# + def fromType(self): + packet = ClientProto_pb2.ClientCommand() + packet.method = ClientProto_pb2.signer_fromType + packet.stringArgs.append(self.signerId) + + fut = TheBridge.sendToBridgeProto(packet) + socketResponse = fut.getVal() + + response = ClientProto_pb2.ReplyNumbers() + response.ParseFromString(socketResponse) + + return response.ints[0] + + ############################################################################# + def canLegacySerialize(self): + packet = ClientProto_pb2.ClientCommand() + packet.method = ClientProto_pb2.signer_canLegacySerialize + packet.stringArgs.append(self.signerId) + + fut = TheBridge.sendToBridgeProto(packet) + socketResponse = fut.getVal() + + response = ClientProto_pb2.ReplyNumbers() + response.ParseFromString(socketResponse) + + return bool(response.ints[0]) + #### TheBridge = ArmoryBridge() diff --git a/armoryengine/Transaction.py b/armoryengine/Transaction.py index f8b67b1dd..46b9ad673 100644 --- a/armoryengine/Transaction.py +++ b/armoryengine/Transaction.py @@ -33,6 +33,11 @@ SIGHASH_SINGLE = 3 SIGHASH_ANYONECANPAY = 0x80 +USTX_TYPE_UNKNOWN = 0 +USTX_TYPE_MODERN = 1 +USTX_TYPE_LEGACY = 2 +USTX_TYPE_PSBT = 3 + BASE_SCRIPT = 'base_script' ################################################################################ @@ -94,16 +99,16 @@ def addRecipient(self, value, script): self.signer.addRecipient(value, script) ############################################################################# - def serializeState(self): - return self.signer.getSerializedState() + def toTxSigCollect(self, ustxType): + return self.signer.toTxSigCollect(ustxType) ############################################################################# - def unserializeState(self, signerState): - self.signer.unserializeState(signerState) + def fromTxSigCollect(self, txSigCollect): + self.signer.fromTxSigCollect(txSigCollect) ############################################################################# def signTx(self, wltId, callback): - self.signer.signTx(wltId, callback, [self]) + self.signer.signTx(wltId, callback) ############################################################################# def getSignedTx(self): @@ -134,6 +139,15 @@ def getSignedStateForInput(self, inputId): def resolve(self, wltId): return self.signer.resolve(wltId) + ############################################################################# + def fromType(self): + return self.signer.fromType() + + ############################################################################# + def canLegacySerialize(self): + return self.signer.canLegacySerialize() + + ################################################################################ def getMultisigScriptInfo(rawScript): """ @@ -2107,13 +2121,13 @@ def createFromUnsignedTxInputSelection(self, ustxiList, scriptValuePairs, return self.createFromUnsignedTxIO(ustxiList, dtxoList, lockTime) ############################################################################# - def createFromSignerState(self, signerState): + def createFromTxSigCollect(self, txSigCollect): if self.signer != None: raise Exception("Initialized signer, cannot overwrite") self.signer = SignerObject() self.signer.setup() - self.signer.unserializeState(signerState) + self.signer.fromTxSigCollect(txSigCollect) self.pytxObj = self.signer.getUnsignedTx() return self.createFromPyTx(self.pytxObj) @@ -2126,7 +2140,7 @@ def calculateFee(self): ############################################################################# - def serialize(self): + def serialize(self, ustxType=USTX_TYPE_MODERN): """ TODO: We should consider the idea that we don't even need to serialize the pytxObj at all... it seems there should only be a single, @@ -2136,22 +2150,12 @@ def serialize(self): LOGERROR('Cannot serialize an uninitialized tx') return None - """ - bp = BinaryPacker() - bp.put(UINT32, self.version) - bp.put(BINARY_CHUNK, MAGIC_BYTES, 4) - bp.put(UINT32, 0) - - bp.put(UINT8, USTX_EXT_SIGNERSTATE) - bp.put(VAR_STR, self.pytxObj.signerState) - """ - - return self.signer.serializeState() + return self.signer.toTxSigCollect(ustxType) ############################################################################# - def unserialize(self, rawData, expectID=None, skipMagicCheck=False): - self.createFromSignerState(rawData) + def unserialize(self, txSigCollect): + self.createFromTxSigCollect(txSigCollect) return self diff --git a/cppForSwig/BridgeAPI/CppBridge.cpp b/cppForSwig/BridgeAPI/CppBridge.cpp index 431fa98b7..abb08c03d 100755 --- a/cppForSwig/BridgeAPI/CppBridge.cpp +++ b/cppForSwig/BridgeAPI/CppBridge.cpp @@ -1734,25 +1734,23 @@ bool CppBridge::signer_addRecipient( } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::signer_getSerializedState(const string& id) const +BridgeReply CppBridge::signer_toTxSigCollect( + const string& id, int ustxType) const { auto iter = signerMap_.find(id); if (iter == signerMap_.end()) throw runtime_error("invalid signer id"); - auto signerState = iter->second->signer_.serializeState(); - string signerStateStr; - if (!signerState.SerializeToString(&signerStateStr)) - throw runtime_error("failed to serialized signer state"); - - auto msg = make_unique(); - msg->add_reply(signerStateStr); + auto msg = make_unique(); + auto txSigCollect = iter->second->signer_.toString( + static_cast(ustxType)); + msg->add_reply(txSigCollect); return msg; } //////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_unserializeState( - const string& id, const BinaryData& state) +bool CppBridge::signer_fromTxSigCollect( + const string& id, const string& txSigCollect) { auto iter = signerMap_.find(id); if (iter == signerMap_.end()) @@ -1760,14 +1758,12 @@ bool CppBridge::signer_unserializeState( try { - Codec_SignerState::SignerState signerState; - if (!signerState.ParseFromArray(state.getPtr(), state.getSize())) - throw runtime_error("invalid signer state"); - - iter->second->signer_.deserializeState(signerState); + iter->second->signer_ = Armory::Signer::Signer::fromString(txSigCollect); } - catch (exception&) + catch (const exception& e) { + LOGWARN << "failed to parse TxSigCollect with error:"; + LOGWARN << e.what(); return false; } @@ -1923,6 +1919,26 @@ BridgeReply CppBridge::signer_getSignedStateForInput( return result; } +//////////////////////////////////////////////////////////////////////////////// +SignerStringFormat CppBridge::signer_fromType(const string& id) const +{ + auto iter = signerMap_.find(id); + if (iter == signerMap_.end()) + throw runtime_error("invalid signer id"); + + return iter->second->signer_.deserializedFromType(); +} + +//////////////////////////////////////////////////////////////////////////////// +bool CppBridge::signer_canLegacySerialize(const string& id) const +{ + auto iter = signerMap_.find(id); + if (iter == signerMap_.end()) + throw runtime_error("invalid signer id"); + + return iter->second->signer_.canLegacySerialize(); +} + //////////////////////////////////////////////////////////////////////////////// void CppBridge::broadcastTx(const vector& rawTxVec) { diff --git a/cppForSwig/BridgeAPI/CppBridge.h b/cppForSwig/BridgeAPI/CppBridge.h index 506d9b510..440e8a14f 100755 --- a/cppForSwig/BridgeAPI/CppBridge.h +++ b/cppForSwig/BridgeAPI/CppBridge.h @@ -242,8 +242,8 @@ namespace Armory bool signer_addRecipient( const std::string&, const BinaryDataRef&, uint64_t); - BridgeReply signer_getSerializedState(const std::string&) const; - bool signer_unserializeState(const std::string&, const BinaryData&); + BridgeReply signer_toTxSigCollect(const std::string&, int) const; + bool signer_fromTxSigCollect(const std::string&, const std::string&); void signer_signTx(const std::string&, const std::string&, unsigned); BridgeReply signer_getSignedTx(const std::string&) const; BridgeReply signer_getUnsignedTx(const std::string&) const; @@ -251,6 +251,9 @@ namespace Armory const std::string&, unsigned); int signer_resolve(const std::string&, const std::string&) const; + Armory::Signer::SignerStringFormat signer_fromType(const std::string&) const; + bool signer_canLegacySerialize(const std::string&) const; + //utils BridgeReply getTxInScriptType( const BinaryData&, const BinaryData&) const; diff --git a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp index fe2de1334..09200a76a 100644 --- a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp +++ b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp @@ -152,7 +152,7 @@ bool ProtobufCommandParser::processData( throw runtime_error("invalid command: getHistoryForWalletSelection"); vector wltIdVec; - for (unsigned i=1; igetHistoryForWalletSelection(msg.stringargs(0), wltIdVec, id); @@ -656,21 +656,22 @@ bool ProtobufCommandParser::processData( break; } - case Methods::signer_getSerializedState: + case Methods::signer_toTxSigCollect: { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: signer_getSerializedState"); - response = bridge->signer_getSerializedState(msg.stringargs(0)); + if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) + throw runtime_error("invalid command: signer_toTxSigCollect"); + response = bridge->signer_toTxSigCollect( + msg.stringargs(0), msg.intargs(0)); break; } - case Methods::signer_unserializeState: + case Methods::signer_fromTxSigCollect: { - if (msg.stringargs_size() != 1 || msg.byteargs_size() != 1) - throw runtime_error("invalid command: signer_unserializeState"); + if (msg.stringargs_size() != 2) + throw runtime_error("invalid command: signer_fromTxSigCollect"); - auto result = bridge->signer_unserializeState( - msg.stringargs(0), BinaryData::fromString(msg.byteargs(0))); + auto result = bridge->signer_fromTxSigCollect( + msg.stringargs(0), msg.stringargs(1)); auto resultProto = make_unique(); resultProto->add_ints(result); @@ -731,6 +732,36 @@ bool ProtobufCommandParser::processData( break; } + case Methods::signer_fromType: + { + if (msg.stringargs_size() != 1) + { + throw runtime_error( + "invalid command: signer_fromType"); + } + + auto result = (int)bridge->signer_fromType(msg.stringargs(0)); + auto resultProto = make_unique(); + resultProto->add_ints(result); + response = move(resultProto); + break; + } + + case Methods::signer_canLegacySerialize: + { + if (msg.stringargs_size() != 1) + { + throw runtime_error( + "invalid command: signer_canLegacySerialize"); + } + + auto result = bridge->signer_canLegacySerialize(msg.stringargs(0)); + auto resultProto = make_unique(); + resultProto->add_ints(result); + response = move(resultProto); + break; + } + case Methods::returnPassphrase: { if (msg.stringargs_size() != 2) diff --git a/cppForSwig/BtcUtils.cpp b/cppForSwig/BtcUtils.cpp index 936cd1553..9deb87ae2 100644 --- a/cppForSwig/BtcUtils.cpp +++ b/cppForSwig/BtcUtils.cpp @@ -271,11 +271,11 @@ BinaryData BtcUtils::getScrAddrForAddrStr(const string& addrStr) try { - scrAddr = move(base58toScrAddr(addrStr)); + scrAddr = base58toScrAddr(addrStr); } catch (const exception&) { - auto scrAddrPair = move(BtcUtils::segWitAddressToScrAddr(addrStr)); + auto scrAddrPair = BtcUtils::segWitAddressToScrAddr(addrStr); if (scrAddrPair.second != 0) throw runtime_error("[createRecipient] unsupported sw version"); @@ -470,28 +470,28 @@ TxOutScriptRef BtcUtils::getTxOutScrAddrNoCopy(BinaryDataRef script) case(TXOUT_SCRIPT_STDHASH160) : { outputRef.type_ = p2pkh_prefix; - outputRef.scriptRef_ = move(script.getSliceRef(3, 20)); + outputRef.scriptRef_ = script.getSliceRef(3, 20); break; } case(TXOUT_SCRIPT_P2WPKH) : { outputRef.type_ = SCRIPT_PREFIX_P2WPKH; - outputRef.scriptRef_ = move(script.getSliceRef(2, 20)); + outputRef.scriptRef_ = script.getSliceRef(2, 20); break; } case(TXOUT_SCRIPT_P2WSH) : { outputRef.type_ = SCRIPT_PREFIX_P2WSH; - outputRef.scriptRef_ = move(script.getSliceRef(2, 32)); + outputRef.scriptRef_ = script.getSliceRef(2, 32); break; } case(TXOUT_SCRIPT_STDPUBKEY65) : { outputRef.type_ = p2pkh_prefix; - outputRef.scriptCopy_ = move(getHash160(script.getSliceRef(1, 65))); + outputRef.scriptCopy_ = getHash160(script.getSliceRef(1, 65)); outputRef.scriptRef_.setRef(outputRef.scriptCopy_); break; } @@ -499,7 +499,7 @@ TxOutScriptRef BtcUtils::getTxOutScrAddrNoCopy(BinaryDataRef script) case(TXOUT_SCRIPT_STDPUBKEY33) : { outputRef.type_ = p2pkh_prefix; - outputRef.scriptCopy_ = move(getHash160(script.getSliceRef(1, 33))); + outputRef.scriptCopy_ = getHash160(script.getSliceRef(1, 33)); outputRef.scriptRef_.setRef(outputRef.scriptCopy_); break; } @@ -507,14 +507,14 @@ TxOutScriptRef BtcUtils::getTxOutScrAddrNoCopy(BinaryDataRef script) case(TXOUT_SCRIPT_P2SH) : { outputRef.type_ = p2sh_prefix; - outputRef.scriptRef_ = move(script.getSliceRef(2, 20)); + outputRef.scriptRef_ = script.getSliceRef(2, 20); break; } case(TXOUT_SCRIPT_NONSTANDARD) : { outputRef.type_ = SCRIPT_PREFIX_NONSTD; - outputRef.scriptCopy_ = move(getHash160(script)); + outputRef.scriptCopy_ = getHash160(script); outputRef.scriptRef_.setRef(outputRef.scriptCopy_); break; } @@ -522,7 +522,7 @@ TxOutScriptRef BtcUtils::getTxOutScrAddrNoCopy(BinaryDataRef script) case(TXOUT_SCRIPT_MULTISIG) : { outputRef.type_ = SCRIPT_PREFIX_MULTISIG; - outputRef.scriptCopy_ = move(getMultisigUniqueKey(script)); + outputRef.scriptCopy_ = getMultisigUniqueKey(script); outputRef.scriptRef_.setRef(outputRef.scriptCopy_); break; } diff --git a/cppForSwig/BtcUtils.h b/cppForSwig/BtcUtils.h index c556fed57..d859a9753 100644 --- a/cppForSwig/BtcUtils.h +++ b/cppForSwig/BtcUtils.h @@ -1742,20 +1742,15 @@ class BtcUtils throw std::range_error("empty BinaryData"); size_t size = b58.size(); - uint8_t* result = new uint8_t[size]; + BinaryData result(size); - if (!btc_base58_decode(result, &size, b58.c_str()) || + if (!btc_base58_decode(result.getPtr(), &size, b58.c_str()) || size > b58.size()) { - delete[] result; throw std::runtime_error("failed to decode b58 string"); } - BinaryData result_bd(size); - memcpy(result_bd.getPtr(), result + b58.size() - size, size); - - delete[] result; - return result_bd; + return result.getSliceCopy(b58.size() - size, size); } static BinaryData extractRSFromDERSig(BinaryDataRef bdr) diff --git a/cppForSwig/Signer/Signer.cpp b/cppForSwig/Signer/Signer.cpp index 986d3ac3e..e7e5e4194 100644 --- a/cppForSwig/Signer/Signer.cpp +++ b/cppForSwig/Signer/Signer.cpp @@ -23,6 +23,13 @@ using namespace Armory::Signer; using namespace Armory::Assets; using namespace Armory::Wallets; +#define TXSIGCOLLECT_VER_LEGACY 1 +#define USTXI_VER_LEGACY 1 +#define USTXO_VER_LEGACY 1 +#define TXSIGCOLLECT_VER 2 +#define TXSIGCOLLECT_WIDTH 64 +#define TXSIGCOLLECT_HEADER "=====TXSIGCOLLECT-" + StackItem::~StackItem() {} @@ -1555,7 +1562,7 @@ void ScriptSpender::injectSignature(SecureBinaryData& sig, unsigned sigId) throw runtime_error("spender is already signed!"); map>* stackPtr = nullptr; - + //grab the stack carrying the sig(s) if (isSegWit()) stackPtr = &witnessStack_; @@ -2112,7 +2119,7 @@ shared_ptr ScriptSpender::fromPSBT( try { StackResolver resolver(spender->getOutputScript(), feed); - resolver.setFlags( + resolver.setFlags( SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_SEGWIT | SCRIPT_VERIFY_P2SH_SHA256); @@ -3227,7 +3234,6 @@ BinaryData Signer::serializeState_Legacy() const throw runtime_error("SW txs cannot be serialized to legacy format"); BinaryWriter bw; - bw.put_uint32_t(1); auto magicBytes = Armory::Config::BitcoinSettings::getMagicBytes(); bw.put_BinaryData(magicBytes); bw.put_uint32_t(0); //4 empty bytes @@ -3237,7 +3243,7 @@ BinaryData Signer::serializeState_Legacy() const for (const auto& spender : spenders_) { BinaryWriter bwTxIn; - bwTxIn.put_uint32_t(1); //unsigned_tx_version + bwTxIn.put_uint32_t(USTXI_VER_LEGACY); bwTxIn.put_BinaryData(magicBytes); bwTxIn.put_BinaryData(spender->getOutpoint()); @@ -3316,7 +3322,7 @@ BinaryData Signer::serializeState_Legacy() const BinaryWriter bwTxOut; for (const auto& recipient : recipientList.second) { - bwTxOut.put_uint32_t(1); //unsigned_tx_version + bwTxOut.put_uint32_t(USTXO_VER_LEGACY); bwTxOut.put_BinaryData(magicBytes); auto output = recipient->getSerializedScript(); @@ -3360,7 +3366,234 @@ BinaryData Signer::serializeState_Legacy() const } //////////////////////////////////////////////////////////////////////////////// -string Signer::getLegacyB58ID() +void Signer::deserializeState_Legacy(const BinaryDataRef& ref) +{ + BinaryRefReader brr(ref); + + auto magicBytes = Armory::Config::BitcoinSettings::getMagicBytes(); + auto magicBytesRef = brr.get_BinaryDataRef(4); + if (magicBytes != magicBytesRef) + throw SignerDeserializationError("legacy deser: magic bytes mismatch!"); + + auto emptyBytes = brr.get_uint32_t(); + if (emptyBytes != 0) + throw SignerDeserializationError("legacy deser: missing empty bytes"); + + + auto spenderCount = brr.get_var_int(); + for (unsigned i=0; i keysAndSigs; + auto keyCount = brrSpender.get_var_int(); + keysAndSigs.resize(keyCount); + + for (unsigned y=0; y(hashRef, outpointIndex); + addSpender(spender); + + spender->setSupportingTx(supportingTxRaw); + auto supportingTx = spender->getSupportingTx(); + auto output = supportingTx.getTxOutCopy(outpointIndex); + + /*** + Resolve the spender state the legacy way: + + We assume the eligible output types are known. We expect the supporting + tx is present and grab the redeemScript from the relevant output. The + redeemScript is either a base script or a nested script. We expect the + following data is provided in the USTXI depending on the redeemScript: + + base script types: + - P2PKH: input should carry the public key + - P2PK: input should carry pubkey + - Multisig: input should carry the many pubkeys + + nested scripts: + - P2SH: input should carry script preimage. We have to parse the + p2sh preimage as the redeemScript to progress. + + The resolver will be fed the relevant entries at which + point it should have the correct state to setup the spender. + ***/ + + auto feed = make_shared(); + + //grab base script + BinaryDataRef baseScript = output.getScriptRef(); + if (!p2shPreimage.empty()) + { + /* + Output script is p2sh, it embeds a hash and we have the preimage + for it. Grab the hash from the script and add the + pair to the feed + */ + + //grab hash from nested script + auto scriptHash = BtcUtils::getTxOutRecipientAddr(baseScript); + if (scriptHash == BtcUtils::BadAddress()) + throw SignerDeserializationError("invalid nested script"); + + //populate feed + feed->hashMap.emplace(scriptHash, p2shPreimage); + + //set the preimage as the base script + baseScript = p2shPreimage; + } + + //get base script type + auto scriptType = BtcUtils::getTxOutScriptType(baseScript); + auto scriptHash = BtcUtils::getTxOutRecipientAddr(baseScript, scriptType); + switch (scriptType) + { + case TXOUT_SCRIPT_STDHASH160: + { + //p2pkh, we should have a pubkey + if (keysAndSigs.size() == 1) + feed->hashMap.emplace(scriptHash, keysAndSigs.begin()->key); + break; + } + + case TXOUT_SCRIPT_STDPUBKEY33: + case TXOUT_SCRIPT_MULTISIG: + { + //these script types carry the pubkey directly + break; + } + + default: + throw SignerDeserializationError( + "unsupported redeem script for legacy utsxi"); + } + + //resolve the spender + try + { + StackResolver resolver(spender->getOutputScript(), feed); + resolver.setFlags( + SCRIPT_VERIFY_P2SH | + SCRIPT_VERIFY_SEGWIT | + SCRIPT_VERIFY_P2SH_SHA256); + + spender->parseScripts(resolver); + } + catch (const exception&) + {} + + //inject sigs, will throw on failure + for (const auto& kas : keysAndSigs) + { + SecureBinaryData sig(kas.sig); + spender->injectSignature(sig, 0); + } + + //sighash type + } + + auto recipientCount = brr.get_var_int(); + for (unsigned i=0; i sigCollectSize) + throw SignerDeserializationError("invalid TxSigCollect length"); + + //get body and footer ref + auto bodyRef = brr.get_BinaryDataRef( + brr.getSizeRemaining() - footerLength); + auto footerRef = brr.get_BinaryDataRef(footerLength); + + //validate footer + if (!validateFooter(footerRef)) + throw SignerDeserializationError("invalid TxSigCollect footer"); + + //reconstruct base64 string from lines, evict line breaks + string bodyStr; + unsigned pos = 0; + while (pos < bodyRef.getSize()) + { + //grab the line break as well + auto len = std::min((size_t)TXSIGCOLLECT_WIDTH + 1, bodyRef.getSize() - pos); + + //do not copy the line break + bodyStr += string(bodyRef.toCharPtr() + pos, len - 1); + + //assume there's a line break after each 64 characters + pos += len; + } + + //convert to binary + auto bodyBin = BtcUtils::base64_decode(bodyStr); + auto bodyBinRef = BinaryDataRef::fromString(bodyBin); + BinaryRefReader bodyRR(bodyBinRef); + + //version + auto version = bodyRR.get_uint32_t(); + auto signerStateRef = bodyRR.get_BinaryDataRef(bodyRR.getSizeRemaining()); + Signer theSigner; + switch (version) + { + case TXSIGCOLLECT_VER_LEGACY: + { + //legacy txsig collect + theSigner.deserializeState_Legacy(signerStateRef); + theSigner.fromType_ = SignerStringFormat::TxSigCollect_Legacy; + break; + } + + case TXSIGCOLLECT_VER: + { + //regular protobuf packet + Codec_SignerState::SignerState signerProto; + if (!signerProto.ParseFromArray( + signerStateRef.toCharPtr(), signerStateRef.getSize())) + { + //could not deser signer proto + throw SignerDeserializationError( + "[fromTxSigCollect] invalid signer proto"); + } + + theSigner.deserializeState(signerProto); + theSigner.fromType_ = SignerStringFormat::TxSigCollect_Modern; + break; + } + + default: + throw SignerDeserializationError("unsupported TxSigCollect version"); + } + + //check vs signer id + auto signerId = theSigner.getSigCollectID(); + if (signerId != sigCollectId) + { + string errStr("tx sig collect id mismatch, "); + errStr = errStr + "expected: " + sigCollectId + ", got: " + signerId; + throw SignerDeserializationError(errStr); + } + + return theSigner; +} + //////////////////////////////////////////////////////////////////////////////// bool Signer::isResolved() const { @@ -4160,6 +4636,18 @@ void Signer::prettyPrint() const cout << ss.str(); } +//////////////////////////////////////////////////////////////////////////////// +SignerStringFormat Signer::deserializedFromType() const +{ + return fromType_; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Signer::canLegacySerialize() const +{ + return !isSegWit(); +} + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //// SignerProxy diff --git a/cppForSwig/Signer/Signer.h b/cppForSwig/Signer/Signer.h index bd28047e0..e44d0486c 100644 --- a/cppForSwig/Signer/Signer.h +++ b/cppForSwig/Signer/Signer.h @@ -76,6 +76,14 @@ namespace Armory Signed }; + enum class SignerStringFormat + { + Unknown = 0, + TxSigCollect_Modern, + TxSigCollect_Legacy, + PSBT + }; + ////////////////////////////////////////////////////////////////////////// class ScriptSpender { @@ -288,6 +296,7 @@ namespace Armory protected: unsigned version_ = 1; unsigned lockTime_ = 0; + SignerStringFormat fromType_ = SignerStringFormat::Unknown; mutable BinaryData serializedSignedTx_; mutable BinaryData serializedUnsignedTx_; @@ -388,15 +397,17 @@ namespace Armory BinaryData getTxId_const(void) const; //state import/export - Codec_SignerState::SignerState serializeState(void) const; void deserializeState(const Codec_SignerState::SignerState&); + void deserializeState_Legacy(const BinaryDataRef&); void merge(const Signer& rhs); + Codec_SignerState::SignerState serializeState(void) const; BinaryData serializeState_Legacy(void) const; - std::string getLegacyB58ID(void); + std::string getSigCollectID(void) const; - std::string getTxSigCollect(void) const; - void fromTxSigCollect(const std::string&); + std::string toString(SignerStringFormat) const; + static Signer fromString(const std::string&); + std::string toTxSigCollect(bool) const; //PSBT BinaryData toPSBT(void) const; @@ -418,6 +429,10 @@ namespace Armory bool isSegWit(void) const; bool hasLegacyInputs (void) const; + //string state + SignerStringFormat deserializedFromType(void) const; + bool canLegacySerialize(void) const; + /*signer setup*/ //tx setup diff --git a/cppForSwig/protobuf/ClientProto.proto b/cppForSwig/protobuf/ClientProto.proto index a29f40c52..f92a766b2 100644 --- a/cppForSwig/protobuf/ClientProto.proto +++ b/cppForSwig/protobuf/ClientProto.proto @@ -75,14 +75,16 @@ enum Methods signer_addSpenderByOutpoint = 134; signer_populateUtxo = 135; signer_addRecipient = 136; - signer_getSerializedState = 137; - signer_unserializeState = 138; + signer_toTxSigCollect = 137; + signer_fromTxSigCollect = 138; signer_signTx = 139; signer_getSignedTx = 140; signer_getUnsignedTx = 141; signer_getSignedStateForInput = 142; signer_resolve = 143; signer_addSupportingTx = 144; + signer_fromType = 145; + signer_canLegacySerialize = 146; getHash160 = 200; getBlockTimeByHeight = 201; diff --git a/qtdialogs/DlgOfflineTx.py b/qtdialogs/DlgOfflineTx.py index 9766d8cc1..6f19e2137 100644 --- a/qtdialogs/DlgOfflineTx.py +++ b/qtdialogs/DlgOfflineTx.py @@ -25,7 +25,7 @@ from qtdialogs.qtdialogs import STRETCH from qtdialogs.ArmoryDialog import ArmoryDialog -from ui.TxFramesOffline import SignBroadcastOfflineTxFrame +from ui.TxFramesOffline import SignBroadcastOfflineTxFrame, SignerSerializeTypeSelector ################################################################################ class ReviewOfflineTxFrame(ArmoryDialog): @@ -94,10 +94,11 @@ def __init__(self, parent=None, main=None, initLabel=''): frmLowerLayout.setRowStretch(3, 1) frmLower.setLayout(frmLowerLayout) - + self.signerTypeSelector = SignerSerializeTypeSelector() + self.signerTypeSelector.connectSignal(self.update) frmAll = makeLayoutFrame(VERTICAL, \ - [lblInstruct, frmUpper, 'Space(5)', frmLower]) + [lblInstruct, frmUpper, 'Space(5)', self.signerTypeSelector.getFrame(), frmLower]) frmAll.layout().setStretch(0, 0) frmAll.layout().setStretch(1, 0) frmAll.layout().setStretch(2, 0) @@ -113,7 +114,11 @@ def __init__(self, parent=None, main=None, initLabel=''): def setUSTX(self, ustx): self.ustx = ustx self.lblUTX.setText(self.tr('Transaction Data \t (Unsigned ID: %s)' % ustx.uniqueIDB58)) - self.txtUSTX.setText(ustx.serializeAscii()) + self.signerTypeSelector.setUSTX(ustx) + self.update() + + def update(self): + self.txtUSTX.setText(self.ustx.serialize(self.signerTypeSelector.selectedType())) def setWallet(self, wlt): self.wlt = wlt diff --git a/ui/TxFrames.py b/ui/TxFrames.py index 59766b79c..5fa5dbf3a 100755 --- a/ui/TxFrames.py +++ b/ui/TxFrames.py @@ -1022,10 +1022,10 @@ def createTxAndBroadcast(self): if len(self.comments[i][0].strip()) > 0: commentStr += '%s (%s); ' % (self.comments[i][0], coin2str_approx(amt).strip()) - def finalizeSignTx(success, signerObj): + def finalizeSignTx(): #this needs to run in the GUI thread - def signTxLastStep(success, signerObj): - finalTx = signerObj.getSignedTx() + def signTxLastStep(): + finalTx = ustx.getSignedPyTx() if len(commentStr) > 0: self.wlt.setComment(finalTx.getHash(), commentStr) @@ -1034,8 +1034,7 @@ def signTxLastStep(success, signerObj): if self.sendCallback: self.sendCallback() - self.main.signalExecution.executeMethod(\ - [signTxLastStep, [success, signerObj]]) + self.main.signalExecution.executeMethod([signTxLastStep]) ustx.signTx(self.wlt.uniqueIDB58, finalizeSignTx) diff --git a/ui/TxFramesOffline.py b/ui/TxFramesOffline.py index 96218490e..e9e1b8c89 100644 --- a/ui/TxFramesOffline.py +++ b/ui/TxFramesOffline.py @@ -2,19 +2,23 @@ print_function, unicode_literals) ################################################################################ # # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # # Distributed under the GNU Affero General Public License (AGPL v3) # # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # +# Copyright (C) 2016-2021, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # ################################################################################ import os import shutil -from PySide2.QtCore import Qt +from PySide2.QtCore import Qt, QObject, Signal from PySide2.QtWidgets import QPushButton, QGridLayout, QFrame, \ QVBoxLayout, QLabel, QMessageBox, QTextEdit, QSizePolicy, \ - QApplication + QApplication, QRadioButton from qtdialogs.qtdefines import ArmoryFrame, tightSizeNChar, \ GETFONT, QRichLabel, HLINE, QLabelButton, USERMODE, \ @@ -27,10 +31,12 @@ from qtdialogs.DlgConfirmSend import DlgConfirmSend from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA -from armoryengine.Transaction import UnsignedTransaction +from armoryengine.Transaction import UnsignedTransaction, \ + USTX_TYPE_MODERN, USTX_TYPE_LEGACY, USTX_TYPE_PSBT, USTX_TYPE_UNKNOWN from armoryengine.ArmoryUtils import LOGEXCEPT, LOGERROR, LOGINFO, \ CPP_TXOUT_STDSINGLESIG, CPP_TXOUT_P2SH, coin2str, enum, \ - script_to_scrAddr, binary_to_hex, coin2strNZS + script_to_scrAddr, binary_to_hex, coin2strNZS, BadAddressError, \ + NetworkIDError, UnserializeError ################################################################################ class SignBroadcastOfflineTxFrame(ArmoryFrame): @@ -163,8 +169,11 @@ def __init__(self, parent=None, main=None, initLabel=''): # frmBottomLayout.addWidget(frmMoreInfo, 1,1, 1,1) frmBottom.setLayout(frmBottomLayout) + self.signerTypeSelect = SignerSerializeTypeSelector(False) + layout = QVBoxLayout() layout.addWidget(frmDescr) + layout.addWidget(self.signerTypeSelect.getFrame()) layout.addWidget(frmBottom) self.setLayout(layout) @@ -190,7 +199,10 @@ def processUSTX(self): ustxStr = str(self.txtUSTX.toPlainText()) if len(ustxStr) > 0: try: - self.ustxObj = UnsignedTransaction().unserializeAscii(ustxStr) + ustxObj = UnsignedTransaction().unserialize(ustxStr) + self.signerTypeSelect.setUSTX(ustxObj) + + self.ustxObj = ustxObj self.signStat = self.ustxObj.evaluateSigningStatus() self.enoughSigs = self.signStat.canBroadcast self.sigsValid = self.ustxObj.verifySigsAllInputs() @@ -224,6 +236,7 @@ def processUSTX(self): self.ustxReadable = False self.btnBroadcast.setEnabled(False) + self.signerTypeSelect.update() self.btnSave.setEnabled(True) self.btnCopyHex.setEnabled(False) @@ -427,16 +440,15 @@ def signTx(self): if not dlg.exec_(): return - def completeSignProcess(success, signerObj): - def signTxLastStep(success, signerObj): - serTx = self.ustxObj.serializeAscii() + def completeSignProcess(): + def signTxLastStep(): + serTx = self.ustxObj.serialize(self.signerTypeSelect.fromType()) self.txtUSTX.setText(serTx) if not self.fileLoaded == None: self.saveTxAuto() - self.main.signalExecution.executeMethod(\ - [signTxLastStep, [success, signerObj]]) + self.main.signalExecution.executeMethod([signTxLastStep]) self.ustxObj.signTx(self.wlt.uniqueIDB58, completeSignProcess) @@ -582,3 +594,96 @@ def copyTxHex(self): clipb.setText(binary_to_hex(\ self.ustxObj.getSignedPyTx().serialize())) self.lblCopied.setText(self.tr('Copied!')) + +################################################################################ +class SignerSerializeTypeSelector(QObject): + toggledSignal = Signal() + + def __init__(self, selectable=True): + super(SignerSerializeTypeSelector, self).__init__() + self.ustx = None + self.signerStrCurrentType = None + self.selectable = selectable + + #ustx type version radio + self.typesRadio = {} + lblType = QRichLabel("USTX Type:") + self.typesRadio[USTX_TYPE_MODERN] = QRadioButton("Modern (0.97+)") + self.typesRadio[USTX_TYPE_PSBT] = QRadioButton("PSBT (0.97+)") + self.typesRadio[USTX_TYPE_LEGACY] = QRadioButton("Legacy (0.96.5 and older)") + self.reset() + + #connect toggle event + for typeR in self.typesRadio: + self.typesRadio[typeR].toggled.connect(self.processToggled) + + #setup frame + widgetList = [lblType] + for typeR in self.typesRadio: + widgetList.append(self.typesRadio[typeR]) + self.frmRadio = makeLayoutFrame(HORIZONTAL, widgetList, STYLE_RAISED) + + + def getFrame(self): + return self.frmRadio + + def fromType(self): + if self.signerStrCurrentType != None: + return self.signerStrCurrentType + + try: + typeR = self.ustx.signer.fromType() + if typeR == USTX_TYPE_UNKNOWN: + typeR = USTX_TYPE_MODERN + return typeR + except: + return USTX_TYPE_MODERN + + def selectedType(self): + typeR = USTX_TYPE_UNKNOWN + for _typer in self.typesRadio: + if self.typesRadio[_typer].isChecked(): + typeR = _typer + break + + return typeR + + def reset(self): + self.typesRadio[USTX_TYPE_PSBT].setChecked(False) + self.typesRadio[USTX_TYPE_LEGACY].setChecked(False) + self.typesRadio[USTX_TYPE_MODERN].setChecked(True) + + for _typer in self.typesRadio: + self.typesRadio[_typer].setEnabled(self.selectable) + self.typesRadio[USTX_TYPE_LEGACY].setEnabled(False) + + self.signerStrCurrentType = None + + def update(self): + if self.ustx == None or self.ustx.signer == None: + self.reset() + return + + if self.ustx.signer.canLegacySerialize() and self.selectable: + self.typesRadio[USTX_TYPE_LEGACY].setEnabled(True) + else: + self.typesRadio[USTX_TYPE_LEGACY].setEnabled(False) + + def connectSignal(self, _method): + self.toggledSignal.connect(_method) + + def processToggled(self): + if self.ustx == None: + return + + typeR = self.selectedType() + if typeR == USTX_TYPE_UNKNOWN or typeR == self.fromType(): + return + + self.signerStrCurrentType = typeR + self.toggledSignal.emit() + + def setUSTX(self, ustx): + self.ustx = ustx + self.update() + self.typesRadio[self.fromType()].setChecked(True) From a0d706b4cf4c7b0745bd972a220e85959d69c1d1 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 30 Mar 2022 15:41:10 +0200 Subject: [PATCH 23/47] fix filename hint in file save dialog --- ArmoryQt.py | 16 ++++++---------- qtdialogs/DlgOfflineTx.py | 7 ++++--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/ArmoryQt.py b/ArmoryQt.py index 27cb4b145..6ba4f3f80 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -2013,10 +2013,10 @@ def finalizeLoadWallets(self): self.execIntroDialog() ############################################################################# - @RemoveRepeatingExtensions + #@RemoveRepeatingExtensions def getFileSave(self, title='Save Wallet File', \ - ffilter=['Wallet files (*.wallet)'], \ - defaultFilename=None): + ffilter=['Wallet files (*.wallet)'], \ + defaultFilename=None): LOGDEBUG('getFileSave') startPath = self.settings.get('LastDirectory') if len(startPath)==0 or not os.path.exists(startPath): @@ -2034,13 +2034,9 @@ def getFileSave(self, title='Save Wallet File', \ # issue of some sort. Some experimental code under ArmoryMac that directly # calls a dialog produces better results but still freezes under some # circumstances. - if not OS_MACOSX: - fullPath = str(QFileDialog.getSaveFileName( - self, title, startPath, typesStr)) - else: - fullPath = str(QFileDialog.getSaveFileName( - self, title, startPath, typesStr, - options=QFileDialog.DontUseNativeDialog)) + fullPath = str(QFileDialog.getSaveFileName( + self, title, startPath, typesStr, + options=QFileDialog.DontUseNativeDialog)) ''' With PySide2, QFileDialog.getSaveFileName return the user selection as diff --git a/qtdialogs/DlgOfflineTx.py b/qtdialogs/DlgOfflineTx.py index 6f19e2137..d9ff767fc 100644 --- a/qtdialogs/DlgOfflineTx.py +++ b/qtdialogs/DlgOfflineTx.py @@ -161,10 +161,11 @@ def doSaveFile(self): """ Save the Unsigned-Tx block of data """ dpid = self.ustx.uniqueIDB58 suffix = ('' if OS_WINDOWS else '.unsigned.tx') + filename = 'armory_{}_{}'.format(dpid, suffix) toSave = self.main.getFileSave(\ - 'Save Unsigned Transaction', \ - ['Armory Transactions (*.unsigned.tx)'], \ - 'armory_%s_%s' % (dpid, suffix)) + title='Save Unsigned Transaction', \ + ffilter=['Armory Transactions (*.unsigned.tx)'], \ + defaultFilename=filename) LOGINFO('Saving unsigned tx file: %s', toSave) try: theFile = open(toSave, 'w') From b8fc4616a21787ad134afb42b90f2f5e7dbe620d Mon Sep 17 00:00:00 2001 From: goatpig Date: Tue, 5 Apr 2022 15:32:57 +0200 Subject: [PATCH 24/47] Update address type frame when selecting a wallet in Address Book dialog --- qtdialogs/DlgAddressBook.py | 25 ++++++++----------------- ui/AddressTypeSelectDialog.py | 8 ++++++-- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/qtdialogs/DlgAddressBook.py b/qtdialogs/DlgAddressBook.py index 1db6c518c..9be8059f2 100644 --- a/qtdialogs/DlgAddressBook.py +++ b/qtdialogs/DlgAddressBook.py @@ -81,7 +81,7 @@ def __init__(self, parent, main, putResultInWidget=None, \ if defaultWltID == None: defaultWltID = self.main.walletIDList[0] - self.wlt = self.main.walletMap[defaultWltID] + wltObj = self.main.walletMap[defaultWltID] lblDescr = QRichLabel(self.tr('Choose an address from your transaction history, ' 'or your own wallet. If you choose to send to one ' @@ -120,10 +120,9 @@ def toggleAddrType(addrtype): self.wltTableClicked(self.wltDispView.selectionModel().currentIndex()) from ui.AddressTypeSelectDialog import AddressLabelFrame - self.addrType = self.wlt.getDefaultAddressType() + self.addrType = wltObj.getDefaultAddressType() self.addrTypeSelectFrame = AddressLabelFrame(self, \ - toggleAddrType, self.wlt.getAddressTypes(), self.addrType) - self.addrTypeSelectFrame.setType(self.addrType) + toggleAddrType, wltObj.getAddressTypes(), self.addrType) # DISPLAY sent-to addresses self.addrBookTxModel = None @@ -373,19 +372,6 @@ def setAddrBookRxModel(self, wltID): self.addrBookRxView.selectionModel().currentChanged.connect(\ self.addrTableRxClicked) - ############################################################################# - def getAddrStr(self, wlt, addrObj): - addrStr = "" - - if self.addrType == 'P2PKH': - addrStr = cppwlt.getP2PKHAddrForIndex(addrObj.chainIndex) - elif self.addrType == 'P2SH-P2PK': - addrStr = cppwlt.getNestedP2PKAddrForIndex(addrObj.chainIndex) - elif self.addrType == 'P2SH-P2WPKH': - addrStr = cppwlt.getNestedSWAddrForIndex(addrObj.chainIndex) - - return addrStr - ############################################################################# def wltTableClicked(self, currIndex, prevIndex=None): if prevIndex == currIndex: @@ -410,6 +396,11 @@ def wltTableClicked(self, currIndex, prevIndex=None): self.selectedCmmt = '' self.addrBookTxModel.reset() + #update address type frame + wltObj = self.main.walletMap[self.selectedWltID] + self.addrType = wltObj.getDefaultAddressType() + self.addrTypeSelectFrame.updateAddressTypes( + wltObj.getAddressTypes(), self.addrType) ############################################################################# def addrTableTxClicked(self, currIndex, prevIndex=None): diff --git a/ui/AddressTypeSelectDialog.py b/ui/AddressTypeSelectDialog.py index f8d956ce3..72dbf0da8 100755 --- a/ui/AddressTypeSelectDialog.py +++ b/ui/AddressTypeSelectDialog.py @@ -121,7 +121,6 @@ class AddressLabelFrame(object): def __init__(self, main, setAddressFunc, addressTypes, currentAddrType): self.main = main self.setAddressFunc = setAddressFunc - self.addressTypes = addressTypes self.frmAddrType = QFrame() self.frmAddrType.setFrameStyle(STYLE_RAISED) @@ -131,7 +130,6 @@ def __init__(self, main, setAddressFunc, addressTypes, currentAddrType): addrLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.typeLabel = QLabelButton("") self.typeLabel.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - self.setType(currentAddrType) self.typeLabel.linkActivated.connect(self.changeType) @@ -139,6 +137,12 @@ def __init__(self, main, setAddressFunc, addressTypes, currentAddrType): frmAddrTypeLayout.addWidget(self.typeLabel, 0, 1, 1, 2) self.frmAddrType.setLayout(frmAddrTypeLayout) + self.updateAddressTypes(addressTypes, currentAddrType) + + def updateAddressTypes(self, addrTypes, currType): + self.addressTypes = addrTypes + self.setType(currType) + def setType(self, _type): self.addrType = _type addrTypeStr = TheBridge.getNameForAddrType(_type) From 2850e32e0e559750d52826069d85cc7b793b3590 Mon Sep 17 00:00:00 2001 From: goatpig Date: Tue, 5 Apr 2022 15:34:27 +0200 Subject: [PATCH 25/47] Fix missing imports in DlgNewAddress.py --- qtdialogs/DlgNewAddress.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/qtdialogs/DlgNewAddress.py b/qtdialogs/DlgNewAddress.py index 7b796b7d4..ecf909ffb 100644 --- a/qtdialogs/DlgNewAddress.py +++ b/qtdialogs/DlgNewAddress.py @@ -16,16 +16,17 @@ QLineEdit, QHBoxLayout, QDialogButtonBox, QTextEdit, QMessageBox, \ QApplication +from armoryengine.ArmoryUtils import DEFAULT_RECEIVE_TYPE +from armoryengine.BDM import TheBDM, BDM_OFFLINE + from armorycolors import Colors from qtdialogs.qtdefines import determineWalletType, \ STYLE_RAISED, QRichLabel, tightSizeStr, makeHorizFrame, \ - makeVertFrame, WLTTYPES, tightSizeNChar, STYLE_SUNKEN + makeVertFrame, WLTTYPES, tightSizeNChar, STYLE_SUNKEN, MSGBOX from qtdialogs.qtdialogs import STRETCH from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.QRCodeWidget import QRCodeWidget -from armoryengine.ArmoryUtils import DEFAULT_RECEIVE_TYPE - -from armoryengine.BDM import TheBDM, BDM_OFFLINE +from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA ################################################################################ @@ -256,7 +257,7 @@ def ShowRecvCoinsWarningIfNecessary(wlt, parent, main): return result[0] if offlineWallet and not dnaaThisWallet: - result = MsgBoxWithDNAA(parent, main, MSGBOX.Warning, parent.tr('This is not your wallet!'), parent.tr( + result = MsgBoxWithDNAA(parent, main, MSGBOX.Warning, parent.tr('This wallet has no private keys!'), parent.tr( 'You are getting an address for a wallet that ' 'you have specified belongs to you, but you cannot actually ' 'spend the funds from this computer. This is usually the case when ' From 6f99c2ca0b6bed9fd537d872b754b01100d931bf Mon Sep 17 00:00:00 2001 From: goatpig Date: Tue, 5 Apr 2022 15:35:17 +0200 Subject: [PATCH 26/47] Fix isMine toggle in Wallet Details dialog --- qtdialogs/DlgWalletDetails.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qtdialogs/DlgWalletDetails.py b/qtdialogs/DlgWalletDetails.py index d3ef2ff75..dc452094b 100644 --- a/qtdialogs/DlgWalletDetails.py +++ b/qtdialogs/DlgWalletDetails.py @@ -12,7 +12,7 @@ from PySide2.QtCore import Qt, QByteArray from PySide2.QtWidgets import QFrame, QVBoxLayout, QGridLayout, QPushButton, \ - QTreeView, QLabel + QTreeView, QLabel, QCheckBox, QLineEdit, QDialogButtonBox from armoryengine.ArmoryUtils import getVersionString, coin2str from armoryengine.BDM import TheBDM, BDM_UNINITIALIZED, BDM_OFFLINE, \ @@ -993,14 +993,14 @@ def __init__(self, wltID, parent=None, main=None): slot = lambda b: self.edtOwnerString.setEnabled(not b) - self.connect(self.chkIsMine, SIGNAL('toggled(bool)'), slot) + self.chkIsMine.toggled.connect(slot) layout.addWidget(QLabel(self.tr('Wallet owner (optional):')), 3, 0) layout.addWidget(self.edtOwnerString, 3, 1) bbox = QDialogButtonBox(QDialogButtonBox.Ok | \ QDialogButtonBox.Cancel) - self.connect(bbox, SIGNAL('accepted()'), self.accept) - self.connect(bbox, SIGNAL('rejected()'), self.reject) + bbox.accepted.connect(self.accept) + bbox.rejected.connect(self.reject) layout.addWidget(bbox, 4, 0) self.setLayout(layout) self.setWindowTitle(self.tr('Set Wallet Owner')) \ No newline at end of file From 3498bd86b919a5eb0211a369583b9bf2a65406da Mon Sep 17 00:00:00 2001 From: goatpig Date: Tue, 5 Apr 2022 16:30:55 +0200 Subject: [PATCH 27/47] Deser sequence from legacy txsigcollect --- cppForSwig/Signer/Signer.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cppForSwig/Signer/Signer.cpp b/cppForSwig/Signer/Signer.cpp index e7e5e4194..1ef414f22 100644 --- a/cppForSwig/Signer/Signer.cpp +++ b/cppForSwig/Signer/Signer.cpp @@ -26,7 +26,7 @@ using namespace Armory::Wallets; #define TXSIGCOLLECT_VER_LEGACY 1 #define USTXI_VER_LEGACY 1 #define USTXO_VER_LEGACY 1 -#define TXSIGCOLLECT_VER 2 +#define TXSIGCOLLECT_VER_MODERN 2 #define TXSIGCOLLECT_WIDTH 64 #define TXSIGCOLLECT_HEADER "=====TXSIGCOLLECT-" @@ -3419,7 +3419,7 @@ void Signer::deserializeState_Legacy(const BinaryDataRef& ref) brrSpender.get_var_int(); //sequence - brrSpender.get_uint32_t(); + auto sequence = brrSpender.get_uint32_t(); //pubkey & sig list struct KeysAndSigs @@ -3548,6 +3548,9 @@ void Signer::deserializeState_Legacy(const BinaryDataRef& ref) } //sighash type + + //sequence + spender->setSequence(sequence); } auto recipientCount = brr.get_var_int(); @@ -3704,7 +3707,7 @@ string Signer::toTxSigCollect(bool isLegacy) const throw runtime_error("failed to serialize signer proto"); //txsig collect version, hardcoded to 2 for regular signers - signerState.put_uint32_t(TXSIGCOLLECT_VER); + signerState.put_uint32_t(TXSIGCOLLECT_VER_MODERN); signerState.put_BinaryData(stateBD); } @@ -3860,7 +3863,7 @@ Signer Signer::fromString(const string& signerState) break; } - case TXSIGCOLLECT_VER: + case TXSIGCOLLECT_VER_MODERN: { //regular protobuf packet Codec_SignerState::SignerState signerProto; From a7d4bc737e096d37d5c865078419c843973e1fcc Mon Sep 17 00:00:00 2001 From: goatpig Date: Tue, 24 May 2022 16:53:16 +0200 Subject: [PATCH 28/47] P2SH-P2PK support for legacy USTX --- cppForSwig/Makefile.am | 1 + cppForSwig/Signer/LegacySigner.cpp | 357 +++++++++++++++++++++++++++++ cppForSwig/Signer/LegacySigner.h | 168 ++++++++++++++ cppForSwig/Signer/Signer.cpp | 76 +++++- cppForSwig/Signer/Signer.h | 2 +- 5 files changed, 596 insertions(+), 8 deletions(-) create mode 100644 cppForSwig/Signer/LegacySigner.cpp create mode 100644 cppForSwig/Signer/LegacySigner.h diff --git a/cppForSwig/Makefile.am b/cppForSwig/Makefile.am index 3a900b0b1..c6cd5aa55 100644 --- a/cppForSwig/Makefile.am +++ b/cppForSwig/Makefile.am @@ -97,6 +97,7 @@ ARMORYWALLETS_SOURCE_FILES = Wallets/BIP32_Node.cpp \ ARMORYSIGNER_SOURCE_FILES = Signer/Script.cpp \ Signer/ScriptRecipient.cpp \ Signer/Signer.cpp \ + Signer/LegacySigner.cpp \ Signer/ResolverFeed.cpp \ Signer/ResolverFeed_Wallets.cpp \ Signer/Transactions.cpp \ diff --git a/cppForSwig/Signer/LegacySigner.cpp b/cppForSwig/Signer/LegacySigner.cpp new file mode 100644 index 000000000..60dafb305 --- /dev/null +++ b/cppForSwig/Signer/LegacySigner.cpp @@ -0,0 +1,357 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (C) 2022, goatpig // +// Distributed under the MIT license // +// See LICENSE-MIT or https://opensource.org/licenses/MIT // +// // +//////////////////////////////////////////////////////////////////////////////// + +#include "LegacySigner.h" + +using namespace std; +using namespace Armory::LegacySigner; + +#define LEGACY_SIGNERTYPE_DEFAULT 'Default' +#define LEGACY_SIGNERTYPE_LEGACY 'Legacy' +#define LEGACY_SIGNERTYPE_CPP '0.96 C++' +#define LEGACY_SIGNERTYPE_BCH 'Bcash' + +#define SERIALIZED_SCRIPT_PREFIX 0x01 +#define WITNESS_SCRIPT_PREFIX 0x02 +#define LEGACY_STACK_PARTIAL 0x03 +#define WITNESS_STACK_PARTIAL 0x04 +#define PREFIX_UTXO 0x05 +#define PREFIX_OUTPOINT 0x06 +#define USTX_EXT_SIGNERTYPE 0x20 +#define USTX_EXT_SIGNERSTATE 0x30 + +//////////////////////////////////////////////////////////////////////////////// +//// +//// Signer +//// +//////////////////////////////////////////////////////////////////////////////// +Signer Signer::deserExtState(BinaryDataRef data) +{ + Signer signer; + BinaryRefReader brr(data); + + while (brr.getSizeRemaining() != 0) + { + auto extType = brr.get_uint8_t(); + auto extSize = brr.get_var_int(); + auto extRef = brr.get_BinaryDataRef(extSize); + + switch (extType) + { + case USTX_EXT_SIGNERTYPE: + //this isnt useful, it's only here to signify which signer + //code to use, a distinction that is obsolete now + break; + + case USTX_EXT_SIGNERSTATE: + //deser legacy signer state, look for sigs + signer.deser(extRef); + break; + + default: + continue; + } + } + + return signer; +} + +//////////////////////////////////////////////////////////////////////////////// +void Signer::deser(BinaryDataRef data) +{ + /* + We're only here for signatures, we do not care for the tx structure as + the python side of the serialized tx carries that data as well + */ + BinaryRefReader brr(data); + + brr.advance(12); //version + locktime + flags + isSegWit_ = brr.get_uint8_t(); + + auto spenderCount = brr.get_var_int(); + for (unsigned i = 0; i < spenderCount; i++) + { + auto spenderLen = brr.get_var_int(); + auto spenderData = brr.get_BinaryDataRef(spenderLen); + + try + { + auto spenderPtr = ScriptSpender::deserExtState(spenderData); + spenders_.push_back(spenderPtr); + } + catch (const exception &e) + { + LOGWARN << "failed to deser legacy spender"; + LOGWARN << "error: " << e.what(); + } + } + + //ignore recipients +} + +//////////////////////////////////////////////////////////////////////////////// +map Signer::getSigs() const +{ + map result; + for (unsigned i=0; igetSig(); + if (sig.empty()) + continue; + + result.emplace(i, sig); + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +//// +//// ScriptSpender +//// +//////////////////////////////////////////////////////////////////////////////// +shared_ptr ScriptSpender::deserExtState(BinaryDataRef data) +{ + BinaryRefReader brr(data); + + //flags + BitUnpacker bup(brr.get_uint8_t()); + + //sighash type, sequence + brr.advance(5); + + //skip the utxo + auto prefix = brr.get_uint8_t(); + switch (prefix) + { + case PREFIX_UTXO: + { + auto utxoLen = brr.get_var_int(); + brr.advance(utxoLen); + break; + } + + case PREFIX_OUTPOINT: + { + auto outpointLen = brr.get_var_int(); + brr.advance(outpointLen); + brr.advance(8); + break; + } + + default: + throw runtime_error("invalid prefix for utxo/outpoint deser"); + } + + //instantiate spender, set stack state + shared_ptr spender{ new ScriptSpender() }; + spender->legacyStatus_ = (SpenderStatus)bup.getBits(2); + spender->segwitStatus_ = (SpenderStatus)bup.getBits(2); + + //cycle through stack items + while (brr.getSizeRemaining() > 0) + { + auto prefix = brr.get_uint8_t(); + + switch (prefix) + { + case SERIALIZED_SCRIPT_PREFIX: + { + auto len = brr.get_var_int(); + spender->serializedScript_ = move(brr.get_BinaryData(len)); + break; + } + + case WITNESS_SCRIPT_PREFIX: + { + auto len = brr.get_var_int(); + spender->witnessData_ = move(brr.get_BinaryData(len)); + break; + } + + case LEGACY_STACK_PARTIAL: + { + auto count = brr.get_var_int(); + for (unsigned i = 0; i < count; i++) + { + auto len = brr.get_var_int(); + auto stackItem = StackItem::deserialize( + brr.get_BinaryDataRef(len)); + spender->partialStack_.emplace( + stackItem->getId(), stackItem); + } + break; + } + + case WITNESS_STACK_PARTIAL: + { + auto count = brr.get_var_int(); + for (unsigned i = 0; i < count; i++) + { + auto len = brr.get_var_int(); + auto stackItem = StackItem::deserialize( + brr.get_BinaryDataRef(len)); + spender->partialWitnessStack_.emplace( + stackItem->getId(), stackItem); + } + break; + } + + default: + throw LegacyScriptException("invalid spender state"); + } + } + + return spender; +} + +//////////////////////////////////////////////////////////////////////////////// +SecureBinaryData ScriptSpender::getSig() const +{ + if (serializedScript_.empty()) + return {}; + + //expect a straight forward single sig redeem script for now, the sig + //should be the first item of the script + BinaryRefReader brr(serializedScript_.getRef()); + auto sigSize = brr.get_uint8_t(); + return brr.get_SecureBinaryData(sigSize); +} + +//////////////////////////////////////////////////////////////////////////////// +//// +//// StackItem +//// +//////////////////////////////////////////////////////////////////////////////// +StackItem::~StackItem() +{} + +//////////////////////////////////////////////////////////////////////////////// +shared_ptr StackItem::deserialize(const BinaryDataRef& dataRef) +{ + shared_ptr itemPtr; + + BinaryRefReader brr(dataRef); + + auto id = brr.get_uint32_t(); + auto prefix = brr.get_uint8_t(); + + switch (prefix) + { + case STACKITEM_PUSHDATA_PREFIX: + { + auto len = brr.get_var_int(); + auto&& data = brr.get_BinaryData(len); + + itemPtr = make_shared(id, move(data)); + break; + } + + case STACKITEM_SIG_PREFIX: + { + auto len = brr.get_var_int(); + SecureBinaryData data(brr.get_BinaryData(len)); + + itemPtr = make_shared(id, move(data)); + break; + } + + case STACKITEM_MULTISIG_PREFIX: + { + auto m = brr.get_uint16_t(); + auto item_ms = make_shared(id, m); + + auto count = brr.get_var_int(); + for (unsigned i = 0; i < count; i++) + { + auto pos = brr.get_uint16_t(); + auto len = brr.get_var_int(); + SecureBinaryData data(brr.get_BinaryData(len)); + + item_ms->setSig(pos, data); + } + + itemPtr = item_ms; + break; + } + + case STACKITEM_OPCODE_PREFIX: + { + auto opcode = brr.get_uint8_t(); + + itemPtr = make_shared(id, opcode); + break; + } + + case STACKITEM_SERSCRIPT_PREFIX: + { + auto len = brr.get_var_int(); + auto&& data = brr.get_BinaryData(len); + + itemPtr = make_shared(id, move(data)); + break; + } + + default: + throw LegacyScriptException("unexpected stack item prefix"); + } + + return itemPtr; +} + +//////////////////////////////////////////////////////////////////////////////// +bool StackItem_PushData::isSame(const StackItem* obj) const +{ + auto obj_cast = dynamic_cast(obj); + if (obj_cast == nullptr) + return false; + + return data_ == obj_cast->data_; +} + +//////////////////////////////////////////////////////////////////////////////// +bool StackItem_Sig::isSame(const StackItem* obj) const +{ + auto obj_cast = dynamic_cast(obj); + if (obj_cast == nullptr) + return false; + + return data_ == obj_cast->data_; +} + +//////////////////////////////////////////////////////////////////////////////// +bool StackItem_MultiSig::isSame(const StackItem* obj) const +{ + auto obj_cast = dynamic_cast(obj); + if (obj_cast == nullptr) + return false; + + return m_ == obj_cast->m_ && + sigs_ == obj_cast->sigs_; +} + +//////////////////////////////////////////////////////////////////////////////// +bool StackItem_OpCode::isSame(const StackItem* obj) const +{ + auto obj_cast = dynamic_cast(obj); + if (obj_cast == nullptr) + return false; + + return opcode_ == obj_cast->opcode_; +} + +//////////////////////////////////////////////////////////////////////////////// +bool StackItem_SerializedScript::isSame(const StackItem* obj) const +{ + auto obj_cast = dynamic_cast(obj); + if (obj_cast == nullptr) + return false; + + return data_ == obj_cast->data_; +} diff --git a/cppForSwig/Signer/LegacySigner.h b/cppForSwig/Signer/LegacySigner.h new file mode 100644 index 000000000..a1cd633d0 --- /dev/null +++ b/cppForSwig/Signer/LegacySigner.h @@ -0,0 +1,168 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (C) 2016-2022, goatpig // +// Distributed under the MIT license // +// See LICENSE-MIT or https://opensource.org/licenses/MIT // +// // +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _H_LEGACY_SIGNER +#define _H_LEGACY_SIGNER + +#include "Script.h" + +namespace Armory +{ + namespace LegacySigner + { + ////////////////////////////////////////////////////////////////////////// + class LegacyScriptException : public std::runtime_error + { + public: + LegacyScriptException(const std::string& what) : std::runtime_error(what) + {} + }; + + ////////////////////////////////////////////////////////////////////////// + struct StackItem + { + public: + const Armory::Signer::StackItemType type_; + + protected: + const unsigned id_; + + public: + StackItem(Armory::Signer::StackItemType type, unsigned id) : + type_(type), id_(id) + {} + + virtual ~StackItem(void) = 0; + virtual bool isSame(const StackItem* obj) const = 0; + unsigned getId(void) const { return id_; } + + virtual bool isValid(void) const { return true; } + static std::shared_ptr deserialize(const BinaryDataRef&); + }; + + //// + struct StackItem_PushData : public StackItem + { + const BinaryData data_; + + StackItem_PushData(unsigned id, BinaryData&& data) : + StackItem(Armory::Signer::StackItemType_PushData, id), + data_(std::move(data)) + {} + + bool isSame(const StackItem* obj) const; + }; + + //// + struct StackItem_Sig : public StackItem + { + const SecureBinaryData data_; + + StackItem_Sig(unsigned id, SecureBinaryData&& data) : + StackItem(Armory::Signer::StackItemType_Sig, id), + data_(std::move(data)) + {} + + bool isSame(const StackItem* obj) const; + }; + + //// + struct StackItem_MultiSig : public StackItem + { + std::map sigs_; + const unsigned m_; + + StackItem_MultiSig(unsigned id, unsigned m) : + StackItem(Armory::Signer::StackItemType_MultiSig, id), m_(m) + {} + + void setSig(unsigned id, SecureBinaryData& sig) + { + auto sigpair = std::make_pair(id, std::move(sig)); + sigs_.insert(move(sigpair)); + } + + bool isSame(const StackItem* obj) const; + bool isValid(void) const { return sigs_.size() == m_; } + }; + + //// + struct StackItem_OpCode : public StackItem + { + const uint8_t opcode_; + + StackItem_OpCode(unsigned id, uint8_t opcode) : + StackItem(Armory::Signer::StackItemType_OpCode, id), + opcode_(opcode) + {} + + bool isSame(const StackItem* obj) const; + }; + + //// + struct StackItem_SerializedScript : public StackItem + { + const BinaryData data_; + + StackItem_SerializedScript(unsigned id, BinaryData&& data) : + StackItem(Armory::Signer::StackItemType_SerializedScript, id), + data_(std::move(data)) + {} + + bool isSame(const StackItem* obj) const; + BinaryData serialize(void) const; + }; + + ////////////////////////////////////////////////////////////////////////// + enum class SpenderStatus + { + SpenderStatus_Unkonwn, + SpenderStatus_Partial, + SpenderStatus_Resolved + }; + + ////////////////////////////////////////////////////////////////////////// + class ScriptSpender + { + private: + SpenderStatus legacyStatus_ = SpenderStatus::SpenderStatus_Unkonwn; + SpenderStatus segwitStatus_ = SpenderStatus::SpenderStatus_Unkonwn; + + BinaryData serializedScript_; + BinaryData witnessData_; + + std::map> partialStack_; + std::map> partialWitnessStack_; + + private: + ScriptSpender(void) {} + + public: + static std::shared_ptr deserExtState(BinaryDataRef); + SecureBinaryData getSig(void) const; + }; + + ////////////////////////////////////////////////////////////////////////// + class Signer + { + private: + bool isSegWit_ = false; + std::vector> spenders_; + + private: + Signer(void) {} + void deser(BinaryDataRef); + + public: + static Signer deserExtState(BinaryDataRef); + std::map getSigs(void) const; + }; + }; //namespace LegacySigner +}; //namespace + +#endif //_H_LEGACY_SIGNER \ No newline at end of file diff --git a/cppForSwig/Signer/Signer.cpp b/cppForSwig/Signer/Signer.cpp index 1ef414f22..1e72baf7a 100644 --- a/cppForSwig/Signer/Signer.cpp +++ b/cppForSwig/Signer/Signer.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // // -// Copyright (C) 2016-2021, goatpig // +// Copyright (C) 2016-2022, goatpig // // Distributed under the MIT license // // See LICENSE-MIT or https://opensource.org/licenses/MIT // // // @@ -8,8 +8,8 @@ #include "Signer.h" #include "Script.h" +#include "LegacySigner.h" #include "Transactions.h" -#include "make_unique.h" #include "BIP32_Node.h" #include "Assets.h" @@ -30,6 +30,8 @@ using namespace Armory::Wallets; #define TXSIGCOLLECT_WIDTH 64 #define TXSIGCOLLECT_HEADER "=====TXSIGCOLLECT-" +#define TXIN_EXT_P2SHSCRIPT 0x10 + StackItem::~StackItem() {} @@ -3379,7 +3381,6 @@ void Signer::deserializeState_Legacy(const BinaryDataRef& ref) if (emptyBytes != 0) throw SignerDeserializationError("legacy deser: missing empty bytes"); - auto spenderCount = brr.get_var_int(); for (unsigned i=0; i p2shExtMap; + while (brrSpender.getSizeRemaining() != 0) + { + auto extFlag = brrSpender.get_uint8_t(); + auto extSize = brrSpender.get_var_int(); + auto extRef = brrSpender.get_BinaryDataRef(extSize); + + switch (extFlag) + { + case TXIN_EXT_P2SHSCRIPT: + { + BinaryRefReader brrExt(extRef); + auto keyCount = brrExt.get_var_int(); + + for (unsigned y=0; yinjectSignature(sig, 0); } - //sighash type + //TODO: sighash type //sequence spender->setSequence(sequence); @@ -3592,7 +3635,26 @@ void Signer::deserializeState_Legacy(const BinaryDataRef& ref) addRecipient(ScriptRecipient::fromScript(outputData.getDataRef())); } - lockTime_ = brr.get_uint32_t(); + //lock time + if (brr.getSizeRemaining() > 4) + lockTime_ = brr.get_uint32_t(); + + //look for legacy signer state in extended data + auto legacySigner = LegacySigner::Signer::deserExtState( + brr.get_BinaryDataRef(brr.getSizeRemaining())); + + //get the sigs if any + auto sigsFromLegacySigner = legacySigner.getSigs(); + + //inject them + for (auto& sigPair : sigsFromLegacySigner) + { + if (sigPair.first >= spenders_.size()) + throw SignerDeserializationError("legacy deser: invalid spender id"); + + auto& spender = spenders_[sigPair.first]; + spender->injectSignature(sigPair.second, 0); + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/Signer/Signer.h b/cppForSwig/Signer/Signer.h index e44d0486c..17f197fab 100644 --- a/cppForSwig/Signer/Signer.h +++ b/cppForSwig/Signer/Signer.h @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // // -// Copyright (C) 2016-2021, goatpig // +// Copyright (C) 2016-2022, goatpig // // Distributed under the MIT license // // See LICENSE-MIT or https://opensource.org/licenses/MIT // // // From c12846ea2e27947164a77e2c59d7256c2124aa22 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 25 May 2022 09:42:28 +0200 Subject: [PATCH 29/47] Fix wallet update labels, disable heavy benchmark in unit tests --- armoryengine/CppBridge.py | 11 ++++ armoryengine/PyBtcWallet.py | 7 +-- cppForSwig/BridgeAPI/CppBridge.cpp | 17 ++++++ cppForSwig/BridgeAPI/CppBridge.h | 1 + .../BridgeAPI/ProtobufCommandParser.cpp | 6 ++ cppForSwig/BridgeAPI/WalletManager.cpp | 8 +++ cppForSwig/BridgeAPI/WalletManager.h | 1 + cppForSwig/gtest/UtilsTests.cpp | 2 +- cppForSwig/protobuf/ClientProto.proto | 1 + qtdialogs/DlgWalletDetails.py | 58 +++++++++++++++++-- qtdialogs/qtdialogs.py | 49 ---------------- 11 files changed, 102 insertions(+), 59 deletions(-) diff --git a/armoryengine/CppBridge.py b/armoryengine/CppBridge.py index 9d42cd90a..5b6a0dd40 100755 --- a/armoryengine/CppBridge.py +++ b/armoryengine/CppBridge.py @@ -1186,6 +1186,17 @@ def setComment(self, wltId, key, val): self.sendToBridgeProto(packet, False) + ############################################################################# + def setWalletLabels(self, wltId, title, desc): + packet = ClientProto_pb2.ClientCommand() + packet.method = ClientProto_pb2.setWalletLabels + + packet.stringArgs.append(wltId) + packet.stringArgs.append(title) + packet.stringArgs.append(desc) + + self.sendToBridgeProto(packet, False) + ############################################################################# def estimateFee(self, blocks, strat): packet = ClientProto_pb2.ClientCommand() diff --git a/armoryengine/PyBtcWallet.py b/armoryengine/PyBtcWallet.py index 83634b1fc..834a973be 100644 --- a/armoryengine/PyBtcWallet.py +++ b/armoryengine/PyBtcWallet.py @@ -1168,12 +1168,9 @@ def getCommentForLE(self, le): def setWalletLabels(self, lshort, llong=''): self.labelName = lshort self.labelDescr = llong - toWriteS = lshort.ljust( 32, '\x00') - toWriteL = llong.ljust(256, '\x00') - updList = [] - updList.append([WLT_UPDATE_MODIFY, self.offsetLabelName, toWriteS]) - updList.append([WLT_UPDATE_MODIFY, self.offsetLabelDescr, toWriteL]) + TheBridge.setWalletLabels(self.uniqueIDB58, + self.labelName, self.labelDescr) ############################################################################# def deleteImportedAddress(self, addr160): diff --git a/cppForSwig/BridgeAPI/CppBridge.cpp b/cppForSwig/BridgeAPI/CppBridge.cpp index abb08c03d..7525e6959 100755 --- a/cppForSwig/BridgeAPI/CppBridge.cpp +++ b/cppForSwig/BridgeAPI/CppBridge.cpp @@ -1547,6 +1547,23 @@ void CppBridge::setComment(const ClientCommand& msg) wltContainer->setComment(hashKey, comment); } +//////////////////////////////////////////////////////////////////////////////// +void CppBridge::setWalletLabels(const ClientCommand& msg) +{ + if (msg.stringargs_size() < 1 || msg.stringargs_size() > 3) + throw runtime_error("invalid command: setWalletLabels"); + + const auto& walletId = msg.stringargs(0); + + auto wai = WalletAccountIdentifier::deserialize(walletId); + auto wltContainer = wltManager_->getWalletContainer( + wai.walletId, wai.accountId); + + const auto& title = msg.stringargs(1); + const auto& desc = msg.stringargs(2); + wltContainer->setLabels(title, desc); +} + //////////////////////////////////////////////////////////////////////////////// void CppBridge::getUtxosForValue(const string& id, uint64_t value, unsigned msgId) diff --git a/cppForSwig/BridgeAPI/CppBridge.h b/cppForSwig/BridgeAPI/CppBridge.h index 440e8a14f..614c62e0b 100755 --- a/cppForSwig/BridgeAPI/CppBridge.h +++ b/cppForSwig/BridgeAPI/CppBridge.h @@ -201,6 +201,7 @@ namespace Armory std::vector, unsigned); void createAddressBook(const std::string&, unsigned); void setComment(const Codec_ClientProto::ClientCommand&); + void setWalletLabels(const Codec_ClientProto::ClientCommand&); //txs & headers void getTxByHash(const BinaryData&, unsigned); diff --git a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp index 09200a76a..377c5b595 100644 --- a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp +++ b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp @@ -497,6 +497,12 @@ bool ProtobufCommandParser::processData( break; } + case Methods::setWalletLabels: + { + bridge->setWalletLabels(msg); + break; + } + case Methods::getUtxosForValue: { if (msg.stringargs_size() != 1 || msg.longargs_size() != 1) diff --git a/cppForSwig/BridgeAPI/WalletManager.cpp b/cppForSwig/BridgeAPI/WalletManager.cpp index 42edaa2e0..c5c3da4bc 100644 --- a/cppForSwig/BridgeAPI/WalletManager.cpp +++ b/cppForSwig/BridgeAPI/WalletManager.cpp @@ -668,6 +668,14 @@ void WalletContainer::setComment(const string& key, const string& val) wallet_->setComment(keyBd, val); } +//////////////////////////////////////////////////////////////////////////////// +void WalletContainer::setLabels(const string& title, const string& desc) +{ + wallet_->setLabel(title); + wallet_->setDescription(desc); +} + + //////////////////////////////////////////////////////////////////////////////// //// //// Armory135Header diff --git a/cppForSwig/BridgeAPI/WalletManager.h b/cppForSwig/BridgeAPI/WalletManager.h index dfa7da269..06fa3d102 100644 --- a/cppForSwig/BridgeAPI/WalletManager.h +++ b/cppForSwig/BridgeAPI/WalletManager.h @@ -217,6 +217,7 @@ class WalletContainer Armory::Backups::WalletBackup getBackupStrings(const PassphraseLambda&) const; void setComment(const std::string&, const std::string&); + void setLabels(const std::string&, const std::string&); }; //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/gtest/UtilsTests.cpp b/cppForSwig/gtest/UtilsTests.cpp index a0e7ae08d..72775bb86 100644 --- a/cppForSwig/gtest/UtilsTests.cpp +++ b/cppForSwig/gtest/UtilsTests.cpp @@ -2014,7 +2014,7 @@ TEST_F(BinaryDataTest, Contains) EXPECT_FALSE(bd4_.contains(d, 8)); } -TEST_F(BinaryDataTest, CompareBench) +TEST_F(BinaryDataTest, DISABLED_CompareBench) { auto start = chrono::system_clock::now(); diff --git a/cppForSwig/protobuf/ClientProto.proto b/cppForSwig/protobuf/ClientProto.proto index f92a766b2..217f947e5 100644 --- a/cppForSwig/protobuf/ClientProto.proto +++ b/cppForSwig/protobuf/ClientProto.proto @@ -29,6 +29,7 @@ enum Methods getHeaderByHeight = 71; createAddressBook = 72; setComment = 73; + setWalletLabels = 74; getNewAddress = 80; getChangeAddress = 81; diff --git a/qtdialogs/DlgWalletDetails.py b/qtdialogs/DlgWalletDetails.py index dc452094b..46a8477f0 100644 --- a/qtdialogs/DlgWalletDetails.py +++ b/qtdialogs/DlgWalletDetails.py @@ -12,9 +12,9 @@ from PySide2.QtCore import Qt, QByteArray from PySide2.QtWidgets import QFrame, QVBoxLayout, QGridLayout, QPushButton, \ - QTreeView, QLabel, QCheckBox, QLineEdit, QDialogButtonBox + QTreeView, QLabel, QCheckBox, QLineEdit, QDialogButtonBox, QTextEdit -from armoryengine.ArmoryUtils import getVersionString, coin2str +from armoryengine.ArmoryUtils import getVersionString, coin2str, isASCII from armoryengine.BDM import TheBDM, BDM_UNINITIALIZED, BDM_OFFLINE, \ BDM_SCANNING from armorycolors import htmlColor @@ -23,7 +23,8 @@ from qtdialogs.qtdefines import USERMODE, determineWalletType, \ relaxedSizeNChar, relaxedSizeStr, QLabelButton, STYLE_SUNKEN, STYLE_NONE, \ QRichLabel, makeHorizFrame, restoreTableView, WLTTYPES, \ - WLTFIELDS, tightSizeStr, saveTableView + WLTFIELDS, tightSizeStr, saveTableView, tightSizeNChar, \ + UnicodeErrorBox from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA @@ -1003,4 +1004,53 @@ def __init__(self, wltID, parent=None, main=None): bbox.rejected.connect(self.reject) layout.addWidget(bbox, 4, 0) self.setLayout(layout) - self.setWindowTitle(self.tr('Set Wallet Owner')) \ No newline at end of file + self.setWindowTitle(self.tr('Set Wallet Owner')) + +################################################################################ +class DlgChangeLabels(ArmoryDialog): + def __init__(self, currName='', currDescr='', parent=None, main=None): + super(DlgChangeLabels, self).__init__(parent, main) + + self.edtName = QLineEdit() + self.edtName.setMaxLength(32) + lblName = QLabel(self.tr("Wallet &name:")) + lblName.setBuddy(self.edtName) + + self.edtDescr = QTextEdit() + tightHeight = tightSizeNChar(self.edtDescr, 1)[1] + self.edtDescr.setMaximumHeight(tightHeight * 4.2) + lblDescr = QLabel(self.tr("Wallet &description:")) + lblDescr.setAlignment(Qt.AlignVCenter) + lblDescr.setBuddy(self.edtDescr) + + self.edtName.setText(currName) + self.edtDescr.setText(currDescr) + + buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | \ + QDialogButtonBox.Cancel) + buttonBox.accepted.connect(self.accept) + buttonBox.rejected.connect(self.reject) + + layout = QGridLayout() + layout.addWidget(lblName, 1, 0, 1, 1) + layout.addWidget(self.edtName, 1, 1, 1, 1) + layout.addWidget(lblDescr, 2, 0, 1, 1) + layout.addWidget(self.edtDescr, 2, 1, 2, 1) + layout.addWidget(buttonBox, 4, 0, 1, 2) + self.setLayout(layout) + + self.setWindowTitle(self.tr('Wallet Descriptions')) + + + def accept(self, *args): + try: + self.edtName.text().encode("ascii") + except UnicodeDecodeError: + UnicodeErrorBox(self) + return + + if len(str(self.edtName.text()).strip()) == 0: + QMessageBox.critical(self, self.tr('Empty Name'), \ + self.tr('All wallets must have a name. '), QMessageBox.Ok) + return + super(DlgChangeLabels, self).accept(*args) diff --git a/qtdialogs/qtdialogs.py b/qtdialogs/qtdialogs.py index 168c71103..e0531e2b2 100755 --- a/qtdialogs/qtdialogs.py +++ b/qtdialogs/qtdialogs.py @@ -555,55 +555,6 @@ def getImportWltPath(self): if self.importFile: self.accept() - -################################################################################ -class DlgChangeLabels(ArmoryDialog): - def __init__(self, currName='', currDescr='', parent=None, main=None): - super(DlgChangeLabels, self).__init__(parent, main) - - self.edtName = QLineEdit() - self.edtName.setMaxLength(32) - lblName = QLabel(self.tr("Wallet &name:")) - lblName.setBuddy(self.edtName) - - self.edtDescr = QTextEdit() - tightHeight = tightSizeNChar(self.edtDescr, 1)[1] - self.edtDescr.setMaximumHeight(tightHeight * 4.2) - lblDescr = QLabel(self.tr("Wallet &description:")) - lblDescr.setAlignment(Qt.AlignVCenter) - lblDescr.setBuddy(self.edtDescr) - - self.edtName.setText(currName) - self.edtDescr.setText(currDescr) - - buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | \ - QDialogButtonBox.Cancel) - self.connect(buttonBox, SIGNAL('accepted()'), self.accept) - self.connect(buttonBox, SIGNAL('rejected()'), self.reject) - - layout = QGridLayout() - layout.addWidget(lblName, 1, 0, 1, 1) - layout.addWidget(self.edtName, 1, 1, 1, 1) - layout.addWidget(lblDescr, 2, 0, 1, 1) - layout.addWidget(self.edtDescr, 2, 1, 2, 1) - layout.addWidget(buttonBox, 4, 0, 1, 2) - self.setLayout(layout) - - self.setWindowTitle(self.tr('Wallet Descriptions')) - - - def accept(self, *args): - if not isASCII(unicode(self.edtName.text())) or \ - not isASCII(unicode(self.edtDescr.toPlainText())): - UnicodeErrorBox(self) - return - - if len(str(self.edtName.text()).strip()) == 0: - QMessageBox.critical(self, self.tr('Empty Name'), \ - self.tr('All wallets must have a name. '), QMessageBox.Ok) - return - super(DlgChangeLabels, self).accept(*args) - ################################################################################ class LoadingDisp(ArmoryDialog): def __init__(self, parent, main): From 0f8a81370f22cfd488e161fa95f128bd3d00f274 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 1 Jun 2022 17:23:54 +0200 Subject: [PATCH 30/47] cleaner coin2str algo --- armoryengine/ArmoryUtils.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/armoryengine/ArmoryUtils.py b/armoryengine/ArmoryUtils.py index 88a28abbb..b2f7d8812 100755 --- a/armoryengine/ArmoryUtils.py +++ b/armoryengine/ArmoryUtils.py @@ -1260,21 +1260,11 @@ def str2coin(theStr, negAllowed=True, maxDec=8, roundHighPrec=True): if len(coinStr.strip())==0: raise ValueError - - if not '.' in coinStr: - result = int(coinStr)*ONE_BTC - else: - isNeg = ('-' in coinStr) - coinStrPos = coinStr.replace('-', '') - lhs,rhs = coinStrPos.strip(' ').split('.') - result = int(lhs) * ONE_BTC + int(rhs.ljust(8, "0")) - if isNeg: - result = -result - - if not negAllowed and result < 0: + floatVal = float(coinStr) + satoshiVal = int(floatVal * ONE_BTC) + if satoshiVal < 0 and negAllowed == False: raise NegativeValueError - return result - + return satoshiVal ################################################################################ def makeAsciiBlock(binStr, headStr='', wid=64, newline='\n'): From 5671742a42661b8895e497450c68491ec72061b5 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 15 Jun 2022 12:29:11 +0200 Subject: [PATCH 31/47] fix right click menu in main ledger --- ArmoryQt.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ArmoryQt.py b/ArmoryQt.py index 6ba4f3f80..725deb08f 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -57,7 +57,7 @@ unixTimeToFormatStr, binary_to_hex, BTC_HOME_DIR, secondsToHumanTime, \ LEVELDB_BLKDATA, LOGRAWDATA, LOGPPRINT, hex_to_binary, \ getRandomHexits_NotSecure, coin2strNZS, bytesToHumanSize, hash256, \ - DEFAULT_ADDR_TYPE + DEFAULT_ADDR_TYPE, hex_switchEndian, BLOCKEXPLORE_NAME from armoryengine.Block import PyBlock from armoryengine.Decorators import RemoveRepeatingExtensions @@ -3164,15 +3164,18 @@ def showContextMenuLedger(self): if len(self.ledgerView.selectedIndexes())==0: return + def toBool(strValue): + return strValue == "True" + row = self.ledgerView.selectedIndexes()[0].row() - wltID = str(self.ledgerView.model().index(row, LEDGERCOLS.WltID).data().toString()) - txHash = str(self.ledgerView.model().index(row, LEDGERCOLS.TxHash).data().toString()) + wltID = str(self.ledgerView.model().index(row, LEDGERCOLS.WltID).data()) + txHash = str(self.ledgerView.model().index(row, LEDGERCOLS.TxHash).data()) txHash = hex_switchEndian(txHash) - amount, flag = self.ledgerView.model().index(row, LEDGERCOLS.Amount).data().toFloat() - rbf = self.ledgerView.model().index(row, LEDGERCOLS.optInRBF).data().toBool() - issts = self.ledgerView.model().index(row, LEDGERCOLS.toSelf).data().toBool() + amount = float(self.ledgerView.model().index(row, LEDGERCOLS.Amount).data()) + rbf = toBool(self.ledgerView.model().index(row, LEDGERCOLS.optInRBF).data()) + issts = toBool(self.ledgerView.model().index(row, LEDGERCOLS.toSelf).data()) flagged = rbf and (amount < 0 or issts) if flagged: From 04e9b51f2452e00b8484b8cb67baa9dc7c37787d Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 15 Jun 2022 15:48:38 +0200 Subject: [PATCH 32/47] fix tx review dialog for unsigned transactions --- qtdialogs/DlgDispTxInfo.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qtdialogs/DlgDispTxInfo.py b/qtdialogs/DlgDispTxInfo.py index 3d55361c4..f1943c503 100644 --- a/qtdialogs/DlgDispTxInfo.py +++ b/qtdialogs/DlgDispTxInfo.py @@ -22,7 +22,7 @@ hex_to_binary, coin2str, coin2strNZS, LOGEXCEPT, LOGERROR, \ CPP_TXIN_SCRIPT_NAMES, CPP_TXOUT_SCRIPT_NAMES, int_to_hex, \ script_to_scrAddr, scrAddr_to_addrStr, unixTimeToFormatStr, \ - UINT32_MAX + UINT32_MAX, hash256 from armoryengine.BDM import TheBDM, BDM_BLOCKCHAIN_READY from armoryengine.Transaction import UnsignedTransaction, \ @@ -929,12 +929,16 @@ def extractTxInfo(pytx, rcvTime=None): pytx = ustx.pytxObj txHash = pytx.getHash() + try: + hasTxHash = len(txHash) == 32 + except TypeError: + hasTxHash = False txSize, txWeight, sumTxIn, txTime, txBlk, txIdx = [None] * 6 txOutToList = pytx.makeRecipientsList() sumTxOut = sum([t[1] for t in txOutToList]) - if TheBDM.getState() == BDM_BLOCKCHAIN_READY: + if TheBDM.getState() == BDM_BLOCKCHAIN_READY and hasTxHash: txProto = TheBridge.getTxByHash(txHash) if txProto.isValid: hgt = txProto.height From 63bd25aae42519265ad59c23ef265163716016d4 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sat, 10 Sep 2022 13:39:47 +0200 Subject: [PATCH 33/47] fix coin control utxo listing --- ui/TreeViewGUI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/TreeViewGUI.py b/ui/TreeViewGUI.py index c3d7eac18..fa1e5cb4f 100644 --- a/ui/TreeViewGUI.py +++ b/ui/TreeViewGUI.py @@ -622,7 +622,7 @@ def setup(self): addrType = addrObj.addrType addrDict = self.treeData['UTXO'][addrType] - if not binAddr in addrDict: + if not addrStr in addrDict: addrDict[addrStr] = [] addrDict[addrStr].append(utxo) From 972949cb7107a09c0d960c861f3de99f209f3de4 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 11 Sep 2022 13:31:04 +0200 Subject: [PATCH 34/47] More readable block parsing options txhints integrity check increase txhints db size ceiling to 50GB (from 20GB) --- cppForSwig/ArmoryConfig.cpp | 5 + cppForSwig/ArmoryConfig.h | 2 + cppForSwig/BlockDataMap.cpp | 46 +++++---- cppForSwig/BlockDataMap.h | 11 ++- cppForSwig/BlockchainScanner.cpp | 6 +- cppForSwig/BlockchainScanner_Super.cpp | 4 +- cppForSwig/DatabaseBuilder.cpp | 128 +++++++++++++++++++++++-- cppForSwig/DatabaseBuilder.h | 1 + cppForSwig/ZeroConf.cpp | 4 +- cppForSwig/lmdb_wrapper.cpp | 10 +- 10 files changed, 183 insertions(+), 34 deletions(-) diff --git a/cppForSwig/ArmoryConfig.cpp b/cppForSwig/ArmoryConfig.cpp index fd6c02d22..16354f610 100755 --- a/cppForSwig/ArmoryConfig.cpp +++ b/cppForSwig/ArmoryConfig.cpp @@ -512,6 +512,7 @@ unsigned DBSettings::zcThreadCount_ = DEFAULT_ZCTHREAD_COUNT; bool DBSettings::reportProgress_ = true; bool DBSettings::checkChain_ = false; bool DBSettings::clearMempool_ = false; +bool DBSettings::checkTxHints_ = false; //////////////////////////////////////////////////////////////////////////////// void DBSettings::processArgs(const map& args) @@ -537,6 +538,10 @@ void DBSettings::processArgs(const map& args) if (iter != args.end()) clearMempool_ = true; + iter = args.find("check-txhints"); + if (iter != args.end()) + checkTxHints_ = true; + //db type iter = args.find("db-type"); if (iter != args.end()) diff --git a/cppForSwig/ArmoryConfig.h b/cppForSwig/ArmoryConfig.h index ae4942cee..9fdb11089 100755 --- a/cppForSwig/ArmoryConfig.h +++ b/cppForSwig/ArmoryConfig.h @@ -126,6 +126,7 @@ namespace Armory static bool reportProgress_; static bool checkChain_; static bool clearMempool_; + static bool checkTxHints_; private: static void processArgs(const std::map&); @@ -158,6 +159,7 @@ namespace Armory static BDM_INIT_MODE initMode(void) { return initMode_; } static bool clearMempool(void) { return clearMempool_; } static bool reportProgress(void) { return reportProgress_; } + static bool checkTxHints(void) { return checkTxHints_; } }; ////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/BlockDataMap.cpp b/cppForSwig/BlockDataMap.cpp index 1c06ebecc..547671784 100644 --- a/cppForSwig/BlockDataMap.cpp +++ b/cppForSwig/BlockDataMap.cpp @@ -24,8 +24,8 @@ BlockData::BlockData(uint32_t blockid) //////////////////////////////////////////////////////////////////////////////// shared_ptr BlockData::deserialize(const uint8_t* data, size_t size, const shared_ptr blockHeader, - function getID, - bool checkMerkle, bool keepHashes) + function getID, + BlockData::CheckHashes mode) { //deser header from raw block and run a quick sanity check if (size < HEADER_SIZE) @@ -73,26 +73,39 @@ shared_ptr BlockData::deserialize(const uint8_t* data, size_t size, result->data_ = data; result->size_ = size; - if (!checkMerkle) - return result; - - //let's check the merkle root - vector allhashes; - for (auto& txn : result->txns_) + vector allHashes; + switch (mode) { - if (!keepHashes) + case CheckHashes::NoChecks: + return result; + + case CheckHashes::MerkleOnly: + case CheckHashes::TxFilters: { - auto txhash = txn->moveHash(); - allhashes.push_back(move(txhash)); + allHashes.reserve(result->txns_.size()); + for (auto& txn : result->txns_) + { + auto txhash = txn->moveHash(); + allHashes.emplace_back(move(txhash)); + } + break; } - else + + case CheckHashes::FullHints: { - auto& txhash = txn->getHash(); - allhashes.push_back(txhash); + allHashes.reserve(result->txns_.size()); + for (auto& txn : result->txns_) + { + const auto& txhash = txn->getHash(); + allHashes.emplace_back(txhash); + } + break; } } - auto&& merkleroot = BtcUtils::calculateMerkleRoot(allhashes); + //any form of later txhash filtering implies we check the merkle + //root, otherwise we would have no guarantees the hashes are valid + auto&& merkleroot = BtcUtils::calculateMerkleRoot(allHashes); if (merkleroot != bh.getMerkleRoot()) { LOGERR << "merkle root mismatch!"; @@ -101,7 +114,8 @@ shared_ptr BlockData::deserialize(const uint8_t* data, size_t size, throw BlockDeserializingException("invalid merkle root"); } - result->computeTxFilter(allhashes); + if (mode == CheckHashes::TxFilters) + result->computeTxFilter(allHashes); return result; } diff --git a/cppForSwig/BlockDataMap.h b/cppForSwig/BlockDataMap.h index c915146e9..b68127713 100644 --- a/cppForSwig/BlockDataMap.h +++ b/cppForSwig/BlockDataMap.h @@ -199,6 +199,15 @@ class BlockData BinaryData blockHash_; +public: + enum class CheckHashes + { + NoChecks, + MerkleOnly, + TxFilters, + FullHints + }; + public: BlockData(uint32_t); @@ -206,7 +215,7 @@ class BlockData const uint8_t*, size_t, const std::shared_ptr, std::function getID, - bool checkMerkle, bool keepHashes); + CheckHashes); bool isInitialized(void) const { diff --git a/cppForSwig/BlockchainScanner.cpp b/cppForSwig/BlockchainScanner.cpp index 681efd0ee..990475356 100644 --- a/cppForSwig/BlockchainScanner.cpp +++ b/cppForSwig/BlockchainScanner.cpp @@ -460,7 +460,7 @@ shared_ptr BlockchainScanner::getBlockData( auto bdata = BlockData::deserialize( filemap->getPtr() + blockheader->getOffset(), blockheader->getBlockSize(), - blockheader, getID, false, false); + blockheader, getID, BlockData::CheckHashes::NoChecks); return bdata; } @@ -1225,7 +1225,7 @@ void BlockchainScanner::undo(Blockchain::ReorganizationState& reorgState) auto bdata = BlockData::deserialize( filemap.get()->getPtr() + blockPtr->getOffset(), blockPtr->getBlockSize(), blockPtr, - getID, false, false); + getID, BlockData::CheckHashes::NoChecks); const auto& txns = bdata->getTxns(); for (unsigned i = 0; i < txns.size(); i++) @@ -1461,7 +1461,7 @@ void BlockchainScanner::processFilterHitsThread( bdata = BlockData::deserialize( fileptr->getPtr() + headerPtr->getOffset(), headerPtr->getBlockSize(), - headerPtr, getID, false, false); + headerPtr, getID, BlockData::CheckHashes::NoChecks); } catch (const BlockDeserializingException& e) { diff --git a/cppForSwig/BlockchainScanner_Super.cpp b/cppForSwig/BlockchainScanner_Super.cpp index bc9d39022..3f8f25971 100644 --- a/cppForSwig/BlockchainScanner_Super.cpp +++ b/cppForSwig/BlockchainScanner_Super.cpp @@ -1268,7 +1268,7 @@ void BlockchainScanner_Super::undo(Blockchain::ReorganizationState& reorgState) auto bdata = BlockData::deserialize( filemap.get()->getPtr() + blockPtr->getOffset(), blockPtr->getBlockSize(), blockPtr, getID, - false, false); + BlockData::CheckHashes::NoChecks); const auto& txns = bdata->getTxns(); for (unsigned i = 0; i < txns.size(); i++) @@ -1443,7 +1443,7 @@ shared_ptr BlockDataBatch::getBlockData(unsigned height) auto bdata = BlockData::deserialize( filemap->getPtr() + blockheader->getOffset(), blockheader->getBlockSize(), - blockheader, getID, false, false); + blockheader, getID, BlockData::CheckHashes::NoChecks); if (!bdata->isInitialized()) { diff --git a/cppForSwig/DatabaseBuilder.cpp b/cppForSwig/DatabaseBuilder.cpp index 7d68c5e3c..e509772a5 100644 --- a/cppForSwig/DatabaseBuilder.cpp +++ b/cppForSwig/DatabaseBuilder.cpp @@ -125,6 +125,9 @@ void DatabaseBuilder::init() double updatetime = TIMER_READ_SEC("updateblocksindb"); LOGINFO << "updated HEADERS db in " << updatetime << "s"; + if (DBSettings::checkTxHints()) + checkTxHintsIntegrity(); + cycleDatabases(); int scanFrom = -1; @@ -315,7 +318,6 @@ Blockchain::ReorganizationState DatabaseBuilder::updateBlocksInDB( baseID); } - auto addblocks = [&](uint16_t fileID, size_t startOffset, shared_ptr bo, bool _verbose)->void { @@ -388,7 +390,7 @@ bool DatabaseBuilder::addBlocksToDB(BlockDataLoader& bdl, uint16_t fileID, size_t startOffset, shared_ptr bo, bool fullHints) { - auto&& blockfilemappointer = bdl.get(fileID); + auto blockfilemappointer = bdl.get(fileID); auto ptr = blockfilemappointer->getPtr(); //ptr is null if we're out of block files @@ -413,7 +415,9 @@ bool DatabaseBuilder::addBlocksToDB(BlockDataLoader& bdl, { bd = BlockData::deserialize( data, size, nullptr, - getID, true, fullHints); + getID, fullHints ? + BlockData::CheckHashes::FullHints : + BlockData::CheckHashes::TxFilters); } catch (const BlockDeserializingException &e) { @@ -791,7 +795,8 @@ map> DatabaseBuilder::assessBlkFile( try { bd = BlockData::deserialize( - data, size, nullptr, getID, true, false); + data, size, nullptr, getID, + BlockData::CheckHashes::TxFilters); } catch (...) { @@ -1129,7 +1134,7 @@ void DatabaseBuilder::verifyTransactions() auto bdata = BlockData::deserialize( fileMap->getPtr() + bhPtr->getOffset(), bhPtr->getBlockSize(), - bhPtr, getID, false, false); + bhPtr, getID, BlockData::CheckHashes::NoChecks); const auto& txns = bdata->getTxns(); if (txid > txns.size()) @@ -1185,7 +1190,7 @@ void DatabaseBuilder::verifyTransactions() auto bdata = BlockData::deserialize( fileMap->getPtr() + blockheader->getOffset(), blockheader->getBlockSize(), - blockheader, getID, false, false); + blockheader, getID, BlockData::CheckHashes::NoChecks); const auto& txns = bdata->getTxns(); for (unsigned i = 1; i < txns.size(); i++) @@ -1525,3 +1530,114 @@ void DatabaseBuilder::cycleDatabases() db_->closeDatabases(); db_->openDatabases(Pathing::dbDir()); } + +///////////////////////////////////////////////////////////////////////////// +void DatabaseBuilder::checkTxHintsIntegrity() +{ + BlockDataLoader bdl(blockFiles_.folderPath()); + unsigned threadcount = min(DBSettings::threadCount(), + blockFiles_.fileCount()); + + auto fileID = std::make_shared>(); + fileID->store(0, std::memory_order_relaxed); + + auto parserThread = [this, fileID, &bdl]() + { + while (true) { + auto counter = fileID->fetch_add(1, std::memory_order_relaxed); + if (counter % 25 == 0) + LOGINFO << "checking txhints for file " << counter; + + auto blockfilemappointer = bdl.get(counter); + auto ptr = blockfilemappointer->getPtr(); + if (ptr == nullptr) + return; + + //tally all blocks in file + std::list> bdList; + parseBlockFile(ptr, blockfilemappointer->size(), + 0, [&bdList](const uint8_t* data, size_t size, size_t)->bool + { + try + { + auto bd = BlockData::deserialize( + data, size, nullptr, + nullptr, BlockData::CheckHashes::FullHints); + bdList.emplace_back(bd); + return true; + } + catch (...) + { + return false; + } + } + ); + + //check hashes can be resolved via tx hints db + int missedCount = 0; + auto dbtx = db_->beginTransaction(TXHINTS, LMDB::ReadOnly); + for (const auto& blockData : bdList) + { + //skip blocks not in the main chain + auto headerPtr = blockchain_->getHeaderByHash(blockData->getHash()); + if (headerPtr == nullptr || !headerPtr->isMainBranch()) + continue; + + const auto& txns = blockData->getTxns(); + for (const auto& txn : txns) + { + auto hash4 = txn->getHash().getSliceRef(0, 4); + BinaryRefReader brrHints = db_->getValueRef( + TXHINTS, DB_PREFIX_TXHINTS, hash4); + + uint32_t valSize = brrHints.getSize(); + if (valSize < 6) + { + ++missedCount; + return; + } + + bool hit = false; + uint32_t numHints = (uint32_t)brrHints.get_var_int(); + for (uint32_t i = 0; i < numHints; i++) + { + BinaryDataRef hint = brrHints.get_BinaryDataRef(6); + + //check this key is on the main branch + auto hintRef = hint.getSliceRef(0, 4); + auto blockId = DBUtils::hgtxToHeight(hintRef); + if (blockId == headerPtr->getThisID()) + { + hit = true; + break; + } + } + + if (!hit) + ++missedCount; + } + } + + if (missedCount != 0) + { + LOGERR << "missed " << missedCount << " hashes" << + " in file " << counter; + } + } + }; + + std::vector threads; + LOGINFO << "checking txhints for " << blockFiles_.fileCount() << + " files on " << threadcount << " threads"; + threads.reserve(threadcount); + for (unsigned i=0; i> ZeroConfContainer::purgeToBranchpoint( auto block = BlockData::deserialize( rawBlock.getPtr(), rawBlock.getSize(), currentHeader, nullptr, - false, false); + BlockData::CheckHashes::NoChecks); const auto& txns = block->getTxns(); for (unsigned txid = 0; txid < txns.size(); txid++) @@ -268,7 +268,7 @@ map> ZeroConfContainer::purge( auto block = BlockData::deserialize( rawBlock.getPtr(), rawBlock.getSize(), currentHeader, nullptr, - false, false); + BlockData::CheckHashes::NoChecks); const auto& txns = block->getTxns(); //gather all outpoints spent by this block diff --git a/cppForSwig/lmdb_wrapper.cpp b/cppForSwig/lmdb_wrapper.cpp index fef6a47c8..fc4bcba78 100644 --- a/cppForSwig/lmdb_wrapper.cpp +++ b/cppForSwig/lmdb_wrapper.cpp @@ -42,7 +42,7 @@ const map LMDBBlockDatabase::mapSizes_ = { {"headers", 50 * 1024 * 1024 * 1024ULL}, {"blkdata", 50 * 1024 * 1024ULL}, {"history", 50 * 1024 * 1024ULL}, - {"txhints", 20 * 1024 * 1024 * 1024ULL}, + {"txhints", 50 * 1024 * 1024 * 1024ULL}, {"ssh", 2000 * 1024 * 1024 * 1024ULL}, {"subssh", 2000 * 1024 * 1024 * 1024ULL}, {"subssh_meta", 1024 * 1024 * 1024ULL}, @@ -678,7 +678,7 @@ BinaryData LMDBBlockDatabase::getDBKeyForHash(const BinaryData& txhash, return BinaryData(); } - BinaryData hash4(txhash.getSliceRef(0, 4)); + auto hash4 = txhash.getSliceRef(0, 4); auto&& txHints = beginTransaction(TXHINTS, LMDB::ReadOnly); BinaryRefReader brrHints = getValueRef(TXHINTS, DB_PREFIX_TXHINTS, hash4); @@ -1740,8 +1740,10 @@ Tx LMDBBlockDatabase::getFullTxCopy( auto getID = [bhPtr] (const BinaryData&)->uint32_t {return bhPtr->getThisID(); }; - auto block = BlockData::deserialize(dataPtr + bhPtr->getOffset(), - bhPtr->getBlockSize(), bhPtr, getID, false, false); + auto block = BlockData::deserialize( + dataPtr + bhPtr->getOffset(), + bhPtr->getBlockSize(), bhPtr, getID, + BlockData::CheckHashes::NoChecks); const auto& bctx = block->getTxns()[txIndex]; BinaryRefReader brr(bctx->data_, bctx->size_); From 5882a94f3f46062b44ad2657b701229b13681699 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 11 Sep 2022 14:59:24 +0200 Subject: [PATCH 35/47] move blockchain database files to their own folder --- cppForSwig/BDM_mainthread.cpp | 1 - cppForSwig/BDM_mainthread.h | 2 +- cppForSwig/BDV_Notification.h | 2 +- cppForSwig/BlockDataViewer.h | 4 +- .../{ => BlockchainDatabase}/BlockDataMap.cpp | 0 .../{ => BlockchainDatabase}/BlockDataMap.h | 0 .../{ => BlockchainDatabase}/BlockObj.cpp | 0 .../{ => BlockchainDatabase}/BlockObj.h | 0 .../{ => BlockchainDatabase}/BlockUtils.cpp | 0 .../{ => BlockchainDatabase}/BlockUtils.h | 4 +- .../{ => BlockchainDatabase}/Blockchain.cpp | 0 .../{ => BlockchainDatabase}/Blockchain.h | 0 .../BlockchainScanner.cpp | 0 .../BlockchainScanner.h | 0 .../BlockchainScanner_Super.cpp | 0 .../BlockchainScanner_Super.h | 0 .../DatabaseBuilder.cpp | 0 .../DatabaseBuilder.h | 0 .../ScrAddrFilter.cpp | 0 .../{ => BlockchainDatabase}/ScrAddrFilter.h | 0 .../{ => BlockchainDatabase}/SshParser.cpp | 0 .../{ => BlockchainDatabase}/SshParser.h | 0 .../StoredBlockObj.cpp | 0 .../{ => BlockchainDatabase}/StoredBlockObj.h | 0 .../TxHashFilters.cpp | 0 .../{ => BlockchainDatabase}/TxHashFilters.h | 0 .../{ => BlockchainDatabase}/lmdb_wrapper.cpp | 0 .../{ => BlockchainDatabase}/lmdb_wrapper.h | 0 cppForSwig/{ => BlockchainDatabase}/txio.cpp | 0 cppForSwig/{ => BlockchainDatabase}/txio.h | 0 cppForSwig/BtcWallet.cpp | 4 +- cppForSwig/BtcWallet.h | 4 +- cppForSwig/HistoryPager.h | 2 +- cppForSwig/LedgerEntry.h | 6 +-- cppForSwig/Makefile.am | 43 +++++++++++++------ cppForSwig/ScrAddrObj.h | 8 ++-- cppForSwig/Signer/Transactions.h | 2 +- cppForSwig/ZeroConf.cpp | 2 +- cppForSwig/ZeroConf.h | 6 +-- cppForSwig/ZeroConfNotifications.cpp | 2 +- cppForSwig/ZeroConfUtils.cpp | 4 +- cppForSwig/ZeroConfUtils.h | 2 +- cppForSwig/gtest/NodeUnitTest.cpp | 1 - cppForSwig/gtest/NodeUnitTest.h | 6 +-- cppForSwig/gtest/TestUtils.h | 10 ++--- cppForSwig/gtest/UtilsTests.cpp | 2 +- 46 files changed, 65 insertions(+), 52 deletions(-) rename cppForSwig/{ => BlockchainDatabase}/BlockDataMap.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockDataMap.h (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockObj.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockObj.h (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockUtils.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockUtils.h (100%) rename cppForSwig/{ => BlockchainDatabase}/Blockchain.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/Blockchain.h (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockchainScanner.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockchainScanner.h (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockchainScanner_Super.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/BlockchainScanner_Super.h (100%) rename cppForSwig/{ => BlockchainDatabase}/DatabaseBuilder.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/DatabaseBuilder.h (100%) rename cppForSwig/{ => BlockchainDatabase}/ScrAddrFilter.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/ScrAddrFilter.h (100%) rename cppForSwig/{ => BlockchainDatabase}/SshParser.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/SshParser.h (100%) rename cppForSwig/{ => BlockchainDatabase}/StoredBlockObj.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/StoredBlockObj.h (100%) rename cppForSwig/{ => BlockchainDatabase}/TxHashFilters.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/TxHashFilters.h (100%) rename cppForSwig/{ => BlockchainDatabase}/lmdb_wrapper.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/lmdb_wrapper.h (100%) rename cppForSwig/{ => BlockchainDatabase}/txio.cpp (100%) rename cppForSwig/{ => BlockchainDatabase}/txio.h (100%) diff --git a/cppForSwig/BDM_mainthread.cpp b/cppForSwig/BDM_mainthread.cpp index 1b8a21b21..8efcda65d 100644 --- a/cppForSwig/BDM_mainthread.cpp +++ b/cppForSwig/BDM_mainthread.cpp @@ -12,7 +12,6 @@ //////////////////////////////////////////////////////////////////////////////// #include "BDM_mainthread.h" -#include "BlockUtils.h" #include "BlockDataViewer.h" #include "nodeRPC.h" diff --git a/cppForSwig/BDM_mainthread.h b/cppForSwig/BDM_mainthread.h index dd30c7b15..58831f271 100644 --- a/cppForSwig/BDM_mainthread.h +++ b/cppForSwig/BDM_mainthread.h @@ -19,7 +19,7 @@ #include "UniversalTimer.h" #include "bdmenums.h" -#include "BlockUtils.h" +#include "BlockchainDatabase/BlockUtils.h" #include "BDM_Server.h" struct BlockDataManagerConfig; diff --git a/cppForSwig/BDV_Notification.h b/cppForSwig/BDV_Notification.h index f37a83179..5fdbf09a6 100644 --- a/cppForSwig/BDV_Notification.h +++ b/cppForSwig/BDV_Notification.h @@ -13,7 +13,7 @@ #include "log.h" #include "bdmenums.h" -#include "Blockchain.h" +#include "BlockchainDatabase/Blockchain.h" #include "LedgerEntry.h" #include "ZeroConf.h" #include "nodeRPC.h" diff --git a/cppForSwig/BlockDataViewer.h b/cppForSwig/BlockDataViewer.h index 96fcce4e7..43e7e845f 100644 --- a/cppForSwig/BlockDataViewer.h +++ b/cppForSwig/BlockDataViewer.h @@ -17,8 +17,8 @@ #include #include -#include "BlockUtils.h" -#include "txio.h" +#include "BlockchainDatabase/BlockUtils.h" +#include "BlockchainDatabase/txio.h" #include "BDV_Notification.h" #include "ZeroConf.h" #include "util.h" diff --git a/cppForSwig/BlockDataMap.cpp b/cppForSwig/BlockchainDatabase/BlockDataMap.cpp similarity index 100% rename from cppForSwig/BlockDataMap.cpp rename to cppForSwig/BlockchainDatabase/BlockDataMap.cpp diff --git a/cppForSwig/BlockDataMap.h b/cppForSwig/BlockchainDatabase/BlockDataMap.h similarity index 100% rename from cppForSwig/BlockDataMap.h rename to cppForSwig/BlockchainDatabase/BlockDataMap.h diff --git a/cppForSwig/BlockObj.cpp b/cppForSwig/BlockchainDatabase/BlockObj.cpp similarity index 100% rename from cppForSwig/BlockObj.cpp rename to cppForSwig/BlockchainDatabase/BlockObj.cpp diff --git a/cppForSwig/BlockObj.h b/cppForSwig/BlockchainDatabase/BlockObj.h similarity index 100% rename from cppForSwig/BlockObj.h rename to cppForSwig/BlockchainDatabase/BlockObj.h diff --git a/cppForSwig/BlockUtils.cpp b/cppForSwig/BlockchainDatabase/BlockUtils.cpp similarity index 100% rename from cppForSwig/BlockUtils.cpp rename to cppForSwig/BlockchainDatabase/BlockUtils.cpp diff --git a/cppForSwig/BlockUtils.h b/cppForSwig/BlockchainDatabase/BlockUtils.h similarity index 100% rename from cppForSwig/BlockUtils.h rename to cppForSwig/BlockchainDatabase/BlockUtils.h index 742752e93..481188bd1 100644 --- a/cppForSwig/BlockUtils.h +++ b/cppForSwig/BlockchainDatabase/BlockUtils.h @@ -22,12 +22,12 @@ #include #include "Blockchain.h" +#include "StoredBlockObj.h" +#include "lmdb_wrapper.h" #include "BinaryData.h" #include "BtcUtils.h" #include "BlockObj.h" -#include "StoredBlockObj.h" #include "ArmoryConfig.h" -#include "lmdb_wrapper.h" #include "ScrAddrObj.h" #include "bdmenums.h" diff --git a/cppForSwig/Blockchain.cpp b/cppForSwig/BlockchainDatabase/Blockchain.cpp similarity index 100% rename from cppForSwig/Blockchain.cpp rename to cppForSwig/BlockchainDatabase/Blockchain.cpp diff --git a/cppForSwig/Blockchain.h b/cppForSwig/BlockchainDatabase/Blockchain.h similarity index 100% rename from cppForSwig/Blockchain.h rename to cppForSwig/BlockchainDatabase/Blockchain.h diff --git a/cppForSwig/BlockchainScanner.cpp b/cppForSwig/BlockchainDatabase/BlockchainScanner.cpp similarity index 100% rename from cppForSwig/BlockchainScanner.cpp rename to cppForSwig/BlockchainDatabase/BlockchainScanner.cpp diff --git a/cppForSwig/BlockchainScanner.h b/cppForSwig/BlockchainDatabase/BlockchainScanner.h similarity index 100% rename from cppForSwig/BlockchainScanner.h rename to cppForSwig/BlockchainDatabase/BlockchainScanner.h diff --git a/cppForSwig/BlockchainScanner_Super.cpp b/cppForSwig/BlockchainDatabase/BlockchainScanner_Super.cpp similarity index 100% rename from cppForSwig/BlockchainScanner_Super.cpp rename to cppForSwig/BlockchainDatabase/BlockchainScanner_Super.cpp diff --git a/cppForSwig/BlockchainScanner_Super.h b/cppForSwig/BlockchainDatabase/BlockchainScanner_Super.h similarity index 100% rename from cppForSwig/BlockchainScanner_Super.h rename to cppForSwig/BlockchainDatabase/BlockchainScanner_Super.h diff --git a/cppForSwig/DatabaseBuilder.cpp b/cppForSwig/BlockchainDatabase/DatabaseBuilder.cpp similarity index 100% rename from cppForSwig/DatabaseBuilder.cpp rename to cppForSwig/BlockchainDatabase/DatabaseBuilder.cpp diff --git a/cppForSwig/DatabaseBuilder.h b/cppForSwig/BlockchainDatabase/DatabaseBuilder.h similarity index 100% rename from cppForSwig/DatabaseBuilder.h rename to cppForSwig/BlockchainDatabase/DatabaseBuilder.h diff --git a/cppForSwig/ScrAddrFilter.cpp b/cppForSwig/BlockchainDatabase/ScrAddrFilter.cpp similarity index 100% rename from cppForSwig/ScrAddrFilter.cpp rename to cppForSwig/BlockchainDatabase/ScrAddrFilter.cpp diff --git a/cppForSwig/ScrAddrFilter.h b/cppForSwig/BlockchainDatabase/ScrAddrFilter.h similarity index 100% rename from cppForSwig/ScrAddrFilter.h rename to cppForSwig/BlockchainDatabase/ScrAddrFilter.h diff --git a/cppForSwig/SshParser.cpp b/cppForSwig/BlockchainDatabase/SshParser.cpp similarity index 100% rename from cppForSwig/SshParser.cpp rename to cppForSwig/BlockchainDatabase/SshParser.cpp diff --git a/cppForSwig/SshParser.h b/cppForSwig/BlockchainDatabase/SshParser.h similarity index 100% rename from cppForSwig/SshParser.h rename to cppForSwig/BlockchainDatabase/SshParser.h diff --git a/cppForSwig/StoredBlockObj.cpp b/cppForSwig/BlockchainDatabase/StoredBlockObj.cpp similarity index 100% rename from cppForSwig/StoredBlockObj.cpp rename to cppForSwig/BlockchainDatabase/StoredBlockObj.cpp diff --git a/cppForSwig/StoredBlockObj.h b/cppForSwig/BlockchainDatabase/StoredBlockObj.h similarity index 100% rename from cppForSwig/StoredBlockObj.h rename to cppForSwig/BlockchainDatabase/StoredBlockObj.h diff --git a/cppForSwig/TxHashFilters.cpp b/cppForSwig/BlockchainDatabase/TxHashFilters.cpp similarity index 100% rename from cppForSwig/TxHashFilters.cpp rename to cppForSwig/BlockchainDatabase/TxHashFilters.cpp diff --git a/cppForSwig/TxHashFilters.h b/cppForSwig/BlockchainDatabase/TxHashFilters.h similarity index 100% rename from cppForSwig/TxHashFilters.h rename to cppForSwig/BlockchainDatabase/TxHashFilters.h diff --git a/cppForSwig/lmdb_wrapper.cpp b/cppForSwig/BlockchainDatabase/lmdb_wrapper.cpp similarity index 100% rename from cppForSwig/lmdb_wrapper.cpp rename to cppForSwig/BlockchainDatabase/lmdb_wrapper.cpp diff --git a/cppForSwig/lmdb_wrapper.h b/cppForSwig/BlockchainDatabase/lmdb_wrapper.h similarity index 100% rename from cppForSwig/lmdb_wrapper.h rename to cppForSwig/BlockchainDatabase/lmdb_wrapper.h diff --git a/cppForSwig/txio.cpp b/cppForSwig/BlockchainDatabase/txio.cpp similarity index 100% rename from cppForSwig/txio.cpp rename to cppForSwig/BlockchainDatabase/txio.cpp diff --git a/cppForSwig/txio.h b/cppForSwig/BlockchainDatabase/txio.h similarity index 100% rename from cppForSwig/txio.h rename to cppForSwig/BlockchainDatabase/txio.h diff --git a/cppForSwig/BtcWallet.cpp b/cppForSwig/BtcWallet.cpp index 683b88b52..fb179ab94 100644 --- a/cppForSwig/BtcWallet.cpp +++ b/cppForSwig/BtcWallet.cpp @@ -11,8 +11,8 @@ // // //////////////////////////////////////////////////////////////////////////////// #include "BtcWallet.h" -#include "BlockUtils.h" -#include "txio.h" +#include "BlockchainDatabase/BlockUtils.h" +#include "BlockchainDatabase/txio.h" #include "BlockDataViewer.h" #include "TxOutScrRef.h" diff --git a/cppForSwig/BtcWallet.h b/cppForSwig/BtcWallet.h index 9a15ec8c6..69da41d2f 100644 --- a/cppForSwig/BtcWallet.h +++ b/cppForSwig/BtcWallet.h @@ -14,9 +14,9 @@ #define _BTCWALLET_H #include "BinaryData.h" -#include "BlockObj.h" +#include "BlockchainDatabase/BlockObj.h" +#include "BlockchainDatabase/StoredBlockObj.h" #include "ScrAddrObj.h" -#include "StoredBlockObj.h" #include "bdmenums.h" #include "ThreadSafeClasses.h" #include "TxClasses.h" diff --git a/cppForSwig/HistoryPager.h b/cppForSwig/HistoryPager.h index afaa447bf..75f8bdda2 100644 --- a/cppForSwig/HistoryPager.h +++ b/cppForSwig/HistoryPager.h @@ -15,7 +15,7 @@ #include "ThreadSafeClasses.h" #include "BinaryData.h" #include "LedgerEntry.h" -#include "BlockObj.h" +#include "BlockchainDatabase/BlockObj.h" class AlreadyPagedException {}; diff --git a/cppForSwig/LedgerEntry.h b/cppForSwig/LedgerEntry.h index c6826592d..f6bae23eb 100644 --- a/cppForSwig/LedgerEntry.h +++ b/cppForSwig/LedgerEntry.h @@ -15,9 +15,9 @@ #include "BinaryData.h" #include "BtcUtils.h" -#include "BlockObj.h" -#include "Blockchain.h" -#include "StoredBlockObj.h" +#include "BlockchainDatabase/BlockObj.h" +#include "BlockchainDatabase/Blockchain.h" +#include "BlockchainDatabase/StoredBlockObj.h" #include "BDVCodec.h" #include "ZeroConf.h" diff --git a/cppForSwig/Makefile.am b/cppForSwig/Makefile.am index c6cd5aa55..524c22867 100644 --- a/cppForSwig/Makefile.am +++ b/cppForSwig/Makefile.am @@ -11,6 +11,7 @@ LIBCHACHA20POLY1305 = chacha20poly1305/libchacha20poly1305.la LIBARMORYWALLETS = libArmoryWallets.la LIBARMORYSIGNER = libArmorySigner.la +LIBBLOCKCHAINDB = libBlockchainDb.la LIBARMORYCOMMON = libArmoryCommon.la LIBARMORYGUI = libArmoryGUI.la LIBARMORYCLI = libArmoryCLI.la @@ -103,6 +104,20 @@ ARMORYSIGNER_SOURCE_FILES = Signer/Script.cpp \ Signer/Transactions.cpp \ Signer/TxEvalState.cpp +BLOCKCHAINDB_SOURCE_FILES = BlockchainDatabase/Blockchain.cpp \ + BlockchainDatabase/BlockchainScanner.cpp \ + BlockchainDatabase/BlockchainScanner_Super.cpp \ + BlockchainDatabase/BlockDataMap.cpp \ + BlockchainDatabase/BlockObj.cpp \ + BlockchainDatabase/BlockUtils.cpp \ + BlockchainDatabase/DatabaseBuilder.cpp \ + BlockchainDatabase/lmdb_wrapper.cpp \ + BlockchainDatabase/ScrAddrFilter.cpp \ + BlockchainDatabase/SshParser.cpp \ + BlockchainDatabase/StoredBlockObj.cpp \ + BlockchainDatabase/TxHashFilters.cpp \ + BlockchainDatabase/txio.cpp + ARMORYDB_SOURCE_FILES = main.cpp ARMORYGUI_SOURCE_FILES = TransactionBatch.cpp ARMORYCLI_SOURCE_FILES = BDM_mainthread.cpp \ @@ -110,31 +125,19 @@ ARMORYCLI_SOURCE_FILES = BDM_mainthread.cpp \ BIP150_151.cpp \ BIP15x_Handshake.cpp \ BitcoinP2P.cpp \ - Blockchain.cpp \ - BlockchainScanner.cpp \ - BlockchainScanner_Super.cpp \ - BlockDataMap.cpp \ BlockDataViewer.cpp \ - BlockObj.cpp \ - BlockUtils.cpp \ BtcWallet.cpp \ - DatabaseBuilder.cpp \ DBUtils.cpp \ HistoryPager.cpp \ HttpMessage.cpp \ JSON_codec.cpp \ LedgerEntry.cpp \ - TxHashFilters.cpp \ - lmdb_wrapper.cpp \ nodeRPC.cpp \ Progress.cpp \ - ScrAddrFilter.cpp \ ScrAddrObj.cpp \ Server.cpp \ SocketService_unix.cpp \ - SshParser.cpp \ StringSockets.cpp \ - txio.cpp \ ZeroConfUtils.cpp \ ZeroConf.cpp \ ZeroConfNotifications.cpp \ @@ -155,9 +158,7 @@ ARMORYCOMMON_SOURCE_FILES = AsyncClient.cpp \ SecureBinaryData.cpp \ ArmoryBackups.cpp \ SocketObject.cpp \ - StoredBlockObj.cpp \ TxClasses.cpp \ - txio.cpp \ TxOutScrRef.cpp \ UniversalTimer.cpp \ WebSocketClient.cpp \ @@ -229,6 +230,17 @@ libArmorySigner_la_LIBADD = $(LIBBTC) \ libArmorySigner_la_LDFLAGS = $(LDFLAGS) +# libBlockchainDb +noinst_LTLIBRARIES += $(LIBBLOCKCHAINDB) +libBlockchainDb_la_SOURCES = $(BLOCKCHAINDB_SOURCE_FILES) +libBlockchainDb_la_CPPFLAGS = $(AM_CPPFLAGS) $(INCLUDE_FILES) +libBlockchainDb_la_CXXFLAGS = $(AM_CXXFLAGS) $(LIBBTC_FLAGS) \ + $(UNIT_TEST_CXXFLAGS) -D__STDC_LIMIT_MACROS +libBlockchainDb_la_LIBADD = $(LIBBTC) \ + -llmdb \ + -lpthread +libBlockchainDb_la_LDFLAGS = $(LDFLAGS) + # libArmoryCommon - Required by all Armory programs/libraries. # Common functionality across GUI and command line noinst_LTLIBRARIES += $(LIBARMORYCOMMON) @@ -242,6 +254,7 @@ libArmoryCommon_la_LIBADD = $(LIBHKDF) \ $(LIBSECP256K1) \ $(LIBARMORYWALLETS) \ $(LIBARMORYSIGNER) \ + $(LIBBLOCKCHAINDB) \ -lpthread libArmoryCommon_la_LDFLAGS = $(LDFLAGS) $(LWSLDFLAGS) $(PROTOBUF_FLAGS) @@ -255,6 +268,7 @@ libArmoryCLI_la_CXXFLAGS = $(AM_CXXFLAGS) $(LIBBTC_FLAGS) \ libArmoryCLI_la_LIBADD = $(LIBCHACHA20POLY1305) \ $(LIBARMORYWALLETS) \ $(LIBARMORYSIGNER) \ + $(LIBBLOCKCHAINDB) \ $(LIBARMORYCOMMON) \ $(LIBLMDBPP) \ $(LIBHKDF) \ @@ -272,6 +286,7 @@ ArmoryDB_CXXFLAGS = $(AM_CXXFLAGS) $(LIBBTC_FLAGS) $(UNIT_TEST_CXXFLAGS) \ ArmoryDB_CPPFLAGS = $(AM_CPPFLAGS) $(INCLUDE_FILES) ArmoryDB_LDADD = $(LIBARMORYWALLETS) \ $(LIBARMORYSIGNER) \ + $(LIBBLOCKCHAINDB) \ $(LIBARMORYCOMMON) \ $(LIBARMORYCLI) \ $(LIBHKDF) \ diff --git a/cppForSwig/ScrAddrObj.h b/cppForSwig/ScrAddrObj.h index 40137b9ee..4b6bbeb0a 100644 --- a/cppForSwig/ScrAddrObj.h +++ b/cppForSwig/ScrAddrObj.h @@ -14,10 +14,10 @@ #define SCRADDROBJ_H #include "BinaryData.h" -#include "lmdb_wrapper.h" -#include "Blockchain.h" -#include "BlockObj.h" -#include "txio.h" +#include "BlockchainDatabase/lmdb_wrapper.h" +#include "BlockchainDatabase/Blockchain.h" +#include "BlockchainDatabase/txio.h" +#include "BlockchainDatabase/BlockObj.h" #include "ZeroConf.h" #include "LedgerEntry.h" #include "HistoryPager.h" diff --git a/cppForSwig/Signer/Transactions.h b/cppForSwig/Signer/Transactions.h index c3f838c08..738f5c9b6 100644 --- a/cppForSwig/Signer/Transactions.h +++ b/cppForSwig/Signer/Transactions.h @@ -15,7 +15,7 @@ #include "BinaryData.h" #include "EncryptionUtils.h" #include "BtcUtils.h" -#include "BlockDataMap.h" +#include "BlockchainDatabase/BlockDataMap.h" #include "Script.h" #include "SigHashEnum.h" diff --git a/cppForSwig/ZeroConf.cpp b/cppForSwig/ZeroConf.cpp index 5b26a7632..51775e2f8 100644 --- a/cppForSwig/ZeroConf.cpp +++ b/cppForSwig/ZeroConf.cpp @@ -12,7 +12,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "ZeroConf.h" -#include "BlockDataMap.h" +#include "BlockchainDatabase/BlockDataMap.h" #include "ArmoryErrors.h" using namespace std; diff --git a/cppForSwig/ZeroConf.h b/cppForSwig/ZeroConf.h index dfb8ad217..00bc428b9 100644 --- a/cppForSwig/ZeroConf.h +++ b/cppForSwig/ZeroConf.h @@ -21,9 +21,9 @@ #include "ThreadSafeClasses.h" #include "BitcoinP2p.h" -#include "lmdb_wrapper.h" -#include "Blockchain.h" -#include "ScrAddrFilter.h" +#include "BlockchainDatabase/lmdb_wrapper.h" +#include "BlockchainDatabase/Blockchain.h" +#include "BlockchainDatabase/ScrAddrFilter.h" #include "ArmoryErrors.h" #include "ZeroConfUtils.h" #include "ZeroConfNotifications.h" diff --git a/cppForSwig/ZeroConfNotifications.cpp b/cppForSwig/ZeroConfNotifications.cpp index 10898718c..3c549af07 100644 --- a/cppForSwig/ZeroConfNotifications.cpp +++ b/cppForSwig/ZeroConfNotifications.cpp @@ -13,7 +13,7 @@ #include "ZeroConf.h" #include "BDM_Server.h" #include "LedgerEntry.h" -#include "txio.h" +#include "BlockchainDatabase/txio.h" using namespace std; using namespace ::Codec_BDVCommand; diff --git a/cppForSwig/ZeroConfUtils.cpp b/cppForSwig/ZeroConfUtils.cpp index b7382d4cb..485e7f89c 100644 --- a/cppForSwig/ZeroConfUtils.cpp +++ b/cppForSwig/ZeroConfUtils.cpp @@ -8,8 +8,8 @@ #include "ZeroConfUtils.h" #include "ZeroConfNotifications.h" -#include "ScrAddrFilter.h" -#include "lmdb_wrapper.h" +#include "BlockchainDatabase/ScrAddrFilter.h" +#include "BlockchainDatabase/lmdb_wrapper.h" using namespace std; using namespace Armory::Config; diff --git a/cppForSwig/ZeroConfUtils.h b/cppForSwig/ZeroConfUtils.h index 61525b57a..8c04aa3cc 100644 --- a/cppForSwig/ZeroConfUtils.h +++ b/cppForSwig/ZeroConfUtils.h @@ -12,7 +12,7 @@ #include #include #include "BinaryData.h" -#include "txio.h" +#include "BlockchainDatabase/txio.h" class LMDBBlockDatabase; diff --git a/cppForSwig/gtest/NodeUnitTest.cpp b/cppForSwig/gtest/NodeUnitTest.cpp index da4744396..e8e653a56 100644 --- a/cppForSwig/gtest/NodeUnitTest.cpp +++ b/cppForSwig/gtest/NodeUnitTest.cpp @@ -7,7 +7,6 @@ //////////////////////////////////////////////////////////////////////////////// #include "NodeUnitTest.h" -#include "../BlockUtils.h" #include "../Signer/Signer.h" using namespace std; diff --git a/cppForSwig/gtest/NodeUnitTest.h b/cppForSwig/gtest/NodeUnitTest.h index 77c70fd5c..d39c124aa 100644 --- a/cppForSwig/gtest/NodeUnitTest.h +++ b/cppForSwig/gtest/NodeUnitTest.h @@ -16,11 +16,11 @@ #include "../BinaryData.h" #include "../BtcUtils.h" -#include "../BlockUtils.h" +#include "../BlockchainDatabase/BlockUtils.h" #include "../BitcoinP2p.h" -#include "../Blockchain.h" +#include "../BlockchainDatabase/Blockchain.h" #include "../Signer/ScriptRecipient.h" -#include "../BlockDataMap.h" +#include "../BlockchainDatabase/BlockDataMap.h" #include "../nodeRPC.h" //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/gtest/TestUtils.h b/cppForSwig/gtest/TestUtils.h index 1244c5382..22892ae19 100644 --- a/cppForSwig/gtest/TestUtils.h +++ b/cppForSwig/gtest/TestUtils.h @@ -21,12 +21,13 @@ #include "../log.h" #include "../BinaryData.h" #include "../BtcUtils.h" -#include "../BlockObj.h" -#include "../StoredBlockObj.h" +#include "../BlockchainDatabase/BlockObj.h" +#include "../BlockchainDatabase/lmdb_wrapper.h" +#include "../BlockchainDatabase/BlockUtils.h" +#include "../BlockchainDatabase/txio.h" +#include "../BlockchainDatabase/StoredBlockObj.h" #include "../PartialMerkle.h" #include "../EncryptionUtils.h" -#include "../lmdb_wrapper.h" -#include "../BlockUtils.h" #include "../ScrAddrObj.h" #include "../BtcWallet.h" #include "../BlockDataViewer.h" @@ -36,7 +37,6 @@ #include "../reorgTest/blkdata.h" #include "../BDM_Server.h" #include "../TxClasses.h" -#include "../txio.h" #include "../bdmenums.h" #include "../Signer/Script.h" #include "../Signer/Signer.h" diff --git a/cppForSwig/gtest/UtilsTests.cpp b/cppForSwig/gtest/UtilsTests.cpp index 72775bb86..0e1919ab9 100644 --- a/cppForSwig/gtest/UtilsTests.cpp +++ b/cppForSwig/gtest/UtilsTests.cpp @@ -13,7 +13,7 @@ #include #include "TestUtils.h" #include "hkdf.h" -#include "TxHashFilters.h" +#include "BlockchainDatabase/TxHashFilters.h" using namespace std; using namespace Armory::Signer; From a26b46edd088c52c0bdc408913e7f1cff3b7056a Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 11 Sep 2022 22:01:01 +0200 Subject: [PATCH 36/47] use srcdir in makefile --- Makefile.am | 2 +- cppForSwig/Makefile.am | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile.am b/Makefile.am index cec9ba7cc..5856c7a93 100755 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ copy-script: # SWIG code and requirements. if BUILD_CLIENT - protoc --python_out=armoryengine --proto_path=cppForSwig/protobuf cppForSwig/protobuf/ClientProto.proto + protoc --python_out=$(srcdir)/armoryengine --proto_path=$(srcdir)/cppForSwig/protobuf $(srcdir)/cppForSwig/protobuf/ClientProto.proto #TODO: build c20p1305 CFFI python lib endif diff --git a/cppForSwig/Makefile.am b/cppForSwig/Makefile.am index 524c22867..3b160d759 100644 --- a/cppForSwig/Makefile.am +++ b/cppForSwig/Makefile.am @@ -58,11 +58,11 @@ AM_CXXFLAGS = protobuf/%.pb.cc protobuf/%.pb.h: protobuf/%.proto # @test -f proto - protoc --cpp_out=protobuf --proto_path=protobuf $< + protoc --cpp_out=protobuf --proto_path=$(srcdir)/protobuf $< -INCLUDE_FILES = -Ibech32/ref/c++ -Ilmdbpp \ - -Ichacha20poly1305 -I$(LIBBTC_LIBDIR)/src/secp256k1/include \ - -I$(LIBBTC_LIBDIR)/include $(LWS_CFLAGS) -Ihkdf -IWallets -ISigner +INCLUDE_FILES = -I$(srcdir)/lmdbpp \ + -I$(srcdir)/chacha20poly1305 -I$(LIBBTC_LIBDIR)/src/secp256k1/include \ + -I$(LIBBTC_LIBDIR)/include $(LWS_CFLAGS) -I$(srcdir)/hkdf -I$(srcdir)/Wallets -I$(srcdir)/Signer # Files should *not* be marked as "common" if at all possible. Also, a refactor # might not hurt one of these days. Some of the "common" files could be GUI/CLI @@ -337,7 +337,7 @@ bin_PROGRAMS += CppBridge CppBridge_SOURCES = $(CPPBRIDGE_SOURCE_FILES) CppBridge_CXXFLAGS = $(AM_CXXFLAGS) $(LIBBTC_FLAGS) $(UNIT_TEST_CXXFLAGS) \ -D__STDC_LIMIT_MACROS -CppBridge_CPPFLAGS = $(AM_CPPFLAGS) $(INCLUDE_FILES) +CppBridge_CPPFLAGS = $(AM_CPPFLAGS) $(INCLUDE_FILES) -Iprotobuf CppBridge_LDADD = $(LIBARMORYCOMMON) \ $(LIBARMORYCLI) \ $(LIBHKDF) \ From 776252fa90658b82546cf09074f34f1df0499393 Mon Sep 17 00:00:00 2001 From: goatpig Date: Tue, 31 Jan 2023 19:44:43 +0100 Subject: [PATCH 37/47] [ARMORY-8] rework bridge api --- Makefile.am | 2 +- cppForSwig/BridgeAPI/CppBridge.cpp | 1246 ++++++-------- cppForSwig/BridgeAPI/CppBridge.h | 99 +- cppForSwig/BridgeAPI/PassphrasePrompt.cpp | 3 +- cppForSwig/BridgeAPI/PassphrasePrompt.h | 6 +- .../BridgeAPI/ProtobufCommandParser.cpp | 1487 ++++++++++------- cppForSwig/BridgeAPI/ProtobufCommandParser.h | 30 +- cppForSwig/BridgeAPI/ProtobufConversions.cpp | 92 +- cppForSwig/BridgeAPI/ProtobufConversions.h | 14 +- cppForSwig/CoinSelection.h | 6 +- cppForSwig/Makefile.am | 6 +- cppForSwig/protobuf/BridgeProto.proto | 921 ++++++++++ cppForSwig/protobuf/ClientProto.proto | 436 ----- 13 files changed, 2395 insertions(+), 1953 deletions(-) create mode 100644 cppForSwig/protobuf/BridgeProto.proto delete mode 100644 cppForSwig/protobuf/ClientProto.proto diff --git a/Makefile.am b/Makefile.am index 5856c7a93..fbb97f743 100755 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ copy-script: # SWIG code and requirements. if BUILD_CLIENT - protoc --python_out=$(srcdir)/armoryengine --proto_path=$(srcdir)/cppForSwig/protobuf $(srcdir)/cppForSwig/protobuf/ClientProto.proto + protoc --python_out=$(srcdir)/armoryengine --proto_path=$(srcdir)/cppForSwig/protobuf $(srcdir)/cppForSwig/protobuf/BridgeProto.proto #TODO: build c20p1305 CFFI python lib endif diff --git a/cppForSwig/BridgeAPI/CppBridge.cpp b/cppForSwig/BridgeAPI/CppBridge.cpp index 7525e6959..ae94c4556 100755 --- a/cppForSwig/BridgeAPI/CppBridge.cpp +++ b/cppForSwig/BridgeAPI/CppBridge.cpp @@ -19,7 +19,7 @@ using namespace std; using namespace ::google::protobuf; -using namespace ::Codec_ClientProto; +using namespace ::BridgeProto; using namespace Armory::Threading; using namespace Armory::Signer; using namespace Armory::Wallets; @@ -32,9 +32,9 @@ enum CppBridgeState CppBridge_Registered }; -#define BRIDGE_CALLBACK_BDM UINT32_MAX -#define BRIDGE_CALLBACK_PROGRESS UINT32_MAX - 1 -#define DISCONNECTED_CALLBACK_ID 0xff543ad8 +#define BRIDGE_CALLBACK_BDM "bdm_callback" +#define BRIDGE_CALLBACK_PROGRESS "progress" +#define DISCONNECTED_CALLBACK_ID "disconnected" //////////////////////////////////////////////////////////////////////////////// //// @@ -47,7 +47,7 @@ CppBridge::CppBridge(const string& path, const string& dbAddr, dbOneWayAuth_(oneWayAuth), dbOffline_(offline) { commandWithCallbackQueue_ = make_shared< - Armory::Threading::BlockingQueue>(); + Armory::Threading::BlockingQueue>(); } //////////////////////////////////////////////////////////////////////////////// @@ -77,7 +77,7 @@ bool CppBridge::processData(BinaryDataRef socketData) } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::queueCommandWithCallback(ClientCommand msg) +void CppBridge::queueCommandWithCallback(BridgeProto::Request msg) { commandWithCallbackQueue_->push_back(move(msg)); } @@ -93,7 +93,7 @@ void CppBridge::processCommandWithCallbackThread() while (true) { - ClientCommand msg; + BridgeProto::Request msg; try { msg = move(commandWithCallbackQueue_->pop_front()); @@ -103,7 +103,7 @@ void CppBridge::processCommandWithCallbackThread() break; } - BinaryDataRef opaqueRef; + /*BinaryDataRef opaqueRef; if (msg.byteargs_size() < 2) { //msg has to carry a callback id @@ -145,7 +145,7 @@ void CppBridge::processCommandWithCallbackThread() //process the commands try { - switch (msg.methodwithcallback()) + switch (msg.method_case()) { case MethodsWithCallback::followUp: { @@ -165,10 +165,8 @@ void CppBridge::processCommandWithCallbackThread() break; } - /* - Entry point to the methods, they will populate their respective - callbacks object with lambdas to process the returned values - */ + //Entry point to the methods, they will populate their respective + //callbacks object with lambdas to process the returned values case MethodsWithCallback::restoreWallet: { auto handler = getCallbackHandler(); @@ -187,16 +185,15 @@ void CppBridge::processCommandWithCallbackThread() //rethrow so that the caller can handle the error throw e; - } + }*/ } } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::writeToClient(BridgeReply msgPtr, unsigned id) const +void CppBridge::writeToClient(BridgePayload msgPtr) const { auto payload = make_unique(); payload->message_ = move(msgPtr); - payload->id_ = id; writeLambda_(move(payload)); } @@ -234,8 +231,9 @@ void CppBridge::loadWallets(unsigned id) { auto lbd = createPassphrasePrompt(UnlockPromptType::migrate); wltManager_ = make_shared(path_, lbd); - auto response = move(createWalletsPacket()); - writeToClient(move(response), id); + auto response = createWalletsPacket(); + response->mutable_reply()->set_reference_id(id); + writeToClient(move(response)); }; thread thr(thrLbd); @@ -244,9 +242,11 @@ void CppBridge::loadWallets(unsigned id) } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::createWalletsPacket() +BridgePayload CppBridge::createWalletsPacket() { - auto response = make_unique(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + auto walletProto = reply->mutable_wallet()->mutable_multiple_wallets(); //grab wallet map auto accountIdMap = wltManager_->getAccountIdMap(); @@ -265,12 +265,13 @@ BridgeReply CppBridge::createWalletsPacket() auto wltContainer = wltManager_->getWalletContainer( idIt.first, accId); - auto payload = response->add_wallets(); + auto payload = walletProto->add_wallet(); CppToProto::wallet(payload, wltPtr, accId, commentMap); } } - return response; + reply->set_success(true); + return payload; } //////////////////////////////////////////////////////////////////////////////// @@ -309,9 +310,9 @@ void CppBridge::setupDB() throw runtime_error("wallet manager is not initialized"); //lambda to push notifications over to the gui socket - auto pushNotif = [this](BridgeReply msg, unsigned id)->void + auto pushNotif = [this](BridgePayload msg)->void { - this->writeToClient(move(msg), id); + this->writeToClient(move(msg)); }; //setup bdv obj @@ -420,39 +421,40 @@ void CppBridge::createBackupStringForWallet( //wind down passphrase prompt passLbd({BridgePassphrasePrompt::concludeKey}); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_reference_id(msgId); + if (backupData.rootClear_.empty()) { //return on error - auto result = make_unique(); - result->set_isvalid(false); - writeToClient(move(result), msgId); + reply->set_success(false); + writeToClient(move(payload)); return; } - auto backupStringProto = make_unique(); - + auto backupStringProto = reply->mutable_wallet()->mutable_backup_string(); for (auto& line : backupData.rootClear_) - backupStringProto->add_rootclear(line); + backupStringProto->add_root_clear(line); for (auto& line : backupData.rootEncr_) - backupStringProto->add_rootencr(line); + backupStringProto->add_root_encr(line); if (!backupData.chaincodeClear_.empty()) { for (auto& line : backupData.chaincodeClear_) - backupStringProto->add_chainclear(line); + backupStringProto->add_chain_clear(line); for (auto& line : backupData.chaincodeEncr_) - backupStringProto->add_chainencr(line); + backupStringProto->add_chain_encr(line); } //secure print passphrase - backupStringProto->set_sppass( + backupStringProto->set_sp_pass( backupData.spPass_.toCharPtr(), backupData.spPass_.getSize()); - - //return to client - backupStringProto->set_isvalid(true); - writeToClient(move(backupStringProto), msgId); + + reply->set_success(true); + writeToClient(move(payload)); }; thread thr(backupStringLbd); @@ -483,9 +485,9 @@ void CppBridge::restoreWallet( auto restoreLbd = [this, handler](RestoreWalletPayload msg) { auto createCallbackMessage = [handler]( - int promptType, - const vector chkResults, - SecureBinaryData& extra)->unique_ptr + int promptType, + const vector chkResults, + SecureBinaryData& extra)->unique_ptr { RestorePrompt opaqueMsg; opaqueMsg.set_prompttype((RestorePromptType)promptType); @@ -496,26 +498,28 @@ void CppBridge::restoreWallet( opaqueMsg.set_extra(extra.toCharPtr(), extra.getSize()); //wrap in opaque payload - auto callbackMsg = make_unique(); - callbackMsg->set_payloadtype(OpaquePayloadType::commandWithCallback); - callbackMsg->set_uniqueid( - handler->id().getPtr(), handler->id().getSize()); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id( + handler->id().getCharPtr(), handler->id().getSize()); + + /*callbackMsg->set_payloadtype(OpaquePayloadType::commandWithCallback); string serializedOpaqueData; opaqueMsg.SerializeToString(&serializedOpaqueData); - callbackMsg->set_payload(serializedOpaqueData); + callbackMsg->set_payload(serializedOpaqueData);*/ - return callbackMsg; + return payload; }; auto callback = [this, handler, createCallbackMessage]( - Armory::Backups::RestorePromptType promptType, - const vector chkResults, + Armory::Backups::RestorePromptType promptType, + const vector chkResults, SecureBinaryData& extra)->bool { //convert prompt args to protobuf - auto callbackMsg = - createCallbackMessage(promptType, chkResults, extra); + auto callbackMsg = createCallbackMessage( + promptType, chkResults, extra); //setup reply lambda auto prom = make_shared>(); @@ -526,10 +530,9 @@ void CppBridge::restoreWallet( }; //register reply lambda will callbacks handler - auto callbackId = handler->addCallback(replyLbd); - callbackMsg->set_intid(callbackId); - - writeToClient(move(callbackMsg), BRIDGE_CALLBACK_PROMPTUSER); + //auto callbackId = handler->addCallback(replyLbd); + //callbackMsg->mutable_callback()->set_callback_id(callbackId); + writeToClient(move(callbackMsg)); //wait on reply auto&& data = fut.get(); @@ -580,7 +583,9 @@ void CppBridge::restoreWallet( SecureBinaryData dummy; auto successMsg = createCallbackMessage( RestorePromptType::Success, {}, dummy); - writeToClient(move(successMsg), BRIDGE_CALLBACK_PROMPTUSER); + successMsg->mutable_callback()->set_callback_id( + BRIDGE_CALLBACK_PROMPTUSER); + writeToClient(move(successMsg)); } catch (const Armory::Backups::RestoreUserException& e) { @@ -600,20 +605,25 @@ void CppBridge::restoreWallet( Report error to client. This will catch throws in the callbacks reply handler too. */ - auto errorMsg = make_unique(); + /*auto errorMsg = make_unique(); errorMsg->set_payloadtype(OpaquePayloadType::commandWithCallback); errorMsg->set_uniqueid( handler->id().getPtr(), handler->id().getSize()); errorMsg->set_intid(UINT32_MAX); //error flag - - ReplyStrings errorVerbose; + + BridgeProto::Strings errorVerbose; errorVerbose.add_reply(e.what()); string serializedOpaqueData; errorVerbose.SerializeToString(&serializedOpaqueData); - errorMsg->set_payload(serializedOpaqueData); - - writeToClient(move(errorMsg), BRIDGE_CALLBACK_PROMPTUSER); + errorMsg->set_payload(serializedOpaqueData);*/ + + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id( + handler->id().toCharPtr(), handler->id().getSize()); + callback->set_error(e.what()); + writeToClient(move(payload)); } handler->flagForCleanup(); @@ -679,14 +689,19 @@ void CppBridge::getHistoryPageForDelegate( ReturnMessage> result)->void { auto&& leVec = result.get(); - auto msgProto = make_unique(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + reply->set_reference_id(msgId); + + auto ledgers = reply->mutable_service()->mutable_ledger_history(); for (auto& le : leVec) { - auto leProto = msgProto->add_le(); + auto leProto = ledgers->add_ledger(); CppToProto::ledger(leProto, le); } - this->writeToClient(move(msgProto), msgId); + this->writeToClient(move(payload)); }; iter->second.getHistoryPage(pageId, lbd); @@ -700,21 +715,26 @@ void CppBridge::getHistoryForWalletSelection( ReturnMessage> result)->void { auto&& leVec = result.get(); - auto msgProto = make_unique(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + reply->set_reference_id(msgId); + + auto ledgers = reply->mutable_service()->mutable_ledger_history(); for (auto& le : leVec) { - auto leProto = msgProto->add_le(); + auto leProto = ledgers->add_ledger(); CppToProto::ledger(leProto, le); } - this->writeToClient(move(msgProto), msgId); + this->writeToClient(move(payload)); }; bdvPtr_->getHistoryForWalletSelection(wltIds, order, lbd); } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getNodeStatus() +BridgePayload CppBridge::getNodeStatus() { //grab node status auto promPtr = make_shared< @@ -735,80 +755,97 @@ BridgeReply CppBridge::getNodeStatus() }; bdvPtr_->getNodeStatus(lbd); - auto msg = make_unique(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); try { auto nodeStatus = fut.get(); - + //create protobuf message - CppToProto::nodeStatus(msg.get(), *nodeStatus); + CppToProto::nodeStatus( + reply->mutable_service()->mutable_node_status(), *nodeStatus); + reply->set_success(true); } - catch(exception&) + catch (const exception&) { - msg->set_isvalid(false); + reply->set_success(false); } - return msg; + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getBalanceAndCount(const string& id) +BridgePayload CppBridge::getBalanceAndCount(const string& id) { auto wai = WalletAccountIdentifier::deserialize(id); auto wltContainer = wltManager_->getWalletContainer( wai.walletId, wai.accountId); - auto msg = make_unique(); - msg->set_full(wltContainer->getFullBalance()); - msg->set_spendable(wltContainer->getSpendableBalance()); - msg->set_unconfirmed(wltContainer->getUnconfirmedBalance()); - msg->set_count(wltContainer->getTxIOCount()); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + + auto balance = reply->mutable_wallet()->mutable_balance_and_count(); + balance->set_full(wltContainer->getFullBalance()); + balance->set_spendable(wltContainer->getSpendableBalance()); + balance->set_unconfirmed(wltContainer->getUnconfirmedBalance()); + balance->set_count(wltContainer->getTxIOCount()); - return msg; + reply->set_success(true); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getAddrCombinedList(const string& id) +BridgePayload CppBridge::getAddrCombinedList(const string& id) { auto wai = WalletAccountIdentifier::deserialize(id); auto wltContainer = wltManager_->getWalletContainer( wai.walletId, wai.accountId); auto addrMap = wltContainer->getAddrBalanceMap(); - auto msg = make_unique(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + + auto aabData = reply->mutable_wallet()->mutable_address_and_balance_data(); for (auto& addrPair : addrMap) { - auto data = msg->add_data(); - data->set_full(addrPair.second[0]); - data->set_spendable(addrPair.second[1]); - data->set_unconfirmed(addrPair.second[2]); - data->set_count(addrPair.second[3]); - - msg->add_ids( - addrPair.first.toCharPtr(), addrPair.first.getSize()); + auto addr = aabData->add_balance(); + addr->set_id(addrPair.first.toCharPtr(), addrPair.first.getSize()); + + auto balance = addr->mutable_balance(); + balance->set_full(addrPair.second[0]); + balance->set_spendable(addrPair.second[1]); + balance->set_unconfirmed(addrPair.second[2]); + balance->set_count(addrPair.second[3]); } - auto&& updatedMap = wltContainer->getUpdatedAddressMap(); + auto updatedMap = wltContainer->getUpdatedAddressMap(); auto accPtr = wltContainer->getAddressAccount(); for (auto& addrPair : updatedMap) { - auto newAsset = msg->add_updatedassets(); + auto newAsset = aabData->add_updated_asset(); CppToProto::addr(newAsset, addrPair.second, accPtr); } - return msg; + reply->set_success(true); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getHighestUsedIndex(const string& id) +BridgePayload CppBridge::getHighestUsedIndex(const string& id) { auto wai = WalletAccountIdentifier::deserialize(id); auto wltContainer = wltManager_->getWalletContainer( wai.walletId, wai.accountId); - auto msg = make_unique(); - msg->add_ints(wltContainer->getHighestUsedIndex()); - return msg; + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto index = reply->mutable_wallet()->mutable_highest_used_index(); + index->set_index(wltContainer->getHighestUsedIndex()); + + return payload; } //////////////////////////////////////////////////////////////////////////////// @@ -849,29 +886,42 @@ void CppBridge::extendAddressPool(const string& wltId, reportedTicks = eventCount; float progressFloat = float(tickCount) / float(tickTotal); - auto msg = make_unique(); - msg->set_progress(progressFloat); - msg->set_progressnumeric(tickCount); - msg->add_ids(callbackId); + auto payloadProgess = make_unique(); + auto callbackProgress = payloadProgess->mutable_callback(); + callbackProgress->set_callback_id(BRIDGE_CALLBACK_PROGRESS); - this->writeToClient(move(msg), BRIDGE_CALLBACK_PROGRESS); + auto progressProto = callbackProgress->mutable_progress(); + progressProto->set_progress(progressFloat); + progressProto->set_progress_numeric(tickCount); + progressProto->add_id(callbackId); + + this->writeToClient(move(payloadProgess)); }; //extend chain accPtr->extendPublicChain(wltPtr->getIface(), count, updateProgress); //shutdown progress dialog - auto msgProgress = make_unique(); - msgProgress->set_progress(0); - msgProgress->set_progressnumeric(0); - msgProgress->set_phase(BDMPhase_Completed); - msgProgress->add_ids(callbackId); - this->writeToClient(move(msgProgress), BRIDGE_CALLBACK_PROGRESS); + auto payloadProgess = make_unique(); + auto callbackProgress = payloadProgess->mutable_callback(); + callbackProgress->set_callback_id(BRIDGE_CALLBACK_PROGRESS); + + auto progressProto = callbackProgress->mutable_progress(); + progressProto->set_progress(0); + progressProto->set_progress_numeric(0); + progressProto->set_phase(BDMPhase_Completed); + progressProto->add_id(callbackId); + this->writeToClient(move(payloadProgess)); //complete process - auto msgComplete = make_unique(); - CppToProto::wallet(msgComplete.get(), wltPtr, accId, {}); - this->writeToClient(move(msgComplete), msgId); + auto payloadComplete = make_unique(); + auto reply = payloadComplete->mutable_reply(); + reply->set_success(true); + reply->set_reference_id(msgId); + + auto walletProto = reply->mutable_wallet()->mutable_wallet_data(); + CppToProto::wallet(walletProto, wltPtr, accId, {}); + this->writeToClient(move(payloadComplete)); }; thread thr(extendChain); @@ -880,24 +930,18 @@ void CppBridge::extendAddressPool(const string& wltId, } //////////////////////////////////////////////////////////////////////////////// -string CppBridge::createWallet(const ClientCommand& msg) +string CppBridge::createWallet(const Utils::CreateWallet& msg) { if (wltManager_ == nullptr) throw runtime_error("wallet manager is not initialized"); - - if (msg.byteargs_size() != 1) - throw runtime_error("invalid create wallet payload"); - - BridgeCreateWalletStruct createWalletProto; - if (!createWalletProto.ParseFromString(msg.byteargs(0))) - throw runtime_error("failed to read create wallet protobuf message"); + const auto& createWalletProto = msg.wallet_struct(); //extra entropy SecureBinaryData extraEntropy; - if (createWalletProto.has_extraentropy()) + if (createWalletProto.has_extra_entropy()) { extraEntropy = SecureBinaryData::fromString( - createWalletProto.extraentropy()); + createWalletProto.extra_entropy()); } //passphrase @@ -910,10 +954,10 @@ string CppBridge::createWallet(const ClientCommand& msg) //control passphrase SecureBinaryData controlPass; - if (createWalletProto.has_controlpassphrase()) + if (createWalletProto.has_control_passphrase()) { controlPass = SecureBinaryData::fromString( - createWalletProto.controlpassphrase()); + createWalletProto.control_passphrase()); } //lookup @@ -935,22 +979,27 @@ string CppBridge::createWallet(const ClientCommand& msg) } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getWalletPacket(const string& id) const +BridgePayload CppBridge::getWalletPacket(const string& id) const { auto wai = WalletAccountIdentifier::deserialize(id); auto wltContainer = wltManager_->getWalletContainer( wai.walletId, wai.accountId); - auto response = make_unique(); + auto wltPtr = wltContainer->getWalletPtr(); auto commentMap = wltPtr->getCommentMap(); - auto payload = response->add_wallets(); - CppToProto::wallet(payload, wltPtr, wai.accountId, commentMap); - return move(response); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto walletProto = reply->mutable_wallet()->mutable_wallet_data(); + CppToProto::wallet(walletProto, wltPtr, wai.accountId, commentMap); + + return move(payload); } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getNewAddress(const string& id, unsigned type) +BridgePayload CppBridge::getNewAddress(const string& id, unsigned type) { auto wai = WalletAccountIdentifier::deserialize(id); auto wltContainer = wltManager_->getWalletContainer( @@ -960,13 +1009,17 @@ BridgeReply CppBridge::getNewAddress(const string& id, unsigned type) auto addrPtr = accPtr->getNewAddress( wltPtr->getIface(), (AddressEntryType)type); - auto msg = make_unique(); - CppToProto::addr(msg.get(), addrPtr, accPtr); - return msg; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto addrProto = reply->mutable_wallet()->mutable_asset(); + CppToProto::addr(addrProto, addrPtr, accPtr); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getChangeAddress(const string& id, unsigned type) +BridgePayload CppBridge::getChangeAddress(const string& id, unsigned type) { auto wai = WalletAccountIdentifier::deserialize(id); auto wltContainer = wltManager_->getWalletContainer( @@ -976,13 +1029,17 @@ BridgeReply CppBridge::getChangeAddress(const string& id, unsigned type) auto addrPtr = accPtr->getNewChangeAddress( wltPtr->getIface(), (AddressEntryType)type); - auto msg = make_unique(); - CppToProto::addr(msg.get(), addrPtr, accPtr); - return msg; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto addrProto = reply->mutable_wallet()->mutable_asset(); + CppToProto::addr(addrProto, addrPtr, accPtr); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::peekChangeAddress(const string& id, unsigned type) +BridgePayload CppBridge::peekChangeAddress(const string& id, unsigned type) { auto wai = WalletAccountIdentifier::deserialize(id); auto wltContainer = wltManager_->getWalletContainer( @@ -992,17 +1049,21 @@ BridgeReply CppBridge::peekChangeAddress(const string& id, unsigned type) auto addrPtr = accPtr->getNewChangeAddress( wltPtr->getIface(), (AddressEntryType)type); - auto msg = make_unique(); - CppToProto::addr(msg.get(), addrPtr, accPtr); - return msg; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto addrProto = reply->mutable_wallet()->mutable_asset(); + CppToProto::addr(addrProto, addrPtr, accPtr); + return payload; } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::getTxByHash(const BinaryData& hash, unsigned msgid) +void CppBridge::getTxByHash(const BinaryData& hash, unsigned msgId) { - auto lbd = [this, msgid](ReturnMessage result)->void + auto lbd = [this, msgId](ReturnMessage result)->void { - shared_ptr tx; + shared_ptr tx; bool valid = false; try { @@ -1012,124 +1073,168 @@ void CppBridge::getTxByHash(const BinaryData& hash, unsigned msgid) } catch(exception&) {} - - auto msg = make_unique(); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_reference_id(msgId); if (valid) { - auto&& txRaw = tx->serialize(); - msg->set_raw(txRaw.getCharPtr(), txRaw.getSize()); - msg->set_isrbf(tx->isRBF()); - msg->set_ischainedzc(tx->isChained()); - msg->set_height(tx->getTxHeight()); - msg->set_txindex(tx->getTxIndex()); - msg->set_isvalid(true); + auto txRaw = tx->serialize(); + + auto txProto = reply->mutable_service()->mutable_tx(); + txProto->set_raw(txRaw.getCharPtr(), txRaw.getSize()); + txProto->set_rbf(tx->isRBF()); + txProto->set_chained_zc(tx->isChained()); + txProto->set_height(tx->getTxHeight()); + txProto->set_tx_index(tx->getTxIndex()); + reply->set_success(true); } else { - msg->set_isvalid(false); + reply->set_success(false); } - - this->writeToClient(move(msg), msgid); + + this->writeToClient(move(payload)); }; bdvPtr_->getTxByHash(hash, lbd); } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getTxInScriptType( +BridgePayload CppBridge::getTxInScriptType( const BinaryData& script, const BinaryData& hash) const { - auto msg = make_unique(); - msg->add_ints(BtcUtils::getTxInScriptTypeInt(script, hash)); - return msg; + auto typeInt = BtcUtils::getTxInScriptTypeInt(script, hash); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto scriptType = reply->mutable_script_utils()->mutable_txin_script_type(); + scriptType->set_script_type(typeInt); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getTxOutScriptType( - const BinaryData& script) const +BridgePayload CppBridge::getTxOutScriptType(const BinaryData& script) const { - auto msg = make_unique(); - msg->add_ints(BtcUtils::getTxOutScriptTypeInt(script)); - return msg; + auto typeInt = BtcUtils::getTxOutScriptTypeInt(script); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto scriptType = reply->mutable_script_utils()->mutable_txout_script_type(); + scriptType->set_script_type(typeInt); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getScrAddrForScript( +BridgePayload CppBridge::getScrAddrForScript( const BinaryData& script) const { - auto msg = make_unique(); - auto&& resultBd = BtcUtils::getScrAddrForScript(script); - msg->add_reply(resultBd.toCharPtr(), resultBd.getSize()); - return msg; + auto resultBd = BtcUtils::getScrAddrForScript(script); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + auto scrAddr = reply->mutable_script_utils()->mutable_scraddr(); + scrAddr->set_scraddr(resultBd.toCharPtr(), resultBd.getSize()); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getScrAddrForAddrStr(const string& addrStr) const +BridgePayload CppBridge::getScrAddrForAddrStr(const string& addrStr) const { + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + try { - auto msg = make_unique(); auto resultBd = BtcUtils::getScrAddrForAddrStr(addrStr); - msg->add_reply(resultBd.toCharPtr(), resultBd.getSize()); - return msg; + reply->set_success(true); + auto scrAddr = reply->mutable_script_utils()->mutable_scraddr(); + scrAddr->set_scraddr(resultBd.toCharPtr(), resultBd.getSize()); } catch (const exception& e) { - auto msg = make_unique(); - msg->set_iserror(true); - msg->set_error(e.what()); - return msg; + reply->set_success(false); + reply->set_error(e.what()); } + + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getLastPushDataInScript( - const BinaryData& script) const +BridgePayload CppBridge::getLastPushDataInScript(const BinaryData& script) const { - auto msg = make_unique(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); auto result = BtcUtils::getLastPushDataInScript(script); - if (!result.empty()) - msg->add_reply(result.getCharPtr(), result.getSize()); - return msg; + if (result.empty()) + { + reply->set_success(false); + } + else + { + reply->set_success(true); + auto pushData = reply->mutable_script_utils()->mutable_push_data(); + pushData->set_data(result.getCharPtr(), result.getSize()); + } + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getHash160(const BinaryDataRef& dataRef) const +BridgePayload CppBridge::getHash160(const BinaryDataRef& dataRef) const { - auto&& hash = BtcUtils::getHash160(dataRef); - auto msg = make_unique(); - msg->add_reply(hash.getCharPtr(), hash.getSize()); - return msg; + auto hash = BtcUtils::getHash160(dataRef); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto hashMsg = reply->mutable_utils()->mutable_hash(); + hashMsg->set_data(hash.getCharPtr(), hash.getSize()); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getTxOutScriptForScrAddr( - const BinaryData& script) const +BridgePayload CppBridge::getTxOutScriptForScrAddr(const BinaryData& script) const { - auto msg = make_unique(); - auto&& resultBd = BtcUtils::getTxOutScriptForScrAddr(script); - msg->add_reply(resultBd.toCharPtr(), resultBd.getSize()); - return msg; + auto resultBd = BtcUtils::getTxOutScriptForScrAddr(script); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto scriptReply = reply->mutable_script_utils()->mutable_script_data(); + scriptReply->set_data(resultBd.toCharPtr(), resultBd.getSize()); + return payload; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::getAddrStrForScrAddr( - const BinaryData& script) const +BridgePayload CppBridge::getAddrStrForScrAddr(const BinaryData& script) const { + auto payload = make_unique(); try { - auto msg = make_unique(); - auto&& resultStr = BtcUtils::getAddressStrFromScrAddr(script); - msg->add_reply(resultStr); - return msg; + auto addrStr = BtcUtils::getAddressStrFromScrAddr(script); + + auto reply = payload->mutable_reply(); + reply->set_success(true); + auto scriptUtilsReply = reply->mutable_script_utils(); + auto addr = scriptUtilsReply->mutable_address_string(); + addr->set_address(addrStr); } catch (const exception& e) { - auto msg = make_unique(); - msg->set_iserror(true); - msg->set_error(e.what()); - return msg; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(false); + reply->set_error(e.what()); } + + return payload; } //////////////////////////////////////////////////////////////////////////////// @@ -1196,7 +1301,7 @@ string CppBridge::getNameForAddrType(int addrTypeInt) const } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::setAddressTypeFor( +BridgePayload CppBridge::setAddressTypeFor( const string& walletId, const string& assetIdStr, uint32_t addrType) const { auto wai = WalletAccountIdentifier::deserialize(walletId); @@ -1216,21 +1321,31 @@ BridgeReply CppBridge::setAddressTypeFor( auto addrPtr = accPtr->getAddressEntryForID(assetId); //return address proto payload - auto result = make_unique(); - CppToProto::addr(result.get(), addrPtr, accPtr); - return result; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto addrProto = reply->mutable_wallet()->mutable_asset(); + CppToProto::addr(addrProto, addrPtr, accPtr); + return payload; } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::getHeaderByHeight(unsigned height, unsigned msgid) +void CppBridge::getHeaderByHeight(unsigned height, unsigned msgId) { - auto lbd = [this, msgid](ReturnMessage result)->void + auto lbd = [this, msgId](ReturnMessage result)->void { auto headerRaw = result.get(); - auto msg = make_unique(); - msg->add_reply(headerRaw.getCharPtr(), headerRaw.getSize()); - - this->writeToClient(move(msg), msgid); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + reply->set_reference_id(msgId); + + auto headerData = reply->mutable_service()->mutable_header_data(); + headerData->set_data(headerRaw.getCharPtr(), headerRaw.getSize()); + + this->writeToClient(move(payload)); }; bdvPtr_->getHeaderByHeight(height, lbd); @@ -1238,7 +1353,7 @@ void CppBridge::getHeaderByHeight(unsigned height, unsigned msgid) //////////////////////////////////////////////////////////////////////////////// void CppBridge::setupNewCoinSelectionInstance(const string& id, - unsigned height, unsigned msgid) + unsigned height, unsigned msgId) { auto wai = WalletAccountIdentifier::deserialize(id); auto wltContainer = wltManager_->getWalletContainer( @@ -1249,8 +1364,8 @@ void CppBridge::setupNewCoinSelectionInstance(const string& id, make_pair(csId, shared_ptr())).first; auto csPtr = &insertIter->second; - auto lbd = [this, wltContainer, csPtr, csId, height, msgid]( - ReturnMessage> result)->void + auto lbd = [this, wltContainer, csPtr, csId, height, msgId]( + ReturnMessage> result)->void { auto fetchLbd = [wltContainer](uint64_t val)->vector { @@ -1267,13 +1382,19 @@ void CppBridge::setupNewCoinSelectionInstance(const string& id, auto&& aeVec = result.get(); *csPtr = make_shared( - wltContainer->getWalletPtr(), fetchLbd, aeVec, + wltContainer->getWalletPtr(), fetchLbd, aeVec, wltContainer->getSpendableBalance(), height); - auto msg = make_unique(); - msg->add_reply(csId); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + reply->set_reference_id(msgId); + + auto walletReply = reply->mutable_wallet(); + auto csReply = walletReply->mutable_coin_selection_id(); + csReply->set_id(csId); - this->writeToClient(move(msg), msgid); + this->writeToClient(move(payload)); }; wltContainer->createAddressBook(lbd); @@ -1286,217 +1407,14 @@ void CppBridge::destroyCoinSelectionInstance(const string& csId) } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::resetCoinSelection(const std::string& csId) -{ - auto iter = csMap_.find(csId); - if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - iter->second->resetRecipients(); -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::setCoinSelectionRecipient( - const string& csId, const string& addrStr, uint64_t value, unsigned recId) -{ - auto iter = csMap_.find(csId); - if (iter == csMap_.end()) - { - LOGERR << "[setCoinSelectionRecipient] invalid csId"; - return false; - } - - try - { - iter->second->updateRecipient(recId, addrStr, value); - } - catch (const exception&) - { - return false; - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::cs_SelectUTXOs( - const string& csId, uint64_t fee, float feeByte, unsigned flags) -{ - auto iter = csMap_.find(csId); - if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - return iter->second->selectUTXOs(fee, feeByte, flags); -} - -//////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::cs_getUtxoSelection(const string& csId) -{ - auto iter = csMap_.find(csId); - if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - auto&& utxoVec = iter->second->getUtxoSelection(); - - auto msg = make_unique(); - for (auto& utxo : utxoVec) - { - auto utxoProto = msg->add_data(); - CppToProto::utxo(utxoProto, utxo); - } - - return msg; -} - -//////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::cs_getFlatFee(const string& csId) -{ - auto iter = csMap_.find(csId); - if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - auto flatFee = iter->second->getFlatFee(); - - auto msg = make_unique(); - msg->add_longs(flatFee); - return msg; -} - -//////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::cs_getFeeByte(const string& csId) -{ - auto iter = csMap_.find(csId); - if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - auto flatFee = iter->second->getFeeByte(); - - auto msg = make_unique(); - msg->add_floats(flatFee); - return msg; -} - -//////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::cs_getSizeEstimate(const string& csId) +shared_ptr CppBridge::coinSelectionInstance( + const std::string& csId) const { auto iter = csMap_.find(csId); if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - auto sizeEstimate = iter->second->getSizeEstimate(); - - auto msg = make_unique(); - msg->add_longs(sizeEstimate); - return msg; -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::cs_ProcessCustomUtxoList(const ClientCommand& msg) -{ - if (msg.stringargs_size() != 1 || - msg.longargs_size() != 1 || - msg.floatargs_size() != 1 || - msg.intargs_size() != 1) - { - throw runtime_error("invalid command cs_ProcessCustomUtxoList"); - } - - auto iter = csMap_.find(msg.stringargs(0)); - if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - auto flatFee = msg.longargs(0); - auto feeByte = msg.floatargs(0); - auto flags = msg.intargs(0); - - vector utxos; - for (int i=0; isecond->processCustomUtxoList(utxos, flatFee, feeByte, flags); - return true; - } - catch (CoinSelectionException&) - {} - - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::cs_getFeeForMaxVal(const ClientCommand& msg) -{ - if (msg.stringargs_size() != 1 || - msg.floatargs_size() != 1) - { - throw runtime_error("invalid command cs_getFeeForMaxVal"); - } - - auto iter = csMap_.find(msg.stringargs(0)); - if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - auto feeByte = msg.floatargs(0); - auto flatFee = iter->second->getFeeForMaxVal(feeByte); - - auto response = make_unique(); - response->add_longs(flatFee); - return response; - -} - -//////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::cs_getFeeForMaxValUtxoVector(const ClientCommand& msg) -{ - if (msg.stringargs_size() != 1 || - msg.floatargs_size() != 1) - { - throw runtime_error("invalid command cs_getFeeForMaxValUtxoVector"); - } - - auto iter = csMap_.find(msg.stringargs(0)); - if (iter == csMap_.end()) - throw runtime_error("invalid cs id"); - - auto feeByte = msg.floatargs(0); - - vector serUtxos; - for (int i=0; isecond->getFeeForMaxValUtxoVector(serUtxos, feeByte); - - auto response = make_unique(); - response->add_longs(flatFee); - return response; + return nullptr; + return iter->second; } //////////////////////////////////////////////////////////////////////////////// @@ -1507,61 +1425,51 @@ void CppBridge::createAddressBook(const string& id, unsigned msgId) wai.walletId, wai.accountId); auto lbd = [this, msgId]( - ReturnMessage> result)->void + ReturnMessage> result)->void { - auto msg = make_unique(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + reply->set_reference_id(msgId); + auto addressBookProto = reply->mutable_wallet()->mutable_address_book(); auto&& aeVec = result.get(); for (auto& ae : aeVec) { - auto bridgeAe = msg->add_data(); + auto bridgeAe = addressBookProto->add_address(); auto& scrAddr = ae.getScrAddr(); bridgeAe->set_scraddr(scrAddr.getCharPtr(), scrAddr.getSize()); auto& hashList = ae.getTxHashList(); for (auto& hash : hashList) - bridgeAe->add_txhashes(hash.getCharPtr(), hash.getSize()); + bridgeAe->add_tx_hash(hash.getCharPtr(), hash.getSize()); } - this->writeToClient(move(msg), msgId); + this->writeToClient(move(payload)); }; wltContainer->createAddressBook(lbd); } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::setComment(const ClientCommand& msg) +void CppBridge::setComment(const string& walletId, + const BridgeProto::Wallet::SetComment& msg) { - if (msg.stringargs_size() != 2 || msg.byteargs_size() != 1) - throw runtime_error("invalid command: setComment"); - - const auto& walletId = msg.stringargs(0); - auto wai = WalletAccountIdentifier::deserialize(walletId); auto wltContainer = wltManager_->getWalletContainer( wai.walletId, wai.accountId); - - const auto& hashKey = msg.byteargs(0); - const auto& comment = msg.stringargs(1); - wltContainer->setComment(hashKey, comment); + wltContainer->setComment(msg.hash_key(), msg.comment()); } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::setWalletLabels(const ClientCommand& msg) +void CppBridge::setWalletLabels(const string& walletId, + const BridgeProto::Wallet::SetLabels& msg) { - if (msg.stringargs_size() < 1 || msg.stringargs_size() > 3) - throw runtime_error("invalid command: setWalletLabels"); - - const auto& walletId = msg.stringargs(0); - auto wai = WalletAccountIdentifier::deserialize(walletId); auto wltContainer = wltManager_->getWalletContainer( wai.walletId, wai.accountId); - - const auto& title = msg.stringargs(1); - const auto& desc = msg.stringargs(2); - wltContainer->setLabels(title, desc); + wltContainer->setLabels(msg.title(), msg.description()); } //////////////////////////////////////////////////////////////////////////////// @@ -1574,15 +1482,19 @@ void CppBridge::getUtxosForValue(const string& id, auto lbd = [this, msgId](ReturnMessage> result)->void { - auto&& utxoVec = result.get(); - auto msg = make_unique(); - for(auto& utxo : utxoVec) + auto utxoVec = move(result.get()); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto utxoList = reply->mutable_wallet()->mutable_utxo_list(); + for (auto& utxo : utxoVec) { - auto utxoProto = msg->add_data(); + auto utxoProto = utxoList->add_utxo(); CppToProto::utxo(utxoProto, utxo); } - this->writeToClient(move(msg), msgId); + this->writeToClient(move(payload)); }; wltContainer->getSpendableTxOutListForValue(value, lbd); @@ -1597,15 +1509,19 @@ void CppBridge::getSpendableZCList(const string& id, unsigned msgId) auto lbd = [this, msgId](ReturnMessage> result)->void { - auto&& utxoVec = result.get(); - auto msg = make_unique(); + auto utxoVec = move(result.get()); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto utxoList = reply->mutable_wallet()->mutable_utxo_list(); for(auto& utxo : utxoVec) { - auto utxoProto = msg->add_data(); + auto utxoProto = utxoList->add_utxo(); CppToProto::utxo(utxoProto, utxo); } - this->writeToClient(move(msg), msgId); + this->writeToClient(move(payload)); }; wltContainer->getSpendableZcTxOutList(lbd); @@ -1620,29 +1536,38 @@ void CppBridge::getRBFTxOutList(const string& id, unsigned msgId) auto lbd = [this, msgId](ReturnMessage> result)->void { - auto&& utxoVec = result.get(); - auto msg = make_unique(); + auto utxoVec = move(result.get()); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + reply->set_reference_id(msgId); + + auto utxoList = reply->mutable_wallet()->mutable_utxo_list(); for(auto& utxo : utxoVec) { - auto utxoProto = msg->add_data(); + auto utxoProto = utxoList->add_utxo(); CppToProto::utxo(utxoProto, utxo); } - this->writeToClient(move(msg), msgId); + this->writeToClient(move(payload)); }; wltContainer->getRBFTxOutList(lbd); } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::initNewSigner() +BridgePayload CppBridge::initNewSigner() { auto id = fortuna_.generateRandom(6).toHexStr(); signerMap_.emplace(make_pair(id, make_shared())); - auto msg = make_unique(); - msg->add_reply(id); - return msg; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto signerId = reply->mutable_signer()->mutable_signer_id(); + signerId->set_id(id); + return payload; } //////////////////////////////////////////////////////////////////////////////// @@ -1652,139 +1577,13 @@ void CppBridge::destroySigner(const string& id) } //////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_SetVersion(const string& id, unsigned version) -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - return false; - - iter->second->signer_.setVersion(version); - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_SetLockTime(const string& id, unsigned locktime) -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - return false; - - iter->second->signer_.setLockTime(locktime); - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_addSpenderByOutpoint( - const string& id, const BinaryDataRef& hash, - unsigned txOutId, unsigned sequence) +shared_ptr CppBridge::signerInstance( + const string& id) const { auto iter = signerMap_.find(id); if (iter == signerMap_.end()) - return false; - - iter->second->signer_.addSpender_ByOutpoint(hash, txOutId, sequence); - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_populateUtxo( - const string& id, const BinaryDataRef& hash, - unsigned txOutId, uint64_t value, const BinaryDataRef& script) -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - return false; - - try - { - UTXO utxo(value, UINT32_MAX, UINT32_MAX, txOutId, hash, script); - iter->second->signer_.populateUtxo(utxo); - } - catch(exception&) - { - return false; - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_addSupportingTx( - const string& id, const BinaryDataRef& rawTxData) -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - return false; - - try - { - iter->second->signer_.addSupportingTx(rawTxData); - } - catch(exception&) - { - return false; - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_addRecipient( - const std::string& id, const BinaryDataRef& script, uint64_t value) -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - return false; - - try - { - auto&& hash = BtcUtils::getTxOutScrAddr(script); - iter->second->signer_.addRecipient( - CoinSelectionInstance::createRecipient(hash, value)); - } - catch (ScriptRecipientException&) - { - return false; - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::signer_toTxSigCollect( - const string& id, int ustxType) const -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - - auto msg = make_unique(); - auto txSigCollect = iter->second->signer_.toString( - static_cast(ustxType)); - msg->add_reply(txSigCollect); - return msg; -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_fromTxSigCollect( - const string& id, const string& txSigCollect) -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - - try - { - iter->second->signer_ = Armory::Signer::Signer::fromString(txSigCollect); - } - catch (const exception& e) - { - LOGWARN << "failed to parse TxSigCollect with error:"; - LOGWARN << e.what(); - return false; - } - - return true; + return nullptr; + return iter->second; } //////////////////////////////////////////////////////////////////////////////// @@ -1832,9 +1631,11 @@ void CppBridge::signer_signTx( } //signal Python that we're done - auto msg = make_unique(); - msg->add_ints(success); - this->writeToClient(move(msg), msgId); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(success); + reply->set_reference_id(msgId); + this->writeToClient(move(payload)); //wind down passphrase prompt passLbd({BridgePassphrasePrompt::concludeKey}); @@ -1846,48 +1647,7 @@ void CppBridge::signer_signTx( } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::signer_getSignedTx(const string& id) const -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - - BinaryDataRef data; - try - { - data = iter->second->signer_.serializeSignedTx(); - } - catch (const exception&) - {} - - auto response = make_unique(); - response->add_reply(data.toCharPtr(), data.getSize()); - return response; -} - -//////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::signer_getUnsignedTx(const string& id) const -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - - BinaryDataRef data; - try - { - data = iter->second->signer_.serializeUnsignedTx(); - } - catch (const exception&) - {} - - auto response = make_unique(); - response->add_reply(data.toCharPtr(), data.getSize()); - return response; -} - - -//////////////////////////////////////////////////////////////////////////////// -int CppBridge::signer_resolve( +bool CppBridge::signer_resolve( const string& sId, const string& wltId) const { //grab signer @@ -1911,11 +1671,11 @@ int CppBridge::signer_resolve( signer.setFeed(feed); signer.resolvePublicData(); - return 1; + return true; } //////////////////////////////////////////////////////////////////////////////// -BridgeReply CppBridge::signer_getSignedStateForInput( +BridgePayload CppBridge::getSignedStateForInput( const string& id, unsigned inputId) { auto iter = signerMap_.find(id); @@ -1929,31 +1689,15 @@ BridgeReply CppBridge::signer_getSignedStateForInput( } const auto signState = iter->second->signState_.get(); - auto result = make_unique(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); auto signStateInput = signState->getSignedStateForInput(inputId); - CppToProto::signatureState(result.get(), signStateInput); - return result; -} + auto inputState = reply->mutable_signer()->mutable_input_signed_state(); + CppToProto::signatureState(inputState, signStateInput); -//////////////////////////////////////////////////////////////////////////////// -SignerStringFormat CppBridge::signer_fromType(const string& id) const -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - - return iter->second->signer_.deserializedFromType(); -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_canLegacySerialize(const string& id) const -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - - return iter->second->signer_.canLegacySerialize(); + reply->set_success(true); + return payload; } //////////////////////////////////////////////////////////////////////////////// @@ -1979,10 +1723,15 @@ void CppBridge::getBlockTimeByHeight(uint32_t height, uint32_t msgId) const " with error: \"" << e.what() << "\""; } - auto result = make_unique(); - result->add_ints(timestamp); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + reply->set_reference_id(msgId); - this->writeToClient(move(result), msgId); + auto blockTime = reply->mutable_service()->mutable_block_time(); + blockTime->set_timestamp(timestamp); + + this->writeToClient(move(payload)); }; bdvPtr_->getHeaderByHeight(height, callback); @@ -1995,23 +1744,25 @@ void CppBridge::estimateFee(uint32_t blocks, auto callback = [this, msgId]( ReturnMessage feeResult) { - auto result = make_unique(); + auto payload = make_unique(); + auto result = payload->mutable_reply(); + result->set_reference_id(msgId); try { auto feeData = feeResult.get(); - result->set_feebyte(feeData.val_); - result->set_smartfee(feeData.isSmart_); - result->set_error(feeData.error_); + result->set_success(true); + auto feeMsg = result->mutable_service()->mutable_fee_estimate(); + feeMsg->set_feebyte(feeData.val_); + feeMsg->set_smartfee(feeData.isSmart_); } catch (const ClientMessageError& e) { - result->set_feebyte(0); - result->set_smartfee(false); + result->set_success(false); result->set_error(e.what()); } - this->writeToClient(move(result), msgId); + this->writeToClient(move(payload)); }; bdvPtr_->estimateFee(blocks, strat, callback); @@ -2066,21 +1817,18 @@ void BridgeCallback::run(BdmNotification notif) case BDMAction_ZC: { - BridgeLedgers payload; + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); + auto zcProto = callback->mutable_zero_conf(); + for (auto& le : notif.ledgers_) { - auto protoLe = payload.add_le(); + auto protoLe = zcProto->add_ledger(); CppToProto::ledger(protoLe, *le); } - vector payloadVec(payload.ByteSizeLong()); - payload.SerializeToArray(&payloadVec[0], payloadVec.size()); - - auto msg = make_unique(); - msg->set_type(BDMAction_ZC); - msg->add_opaque(&payloadVec[0], payloadVec.size()); - - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + pushNotifLbd_(move(payload)); break; } @@ -2092,13 +1840,15 @@ void BridgeCallback::run(BdmNotification notif) case BDMAction_Refresh: { - auto msg = make_unique(); - msg->set_type(BDMAction_Refresh); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); + auto refreshProto = callback->mutable_refresh(); for (auto& id : notif.ids_) { string idStr(id.toCharPtr(), id.getSize()); - msg->add_ids(idStr); + refreshProto->add_id(idStr); //TODO: dumb way to watch over the pre bdm_ready wallet //registration, fix this crap @@ -2106,7 +1856,7 @@ void BridgeCallback::run(BdmNotification notif) idQueue_.push_back(move(idStr)); } - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + pushNotifLbd_(move(payload)); break; } @@ -2125,18 +1875,13 @@ void BridgeCallback::run(BdmNotification notif) case BDMAction_NodeStatus: { //notify node status - BridgeNodeStatus nodeStatusMsg; - CppToProto::nodeStatus(&nodeStatusMsg, *notif.nodeStatus_); - vector serializedNodeStatus(nodeStatusMsg.ByteSizeLong()); - nodeStatusMsg.SerializeToArray( - &serializedNodeStatus[0], serializedNodeStatus.size()); - - auto msg = make_unique(); - msg->set_type(BDMAction_NodeStatus); - msg->add_opaque( - &serializedNodeStatus[0], serializedNodeStatus.size()); - - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); + auto nodeProto = callback->mutable_node_status(); + CppToProto::nodeStatus(nodeProto, *notif.nodeStatus_); + + pushNotifLbd_(move(payload)); break; } @@ -2162,76 +1907,90 @@ void BridgeCallback::progress( float progress, unsigned secondsRem, unsigned progressNumeric) { - auto msg = make_unique(); - msg->set_phase((uint32_t)phase); - msg->set_progress(progress); - msg->set_etasec(secondsRem); - msg->set_progressnumeric(progressNumeric); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_PROGRESS); + auto progressMsg = callback->mutable_progress(); - for (auto& id : walletIdVec) - msg->add_ids(id); + progressMsg->set_phase((uint32_t)phase); + progressMsg->set_progress(progress); + progressMsg->set_etasec(secondsRem); + progressMsg->set_progress_numeric(progressNumeric); - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_PROGRESS); + for (auto& id : walletIdVec) + progressMsg->add_id(id); + pushNotifLbd_(move(payload)); } //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_SetupDone() { - auto msg = make_unique(); - msg->set_type(CppBridgeState::CppBridge_Ready); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); + callback->mutable_setup_done(); - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + pushNotifLbd_(move(payload)); } //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_SetupRegistrationDone(const set& ids) { - auto msg = make_unique(); - msg->set_type(CppBridgeState::CppBridge_Registered); - for (auto& id : ids) - msg->add_ids(id); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + auto registered = callback->mutable_registered(); + for (auto& id : ids) + registered->add_id(id); + pushNotifLbd_(move(payload)); } //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_RegistrationDone(const set& ids) { - auto msg = make_unique(); - msg->set_type(BDMAction_Refresh); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); + + auto refresh = callback->mutable_refresh(); for (auto& id : ids) - msg->add_ids(id); + refresh->add_id(id); - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + pushNotifLbd_(move(payload)); } //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_NewBlock(unsigned height) { - auto msg = make_unique(); - msg->set_type(BDMAction_NewBlock); - msg->set_height(height); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); + callback->mutable_new_block()->set_height(height); - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + pushNotifLbd_(move(payload)); } //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::notify_Ready(unsigned height) { - auto msg = make_unique(); - msg->set_type(BDMAction_Ready); - msg->set_height(height); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); + callback->mutable_ready()->set_height(height); - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + pushNotifLbd_(move(payload)); } //////////////////////////////////////////////////////////////////////////////// void BridgeCallback::disconnected() { - auto msg = make_unique(); - msg->set_type(DISCONNECTED_CALLBACK_ID); + auto payload = make_unique(); + auto callback = payload->mutable_callback(); + callback->set_callback_id(BRIDGE_CALLBACK_BDM); + callback->mutable_disconnected(); - pushNotifLbd_(move(msg), BRIDGE_CALLBACK_BDM); + pushNotifLbd_(move(payload)); } //////////////////////////////////////////////////////////////////////////////// @@ -2261,23 +2020,24 @@ void MethodCallbacksHandler::flagForCleanup() if (parentCommandQueue_ == nullptr) return; - ClientCommand msg; + BridgeProto::Request msg; - msg.set_method(Methods::methodWithCallback); + /*msg.set_method(Methods::methodWithCallback); msg.set_methodwithcallback(MethodsWithCallback::cleanup); - msg.add_byteargs(id_.toCharPtr(), id_.getSize()); + msg.add_byteargs(id_.toCharPtr(), id_.getSize());*/ parentCommandQueue_->push_back(move(msg)); parentCommandQueue_ = nullptr; } //////////////////////////////////////////////////////////////////////////////// -unsigned MethodCallbacksHandler::addCallback(const function& cbk) +unsigned MethodCallbacksHandler::addCallback( + const function& callback) { /* This method isn't thread safe. */ auto id = counter_++; - callbacks_.emplace(id, cbk); + callbacks_.emplace(id, callback); return id; } \ No newline at end of file diff --git a/cppForSwig/BridgeAPI/CppBridge.h b/cppForSwig/BridgeAPI/CppBridge.h index 614c62e0b..70c4f9808 100755 --- a/cppForSwig/BridgeAPI/CppBridge.h +++ b/cppForSwig/BridgeAPI/CppBridge.h @@ -12,7 +12,7 @@ #include "../ArmoryConfig.h" #include "WalletManager.h" #include "btc/ecc.h" -#include "../protobuf/ClientProto.pb.h" +#include "../protobuf/BridgeProto.pb.h" #include "../AsyncClient.h" namespace Armory @@ -23,7 +23,7 @@ namespace Armory ////////////////////////////////////////////////////////////////////////// typedef std::function, unsigned)> notifLbd; + std::unique_ptr)> notifLbd; //// class BridgeCallback : public RemoteCallback @@ -77,7 +77,7 @@ namespace Armory ////////////////////////////////////////////////////////////////////////// using CommandQueue = std::shared_ptr>; + ::BridgeProto::Request>>; //// class CppBridge; @@ -117,7 +117,7 @@ namespace Armory ////////////////////////////////////////////////////////////////////////// struct ProtobufCommandParser; - using BridgeReply = std::unique_ptr; + using BridgePayload = std::unique_ptr; class CppBridge { @@ -160,34 +160,34 @@ namespace Armory private: //commands with callback - void queueCommandWithCallback(::Codec_ClientProto::ClientCommand); + void queueCommandWithCallback(BridgeProto::Request); void processCommandWithCallbackThread(void); //wallet setup void loadWallets(unsigned id); - BridgeReply createWalletsPacket(void); + BridgePayload createWalletsPacket(void); bool deleteWallet(const std::string&); - BridgeReply getWalletPacket(const std::string&) const; + BridgePayload getWalletPacket(const std::string&) const; //AsyncClient::BlockDataViewer setup void setupDB(void); void registerWallets(void); void registerWallet(const std::string&, bool isNew); - BridgeReply getNodeStatus(void); + BridgePayload getNodeStatus(void); //balance and counts - BridgeReply getBalanceAndCount(const std::string&); - BridgeReply getAddrCombinedList(const std::string&); - BridgeReply getHighestUsedIndex(const std::string&); + BridgePayload getBalanceAndCount(const std::string&); + BridgePayload getAddrCombinedList(const std::string&); + BridgePayload getHighestUsedIndex(const std::string&); //wallet & addresses void extendAddressPool(const std::string&, unsigned, const std::string&, unsigned); - BridgeReply getNewAddress(const std::string&, unsigned); - BridgeReply getChangeAddress(const std::string&, unsigned); - BridgeReply peekChangeAddress(const std::string&, unsigned); - std::string createWallet(const ::Codec_ClientProto::ClientCommand&); + BridgePayload getNewAddress(const std::string&, unsigned); + BridgePayload getChangeAddress(const std::string&, unsigned); + BridgePayload peekChangeAddress(const std::string&, unsigned); + std::string createWallet(const BridgeProto::Utils::CreateWallet&); void createBackupStringForWallet(const std::string&, unsigned); void restoreWallet(const BinaryDataRef&, std::shared_ptr); @@ -200,8 +200,10 @@ namespace Armory void getHistoryForWalletSelection(const std::string&, std::vector, unsigned); void createAddressBook(const std::string&, unsigned); - void setComment(const Codec_ClientProto::ClientCommand&); - void setWalletLabels(const Codec_ClientProto::ClientCommand&); + void setComment(const std::string&, + const BridgeProto::Wallet::SetComment&); + void setWalletLabels(const std::string&, + const BridgeProto::Wallet::SetLabels&); //txs & headers void getTxByHash(const BinaryData&, unsigned); @@ -216,65 +218,40 @@ namespace Armory void setupNewCoinSelectionInstance( const std::string&, unsigned, unsigned); void destroyCoinSelectionInstance(const std::string&); - void resetCoinSelection(const std::string&); - bool setCoinSelectionRecipient( - const std::string&, const std::string&, uint64_t, unsigned); - bool cs_SelectUTXOs(const std::string&, uint64_t, float, unsigned); - BridgeReply cs_getUtxoSelection(const std::string&); - BridgeReply cs_getFlatFee(const std::string&); - BridgeReply cs_getFeeByte(const std::string&); - BridgeReply cs_getSizeEstimate(const std::string&); - bool cs_ProcessCustomUtxoList(const Codec_ClientProto::ClientCommand&); - BridgeReply cs_getFeeForMaxVal(const Codec_ClientProto::ClientCommand&); - BridgeReply cs_getFeeForMaxValUtxoVector(const Codec_ClientProto::ClientCommand&); + std::shared_ptr + coinSelectionInstance(const std::string&) const; //signer - BridgeReply initNewSigner(void); + BridgePayload initNewSigner(void); void destroySigner(const std::string&); - bool signer_SetVersion(const std::string&, unsigned); - bool signer_SetLockTime(const std::string&, unsigned); - - bool signer_addSpenderByOutpoint( - const std::string&, const BinaryDataRef&, unsigned, unsigned); - bool signer_populateUtxo( - const std::string&, const BinaryDataRef&, unsigned, uint64_t, - const BinaryDataRef&); - bool signer_addSupportingTx(const std::string&, const BinaryDataRef&); - - bool signer_addRecipient( - const std::string&, const BinaryDataRef&, uint64_t); - BridgeReply signer_toTxSigCollect(const std::string&, int) const; - bool signer_fromTxSigCollect(const std::string&, const std::string&); + std::shared_ptr + signerInstance(const std::string&) const; + void signer_signTx(const std::string&, const std::string&, unsigned); - BridgeReply signer_getSignedTx(const std::string&) const; - BridgeReply signer_getUnsignedTx(const std::string&) const; - BridgeReply signer_getSignedStateForInput( + BridgePayload getSignedStateForInput( const std::string&, unsigned); - int signer_resolve(const std::string&, const std::string&) const; - - Armory::Signer::SignerStringFormat signer_fromType(const std::string&) const; - bool signer_canLegacySerialize(const std::string&) const; + bool signer_resolve(const std::string&, const std::string&) const; //utils - BridgeReply getTxInScriptType( + BridgePayload getTxInScriptType( const BinaryData&, const BinaryData&) const; - BridgeReply getTxOutScriptType(const BinaryData&) const; - BridgeReply getScrAddrForScript(const BinaryData&) const; - BridgeReply getScrAddrForAddrStr(const std::string&) const; - BridgeReply getLastPushDataInScript(const BinaryData&) const; - BridgeReply getHash160(const BinaryDataRef&) const; + BridgePayload getTxOutScriptType(const BinaryData&) const; + BridgePayload getScrAddrForScript(const BinaryData&) const; + BridgePayload getScrAddrForAddrStr(const std::string&) const; + BridgePayload getLastPushDataInScript(const BinaryData&) const; + BridgePayload getHash160(const BinaryDataRef&) const; void broadcastTx(const std::vector&); - BridgeReply getTxOutScriptForScrAddr(const BinaryData&) const; - BridgeReply getAddrStrForScrAddr(const BinaryData&) const; + BridgePayload getTxOutScriptForScrAddr(const BinaryData&) const; + BridgePayload getAddrStrForScrAddr(const BinaryData&) const; std::string getNameForAddrType(int) const; - BridgeReply setAddressTypeFor( + BridgePayload setAddressTypeFor( const std::string&, const std::string&, uint32_t) const; void getBlockTimeByHeight(uint32_t, uint32_t) const; void estimateFee(uint32_t, const std::string&, uint32_t) const; //passphrase prompt PassphraseLambda createPassphrasePrompt( - ::Codec_ClientProto::UnlockPromptType); + ::BridgeProto::UnlockPromptType); bool returnPassphrase(const std::string&, const std::string&); public: @@ -282,7 +259,7 @@ namespace Armory const std::string&, bool, bool); bool processData(BinaryDataRef socketData); - void writeToClient(BridgeReply msgPtr, unsigned id) const; + void writeToClient(BridgePayload msgPtr) const; void setWriteLambda( std::function)> lbd) diff --git a/cppForSwig/BridgeAPI/PassphrasePrompt.cpp b/cppForSwig/BridgeAPI/PassphrasePrompt.cpp index 930dcd7dd..261c6370e 100644 --- a/cppForSwig/BridgeAPI/PassphrasePrompt.cpp +++ b/cppForSwig/BridgeAPI/PassphrasePrompt.cpp @@ -11,7 +11,7 @@ using namespace Armory::Bridge; using namespace std; -using namespace Codec_ClientProto; +using namespace BridgeProto; //////////////////////////////////////////////////////////////////////////////// //// @@ -90,7 +90,6 @@ PassphraseLambda BridgePassphrasePrompt::getLambda(UnlockPromptType type) //push over socket auto payload = make_unique(); payload->message_ = move(msg); - payload->id_ = BRIDGE_CALLBACK_PROMPTUSER; writeLambda_(move(payload)); if (exit) diff --git a/cppForSwig/BridgeAPI/PassphrasePrompt.h b/cppForSwig/BridgeAPI/PassphrasePrompt.h index b994607cc..26de981d8 100644 --- a/cppForSwig/BridgeAPI/PassphrasePrompt.h +++ b/cppForSwig/BridgeAPI/PassphrasePrompt.h @@ -11,11 +11,11 @@ #include -#include "../protobuf/ClientProto.pb.h" +#include "../protobuf/BridgeProto.pb.h" #include "../Wallets/WalletIdTypes.h" #include "../Wallets/PassphraseLambda.h" -#define BRIDGE_CALLBACK_PROMPTUSER UINT32_MAX - 2 +#define BRIDGE_CALLBACK_PROMPTUSER "prompt" namespace Armory { @@ -44,7 +44,7 @@ namespace Armory promptId_(id), writeLambda_(lbd) {} - PassphraseLambda getLambda(::Codec_ClientProto::UnlockPromptType); + PassphraseLambda getLambda(::BridgeProto::UnlockPromptType); void setReply(const std::string&); }; }; //namespace Bridge diff --git a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp index 377c5b595..0f7d53efa 100644 --- a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp +++ b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // // -// Copyright (C) 2020-21, goatpig // +// Copyright (C) 2020-23, goatpig // // Distributed under the MIT license // // See LICENSE-MIT or https://opensource.org/licenses/MIT // // // @@ -8,767 +8,1001 @@ #include "ProtobufCommandParser.h" #include "CppBridge.h" -#include "../protobuf/ClientProto.pb.h" +#include "../protobuf/BridgeProto.pb.h" +#include "ProtobufConversions.h" using namespace std; using namespace Armory::Bridge; using namespace ::google::protobuf; -using namespace ::Codec_ClientProto; +using namespace ::BridgeProto; //////////////////////////////////////////////////////////////////////////////// -bool ProtobufCommandParser::processData( - CppBridge* bridge, BinaryDataRef socketData) +bool ProtobufCommandParser::processBlockchainServiceCommands(CppBridge* bridge, + unsigned referenceId, const BridgeProto::BlockchainService& msg) { - ClientCommand msg; - if (!msg.ParseFromArray(socketData.getPtr() + 1, socketData.getSize() - 1)) + BridgePayload response; + switch (msg.method_case()) { - LOGERR << "failed to parse protobuf msg"; - return false; - } - - auto id = msg.payloadid(); - BridgeReply response; - - switch (msg.method()) - { - case Methods::methodWithCallback: - { - try + case BridgeProto::BlockchainService::kEstimateFee: { - bridge->queueCommandWithCallback(move(msg)); + const auto& estimateFeeMsg = msg.estimate_fee(); + bridge->estimateFee(estimateFeeMsg.blocks(), + estimateFeeMsg.strat(), referenceId); + break; } - catch (const exception& e) - { - LOGERR << "[methodWithCallback] " << e.what(); - auto errMsg = make_unique(); - errMsg->set_iserror(true); - errMsg->set_error(e.what()); - response = move(errMsg); + case BridgeProto::BlockchainService::kLoadWallets: + { + bridge->loadWallets(referenceId); + break; } - break; - } + case BridgeProto::BlockchainService::kSetupDb: + { + bridge->setupDB(); + break; + } - case Methods::loadWallets: - { - bridge->loadWallets(id); - break; - } + case BridgeProto::BlockchainService::kGoOnline: + { + if (bridge->bdvPtr_ == nullptr) + throw runtime_error("null bdv ptr"); + bridge->bdvPtr_->goOnline(); + break; + } - case Methods::setupDB: - { - bridge->setupDB(); - break; - } + case BridgeProto::BlockchainService::kShutdown: + { + if (bridge->bdvPtr_ != nullptr) + { + bridge->bdvPtr_->unregisterFromDB(); + bridge->bdvPtr_.reset(); + bridge->callbackPtr_.reset(); + } + return false; + } - case Methods::registerWallets: - { - bridge->registerWallets(); - break; - } + case BridgeProto::BlockchainService::kRegisterWallets: + { + bridge->registerWallets(); + break; + } - case Methods::registerWallet: - { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) - throw runtime_error("invalid command: registerWallet"); - bridge->registerWallet(msg.stringargs(0), msg.intargs(0)); - break; - } + case BridgeProto::BlockchainService::kRegisterWallet: + { + const auto& registerWalletMsg = msg.register_wallet(); + bridge->registerWallet(registerWalletMsg.id(), + registerWalletMsg.is_new()); + break; + } - case Methods::createBackupStringForWallet: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: getRootData"); - bridge->createBackupStringForWallet(msg.stringargs(0), id); - break; - } + case BridgeProto::BlockchainService::kGetLedgerDelegateIdForWallets: + { + auto& delegateId = bridge->getLedgerDelegateIdForWallets(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto service = reply->mutable_service(); + auto idReply = service->mutable_ledger_delegate_id(); + idReply->set_id(delegateId); + response = move(payload); + break; + } - case Methods::goOnline: - { - if (bridge->bdvPtr_ == nullptr) - throw runtime_error("null bdv ptr"); - bridge->bdvPtr_->goOnline(); - break; - } + case BridgeProto::BlockchainService::kUpdateWalletsLedgerFilter: + { + vector idVec; + const auto& updateWalletsMsg = msg.update_wallets_ledger_filter(); + idVec.reserve(updateWalletsMsg.wallet_id_size()); + + for (int i=0; ibdvPtr_->updateWalletsLedgerFilter(idVec); + break; + } - case Methods::shutdown: - { - if (bridge->bdvPtr_ != nullptr) + case BridgeProto::BlockchainService::kGetHistoryPageForDelegate: { - bridge->bdvPtr_->unregisterFromDB(); - bridge->bdvPtr_.reset(); - bridge->callbackPtr_.reset(); + const auto& getHistoryPageMsg = msg.get_history_page_for_delegate(); + bridge->getHistoryPageForDelegate(getHistoryPageMsg.delegate_id(), + getHistoryPageMsg.page_id(), referenceId); + break; } - return false; - } + case BridgeProto::BlockchainService::kGetHistoryForWalletSelection: + { + const auto& getHistoryMsg = msg.get_history_for_wallet_selection(); + vector wltIdVec; + wltIdVec.reserve(getHistoryMsg.wallet_id_size()); - case Methods::getLedgerDelegateIdForWallets: - { - auto& delegateId = bridge->getLedgerDelegateIdForWallets(); - auto replyMsg = make_unique(); - replyMsg->add_reply(delegateId); - response = move(replyMsg); - break; - } + for (int i=1; i idVec; - for (int i=0; igetHistoryForWalletSelection(getHistoryMsg.order(), + wltIdVec, referenceId); + break; + } - bridge->bdvPtr_->updateWalletsLedgerFilter(idVec); - break; - } + case BridgeProto::BlockchainService::kGetNodeStatus: + { + response = move(bridge->getNodeStatus()); + break; + } - case Methods::getLedgerDelegateIdForScrAddr: - { - if (msg.stringargs_size() == 0 || msg.byteargs_size() == 0) - throw runtime_error("invalid command: getLedgerDelegateIdForScrAddr"); + case BridgeProto::BlockchainService::kGetHeaderByHeight: + { + bridge->getHeaderByHeight(msg.get_header_by_height().height(), + referenceId); + break; + } - auto addrHashRef = BinaryDataRef::fromString(msg.byteargs(0)); - auto& delegateId = bridge->getLedgerDelegateIdForScrAddr( - msg.stringargs(0), addrHashRef); + case BridgeProto::BlockchainService::kGetTxByHash: + { + const auto& hash = BinaryData::fromString( + msg.get_tx_by_hash().tx_hash()); + bridge->getTxByHash(hash, referenceId); + break; + } - auto replyMsg = make_unique(); - replyMsg->add_reply(delegateId); - response = move(replyMsg); - break; + case BridgeProto::BlockchainService::kBroadcastTx: + { + const auto& broadcastMsg = msg.broadcast_tx(); + vector bdVec; + bdVec.reserve(broadcastMsg.raw_tx_size()); + for (int i=0; ibroadcastTx(bdVec); + break; + } + + case BridgeProto::BlockchainService::kGetBlockTimeByHeight: + { + bridge->getBlockTimeByHeight( + msg.get_block_time_by_height().height(), referenceId); + break; + } } - case Methods::getHistoryPageForDelegate: + if (response != nullptr) { - if (msg.stringargs_size() == 0 || msg.intargs_size() == 0) - throw runtime_error("invalid command: getHistoryPageForDelegate"); - bridge->getHistoryPageForDelegate(msg.stringargs(0), msg.intargs(0), id); - break; + //write response to socket + response->mutable_reply()->set_reference_id(referenceId); + bridge->writeToClient(move(response)); } + return true; +} - case Methods::getHistoryForWalletSelection: +//////////////////////////////////////////////////////////////////////////////// +bool ProtobufCommandParser::processWalletCommands(CppBridge* bridge, + unsigned referenceId, const BridgeProto::Wallet& msg) +{ + BridgePayload response; + switch (msg.method_case()) { - if (msg.stringargs_size() < 1) - throw runtime_error("invalid command: getHistoryForWalletSelection"); + case BridgeProto::Wallet::kCreateBackupString: + { + bridge->createBackupStringForWallet(msg.id(), referenceId); + break; + } - vector wltIdVec; - for (int i=1; igetLedgerDelegateIdForScrAddr( + msg.id(), addrHashRef); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto walletMsg = reply->mutable_wallet(); + auto delegateMsg = walletMsg->mutable_ledger_delegate_id(); + delegateMsg->set_id(delegateId); + response = move(payload); + break; + } - bridge->getHistoryForWalletSelection(msg.stringargs(0), wltIdVec, id); - break; - } + case BridgeProto::Wallet::kGetBalanceAndCount: + { + response = move(bridge->getBalanceAndCount(msg.id())); + break; + } - case Methods::getNodeStatus: - { - response = move(bridge->getNodeStatus()); - break; - } + case BridgeProto::Wallet::kSetupNewCoinSelectionInstance: + { + bridge->setupNewCoinSelectionInstance(msg.id(), + msg.setup_new_coin_selection_instance().height(), referenceId); + break; + } - case Methods::getBalanceAndCount: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: getBalanceAndCount"); - response = move(bridge->getBalanceAndCount(msg.stringargs(0))); - break; - } + case BridgeProto::Wallet::kGetAddrCombinedList: + { + response = move(bridge->getAddrCombinedList(msg.id())); + break; + } - case Methods::getAddrCombinedList: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: getAddrCombinedList"); - response = move(bridge->getAddrCombinedList(msg.stringargs(0))); - break; - } + case BridgeProto::Wallet::kGetHighestUsedIndex: + { + response = move(bridge->getHighestUsedIndex(msg.id())); + break; + } - case Methods::getHighestUsedIndex: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: getHighestUsedIndex"); - response = move(bridge->getHighestUsedIndex(msg.stringargs(0))); - break; - } + case BridgeProto::Wallet::kExtendAddressPool: + { + const auto& extendMsg = msg.extend_address_pool(); + bridge->extendAddressPool(msg.id(), extendMsg.count(), + extendMsg.callback_id(), referenceId); + break; + } - case Methods::extendAddressPool: - { - if (msg.stringargs_size() != 2 || msg.intargs_size() != 1) - throw runtime_error("invalid command: extendAddressPool"); - bridge->extendAddressPool( - msg.stringargs(0), msg.intargs(0), msg.stringargs(1), id); - break; - } + case BridgeProto::Wallet::kDelete: + { + auto result = bridge->deleteWallet(msg.id()); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(result); + response = move(payload); + break; + } - case Methods::createWallet: - { - auto&& wltId = bridge->createWallet(msg); - auto replyMsg = make_unique(); - replyMsg->add_reply(wltId); - response = move(replyMsg); - break; - } + case BridgeProto::Wallet::kGetData: + { + response = move(bridge->getWalletPacket(msg.id())); + break; + } - case Methods::deleteWallet: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: deleteWallet"); - auto result = bridge->deleteWallet(msg.stringargs(0)); + case BridgeProto::Wallet::kSetAddressTypeFor: + { + const auto& setAddrMsg = msg.set_address_type_for(); + response = bridge->setAddressTypeFor( + msg.id(), setAddrMsg.address(), setAddrMsg.address_type()); + response->mutable_reply()->set_reference_id(referenceId); + break; + } - auto replyMsg = make_unique(); - replyMsg->add_ints(result); - response = move(replyMsg); - break; - } + case BridgeProto::Wallet::kCreateAddressBook: + { + bridge->createAddressBook(msg.id(), referenceId); + break; + } - case Methods::getWalletData: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: deleteWallet"); - response = move(bridge->getWalletPacket(msg.stringargs(0))); - break; - } + case BridgeProto::Wallet::kSetComment: + { + bridge->setComment(msg.id(), msg.set_comment()); + break; + } - case Methods::getTxByHash: - { - if (msg.byteargs_size() != 1) - throw runtime_error("invalid command: getTxByHash"); - auto& byteargs = msg.byteargs(0); - BinaryData hash((uint8_t*)byteargs.c_str(), byteargs.size()); - bridge->getTxByHash(hash, id); - break; - } + case BridgeProto::Wallet::kSetLabels: + { + bridge->setWalletLabels(msg.id(), msg.set_labels()); + break; + } - case Methods::getTxInScriptType: - { - if (msg.byteargs_size() != 2) - throw runtime_error("invalid command: getTxInScriptType"); + case BridgeProto::Wallet::kGetUtxosForValue: + { + bridge->getUtxosForValue(msg.id(), + msg.get_utxos_for_value().value(), referenceId); + break; + } - const auto& script = msg.byteargs(0); - BinaryData scriptBd((uint8_t*)script.c_str(), script.size()); + case BridgeProto::Wallet::kGetSpendableZcList: + { + bridge->getSpendableZCList(msg.id(), referenceId); + break; + } - const auto& hash = msg.byteargs(1); - BinaryData hashBd((uint8_t*)hash.c_str(), hash.size()); + case BridgeProto::Wallet::kGetRbfTxoutList: + { + bridge->getRBFTxOutList(msg.id(), referenceId); + break; + } - response = bridge->getTxInScriptType(scriptBd, hashBd); - break; - } + case BridgeProto::Wallet::kGetNewAddress: + { + response = bridge->getNewAddress(msg.id(), + msg.get_new_address().type()); + break; + } - case Methods::getTxOutScriptType: - { - if (msg.byteargs_size() != 1) - throw runtime_error("invalid command: getTxOutScriptType"); - const auto& byteargs = msg.byteargs(0); - BinaryData script((uint8_t*)byteargs.c_str(), byteargs.size()); - response = bridge->getTxOutScriptType(script); - break; - } + case BridgeProto::Wallet::kGetChangeAddress: + { + response = bridge->getChangeAddress(msg.id(), + msg.get_change_address().type()); + break; + } - case Methods::getScrAddrForScript: - { - if (msg.byteargs_size() != 1) - throw runtime_error("invalid command: getScrAddrForScript"); - const auto& byteargs = msg.byteargs(0); - BinaryData script((uint8_t*)byteargs.c_str(), byteargs.size()); - response = bridge->getScrAddrForScript(script); - break; + case BridgeProto::Wallet::kPeekChangeAddress: + { + response = bridge->peekChangeAddress(msg.id(), + msg.peek_change_address().type()); + break; + } } - case Methods::getScrAddrForAddrStr: + if (response != nullptr) { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: getScrAddrForScript"); - response = bridge->getScrAddrForAddrStr(msg.stringargs(0)); - break; + //write response to socket + response->mutable_reply()->set_reference_id(referenceId); + bridge->writeToClient(move(response)); } + return true; +} - case Methods::getLastPushDataInScript: +//////////////////////////////////////////////////////////////////////////////// +bool ProtobufCommandParser::processCoinSelectionCommands(CppBridge* bridge, + unsigned referenceId, const BridgeProto::CoinSelection& msg) +{ + auto cs = bridge->coinSelectionInstance(msg.id()); + if (cs == nullptr) { - if (msg.byteargs_size() != 1) - throw runtime_error("invalid command: getLastPushDataInScript"); - - const auto& script = msg.byteargs(0); - BinaryData scriptBd((uint8_t*)script.c_str(), script.size()); - - response = bridge->getLastPushDataInScript(scriptBd); - break; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(false); + bridge->writeToClient(move(payload)); + return true; } - case Methods::getTxOutScriptForScrAddr: + BridgePayload response; + switch (msg.method_case()) { - if (msg.byteargs_size() != 1) - throw runtime_error("invalid command: getTxOutScriptForScrAddr"); - - const auto& script = msg.byteargs(0); - BinaryData scriptBd((uint8_t*)script.c_str(), script.size()); + case BridgeProto::CoinSelection::kCleanup: + { + bridge->destroyCoinSelectionInstance(msg.id()); + break; + } - response = bridge->getTxOutScriptForScrAddr(scriptBd); - break; - } + case BridgeProto::CoinSelection::kReset: + { + cs->resetRecipients(); + break; + } - case Methods::getAddrStrForScrAddr: - { - if (msg.byteargs_size() != 1) - throw runtime_error("invalid command: getAddrStrForScrAddr"); - const auto& byteargs = msg.byteargs(0); - BinaryData script((uint8_t*)byteargs.c_str(), byteargs.size()); - response = bridge->getAddrStrForScrAddr(script); - break; - } + case BridgeProto::CoinSelection::kSetRecipient: + { + const auto& setRecipientMsg = msg.set_recipient(); + cs->updateRecipient(setRecipientMsg.id(), + setRecipientMsg.address(), setRecipientMsg.value()); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::getNameForAddrType: - { - if (msg.intargs_size() != 1) - throw runtime_error("invalid command: getNameForAddrType"); - auto addrTypeInt = msg.intargs(0); - auto typeName = bridge->getNameForAddrType(addrTypeInt); - - auto replyMsg = make_unique(); - replyMsg->add_reply(typeName); - response = move(replyMsg); - break; - } + case BridgeProto::CoinSelection::kSelectUtxos: + { + const auto& selectMsg = msg.select_utxos(); + uint64_t flatFee = 0; + float feeByte = 0; + switch (selectMsg.fee_case()) + { + case BridgeProto::CoinSelection::SelectUTXOs::kFlatFee: + flatFee = selectMsg.flat_fee(); + break; + + case BridgeProto::CoinSelection::SelectUTXOs::kFeeByte: + feeByte = selectMsg.fee_byte(); + break; + } + cs->selectUTXOs(flatFee, feeByte, selectMsg.flags()); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::setAddressTypeFor: - { - if (msg.intargs_size() != 1 || msg.stringargs_size() != 1 || - msg.byteargs_size() != 1) + case BridgeProto::CoinSelection::kGetUtxoSelection: { - throw runtime_error("invalid command: setAddressTypeFor"); + auto&& utxoVec = cs->getUtxoSelection(); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto utxoList = reply->mutable_coin_selection()->mutable_utxo_list(); + for (auto& utxo : utxoVec) + { + auto utxoProto = utxoList->add_utxo(); + CppToProto::utxo(utxoProto, utxo); + } + response = move(payload); + break; } - response = bridge->setAddressTypeFor( - msg.stringargs(0), msg.byteargs(0), msg.intargs(0)); - break; - } - case Methods::getHeaderByHeight: - { - if (msg.intargs_size() != 1) - throw runtime_error("invalid command: getHeaderByHeight"); - auto intArgs = msg.intargs(0); - bridge->getHeaderByHeight(intArgs, id); - break; - } + case BridgeProto::CoinSelection::kGetFlatFee: + { + auto flatFee = cs->getFlatFee(); - case Methods::setupNewCoinSelectionInstance: - { - if (msg.intargs_size() != 1 || msg.stringargs_size() != 1) - throw runtime_error("invalid command: setupNewCoinSelectionInstance"); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); - bridge->setupNewCoinSelectionInstance( - msg.stringargs(0), msg.intargs(0), id); - break; - } + auto cs = reply->mutable_coin_selection(); + auto fee = cs->mutable_flat_fee(); + fee->set_fee(flatFee); + response = move(payload); + break; + } - case Methods::destroyCoinSelectionInstance: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: destroyCoinSelectionInstance"); + case BridgeProto::CoinSelection::kGetFeeByte: + { + auto feeByte = cs->getFeeByte(); - bridge->destroyCoinSelectionInstance(msg.stringargs(0)); - break; - } + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); - case Methods::resetCoinSelection: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: resetCoinSelection"); - bridge->resetCoinSelection(msg.stringargs(0)); - break; - } + auto cs = reply->mutable_coin_selection(); + auto fee = cs->mutable_fee_byte(); + fee->set_fee(feeByte); + response = move(payload); + break; + } - case Methods::setCoinSelectionRecipient: - { - if (msg.longargs_size() != 1 || - msg.stringargs_size() != 2 || - msg.intargs_size() != 1) + case BridgeProto::CoinSelection::kGetSizeEstimate: { - throw runtime_error("invalid command: setCoinSelectionRecipient"); - } + auto sizeEstimate = cs->getSizeEstimate(); - auto success = bridge->setCoinSelectionRecipient(msg.stringargs(0), - msg.stringargs(1), msg.longargs(0), msg.intargs(0)); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); - auto responseProto = make_unique(); - responseProto->add_ints(success); - response = move(responseProto); - break; - } + auto cs = reply->mutable_coin_selection(); + auto size = cs->mutable_size_estimate(); + size->set_size(sizeEstimate); + response = move(payload); + break; + } - case Methods::cs_SelectUTXOs: - { - if (msg.longargs_size() != 1 || - msg.stringargs_size() != 1 || - msg.intargs_size() != 1 || - msg.floatargs_size() != 1) + case BridgeProto::CoinSelection::kProcessCustomUtxoList: { - throw runtime_error("invalid command: cs_SelectUTXOs"); + const auto& processMsg = msg.process_custom_utxo_list(); + + vector utxos; + utxos.reserve(processMsg.utxos_size()); + for (int i=0; iprocessCustomUtxoList(utxos, flatFee, feeByte, processMsg.flags()); + } + catch (exception&) + { + success = false; + } + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(success); + response = move(payload); + break; } - auto success = bridge->cs_SelectUTXOs(msg.stringargs(0), - msg.longargs(0), msg.floatargs(0), msg.intargs(0)); - - auto responseProto = make_unique(); - responseProto->add_ints(success); - response = move(responseProto); - break; + case BridgeProto::CoinSelection::kGetFeeForMaxVal: + { + const auto& getFeeMsg = msg.get_fee_for_max_val(); + auto feeByte = getFeeMsg.fee_byte(); + + float flatFee = 0; + if (getFeeMsg.utxos_size() == 0) + { + flatFee = cs->getFeeForMaxVal(feeByte); + } + else + { + vector serUtxos; + serUtxos.reserve(getFeeMsg.utxos_size()); + for (int i=0; igetFeeForMaxValUtxoVector(serUtxos, feeByte); + } + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto cs = reply->mutable_coin_selection(); + auto fee = cs->mutable_flat_fee(); + fee->set_fee(flatFee); + response = move(payload); + break; + } } - case Methods::cs_getUtxoSelection: + if (response != nullptr) { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: cs_getUtxoSelection"); - - response = bridge->cs_getUtxoSelection(msg.stringargs(0)); - break; + //write response to socket + response->mutable_reply()->set_reference_id(referenceId); + bridge->writeToClient(move(response)); } + return true; +} - case Methods::cs_getFlatFee: +//////////////////////////////////////////////////////////////////////////////// +bool ProtobufCommandParser::processSignerCommands(CppBridge* bridge, + unsigned referenceId, const BridgeProto::Signer& msg) +{ + auto signer = bridge->signerInstance(msg.id()); + if (signer == nullptr) { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: cs_getFlatFee"); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(false); + reply->set_reference_id(referenceId); - response = bridge->cs_getFlatFee(msg.stringargs(0)); - break; + bridge->writeToClient(move(payload)); + return true; } - case Methods::cs_getFeeByte: + BridgePayload response; + switch (msg.method_case()) { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: cs_getFeeByte"); + case BridgeProto::Signer::kGetNew: + { + response = bridge->initNewSigner(); + response->mutable_reply()->set_reference_id(referenceId); + break; + } - response = bridge->cs_getFeeByte(msg.stringargs(0)); - break; - } + case BridgeProto::Signer::kCleanup: + { + bridge->destroySigner(msg.id()); + break; + } - case Methods::cs_getSizeEstimate: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: cs_getSizeEstimate"); + case BridgeProto::Signer::kSetVersion: + { + signer->signer_.setVersion(msg.set_version().version()); - response = bridge->cs_getSizeEstimate(msg.stringargs(0)); - break; - } + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::cs_ProcessCustomUtxoList: - { - auto success = bridge->cs_ProcessCustomUtxoList(msg); + case BridgeProto::Signer::kSetLockTime: + { + signer->signer_.setLockTime(msg.set_lock_time().lock_time()); - auto responseProto = make_unique(); - responseProto->add_ints(success); - response = move(responseProto); - break; - } + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::cs_getFeeForMaxVal: - { - response = bridge->cs_getFeeForMaxVal(msg); - break; - } + case BridgeProto::Signer::kAddSpenderByOutpoint: + { + const auto& addSpenderMsg = msg.add_spender_by_outpoint(); + const auto& hash = BinaryData::fromString(addSpenderMsg.hash()); + signer->signer_.addSpender_ByOutpoint(hash, + addSpenderMsg.tx_out_id(), addSpenderMsg.sequence()); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::cs_getFeeForMaxValUtxoVector: - { - response = bridge->cs_getFeeForMaxValUtxoVector(msg); - break; - } + case BridgeProto::Signer::kPopulateUtxo: + { + const auto& populateUtxoMsg = msg.populate_utxo(); + const auto& hash = BinaryData::fromString(populateUtxoMsg.hash()); + const auto& script = BinaryData::fromString(populateUtxoMsg.script()); + UTXO utxo(populateUtxoMsg.value(), UINT32_MAX, UINT32_MAX, + populateUtxoMsg.tx_out_id(), hash, script); + + signer->signer_.populateUtxo(utxo); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::generateRandomHex: - { - if (msg.intargs_size() != 1) - throw runtime_error("invalid command: generateRandomHex"); - auto size = msg.intargs(0); - auto&& str = bridge->fortuna_.generateRandom(size).toHexStr(); - - auto msg = make_unique(); - msg->add_reply(str); - response = move(msg); - break; - } + case BridgeProto::Signer::kAddSupportingTx: + { + BinaryDataRef rawTxData; rawTxData.setRef( + msg.add_supporting_tx().raw_tx()); + signer->signer_.addSupportingTx(rawTxData); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::createAddressBook: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: createAddressBook"); - bridge->createAddressBook(msg.stringargs(0), id); - break; - } + case BridgeProto::Signer::kAddRecipient: + { + const auto& addMsg = msg.add_recipient(); + const auto& script = BinaryDataRef::fromString(addMsg.script()); + const auto hash = BtcUtils::getTxOutScrAddr(script); + signer->signer_.addRecipient( + Armory::CoinSelection::CoinSelectionInstance::createRecipient( + hash, addMsg.value())); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::setComment: - { - bridge->setComment(msg); - break; - } + case BridgeProto::Signer::kToTxSigCollect: + { + auto txSigCollect = signer->signer_.toString( + static_cast( + msg.to_tx_sig_collect().ustx_type())); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto sigCollectReply = + reply->mutable_signer()->mutable_tx_sig_collect(); + sigCollectReply->set_data(txSigCollect); + response = move(payload); + break; + } - case Methods::setWalletLabels: - { - bridge->setWalletLabels(msg); - break; - } + case BridgeProto::Signer::kFromTxSigCollect: + { + signer->signer_ = Armory::Signer::Signer::fromString( + msg.from_tx_sig_collect().tx_sig_collect()); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + response = move(payload); + break; + } - case Methods::getUtxosForValue: - { - if (msg.stringargs_size() != 1 || msg.longargs_size() != 1) - throw runtime_error("invalid command: getUtxosForValue"); - bridge->getUtxosForValue(msg.stringargs(0), msg.longargs(0), id); - break; - } + case BridgeProto::Signer::kSignTx: + { + bridge->signer_signTx(msg.id(), + msg.sign_tx().wallet_id(), referenceId); + break; + } - case Methods::getSpendableZCList: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command getSpendableZCList"); - bridge->getSpendableZCList(msg.stringargs(0), id); - break; - } + case BridgeProto::Signer::kGetSignedTx: + { + BinaryDataRef data; + try + { + data = signer->signer_.serializeSignedTx(); + } + catch (const exception&) + {} + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto txData = reply->mutable_signer()->mutable_tx_data(); + txData->set_data(data.toCharPtr(), data.getSize()); + response = move(payload); + break; + } - case Methods::getRBFTxOutList: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: getRBFTxOutList"); - bridge->getRBFTxOutList(msg.stringargs(0), id); - break; - } + case BridgeProto::Signer::kGetUnsignedTx: + { + BinaryDataRef data; + try + { + data = signer->signer_.serializeUnsignedTx(); + } + catch (const exception&) + {} + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto txData = reply->mutable_signer()->mutable_tx_data(); + txData->set_data(data.toCharPtr(), data.getSize()); + response = move(payload); + break; + } - case Methods::getNewAddress: - { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) - throw runtime_error("invalid command: getNewAddress"); - response = bridge->getNewAddress(msg.stringargs(0), msg.intargs(0)); - break; - } + case BridgeProto::Signer::kResolve: + { + auto result = bridge->signer_resolve(msg.id(), + msg.resolve().wallet_id()); + + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(result); + response = move(payload); + break; + } - case Methods::getChangeAddress: - { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) - throw runtime_error("invalid command: getChangeAddress"); - response = bridge->getChangeAddress(msg.stringargs(0), msg.intargs(0)); - break; - } + case BridgeProto::Signer::kGetSignedStateForInput: + { + response = bridge->getSignedStateForInput( + msg.id(), msg.get_signed_state_for_input().input_id()); + break; + } - case Methods::peekChangeAddress: - { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) - throw runtime_error("invalid command: peekChangeAddress"); - response = bridge->peekChangeAddress(msg.stringargs(0), msg.intargs(0)); - break; - } + case BridgeProto::Signer::kFromType: + { + auto result = signer->signer_.deserializedFromType(); - case Methods::getHash160: - { - if (msg.byteargs_size() != 1) - throw runtime_error("invalid command: getHash160"); - BinaryDataRef bdRef; bdRef.setRef(msg.byteargs(0)); - response = bridge->getHash160(bdRef); - break; - } + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); - case Methods::initNewSigner: - { - response = bridge->initNewSigner(); - break; - } + auto signer = reply->mutable_signer(); + auto type = signer->mutable_from_type(); + type->set_type((int)result); + response = move(payload); + break; + } - case Methods::destroySigner: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: destroySigner"); - bridge->destroySigner(msg.stringargs(0)); - break; + case BridgeProto::Signer::kCanLegacySerialize: + { + auto result = signer->signer_.canLegacySerialize(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(result); + response = move(payload); + break; + } } - case Methods::signer_SetVersion: + if (response != nullptr) { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) - throw runtime_error("invalid command: signer_SetVersion"); - auto success = bridge->signer_SetVersion(msg.stringargs(0), msg.intargs(0)); - auto resultProto = make_unique(); - resultProto->add_ints(success); - response = move(resultProto); - break; + //write response to socket + response->mutable_reply()->set_reference_id(referenceId); + bridge->writeToClient(move(response)); } + return true; +} - case Methods::signer_SetLockTime: +//////////////////////////////////////////////////////////////////////////////// +bool ProtobufCommandParser::processUtilsCommands(CppBridge* bridge, + unsigned referenceId, const BridgeProto::Utils& msg) +{ + BridgePayload response; + switch (msg.method_case()) { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) - throw runtime_error("invalid command: signer_SetLockTime"); - auto result = bridge->signer_SetLockTime(msg.stringargs(0), msg.intargs(0)); - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; - } + case BridgeProto::Utils::kCreateWallet: + { + auto wltId = bridge->createWallet(msg.create_wallet()); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto idMsg = reply->mutable_utils()->mutable_wallet_id(); + idMsg->set_id(wltId); + response = move(payload); + break; + } - case Methods::signer_addSpenderByOutpoint: - { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 2 || - msg.byteargs_size() != 1) - throw runtime_error("invalid command: signer_addSpenderByOutpoint"); + case BridgeProto::Utils::kGenerateRandomHex: + { + auto size = msg.generate_random_hex().length(); + auto str = bridge->fortuna_.generateRandom(size).toHexStr(); - BinaryDataRef hash; hash.setRef(msg.byteargs(0)); - auto result = bridge->signer_addSpenderByOutpoint(msg.stringargs(0), - hash, msg.intargs(0), msg.intargs(1)); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; - } + auto hexMsg = reply->mutable_utils()->mutable_random_hex(); + hexMsg->set_data(str); + response = move(payload); + break; + } - case Methods::signer_populateUtxo: - { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1 || - msg.byteargs_size() != 2 || msg.longargs_size() != 1) - throw runtime_error("invalid command: signer_populateUtxo"); + case BridgeProto::Utils::kGetHash160: + { + const auto& getHashMsg = msg.get_hash_160(); + const auto& data = BinaryDataRef::fromString(getHashMsg.data()); + response = bridge->getHash160(data); + response->mutable_reply()->set_reference_id(referenceId); + break; + } - BinaryDataRef hash; hash.setRef(msg.byteargs(0)); - BinaryDataRef script; script.setRef(msg.byteargs(1)); + case BridgeProto::Utils::kGetScraddrForAddrstr: + { + response = bridge->getScrAddrForAddrStr( + msg.get_scraddr_for_addrstr().address()); + response->mutable_reply()->set_reference_id(referenceId); + break; + } - auto result = bridge->signer_populateUtxo(msg.stringargs(0), - hash, msg.intargs(0), msg.longargs(0), script); + case BridgeProto::Utils::kGetNameForAddrType: + { + auto addrTypeInt = msg.get_name_for_addr_type().address_type(); + auto typeName = bridge->getNameForAddrType(addrTypeInt); - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(true); + + auto nameReply = reply->mutable_utils()->mutable_address_type_name(); + nameReply->set_name(typeName); + response = move(payload); + break; + } } - case Methods::signer_addSupportingTx: + if (response != nullptr) { - if (msg.stringargs_size() != 1 || msg.byteargs_size() != 1) - throw runtime_error("invalid command: signer_addSupportingTx"); - - BinaryDataRef rawTxData; rawTxData.setRef(msg.byteargs(0)); - - auto result = bridge->signer_addSupportingTx( - msg.stringargs(0), rawTxData); - - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; + //write response to socket + response->mutable_reply()->set_reference_id(referenceId); + bridge->writeToClient(move(response)); } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool ProtobufCommandParser::processScriptUtilsCommands(CppBridge* bridge, + unsigned referenceId, const BridgeProto::ScriptUtils& msg) +{ + BridgePayload response; + const auto& script = BinaryDataRef::fromString(msg.script()); - case Methods::signer_addRecipient: + switch (msg.method_case()) { - if (msg.stringargs_size() != 1 || - msg.byteargs_size() != 1 || msg.longargs_size() != 1) - throw runtime_error("invalid command: signer_addRecipient"); + case BridgeProto::ScriptUtils::kGetTxinScriptType: + { + const auto& getTxInMsg = msg.get_txin_script_type(); + const auto& hash = BinaryDataRef::fromString(getTxInMsg.hash()); - BinaryDataRef script; script.setRef(msg.byteargs(0)); - auto result = bridge->signer_addRecipient(msg.stringargs(0), - script, msg.longargs(0)); + response = bridge->getTxInScriptType(script, hash); + break; + } - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; - } + case BridgeProto::ScriptUtils::kGetTxoutScriptType: + { + response = bridge->getTxOutScriptType(script); + break; + } - case Methods::signer_toTxSigCollect: - { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) - throw runtime_error("invalid command: signer_toTxSigCollect"); - response = bridge->signer_toTxSigCollect( - msg.stringargs(0), msg.intargs(0)); - break; - } + case BridgeProto::ScriptUtils::kGetScraddrForScript: + { + response = bridge->getScrAddrForScript(script); + break; + } - case Methods::signer_fromTxSigCollect: - { - if (msg.stringargs_size() != 2) - throw runtime_error("invalid command: signer_fromTxSigCollect"); + case BridgeProto::ScriptUtils::kGetLastPushDataInScript: + { + response = bridge->getLastPushDataInScript(script); + break; + } - auto result = bridge->signer_fromTxSigCollect( - msg.stringargs(0), msg.stringargs(1)); + case BridgeProto::ScriptUtils::kGetTxoutScriptForScraddr: + { + response = bridge->getTxOutScriptForScrAddr(script); + break; + } - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; + case BridgeProto::ScriptUtils::kGetAddrstrForScraddr: + { + response = bridge->getAddrStrForScrAddr(script); + break; + } } - case Methods::signer_signTx: + if (response != nullptr) { - if (msg.stringargs_size() != 2) - throw runtime_error("invalid command: signer_signTx"); - bridge->signer_signTx(msg.stringargs(0), msg.stringargs(1), id); - break; + //write response to socket + response->mutable_reply()->set_reference_id(referenceId); + bridge->writeToClient(move(response)); } + return true; +} - case Methods::signer_getSignedTx: - { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: signer_getSignedTx"); - - response = bridge->signer_getSignedTx(msg.stringargs(0)); - break; - } +//////////////////////////////////////////////////////////////////////////////// +bool ProtobufCommandParser::processMethodsWithCallback(CppBridge*, + unsigned, const BridgeProto::MethodsWithCallback&) +{ + return true; +} - case Methods::signer_getUnsignedTx: +//////////////////////////////////////////////////////////////////////////////// +bool ProtobufCommandParser::processData( + CppBridge* bridge, BinaryDataRef socketData) +{ + BridgeProto::Request msg; + if (!msg.ParseFromArray(socketData.getPtr() + 1, socketData.getSize() - 1)) { - if (msg.stringargs_size() != 1) - throw runtime_error("invalid command: signer_getUnsignedTx"); - - response = bridge->signer_getUnsignedTx(msg.stringargs(0)); - break; + LOGERR << "failed to parse protobuf msg"; + return false; } - case Methods::signer_resolve: + const auto& id = msg.reference_id(); + switch (msg.method_case()) { - if (msg.stringargs_size() != 2) - throw runtime_error("invalid command: signer_resolve"); - - auto result = bridge->signer_resolve( - msg.stringargs(0), msg.stringargs(1)); - - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; + case BridgeProto::Request::kService: + return processBlockchainServiceCommands(bridge, id, msg.service()); + case BridgeProto::Request::kWallet: + return processWalletCommands(bridge, id, msg.wallet()); + case BridgeProto::Request::kCoinSelection: + return processCoinSelectionCommands(bridge, id, msg.coin_selection()); + case BridgeProto::Request::kSigner: + return processSignerCommands(bridge, id, msg.signer()); + case BridgeProto::Request::kUtils: + return processUtilsCommands(bridge, id, msg.utils()); + case BridgeProto::Request::kScriptUtils: + return processScriptUtilsCommands(bridge, id, msg.script_utils()); + case BridgeProto::Request::kCallback: + return processMethodsWithCallback(bridge, id, msg.callback()); } - case Methods::signer_getSignedStateForInput: - { - if (msg.stringargs_size() != 1 || msg.intargs_size() != 1) - { - throw runtime_error( - "invalid command: signer_getSignedStateForInput"); - } - - response = bridge->signer_getSignedStateForInput( - msg.stringargs(0), msg.intargs(0)); - break; - } + return true; - case Methods::signer_fromType: + /*case Methods::methodWithCallback: { - if (msg.stringargs_size() != 1) + try { - throw runtime_error( - "invalid command: signer_fromType"); + bridge->queueCommandWithCallback(move(msg)); } - - auto result = (int)bridge->signer_fromType(msg.stringargs(0)); - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; - } - - case Methods::signer_canLegacySerialize: - { - if (msg.stringargs_size() != 1) + catch (const exception& e) { - throw runtime_error( - "invalid command: signer_canLegacySerialize"); + LOGERR << "[methodWithCallback] " << e.what(); + auto errMsg = make_unique(); + errMsg->set_iserror(true); + errMsg->set_error(e.what()); + + response = move(errMsg); } - auto result = bridge->signer_canLegacySerialize(msg.stringargs(0)); - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); break; - } + }*/ - case Methods::returnPassphrase: + + /*case Methods::returnPassphrase: { if (msg.stringargs_size() != 2) throw runtime_error("invalid command: returnPassphrase"); @@ -779,48 +1013,5 @@ bool ProtobufCommandParser::processData( resultProto->add_ints(result); response = move(resultProto); break; - } - - case Methods::broadcastTx: - { - if (msg.byteargs_size() == 0) - throw runtime_error("invalid command: broadcastTx"); - - vector bdVec; - for (int i=0; ibroadcastTx(bdVec); - break; - } - - case Methods::getBlockTimeByHeight: - { - if (msg.intargs_size() != 1) - throw runtime_error("invalid command: getBlockTimeByHeight"); - bridge->getBlockTimeByHeight(msg.intargs(0), id); - break; - } - - case Methods::estimateFee: - { - if (msg.intargs_size() != 1 || msg.stringargs_size() != 1) - throw runtime_error("invalid command: estimateFee"); - bridge->estimateFee(msg.intargs(0), msg.stringargs(0), id); - break; - } - - default: - stringstream ss; - ss << "unknown client method: " << msg.method(); - throw runtime_error(ss.str()); - } - - if (response != nullptr) - { - //write response to socket - bridge->writeToClient(move(response), id); - } - - return true; + }*/ } diff --git a/cppForSwig/BridgeAPI/ProtobufCommandParser.h b/cppForSwig/BridgeAPI/ProtobufCommandParser.h index 313979489..8519868fb 100644 --- a/cppForSwig/BridgeAPI/ProtobufCommandParser.h +++ b/cppForSwig/BridgeAPI/ProtobufCommandParser.h @@ -11,14 +11,42 @@ #include "BinaryData.h" +namespace BridgeProto +{ + class BlockchainService; + class Wallet; + class CoinSelection; + class Signer; + class Utils; + class ScriptUtils; + class MethodsWithCallback; +}; + namespace Armory { namespace Bridge { class CppBridge; - struct ProtobufCommandParser + class ProtobufCommandParser { + private: + static bool processBlockchainServiceCommands(CppBridge*, unsigned, + const BridgeProto::BlockchainService&); + static bool processWalletCommands(CppBridge*, unsigned, + const BridgeProto::Wallet&); + static bool processCoinSelectionCommands(CppBridge*, unsigned, + const BridgeProto::CoinSelection&); + static bool processSignerCommands(CppBridge*, unsigned, + const BridgeProto::Signer&); + static bool processUtilsCommands(CppBridge*, unsigned, + const BridgeProto::Utils&); + static bool processScriptUtilsCommands(CppBridge*, unsigned, + const BridgeProto::ScriptUtils&); + static bool processMethodsWithCallback(CppBridge*, unsigned, + const BridgeProto::MethodsWithCallback&); + + public: static bool processData(CppBridge*, BinaryDataRef); }; }; //namespace Bridge diff --git a/cppForSwig/BridgeAPI/ProtobufConversions.cpp b/cppForSwig/BridgeAPI/ProtobufConversions.cpp index b931c9a32..17e85055d 100644 --- a/cppForSwig/BridgeAPI/ProtobufConversions.cpp +++ b/cppForSwig/BridgeAPI/ProtobufConversions.cpp @@ -18,7 +18,7 @@ using namespace Armory::Bridge; using namespace Armory::Accounts; using namespace Armory::Wallets; -using namespace Codec_ClientProto; +using namespace BridgeProto; /*** TODO: use the same protobuf as the DB for all things regarding wallet balance @@ -26,8 +26,8 @@ TODO: use the same protobuf as the DB for all things regarding wallet balance ***/ //////////////////////////////////////////////////////////////////////////////// -void CppToProto::ledger( - BridgeLedger* ledgerProto, const DBClientClasses::LedgerEntry& ledgerCpp) +void CppToProto::ledger(Ledger* ledgerProto, + const DBClientClasses::LedgerEntry& ledgerCpp) { ledgerProto->set_value(ledgerCpp.getValue()); @@ -36,21 +36,21 @@ void CppToProto::ledger( ledgerProto->set_id(ledgerCpp.getID()); ledgerProto->set_height(ledgerCpp.getBlockNum()); - ledgerProto->set_txindex(ledgerCpp.getIndex()); - ledgerProto->set_txtime(ledgerCpp.getTxTime()); - ledgerProto->set_iscoinbase(ledgerCpp.isCoinbase()); - ledgerProto->set_issenttoself(ledgerCpp.isSentToSelf()); - ledgerProto->set_ischangeback(ledgerCpp.isChangeBack()); - ledgerProto->set_ischainedzc(ledgerCpp.isChainedZC()); - ledgerProto->set_iswitness(ledgerCpp.isWitness()); - ledgerProto->set_isrbf(ledgerCpp.isOptInRBF()); + ledgerProto->set_tx_index(ledgerCpp.getIndex()); + ledgerProto->set_tx_time(ledgerCpp.getTxTime()); + ledgerProto->set_coinbase(ledgerCpp.isCoinbase()); + ledgerProto->set_sent_to_self(ledgerCpp.isSentToSelf()); + ledgerProto->set_change_back(ledgerCpp.isChangeBack()); + ledgerProto->set_chained_zc(ledgerCpp.isChainedZC()); + ledgerProto->set_witness(ledgerCpp.isWitness()); + ledgerProto->set_rbf(ledgerCpp.isOptInRBF()); for (auto& scrAddr : ledgerCpp.getScrAddrList()) - ledgerProto->add_scraddrlist(scrAddr.getCharPtr(), scrAddr.getSize()); + ledgerProto->add_scraddr(scrAddr.getCharPtr(), scrAddr.getSize()); } //////////////////////////////////////////////////////////////////////////////// -void CppToProto::addr(WalletAsset* assetPtr, +void CppToProto::addr(WalletReply::Asset* assetPtr, shared_ptr addrPtr, shared_ptr accPtr) { if (accPtr == nullptr) @@ -61,7 +61,7 @@ void CppToProto::addr(WalletAsset* assetPtr, //address auto& addrHash = addrPtr->getPrefixedHash(); - assetPtr->set_prefixedhash(addrHash.toCharPtr(), addrHash.getSize()); + assetPtr->set_prefixed_hash(addrHash.toCharPtr(), addrHash.getSize()); //address type & pubkey BinaryDataRef pubKeyRef; @@ -77,35 +77,36 @@ void CppToProto::addr(WalletAsset* assetPtr, pubKeyRef = addrPtr->getPreimage().getRef(); } - assetPtr->set_addrtype(addrType); - assetPtr->set_publickey(pubKeyRef.toCharPtr(), pubKeyRef.getSize()); + assetPtr->set_addr_type(addrType); + assetPtr->set_public_key(pubKeyRef.toCharPtr(), pubKeyRef.getSize()); //index assetPtr->set_id(wltAsset->getIndex()); const auto& serAssetId = assetID.getSerializedKey(PROTO_ASSETID_PREFIX); - assetPtr->set_assetid(serAssetId.getCharPtr(), serAssetId.getSize()); + assetPtr->set_asset_id(serAssetId.getCharPtr(), serAssetId.getSize()); //address string const auto& addrStr = addrPtr->getAddress(); - assetPtr->set_addressstring(addrStr); + assetPtr->set_address_string(addrStr); auto isUsed = accPtr->isAssetInUse(addrPtr->getID()); - assetPtr->set_isused(isUsed); + assetPtr->set_is_used(isUsed); //resolve change status bool isChange = accPtr->isAssetChange(addrPtr->getID()); - assetPtr->set_ischange(isChange); + assetPtr->set_is_change(isChange); //precursor, if any if (addrNested == nullptr) return; auto& precursor = addrNested->getPredecessor()->getScript(); - assetPtr->set_precursorscript(precursor.getCharPtr(), precursor.getSize()); + assetPtr->set_precursor_script(precursor.getCharPtr(), precursor.getSize()); } //////////////////////////////////////////////////////////////////////////////// -void CppToProto::wallet(WalletData* wltProto, shared_ptr wltPtr, +void CppToProto::wallet(WalletReply::WalletData* wltProto, + shared_ptr wltPtr, const Armory::Wallets::AddressAccountId& accId, const map& commentMap) { @@ -117,7 +118,7 @@ void CppToProto::wallet(WalletData* wltProto, shared_ptr wltPtr, auto wltSingle = dynamic_pointer_cast(wltPtr); if (wltSingle != nullptr) isWO = wltSingle->isWatchingOnly(); - wltProto->set_watchingonly(isWO); + wltProto->set_watching_only(isWO); //the address account auto accPtr = wltSingle->getAccountForID(accId); @@ -125,19 +126,19 @@ void CppToProto::wallet(WalletData* wltProto, shared_ptr wltPtr, //address types const auto& addrTypes = accPtr->getAddressTypeSet(); for (const auto& addrType : addrTypes) - wltProto->add_addresstypes(addrType); - wltProto->set_defaultaddresstype((uint32_t)accPtr->getDefaultAddressType()); + wltProto->add_address_type(addrType); + wltProto->set_default_address_type((uint32_t)accPtr->getDefaultAddressType()); //use index auto assetAccountPtr = accPtr->getOuterAccount(); - wltProto->set_lookupcount(assetAccountPtr->getLastComputedIndex()); - wltProto->set_usecount(assetAccountPtr->getHighestUsedIndex()); + wltProto->set_lookup_count(assetAccountPtr->getLastComputedIndex()); + wltProto->set_use_count(assetAccountPtr->getHighestUsedIndex()); //address map auto addrMap = accPtr->getUsedAddressMap(); for (auto& addrPair : addrMap) { - auto assetPtr = wltProto->add_assets(); + auto assetPtr = wltProto->add_asset(); CppToProto::addr(assetPtr, addrPair.second, accPtr); } @@ -156,15 +157,15 @@ void CppToProto::wallet(WalletData* wltProto, shared_ptr wltPtr, } //////////////////////////////////////////////////////////////////////////////// -void CppToProto::utxo(BridgeUtxo* utxoProto, const UTXO& utxo) +void CppToProto::utxo(Utxo* utxoProto, const UTXO& utxo) { auto& hash = utxo.getTxHash(); - utxoProto->set_txhash(hash.getCharPtr(), hash.getSize()); - utxoProto->set_txoutindex(utxo.getTxOutIndex()); + utxoProto->set_tx_hash(hash.getCharPtr(), hash.getSize()); + utxoProto->set_txout_index(utxo.getTxOutIndex()); utxoProto->set_value(utxo.getValue()); - utxoProto->set_txheight(utxo.getHeight()); - utxoProto->set_txindex(utxo.getTxIndex()); + utxoProto->set_tx_height(utxo.getHeight()); + utxoProto->set_tx_index(utxo.getTxIndex()); auto& script = utxo.getScript(); utxoProto->set_script(script.getCharPtr(), script.getSize()); @@ -174,28 +175,29 @@ void CppToProto::utxo(BridgeUtxo* utxoProto, const UTXO& utxo) } //////////////////////////////////////////////////////////////////////////////// -void CppToProto::nodeStatus( - BridgeNodeStatus* nsProto, const DBClientClasses::NodeStatus& nsCpp) +void CppToProto::nodeStatus(NodeStatus* nsProto, + const DBClientClasses::NodeStatus& nsCpp) { auto chainStatus = nsCpp.chainStatus(); - nsProto->set_isvalid(true); - nsProto->set_nodestate(nsCpp.state()); - nsProto->set_issegwitenabled(nsCpp.isSegWitEnabled()); - nsProto->set_rpcstate(nsCpp.rpcState()); + nsProto->set_is_valid(true); + nsProto->set_node_state(nsCpp.state()); + nsProto->set_is_segwit_enabled(nsCpp.isSegWitEnabled()); + nsProto->set_rpc_state(nsCpp.rpcState()); - auto chainStatusProto = nsProto->mutable_chainstatus(); + auto chainStatusProto = nsProto->mutable_chain_status(); - chainStatusProto->set_chainstate(chainStatus.state()); - chainStatusProto->set_blockspeed(chainStatus.getBlockSpeed()); - chainStatusProto->set_progresspct(chainStatus.getProgressPct()); + chainStatusProto->set_chain_state(chainStatus.state()); + chainStatusProto->set_block_speed(chainStatus.getBlockSpeed()); + chainStatusProto->set_progress_pct(chainStatus.getProgressPct()); chainStatusProto->set_eta(chainStatus.getETA()); - chainStatusProto->set_blocksleft(chainStatus.getBlocksLeft()); + chainStatusProto->set_blocks_left(chainStatus.getBlocksLeft()); } //////////////////////////////////////////////////////////////////////////////// void CppToProto::signatureState( - BridgeInputSignedState* ssProto, const Armory::Signer::TxInEvalState& ssCpp) + SignerReply::InputSignedState* ssProto, + const Armory::Signer::TxInEvalState& ssCpp) { ssProto->set_isvalid(ssCpp.isValid()); ssProto->set_m(ssCpp.getM()); diff --git a/cppForSwig/BridgeAPI/ProtobufConversions.h b/cppForSwig/BridgeAPI/ProtobufConversions.h index c139cba5f..0c0ed490c 100644 --- a/cppForSwig/BridgeAPI/ProtobufConversions.h +++ b/cppForSwig/BridgeAPI/ProtobufConversions.h @@ -11,7 +11,7 @@ #include -#include "../protobuf/ClientProto.pb.h" +#include "../protobuf/BridgeProto.pb.h" #define PROTO_ASSETID_PREFIX 0xAFu @@ -50,30 +50,30 @@ namespace Armory struct CppToProto { static void ledger( - Codec_ClientProto::BridgeLedger*, + BridgeProto::Ledger*, const DBClientClasses::LedgerEntry&); static void addr( - Codec_ClientProto::WalletAsset*, + BridgeProto::WalletReply::Asset*, std::shared_ptr, std::shared_ptr); static void wallet( - Codec_ClientProto::WalletData*, + BridgeProto::WalletReply::WalletData*, std::shared_ptr, const Wallets::AddressAccountId&, const std::map&); static void utxo( - Codec_ClientProto::BridgeUtxo*, + BridgeProto::Utxo*, const UTXO& utxo); static void nodeStatus( - Codec_ClientProto::BridgeNodeStatus*, + BridgeProto::NodeStatus*, const DBClientClasses::NodeStatus&); static void signatureState( - Codec_ClientProto::BridgeInputSignedState*, + BridgeProto::SignerReply::InputSignedState*, const Signer::TxInEvalState&); }; }; //namespace Bridge diff --git a/cppForSwig/CoinSelection.h b/cppForSwig/CoinSelection.h index fca32926b..a62f0fb31 100644 --- a/cppForSwig/CoinSelection.h +++ b/cppForSwig/CoinSelection.h @@ -7,7 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #ifndef _H_COINSELECTION -#define _H_COINSELECTOIN +#define _H_COINSELECTION #include #include @@ -320,8 +320,6 @@ namespace Armory unsigned topHeight, unsigned ruleset); }; - #endif - ////////////////////////////////////////////////////////////////////////// struct CoinSubSelection { @@ -446,3 +444,5 @@ namespace Armory }; }; //namespace CoinSelection }; //namespace Armory + +#endif \ No newline at end of file diff --git a/cppForSwig/Makefile.am b/cppForSwig/Makefile.am index 3b160d759..331a3098a 100644 --- a/cppForSwig/Makefile.am +++ b/cppForSwig/Makefile.am @@ -167,7 +167,7 @@ ARMORYCOMMON_SOURCE_FILES = AsyncClient.cpp \ PROTOBUF_PROTO = protobuf/AddressBook.proto \ protobuf/AddressData.proto \ protobuf/BDVCommand.proto \ - protobuf/ClientProto.proto \ + protobuf/BridgeProto.proto \ protobuf/CommonTypes.proto \ protobuf/FeeEstimate.proto \ protobuf/LedgerEntry.proto \ @@ -178,7 +178,7 @@ PROTOBUF_PROTO = protobuf/AddressBook.proto \ PROTOBUF_CC = protobuf/AddressBook.pb.cc \ protobuf/AddressData.pb.cc \ protobuf/BDVCommand.pb.cc \ - protobuf/ClientProto.pb.cc \ + protobuf/BridgeProto.pb.cc \ protobuf/CommonTypes.pb.cc \ protobuf/FeeEstimate.pb.cc \ protobuf/LedgerEntry.pb.cc \ @@ -189,7 +189,7 @@ PROTOBUF_CC = protobuf/AddressBook.pb.cc \ PROTOBUF_H = protobuf/AddressBook.pb.h \ protobuf/AddressData.pb.h \ protobuf/BDVCommand.pb.h \ - protobuf/ClientProto.pb.h \ + protobuf/BridgeProto.pb.h \ protobuf/CommonTypes.pb.h \ protobuf/FeeEstimate.pb.h \ protobuf/LedgerEntry.pb.h \ diff --git a/cppForSwig/protobuf/BridgeProto.proto b/cppForSwig/protobuf/BridgeProto.proto new file mode 100644 index 000000000..49e084680 --- /dev/null +++ b/cppForSwig/protobuf/BridgeProto.proto @@ -0,0 +1,921 @@ +syntax = "proto2"; + +package BridgeProto; + +//////////////////////////////////////////////////////////////////////////////// +// +//// weird shit that needs to go +// +//////////////////////////////////////////////////////////////////////////////// +// Unlock prompt +enum UnlockPromptState +{ + start = 1; + stop = 2; + cycle = 3; +} + +enum UnlockPromptType +{ + decrypt = 1; + migrate = 2; +} + +message UnlockPromptCallback +{ + required bytes promptID = 1; + required UnlockPromptType promptType = 2; + required string verbose = 3; + required UnlockPromptState state = 4; + optional string walletID = 5; +} + +//////////////////////////////////////////////////////////////////////////////// +// RestoreWallet messages +message RestoreWalletPayload +{ + repeated string root = 1; + repeated string secondary = 2; + optional string spPass = 3; +} + +enum RestorePromptType +{ + FormatError = 1; + Failure = 2; + ChecksumError = 3; + DecryptError = 4; + Passphrase = 5; + Control = 6; + Id = 7; + TypeError = 8; + Success = 9; + UnknownError = 10; +} + +message RestorePrompt +{ + + required RestorePromptType promptType = 1; + repeated int32 checksums = 2; + optional string extra = 3; +} + +message RestoreReply +{ + required bool result = 1; + optional bytes extra = 2; +} + +//////////////////////////////////////////////////////////////////////////////// +// Opaque payloads +enum OpaquePayloadType +{ + prompt = 1; + commandWithCallback = 2; +} + +message OpaquePayload +{ + required OpaquePayloadType payloadType = 1; + optional bytes uniqueId = 2; + optional uint32 intId = 3; + + optional bytes payload = 10; +} + +//////////////////////////////////////////////////////////////////////////////// +// +//// Data types +// +//////////////////////////////////////////////////////////////////////////////// +message Ledger +{ + required int64 value = 1; + required bytes hash = 2; + required string id = 3; + required uint32 height = 4; + required uint32 tx_index = 5; + required uint32 tx_time = 6; + required bool coinbase = 7; + required bool sent_to_self = 8; + required bool change_back = 9; + required bool chained_zc = 10; + required bool witness = 11; + required bool rbf = 12; + repeated bytes scraddr = 13; +} + +message NodeStatus +{ + message NodeChainStatus + { + required uint32 chain_state = 2; + required float block_speed = 3; + required float progress_pct = 4; + required uint64 eta = 5; + required uint32 blocks_left = 6; + } + + required bool is_valid = 1; + optional uint32 node_state = 10; + optional bool is_segwit_enabled = 11; + optional uint32 rpc_state = 12; + optional NodeChainStatus chain_status = 13; +} + +message Utxo +{ + required bytes tx_hash = 1; + required uint32 txout_index = 2; + + required uint64 value = 3; + required uint32 tx_height = 4; + required uint32 tx_index = 5; + + required bytes script = 6; + required bytes scraddr = 7; +} + +//////////////////////////////////////////////////////////////////////////////// +// +//// Callbacks +// +//////////////////////////////////////////////////////////////////////////////// + +message Callback +{ + message Ready { + required uint32 height = 1; + } + message SetupDone{} + message Disconnected{} + + message Registered { + repeated string id = 1; + } + + message Refresh { + repeated string id = 1; + } + + message NewBlock { + required uint32 height = 1; + } + + message Progress { + optional uint32 phase = 1; + required float progress = 2; + optional uint32 etaSec = 3; + required uint32 progress_numeric = 4; + + repeated string id = 10; + } + + message ZeroConf { + repeated Ledger ledger = 1; + } + + //// + required string callback_id = 1; + + oneof payload { + Ready ready = 10; + SetupDone setup_done = 11; + Registered registered = 12; + Refresh refresh = 13; + NewBlock new_block = 14; + Disconnected disconnected = 15; + Progress progress = 20; + NodeStatus node_status = 21; + ZeroConf zero_conf = 22; + string error = 30; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +//// Request +// +//////////////////////////////////////////////////////////////////////////////// +message BlockchainService +{ + message Shutdown{} + message SetupDB{} + message GoOnline{} + message GetNodeStatus{} + message LoadWallets{} + + message RegisterWallets{} + message RegisterWallet { + required string id = 1; + required bool is_new = 2; + } + + message BroadcastTx { + repeated bytes raw_tx = 1; + } + message GetTxByHash { + required bytes tx_hash = 1; + } + message GetHeaderByHeight { + required uint32 height = 1; + } + message GetBlockTimeByHeight { + required uint32 height = 1; + } + message EstimateFee { + required uint32 blocks = 1; + required string strat = 2; + } + + message GetLedgerDelegateIdForWallets{} + message UpdateWalletsLedgerFilter { + repeated string wallet_id = 1; + } + + message GetHistoryPageForDelegate { + required string delegate_id = 1; + required uint32 page_id = 2; + } + + message GetHistoryForWalletSelection { + repeated string wallet_id = 1; + required string order = 2; + } + + //// + oneof method { + Shutdown shutdown = 1; + SetupDB setup_db = 2; + GoOnline go_online = 3; + GetNodeStatus get_node_status = 4; + LoadWallets load_wallets = 5; + + RegisterWallets register_wallets = 6; + RegisterWallet register_wallet = 7; + + BroadcastTx broadcast_tx = 8; + GetTxByHash get_tx_by_hash = 9; + GetHeaderByHeight get_header_by_height = 10; + GetBlockTimeByHeight get_block_time_by_height = 11; + EstimateFee estimate_fee = 12; + + GetLedgerDelegateIdForWallets get_ledger_delegate_id_for_wallets = 20; + UpdateWalletsLedgerFilter update_wallets_ledger_filter = 21; + GetHistoryPageForDelegate get_history_page_for_delegate = 22; + GetHistoryForWalletSelection get_history_for_wallet_selection = 23; + } +} + +message Wallet +{ + message GetNewAddress { + required uint32 type = 1; + } + message GetChangeAddress { + required uint32 type = 1; + } + message PeekChangeAddress { + required uint32 type = 1; + } + + message GetHighestUsedIndex{} + message ExtendAddressPool { + required uint32 count = 1; + required string callback_id = 2; + } + + message CreateBackupString{} + message Delete{} + message GetData{} + + message GetAddrCombinedList{} + message SetAddressTypeFor { + required string address = 1; + required uint32 address_type = 2; + } + + message GetLedgerDelegateIdForScrAddr { + required bytes hash = 1; + } + message GetBalanceAndCount{} + + message SetupNewCoinSelectionInstance{ + required uint32 height = 1; + } + message GetUtxosForValue { + required uint64 value = 1; + } + message GetSpendableZCList{} + message GetRBFTxOutList{} + + message CreateAddressBook{} + + message SetComment { + required bytes hash_key = 1; + required string comment = 2; + } + message SetLabels { + required string title = 1; + required string description = 2; + } + + //// + required string id = 1; + oneof method { + GetNewAddress get_new_address = 2; + GetChangeAddress get_change_address = 3; + PeekChangeAddress peek_change_address = 4; + + GetHighestUsedIndex get_highest_used_index = 10; + ExtendAddressPool extend_address_pool = 11; + + CreateBackupString create_backup_string = 20; + Delete delete = 21; + GetData get_data = 22; + + GetAddrCombinedList get_addr_combined_list = 30; + SetAddressTypeFor set_address_type_for = 31; + + GetLedgerDelegateIdForScrAddr get_ledger_delegate_id_for_scraddr = 40; + GetBalanceAndCount get_balance_and_count = 41; + + SetupNewCoinSelectionInstance setup_new_coin_selection_instance = 50; + GetUtxosForValue get_utxos_for_value = 51; + GetSpendableZCList get_spendable_zc_list = 52; + GetRBFTxOutList get_rbf_txout_list = 53; + + CreateAddressBook create_address_book = 60; + SetComment set_comment = 61; + SetLabels set_labels = 62; + } +} + +message CoinSelection +{ + message Cleanup{} + message Reset{} + + message SetRecipient { + required string address = 1; + required uint64 value = 2; + required uint32 id = 3; + } + + message SelectUTXOs { + required uint32 flags = 1; + oneof fee { + uint64 flat_fee = 10; + float fee_byte = 11; + } + } + + message GetUtxoSelection{} + message GetFlatFee{} + message GetFeeByte{} + message GetSizeEstimate{} + + message ProcessCustomUtxoList { + repeated Utxo utxos = 1; + required uint32 flags = 2; + oneof fee { + uint64 flat_fee = 10; + float fee_byte = 11; + } + } + + message GetFeeForMaxVal { + repeated Utxo utxos = 1; + optional float fee_byte = 2; + } + + //// + required string id = 1; + oneof method { + Cleanup cleanup = 2; + Reset reset = 3; + + SetRecipient set_recipient = 4; + SelectUTXOs select_utxos = 5; + + GetUtxoSelection get_utxo_selection = 6; + GetFlatFee get_flat_fee = 7; + GetFeeByte get_fee_byte = 8; + GetSizeEstimate get_size_estimate = 9; + + ProcessCustomUtxoList process_custom_utxo_list = 10; + GetFeeForMaxVal get_fee_for_max_val = 11; + } +} + +message Signer +{ + message GetNew{} + message Cleanup{} + + message SetVersion { + required uint32 version = 1; + } + message SetLockTime { + required uint32 lock_time = 1; + } + + message AddSpenderByOutpoint { + required bytes hash = 1; + required uint32 tx_out_id = 2; + required uint32 sequence = 3; + } + + message PopulateUtxo { + required bytes hash = 1; + required bytes script = 2; + required uint32 tx_out_id = 3; + required uint64 value = 4; + } + + message AddRecipient{ + required bytes script = 1; + required uint64 value = 2; + } + + message ToTxSigCollect { + required uint32 ustx_type = 1; + } + message FromTxSigCollect{ + required string tx_sig_collect = 1; + } + + message SignTx { + required string wallet_id = 1; + } + message GetSignedTx{} + message GetUnsignedTx{} + message GetSignedStateForInput { + required uint32 input_id = 1; + } + + message Resolve { + required string wallet_id = 1; + } + message AddSupportingTx { + required bytes raw_tx = 1; + } + + message FromType{} + message CanLegacySerialize{} + + //// + required string id = 1; + oneof method { + GetNew get_new = 2; + Cleanup cleanup = 3; + + SetVersion set_version = 4; + SetLockTime set_lock_time = 5; + + AddSpenderByOutpoint add_spender_by_outpoint = 6; + PopulateUtxo populate_utxo = 7; + AddRecipient add_recipient = 8; + + ToTxSigCollect to_tx_sig_collect = 9; + FromTxSigCollect from_tx_sig_collect = 10; + + SignTx sign_tx = 11; + GetSignedTx get_signed_tx = 12; + GetUnsignedTx get_unsigned_tx = 13; + GetSignedStateForInput get_signed_state_for_input = 14; + + Resolve resolve = 15; + AddSupportingTx add_supporting_tx = 16; + + FromType from_type = 17; + CanLegacySerialize can_legacy_serialize = 18; + } +} + +message Utils +{ + message GenerateRandomHex { + required uint32 length = 1; + } + message GetHash160 { + required bytes data = 1; + } + + message GetScrAddrForAddrStr { + required string address = 1; + } + message GetNameForAddrType { + required int32 address_type = 1; + } + + message CreateWalletStruct + { + required uint32 lookup = 1; + optional string passphrase = 10; + optional string control_passphrase = 11; + optional bytes extra_entropy = 20; + + optional string label = 30; + optional string description = 31; + } + + message CreateWallet { + required CreateWalletStruct wallet_struct = 1; + } + + oneof method { + GenerateRandomHex generate_random_hex = 1; + GetHash160 get_hash_160 = 2; + + GetScrAddrForAddrStr get_scraddr_for_addrstr = 3; + GetNameForAddrType get_name_for_addr_type = 4; + + CreateWallet create_wallet = 5; + } +} + +message ScriptUtils +{ + message GetTxInScriptType { + required bytes hash = 1; + } + message GetTxOutScriptType {} + + message GetScrAddrForScript{} + message GetLastPushDataInScript{} + message GetTxOutScriptForScrAddr{} + message GetAddrStrForScrAddr{} + + //// + required bytes script = 1; + oneof method { + GetTxInScriptType get_txin_script_type = 2; + GetTxOutScriptType get_txout_script_type = 3; + + GetScrAddrForScript get_scraddr_for_script = 10; + GetLastPushDataInScript get_last_push_data_in_script = 11; + GetTxOutScriptForScrAddr get_txout_script_for_scraddr = 12; + GetAddrStrForScrAddr get_addrstr_for_scraddr = 13; + } +} + +message MethodsWithCallback +{ + optional int32 followUp = 1; + optional int32 cleanup = 2; + + //restoreWallet = 10; + //returnPassphrase = 22; +} + +message Request +{ + required uint32 reference_id = 1; + + oneof method { + BlockchainService service = 20; + Wallet wallet = 21; + CoinSelection coin_selection = 22; + Signer signer = 23; + Utils utils = 24; + ScriptUtils script_utils = 25; + MethodsWithCallback callback = 26; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +//// Replies +// +//////////////////////////////////////////////////////////////////////////////// +message BlockchainServiceReply +{ + message BlockTime { + required uint32 timestamp = 1; + } + + message LedgerDelegateId { + required string id = 1; + } + + message HeaderData { + required bytes data = 1; + } + + message FeeEstimate + { + required float feeByte = 1; + required bool smartFee = 2; + } + + message LedgerHistory { + repeated Ledger ledger = 1; + } + + message Tx + { + required bytes raw = 2; + optional uint32 height = 10; + optional uint32 tx_index = 11; + optional bool rbf = 20; + optional bool chained_zc = 21; + } + + // + oneof reply { + BlockTime block_time = 10; + LedgerDelegateId ledger_delegate_id = 11; + HeaderData header_data = 12; + FeeEstimate fee_estimate = 13; + LedgerHistory ledger_history = 14; + Tx tx = 15; + NodeStatus node_status = 16; + } +} + +//// +message WalletReply +{ + message HighestUsedIndex { + required int32 index = 1; + } + + message CoinSelectionId { + required string id = 1; + } + + message LedgerDelegateId { + required string id = 1; + } + + message UtxoList { + repeated Utxo utxo = 1; + } + + //address book + message AddressBook { + message AddressBookEntry { + required bytes scraddr = 10; + repeated bytes tx_hash = 11; + } + + repeated AddressBookEntry address = 1; + } + + //wallet data + message Asset + { + required int32 id = 1; + required uint32 addr_type = 2; + required bool is_used = 3; + required bool is_change = 4; + required bytes asset_id = 5; + + required bytes prefixed_hash = 10; + required bytes public_key = 11; + optional bytes precursor_script = 12; + + required string address_string = 20; + } + + message WalletData + { + required string id = 1; + required int64 use_count = 2; + required int64 lookup_count = 3; + required bool watching_only = 4; + repeated uint32 address_type = 5; + required uint32 default_address_type = 6; + + optional string label = 10; + optional string desc = 11; + + repeated Asset asset = 20; + + message Comment + { + required bytes key = 1; + required bytes val = 2; + } + repeated Comment comments = 30; + } + + message MultipleWalletData { + repeated WalletData wallet = 1; + } + + message BackupString + { + repeated string root_clear = 10; + repeated string chain_clear = 11; + + repeated string root_encr = 20; + repeated string chain_encr = 21; + + optional string sp_pass = 30; + } + + //address balance + message AddressBalanceData { + required bytes id = 1; + required BalanceAndCount balance = 2; + } + + message AddressAndBalanceData { + repeated AddressBalanceData balance = 1; + repeated Asset updated_asset = 2; + } + + message BalanceAndCount { + required uint64 full = 1; + required uint64 spendable = 2; + required uint64 unconfirmed = 3; + required uint64 count = 4; + } + + // + oneof reply { + HighestUsedIndex highest_used_index = 10; + CoinSelectionId coin_selection_id = 11; + LedgerDelegateId ledger_delegate_id = 12; + BalanceAndCount balance_and_count = 13; + AddressAndBalanceData address_and_balance_data = 14; + UtxoList utxo_list = 15; + AddressBook address_book = 16; + + Asset asset = 20; + WalletData wallet_data = 21; + MultipleWalletData multiple_wallets = 22; + BackupString backup_string = 23; + } +} + +//// +message CoinSelectionReply +{ + message FlatFee { + required uint64 fee = 1; + } + + message FeeByte { + required float fee = 1; + } + + message SizeEstimate { + required uint32 size = 1; + } + + message UtxoList { + repeated Utxo utxo = 1; + } + + // + oneof reply { + FlatFee flat_fee = 10; + FeeByte fee_byte = 11; + SizeEstimate size_estimate = 12; + UtxoList utxo_list = 13; + } +} + +//// +message SignerReply +{ + message SignerId { + required string id = 1; + } + + message FromType { + required uint32 type = 1; + } + + message TxSigCollect { + required string data = 1; + } + + message TxData { + required bytes data = 1; + } + + message InputSignedState { + required bool isValid = 1; + required uint32 m = 2; + required uint32 n = 3; + + required uint32 sigCount = 10; + repeated PubKeySignatureState signStateList = 11; + + message PubKeySignatureState + { + required bytes pubKey = 1; + required bool hasSig = 2; + } + } + + // + oneof reply { + SignerId signer_id = 10; + FromType from_type = 11; + TxSigCollect tx_sig_collect = 12; + TxData tx_data = 13; + InputSignedState input_signed_state = 14; + } +} + +//// +message UtilsReply +{ + message RandomHex { + required string data = 1; + } + + message AddressTypeName { + required string name = 1; + } + + message WalletId { + required string id = 1; + } + + message Hash { + required bytes data = 1; + } + + // + oneof reply { + RandomHex random_hex = 10; + AddressTypeName address_type_name = 11; + WalletId wallet_id = 12; + Hash hash = 13; + } +} + +//// +message ScriptUtilsReply +{ + message TxInScriptType { + required uint32 script_type = 1; + } + + message TxOutScriptType { + required uint32 script_type = 1; + } + + message AddressString { + required string address = 1; + } + + message ScriptAddress { + required bytes scraddr = 1; + } + + message PushData { + required bytes data = 1; + } + + message ScriptData { + required bytes data = 1; + } + + // + oneof reply { + TxInScriptType txin_script_type = 10; + TxOutScriptType txout_script_type = 11; + AddressString address_string = 12; + ScriptAddress scraddr = 13; + PushData push_data = 14; + ScriptData script_data = 15; + } +} + +//// +message Reply +{ + required bool success = 1; + optional uint32 reference_id = 2; + optional string error = 3; + + oneof reply_type { + BlockchainServiceReply service = 10; + WalletReply wallet = 11; + CoinSelectionReply coin_selection = 12; + SignerReply signer = 13; + UtilsReply utils = 14; + ScriptUtilsReply script_utils = 15; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +//// Wrapper message +// +//////////////////////////////////////////////////////////////////////////////// +message Payload +{ + oneof payload { + Reply reply = 1; + Callback callback = 2; + } +} \ No newline at end of file diff --git a/cppForSwig/protobuf/ClientProto.proto b/cppForSwig/protobuf/ClientProto.proto deleted file mode 100644 index 217f947e5..000000000 --- a/cppForSwig/protobuf/ClientProto.proto +++ /dev/null @@ -1,436 +0,0 @@ -syntax = "proto2"; - -package Codec_ClientProto; - -enum Methods -{ - shutdown = 1; - - setupDB = 10; - goOnline = 11; - registerWallets = 12; - registerWallet = 13; - - loadWallets = 20; - getNodeStatus = 21; - returnPassphrase = 22; - broadcastTx = 23; - - getLedgerDelegateIdForWallets = 30; - updateWalletsLedgerFilter = 31; - getLedgerDelegateIdForScrAddr = 32; - - getHistoryPageForDelegate = 40; - getBalanceAndCount = 41; - getAddrCombinedList = 42; - getHistoryForWalletSelection = 43; - - getTxByHash = 70; - getHeaderByHeight = 71; - createAddressBook = 72; - setComment = 73; - setWalletLabels = 74; - - getNewAddress = 80; - getChangeAddress = 81; - peekChangeAddress = 82; - getHighestUsedIndex = 83; - extendAddressPool = 84; - createWallet = 85; - createBackupStringForWallet = 86; - deleteWallet = 87; - getWalletData = 88; - - generateRandomHex = 90; - getTxInScriptType = 91; - getTxOutScriptType = 92; - getScrAddrForScript = 93; - getLastPushDataInScript = 94; - getTxOutScriptForScrAddr = 95; - getAddrStrForScrAddr = 96; - getNameForAddrType = 97; - setAddressTypeFor = 98; - getScrAddrForAddrStr = 99; - - getUtxosForValue = 100; - getSpendableZCList = 101; - getRBFTxOutList = 102; - - setupNewCoinSelectionInstance = 110; - destroyCoinSelectionInstance = 111; - resetCoinSelection = 112; - setCoinSelectionRecipient = 113; - cs_SelectUTXOs = 114; - cs_getUtxoSelection = 115; - cs_getFlatFee = 116; - cs_getFeeByte = 117; - cs_getSizeEstimate = 118; - cs_ProcessCustomUtxoList = 119; - cs_getFeeForMaxVal = 120; - cs_getFeeForMaxValUtxoVector = 121; - - initNewSigner = 130; - destroySigner = 131; - signer_SetVersion = 132; - signer_SetLockTime = 133; - signer_addSpenderByOutpoint = 134; - signer_populateUtxo = 135; - signer_addRecipient = 136; - signer_toTxSigCollect = 137; - signer_fromTxSigCollect = 138; - signer_signTx = 139; - signer_getSignedTx = 140; - signer_getUnsignedTx = 141; - signer_getSignedStateForInput = 142; - signer_resolve = 143; - signer_addSupportingTx = 144; - signer_fromType = 145; - signer_canLegacySerialize = 146; - - getHash160 = 200; - getBlockTimeByHeight = 201; - estimateFee = 202; - - methodWithCallback = 220; -} - -enum MethodsWithCallback -{ - followUp = 1; - cleanup = 2; - - restoreWallet = 10; -} - -message ClientCommand -{ - required Methods method = 1; - required uint32 payloadId = 2; - - repeated string stringArgs = 3; - repeated uint32 intArgs = 4; - repeated bytes byteArgs = 5; - repeated uint64 longArgs = 6; - repeated float floatArgs = 7; - - optional MethodsWithCallback methodWithCallback = 10; -} - -//////////////////////////////////////////////////////////////////////////////// -// wallet public data -message WalletAsset -{ - required int32 id = 1; - required uint32 addrType = 2; - required bool isUsed = 3; - required bool isChange = 4; - required bytes assetId = 5; - - required bytes prefixedHash = 10; - required bytes publicKey = 11; - optional bytes precursorScript = 12; - - required string addressString = 20; -} - -message WalletData -{ - required string id = 1; - required int64 useCount = 2; - required int64 lookupCount = 3; - required bool watchingOnly = 4; - repeated uint32 addressTypes = 5; - required uint32 defaultAddressType = 6; - - optional string label = 10; - optional string desc = 11; - - repeated WalletAsset assets = 20; - - message Comment - { - required bytes key = 1; - required bytes val = 2; - } - - repeated Comment comments = 30; -} - -message WalletPayload -{ - repeated WalletData wallets = 1; -} - -//////////////////////////////////////////////////////////////////////////////// -// BDV callback -message CppBridgeCallbackMsg -{ - required uint32 type = 1; - - optional uint32 height = 10; - repeated string ids = 11; - - repeated bytes opaque = 20; -} - -message CppProgressCallbackMsg -{ - optional uint32 phase = 1; - required float progress = 2; - optional uint32 etaSec = 3; - required uint32 progressNumeric = 4; - - repeated string ids = 10; -} - -//////////////////////////////////////////////////////////////////////////////// -// Unlock prompt -enum UnlockPromptState -{ - start = 1; - stop = 2; - cycle = 3; -} - -enum UnlockPromptType -{ - decrypt = 1; - migrate = 2; -} - -message UnlockPromptCallback -{ - required bytes promptID = 1; - required UnlockPromptType promptType = 2; - required string verbose = 3; - required UnlockPromptState state = 4; - optional string walletID = 5; -} - -//////////////////////////////////////////////////////////////////////////////// -message BridgeLedger -{ - required int64 value = 1; - required bytes hash = 2; - required string id = 3; - required uint32 height = 4; - required uint32 txIndex = 5; - required uint32 txTime = 6; - required bool isCoinbase = 7; - required bool isSentToSelf = 8; - required bool isChangeBack = 9; - required bool isChainedZC = 10; - required bool isWitness = 11; - required bool isRBF = 12; - repeated bytes scrAddrList = 13; -} - -message BridgeLedgers -{ - repeated BridgeLedger le = 1; -} - -message BridgeNodeChainStatus -{ - required uint32 chainState = 2; - required float blockSpeed = 3; - required float progressPct = 4; - required uint64 eta = 5; - required uint32 blocksLeft = 6; -} - -message BridgeNodeStatus -{ - required bool isValid = 1; - - optional uint32 nodeState = 10; - optional bool isSegWitEnabled = 11; - optional uint32 rpcState = 12; - optional BridgeNodeChainStatus chainStatus = 13; -} - -message BridgeBalanceAndCount -{ - required uint64 full = 1; - required uint64 spendable = 2; - required uint64 unconfirmed = 3; - required uint64 count = 4; -} - -message BridgeMultipleBalanceAndCount -{ - repeated bytes ids = 1; - repeated BridgeBalanceAndCount data = 2; - repeated WalletAsset updatedAssets = 3; -} - -message BridgeTx -{ - required bool isValid = 1; - optional bytes raw = 2; - - optional uint32 height = 10; - optional uint32 txIndex = 11; - - optional bool isRBF = 20; - optional bool isChainedZC = 21; -} - -message ReplyBinary -{ - repeated bytes reply = 30; -} - -message ReplyStrings -{ - repeated string reply = 31; -} - -message ReplyNumbers -{ - repeated uint32 ints = 40; - repeated uint64 longs = 41; - repeated float floats = 42; -} - -message BridgeAddressBookEntry -{ - required bytes scrAddr = 1; - repeated bytes txHashes = 2; -} - -message BridgeAddressBook -{ - repeated BridgeAddressBookEntry data = 1; -} - -message BridgeUtxo -{ - required bytes txHash = 1; - required uint32 txOutIndex = 2; - - required uint64 value = 3; - required uint32 txHeight = 4; - required uint32 txIndex = 5; - - required bytes script = 6; - required bytes scrAddr = 7; -} - -message BridgeUtxoList -{ - repeated BridgeUtxo data = 1; -} - -message BridgeInputSignedState -{ - required bool isValid = 1; - required uint32 m = 2; - required uint32 n = 3; - - required uint32 sigCount = 10; - repeated PubKeySignatureState signStateList = 11; - - message PubKeySignatureState - { - required bytes pubKey = 1; - required bool hasSig = 2; - } -} - -message ReplyError -{ - required bool isError = 24; - required string error = 25; - optional uint32 code = 26; -} - -message BridgeFeeEstimate -{ - required float feeByte = 1; - optional bool smartFee = 2; - optional string error = 3; -} - -//////////////////////////////////////////////////////////////////////////////// -// Wallet creation messages -message BridgeCreateWalletStruct -{ - required uint32 lookup = 1; - - optional string passphrase = 10; - optional string controlPassphrase = 11; - - optional bytes extraEntropy = 20; - - optional string label = 30; - optional string description = 31; -} - -message BridgeBackupString -{ - required bool isvalid = 1; - - repeated string rootclear = 10; - repeated string chainclear = 11; - - repeated string rootencr = 20; - repeated string chainencr = 21; - - optional string sppass = 5; -} - -//////////////////////////////////////////////////////////////////////////////// -// Opaque payloads - -enum OpaquePayloadType -{ - prompt = 1; - commandWithCallback = 2; -} - -message OpaquePayload -{ - required OpaquePayloadType payloadType = 1; - optional bytes uniqueId = 2; - optional uint32 intId = 3; - - optional bytes payload = 10; -} - -//////////////////////////////////////////////////////////////////////////////// -// RestoreWallet messages -message RestoreWalletPayload -{ - repeated string root = 1; - repeated string secondary = 2; - optional string spPass = 3; -} - -enum RestorePromptType -{ - FormatError = 1; - Failure = 2; - ChecksumError = 3; - DecryptError = 4; - Passphrase = 5; - Control = 6; - Id = 7; - TypeError = 8; - Success = 9; - UnknownError = 10; -} - -message RestorePrompt -{ - - required RestorePromptType promptType = 1; - repeated int32 checksums = 2; - optional string extra = 3; -} - -message RestoreReply -{ - required bool result = 1; - optional bytes extra = 2; -} From 48c16734f1657fa90e8eb009924c671788def7ab Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 14 Apr 2023 11:14:55 +0200 Subject: [PATCH 38/47] [ARMORY-8] python side --- .gitignore | 3 +- ArmoryQt.py | 428 ++-- armoryengine/ArmoryUtils.py | 334 +-- armoryengine/BDM.py | 161 +- armoryengine/CoinSelection.py | 14 +- armoryengine/CppBridge.py | 1925 ++++++++--------- armoryengine/PyBtcAddress.py | 20 +- armoryengine/PyBtcWallet.py | 157 +- armoryengine/Settings.py | 208 ++ armoryengine/Transaction.py | 227 +- armoryengine/UserAddressUtils.py | 12 +- armorymodels.py | 32 +- configure.ac | 4 +- cppForSwig/ArmoryBackups.cpp | 4 +- cppForSwig/ArmoryConfig.cpp | 1 + cppForSwig/BridgeAPI/BridgeMain.cpp | 22 +- cppForSwig/BridgeAPI/BridgeSocket.cpp | 11 +- cppForSwig/BridgeAPI/CppBridge.cpp | 599 ++--- cppForSwig/BridgeAPI/CppBridge.h | 105 +- cppForSwig/BridgeAPI/PassphrasePrompt.cpp | 137 +- cppForSwig/BridgeAPI/PassphrasePrompt.h | 41 +- .../BridgeAPI/ProtobufCommandParser.cpp | 173 +- cppForSwig/BridgeAPI/ProtobufCommandParser.h | 6 +- cppForSwig/BridgeAPI/ProtobufConversions.cpp | 74 +- cppForSwig/BridgeAPI/ProtobufConversions.h | 8 +- cppForSwig/BridgeAPI/WalletManager.cpp | 10 +- cppForSwig/BridgeAPI/WalletManager.h | 3 + cppForSwig/BtcUtils.cpp | 2 +- cppForSwig/BtcUtils.h | 2 +- cppForSwig/Signer/Signer.cpp | 11 +- cppForSwig/Wallets/AssetEncryption.cpp | 20 + cppForSwig/Wallets/AssetEncryption.h | 12 +- cppForSwig/Wallets/DecryptedDataContainer.cpp | 30 +- cppForSwig/Wallets/DecryptedDataContainer.h | 14 +- cppForSwig/Wallets/Wallets.cpp | 16 + cppForSwig/Wallets/Wallets.h | 5 +- cppForSwig/gtest/SupernodeTests.cpp | 2 +- cppForSwig/gtest/WalletTests.cpp | 4 +- cppForSwig/protobuf/BridgeProto.proto | 443 ++-- qtdialogs/ArmoryDialog.py | 7 +- qtdialogs/DlgAddressBook.py | 28 +- qtdialogs/DlgAddressInfo.py | 416 ++++ qtdialogs/DlgBackupCenter.py | 64 +- qtdialogs/DlgChangePassphrase.py | 2 +- qtdialogs/DlgDispTxInfo.py | 61 +- qtdialogs/DlgEULA.py | 5 +- qtdialogs/DlgExportTxHistory.py | 4 +- qtdialogs/DlgInflatedQR.py | 43 - qtdialogs/DlgIntroMessage.py | 4 +- qtdialogs/DlgKeypoolSettings.py | 6 +- qtdialogs/DlgMigrateWallet.py | 14 +- qtdialogs/DlgNewAddress.py | 8 +- qtdialogs/DlgOfflineTx.py | 17 +- qtdialogs/DlgPasswd3.py | 6 +- qtdialogs/DlgProgress.py | 11 +- qtdialogs/DlgRequestPayment.py | 8 +- qtdialogs/DlgRestore.py | 1619 ++++++++++++++ qtdialogs/DlgRestoreFragged.py | 588 ----- qtdialogs/DlgRestoreSingle.py | 407 ---- qtdialogs/DlgRestoreWOData.py | 274 --- qtdialogs/DlgSendBitcoins.py | 4 +- qtdialogs/DlgSettings.py | 106 +- qtdialogs/DlgShowKeyList.py | 4 +- qtdialogs/DlgUniversalRestoreSelect.py | 5 +- qtdialogs/DlgUnlockWallet.py | 95 +- qtdialogs/DlgWalletDetails.py | 67 +- qtdialogs/QRCodeWidget.py | 35 +- qtdialogs/qtdefines.py | 164 +- qtdialogs/qtdialogs.py | 863 +------- samplemodules/DustBGonePlugin.py | 2 +- ui/AddressTypeSelectDialog.py | 2 +- ui/CoinControlUI.py | 12 +- ui/FeeSelectUI.py | 50 +- ui/MultiSigDialogs.py | 18 +- ui/QrCodeMatrix.py | 2 +- ui/QtExecuteSignal.py | 43 +- ui/TxFrames.py | 159 +- ui/TxFramesOffline.py | 39 +- ui/WalletFrames.py | 109 +- 79 files changed, 4937 insertions(+), 5714 deletions(-) create mode 100644 armoryengine/Settings.py create mode 100644 qtdialogs/DlgAddressInfo.py delete mode 100644 qtdialogs/DlgInflatedQR.py create mode 100644 qtdialogs/DlgRestore.py delete mode 100644 qtdialogs/DlgRestoreFragged.py delete mode 100644 qtdialogs/DlgRestoreSingle.py delete mode 100644 qtdialogs/DlgRestoreWOData.py diff --git a/.gitignore b/.gitignore index 6d588bcce..bc5215624 100755 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,7 @@ _CppBlockUtils.* /cppForSwig/gtest/*.lmdb* /cppForSwig/gtest/DB1kIterTest /cppForSwig/gtest/SignerTests +/cppForSwig/gtest/fakehomedir/* /cppForSwig/libbtc/bitcoin-spv @@ -176,7 +177,7 @@ test-driver /cppForSwig/protobuf/*.pb.* /cppForSwig/x64/*.* CppBridge -armoryengine/ClientProto_pb2.py +armoryengine/BridgeProto_pb2.py cppForSwig/gtest/UtilsTests cppForSwig/gtest/ZeroConfTests c20p1305_cffi/c20p1305.c diff --git a/ArmoryQt.py b/ArmoryQt.py index 725deb08f..8d8b29560 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -6,7 +6,7 @@ # Distributed under the GNU Affero General Public License (AGPL v3) # # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # -# Copyright (C) 2016-17, goatpig # +# Copyright (C) 2016-2023, goatpig # # Distributed under the MIT license # # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # @@ -27,7 +27,6 @@ import platform import random import shutil -import signal import socket import subprocess import sys @@ -48,20 +47,21 @@ QLineEdit, QFileDialog, QApplication, QWhatsThis from armorycolors import Colors, htmlColor, QAPP -from armoryengine.ArmoryUtils import HMAC256, GUI_LANGUAGE, \ +from armoryengine.ArmoryUtils import HMAC256, \ OS_LINUX, OS_MACOSX, OS_WINDOWS, AllowAsync, USE_TESTNET, USE_REGTEST, \ - CLI_OPTIONS, SettingsFile, getVersionString, BTCARMORY_VERSION, \ + CLI_OPTIONS, getVersionString, BTCARMORY_VERSION, \ LOGINFO, LOGWARN, LOGDEBUG, LOGEXCEPT, LOGERROR, INTERNET_STATUS, \ enum, GetExecDir, RightNow, CLI_ARGS, ARMORY_HOME_DIR, DEFAULT, \ ARMORY_DB_DIR, coin2str, DEFAULT_DATE_FORMAT, \ unixTimeToFormatStr, binary_to_hex, BTC_HOME_DIR, secondsToHumanTime, \ LEVELDB_BLKDATA, LOGRAWDATA, LOGPPRINT, hex_to_binary, \ getRandomHexits_NotSecure, coin2strNZS, bytesToHumanSize, hash256, \ - DEFAULT_ADDR_TYPE, hex_switchEndian, BLOCKEXPLORE_NAME + DEFAULT_ADDR_TYPE, hex_switchEndian, BLOCKEXPLORE_NAME, getBridgeArgList +from armoryengine.Settings import TheSettings from armoryengine.Block import PyBlock from armoryengine.Decorators import RemoveRepeatingExtensions -from armoryengine.CppBridge import TheBridge +from armoryengine.CppBridge import TheBridge, ServerPush from armoryengine.UserAddressUtils import getScriptForUserStringImpl, \ getDisplayStringForScriptImpl from armoryengine.BDM import TheBDM, \ @@ -75,14 +75,14 @@ from armoryengine.PyBtcWallet import PyBtcWallet from armoryengine.Transaction import PyTx -from armoryengine import ClientProto_pb2 +from armoryengine import BridgeProto_pb2 from qtdialogs.qtdefines import GETFONT, NETWORKMODE, \ QRichLabel_AutoToolTip, tightSizeNChar, USERMODE, initialColResize, \ makeLayoutFrame, HORIZONTAL, QRichLabel, relaxedSizeStr, STYLE_SUNKEN, \ makeHorizFrame, DASHBTNS, STYLE_NONE, UserModeStr, makeVertFrame, \ restoreTableView, determineWalletType, WLTTYPES, tightSizeStr, \ - QLabelButton, MSGBOX, saveTableView + QLabelButton, MSGBOX, saveTableView, createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.qtdialogs import URLHandler, ArmorySplashScreen, LoadingDisp @@ -108,7 +108,7 @@ from qtdialogs.DlgUniversalRestoreSelect import DlgUniversalRestoreSelect -from ui.QtExecuteSignal import QtExecuteSignal +from ui.QtExecuteSignal import TheSignalExecution from armorymodels import AllWalletsDispModel, AllWalletsCheckboxDelegate, \ WLTVIEWCOLS, LedgerDispModelSimple, LedgerDispDelegate, LEDGERCOLS @@ -130,22 +130,6 @@ ChainStatus_Ready = 2 #### - -# Setup translations -translator = QTranslator(QAPP) - -app_dir = "./" -try: - app_dir = os.path.dirname(os.path.realpath(__file__)) -except: - if OS_WINDOWS and getattr(sys, 'frozen', False): - app_dir = os.path.dirname(sys.executable) - -translator.load(GUI_LANGUAGE, os.path.join(app_dir, "lang/")) -QAPP.installTranslator(translator) - - - if sys.version_info < (3,0): import qrc_img_resources from ui.MultiSigDialogs import DlgSelectMultiSigOption, DlgLockboxManager, \ @@ -191,10 +175,6 @@ def __init__(self, parent=None, splashScreen=None): self.isShuttingDown = False self.ledgerView = None - # Load the settings file - self.settingsPath = CLI_OPTIONS.settingsPath - self.settings = SettingsFile(self.settingsPath) - # SETUP THE WINDOWS DECORATIONS self.lblLogoIcon = QLabel() if USE_TESTNET: @@ -291,13 +271,11 @@ def __init__(self, parent=None, splashScreen=None): thread. QtExecuteSignal is a utility class that handles the signaling and delaying/threading of execution ''' - self.signalExecution = QtExecuteSignal(self) #push model BDM notify signal - def cppNotifySignal(action, arglist): - fullArgList = [action, arglist] - self.signalExecution.executeMethod(\ - [self.handleCppNotification, fullArgList]) + def cppNotifySignal(action, *arglist): + TheSignalExecution.executeMethod(self.handleCppNotification, + action, *arglist) TheBDM.registerCppNotification(cppNotifySignal) TheBDM.registerUserPrompt(self.promptUser) @@ -306,12 +284,12 @@ def cppNotifySignal(action, arglist): # We want to determine whether the user just upgraded to a new version self.firstLoadNewVersion = False currVerStr = 'v'+getVersionString(BTCARMORY_VERSION) - if self.settings.hasSetting('LastVersionLoad'): - lastVerStr = self.settings.get('LastVersionLoad') + if TheSettings.hasSetting('LastVersionLoad'): + lastVerStr = TheSettings.get('LastVersionLoad') if not lastVerStr==currVerStr: LOGINFO('First load of new version: %s', currVerStr) self.firstLoadNewVersion = True - self.settings.set('LastVersionLoad', currVerStr) + TheSettings.set('LastVersionLoad', currVerStr) # Because dynamically retrieving addresses for querying transaction # comments can be so slow, I use this txAddrMap to cache the mappings @@ -320,12 +298,12 @@ def cppNotifySignal(action, arglist): # exist and this is needed to accommodate wallets with lots of them. self.txAddrMap = {} - eulaAgreed = self.getSettingOrSetDefault('Agreed_to_EULA', False) + eulaAgreed = TheSettings.getSettingOrSetDefault('Agreed_to_EULA', False) if not eulaAgreed: DlgEULA(self,self).exec_() DEFAULT_ADDR_TYPE = \ - self.getSettingOrSetDefault('Default_ReceiveType', 'P2PKH') + TheSettings.getSettingOrSetDefault('Default_ReceiveType', 'P2PKH') if not self.abortLoad: @@ -340,12 +318,12 @@ def cppNotifySignal(action, arglist): # strange behavior if the user changes the setting but hasn't # restarted yet... self.doAutoBitcoind = \ - self.getSettingOrSetDefault('ManageSatoshi', not OS_MACOSX) + TheSettings.getSettingOrSetDefault('ManageSatoshi', not OS_MACOSX) # This is a list of alerts that the user has chosen to no longer # be notified about - alert_str = str(self.getSettingOrSetDefault('IgnoreAlerts', "")) + alert_str = str(TheSettings.getSettingOrSetDefault('IgnoreAlerts', "")) if alert_str == "": alerts = [] else: @@ -401,8 +379,8 @@ def cppNotifySignal(action, arglist): initialColResize(self.walletsView, [20, 0.15, 0.30, 0.2, 0.20]) - if self.settings.hasSetting('LastFilterState'): - if self.settings.get('LastFilterState')==4: + if TheSettings.hasSetting('LastFilterState'): + if TheSettings.get('LastFilterState')==4: self.walletsView.showColumn(0) @@ -473,11 +451,11 @@ def cppNotifySignal(action, arglist): self.lblBTC1 = QRichLabel('BTC', doWrap=False) self.lblBTC2 = QRichLabel('BTC', doWrap=False) self.lblBTC3 = QRichLabel('BTC', doWrap=False) - self.ttipTot = self.createToolTipWidget( self.tr( + self.ttipTot = createToolTipWidget( self.tr( 'Funds if all current transactions are confirmed. ' 'Value appears gray when it is the same as your spendable funds.')) - self.ttipSpd = self.createToolTipWidget( self.tr('Funds that can be spent right now')) - self.ttipUcn = self.createToolTipWidget( self.tr( + self.ttipSpd = createToolTipWidget( self.tr('Funds that can be spent right now')) + self.ttipUcn = createToolTipWidget( self.tr( 'Funds that have less than 6 confirmations, and thus should not ' 'be considered yours, yet.')) @@ -575,7 +553,7 @@ def cppNotifySignal(action, arglist): ########################################################################## # Set up menu and actions #MENUS = enum('File', 'Wallet', 'User', "Tools", "Network") - currmode = self.getSettingOrSetDefault('User_Mode', 'Advanced') + currmode = TheSettings.getSettingOrSetDefault('User_Mode', 'Advanced') MENUS = enum('File', 'User', 'Tools', 'Addresses', 'Wallets', \ 'MultiSig', 'Help') self.menu = self.menuBar() @@ -754,9 +732,9 @@ def msrevsign(): self.menusList[MENUS.MultiSig].addAction(actMultiSpend) # Restore any main-window geometry saved in the settings file - hexgeom = self.settings.get('MainGeometry') + hexgeom = TheSettings.get('MainGeometry') - hexwltsz = self.settings.get('MainWalletCols') + hexwltsz = TheSettings.get('MainWalletCols') if len(hexgeom)>0: #QByteArray is weak sauce, have to deser the hexit on our own geom = QByteArray(bytes.fromhex(hexgeom)) @@ -767,12 +745,12 @@ def msrevsign(): self.blkReceived = RightNow() self.setDashboardDetails() - if self.getSettingOrSetDefault('MinimizeOnOpen', False) and not CLI_ARGS: + if TheSettings.getSettingOrSetDefault('MinimizeOnOpen', False) and not CLI_ARGS: LOGINFO('MinimizeOnOpen is True') self.minimizeArmory() if CLI_ARGS: - self.signalExecution.callLater(1, self.uriLinkClicked, CLI_ARGS[0]) + TheSignalExecution.callLater(1, self.uriLinkClicked, CLI_ARGS[0]) if OS_MACOSX: self.macNotifHdlr = ArmoryMac.MacNotificationHandler() @@ -802,7 +780,7 @@ def msrevsign(): # 'the Bitcoin directory specified in the command line could ' # 'not be found.'), QMessageBox.Ok) - if not self.getSettingOrSetDefault('DNAA_DeleteLevelDB', False) and \ + if not TheSettings.getSettingOrSetDefault('DNAA_DeleteLevelDB', False) and \ os.path.exists(os.path.join(ARMORY_DB_DIR, LEVELDB_BLKDATA)): reply = MsgBoxWithDNAA(self, self, MSGBOX.Question, \ @@ -817,13 +795,16 @@ def msrevsign(): shutil.rmtree(os.path.join(ARMORY_DB_DIR, LEVELDB_HEADERS)) if reply[1]==True: - self.writeSetting('DNAA_DeleteLevelDB', True) + TheSettings.set('DNAA_DeleteLevelDB', True) - #################################################### + ############################################################################# def networkReadyCallback(self): - TheBridge.loadWallets(self.loadWallets) + #this ServerPush obj should be a child class implementating the handling + #of the bridge server requests + pushObj = ServerPush() + TheBridge.service.loadWallets(self.loadWallets, pushObj) - #################################################### + ############################################################################# def getWatchingOnlyWallets(self): result = [] for wltID in self.walletIDList: @@ -832,7 +813,7 @@ def getWatchingOnlyWallets(self): return result - #################################################### + ############################################################################# def changeWltFilter(self): if self.netMode == NETWORKMODE.Offline: @@ -892,7 +873,7 @@ def changeWltFilter(self): if self.walletMap[wltid].isEnabled: self.wltIDList.append(wltid) - TheBridge.updateWalletsLedgerFilter(self.wltIDList) + TheBridge.service.updateWalletsLedgerFilter(self.wltIDList) ############################################################################ @@ -1467,7 +1448,7 @@ def setupUriRegistration(self, justDoIt=False): ############################################################################# def warnNewUSTXFormat(self): - if not self.getSettingOrSetDefault('DNAA_Version092Warn', False): + if not TheSettings.getSettingOrSetDefault('DNAA_Version092Warn', False): reply = MsgBoxWithDNAA(self, self, MSGBOX.Warning, self.tr("Version Warning"), self.tr( 'Since Armory version 0.92 the formats for offline transaction ' 'operations has changed to accommodate multi-signature ' @@ -1478,7 +1459,7 @@ def warnNewUSTXFormat(self): 'to version 0.92 or later. If you cannot upgrade the other ' 'system, you will need to reinstall an older version of Armory ' 'on this system.'), dnaaMsg=self.tr('Do not show this warning again')) - self.writeSetting('DNAA_Version092Warn', reply[1]) + TheSettings.set('DNAA_Version092Warn', reply[1]) ############################################################################# @@ -1512,12 +1493,12 @@ def openToolsDlg(self): ############################################################################# def execIntroDialog(self): - if not self.getSettingOrSetDefault('DNAA_IntroDialog', False): + if not TheSettings.getSettingOrSetDefault('DNAA_IntroDialog', False): dlg = DlgIntroMessage(self, self) result = dlg.exec_() if dlg.chkDnaaIntroDlg.isChecked(): - self.writeSetting('DNAA_IntroDialog', True) + TheSettings.set('DNAA_IntroDialog', True) if dlg.requestCreate: self.startWalletWizard() @@ -1618,15 +1599,15 @@ def createAction(self, txt, slot, isCheckable=False, \ ############################################################################# def setUserMode(self, mode): LOGINFO('Changing usermode:') - LOGINFO(' From: %s', self.settings.get('User_Mode')) + LOGINFO(' From: %s', TheSettings.get('User_Mode')) self.usermode = mode if mode==USERMODE.Standard: - self.writeSetting('User_Mode', 'Standard') + TheSettings.set('User_Mode', 'Standard') if mode==USERMODE.Advanced: - self.writeSetting('User_Mode', 'Advanced') + TheSettings.set('User_Mode', 'Advanced') if mode==USERMODE.Expert: - self.writeSetting('User_Mode', 'Expert') - LOGINFO(' To: %s', self.settings.get('User_Mode')) + TheSettings.set('User_Mode', 'Expert') + LOGINFO(' To: %s', TheSettings.get('User_Mode')) if not self.firstModeSwitch: QMessageBox.information(self,self.tr('Restart Armory'), @@ -1638,10 +1619,10 @@ def setUserMode(self, mode): ############################################################################# def setLang(self, lang): LOGINFO('Changing language:') - LOGINFO(' From: %s', self.settings.get('Language')) + LOGINFO(' From: %s', TheSettings.get('Language')) self.language = lang - self.writeSetting("Language", lang) - LOGINFO(' To: %s', self.settings.get('Language')) + TheSettings.set("Language", lang) + LOGINFO(' To: %s', TheSettings.get('Language')) if not self.firstModeSwitch: QMessageBox.information(self, self.tr('Restart Armory'), @@ -1655,7 +1636,7 @@ def getPreferredDateFormat(self): # Treat the format as "binary" to make sure any special symbols don't # interfere with the SettingsFile symbols globalDefault = hexlify(DEFAULT_DATE_FORMAT.encode('ascii')).decode('ascii') - fmt = self.getSettingOrSetDefault('DateFormat', globalDefault) + fmt = TheSettings.getSettingOrSetDefault('DateFormat', globalDefault) return unhexlify(fmt).decode('utf-8') # short hex strings could look like int() ############################################################################# @@ -1670,7 +1651,7 @@ def setPreferredDateFormat(self, fmtStr): 'it using only the strftime symbols shown in the help text.'), QMessageBox.Ok) return False - self.writeSetting('DateFormat', hexlify(fmtStr.encode('utf-8')).decode('ascii')) + TheSettings.set('DateFormat', hexlify(fmtStr.encode('utf-8')).decode('ascii')) return True ############################################################################# @@ -1695,8 +1676,8 @@ def uriClick_partial(a): ############################################################################ def notifyBitcoindIsReady(self): - self.signalExecution.executeMethod(\ - [self.completeBlockchainProcessingInitialization], []) + TheSignalExecution.executeMethod( + self.completeBlockchainProcessingInitialization) ############################################################################ def setSatoshiPaths(self): @@ -1704,21 +1685,21 @@ def setSatoshiPaths(self): # We skip the getSettingOrSetDefault call, because we don't want to set # it if it doesn't exist - if self.settings.hasSetting('SatoshiExe'): - if not os.path.exists(self.settings.get('SatoshiExe')): + if TheSettings.hasSetting('SatoshiExe'): + if not os.path.exists(TheSettings.get('SatoshiExe')): LOGERROR('Bitcoin installation setting is a non-existent directory') - self.satoshiExeSearchPath = [self.settings.get('SatoshiExe')] + self.satoshiExeSearchPath = [TheSettings.get('SatoshiExe')] else: self.satoshiExeSearchPath = [] self.satoshiHomePath = BTC_HOME_DIR - if self.settings.hasSetting('SatoshiDatadir'): + if TheSettings.hasSetting('SatoshiDatadir'): # Setting override BTC_HOME_DIR only if it wasn't explicitly # set as the command line. - manageSatoshi = self.settings.get('ManageSatoshi') + manageSatoshi = TheSettings.get('ManageSatoshi') if manageSatoshi == True: - self.satoshiHomePath = str(self.settings.get('SatoshiDatadir')) + self.satoshiHomePath = str(TheSettings.get('SatoshiDatadir')) LOGINFO('Setting satoshi datadir = %s' % self.satoshiHomePath) TheBDM.setSatoshiDir(self.satoshiHomePath) @@ -1738,10 +1719,10 @@ def loadBlockchainIfNecessary(self): # Track number of times we start loading the blockchain. # We will decrement the number when loading finishes # We can use this to detect problems with mempool or blkxxxx.dat - self.numTriesOpen = self.getSettingOrSetDefault('FailedLoadCount', 0) + self.numTriesOpen = TheSettings.getSettingOrSetDefault('FailedLoadCount', 0) if self.numTriesOpen>2: self.loadFailedManyTimesFunc(self.numTriesOpen) - self.settings.set('FailedLoadCount', self.numTriesOpen+1) + TheSettings.set('FailedLoadCount', self.numTriesOpen+1) ############################################################################# def switchNetworkMode(self, newMode): @@ -1882,34 +1863,34 @@ def uriLinkClicked(self, uriStr): def loadSettings(self): LOGINFO('Loading settings...') - self.getSettingOrSetDefault('First_Load', True) - self.getSettingOrSetDefault('Load_Count', 0) - self.getSettingOrSetDefault('User_Mode', 'Advanced') - self.getSettingOrSetDefault('UnlockTimeout', 10) - self.getSettingOrSetDefault('DNAA_UnlockTimeout', False) + TheSettings.getSettingOrSetDefault('First_Load', True) + TheSettings.getSettingOrSetDefault('Load_Count', 0) + TheSettings.getSettingOrSetDefault('User_Mode', 'Advanced') + TheSettings.getSettingOrSetDefault('UnlockTimeout', 10) + TheSettings.getSettingOrSetDefault('DNAA_UnlockTimeout', False) # Determine if we need to do new-user operations, increment load-count - if self.getSettingOrSetDefault('First_Load', True): + if TheSettings.getSettingOrSetDefault('First_Load', True): self.firstLoad = True - self.writeSetting('First_Load', False) - self.writeSetting('First_Load_Date', int(RightNow())) - self.writeSetting('Load_Count', 1) - self.writeSetting('AdvFeature_UseCt', 0) + TheSettings.set('First_Load', False) + TheSettings.set('First_Load_Date', int(RightNow())) + TheSettings.set('Load_Count', 1) + TheSettings.set('AdvFeature_UseCt', 0) else: - self.writeSetting('Load_Count', (self.settings.get('Load_Count')+1) % 100) + TheSettings.set('Load_Count', (TheSettings.get('Load_Count')+1) % 100) # Set the usermode, default to standard self.usermode = USERMODE.Standard - if self.settings.get('User_Mode') == 'Advanced': + if TheSettings.get('User_Mode') == 'Advanced': self.usermode = USERMODE.Advanced - elif self.settings.get('User_Mode') == 'Expert': + elif TheSettings.get('User_Mode') == 'Expert': self.usermode = USERMODE.Expert # Set the language, default to English self.language = 'en' - if self.settings.get('Language') != '': - self.language = self.settings.get('Language') + if TheSettings.get('Language') != '': + self.language = TheSettings.get('Language') # The user may have asked to never be notified of a particular @@ -1917,7 +1898,7 @@ def loadSettings(self): # load), and a long-term list (saved in settings). We simply # initialize the short-term list with the long-term list, and add # short-term ignore requests to it - notifyStr = self.getSettingOrSetDefault('NotifyIgnore', '') + notifyStr = TheSettings.getSettingOrSetDefault('NotifyIgnore', '') nsz = len(notifyStr) self.notifyIgnoreLong = set(notifyStr[8*i:8*(i+1)] for i in range(nsz//8)) self.notifyIgnoreShort = set(notifyStr[8*i:8*(i+1)] for i in range(nsz//8)) @@ -1940,13 +1921,17 @@ def loadSettings(self): self.promptMap = {} ############################################################################# - def loadWallets(self, walletsPayload): + def loadWallets(self, proto): LOGINFO('Loading wallets...') - wltExclude = self.settings.get('Excluded_Wallets', expectList=True) + wltExclude = TheSettings.get('Excluded_Wallets', expectList=True) + if not proto.success: + LOGERROR(f"failed to load wallets wit error: {proto.error}") + raise Exception("failed to load wallets") - for wltProto in walletsPayload.wallets: - wltLoad = PyBtcWallet() - wltLoad.loadFromProtobufPayload(wltProto) + walletsPayload = proto.wallet.multiple_wallets + + for wltProto in walletsPayload.wallet: + wltLoad = PyBtcWallet(proto=wltProto) wltID = wltLoad.uniqueIDB58 wltLoaded = True @@ -1996,14 +1981,14 @@ def loadWallets(self, walletsPayload): #self.loadLockboxesFromFile(MULTISIG_FILE) # Get the last directory - savedDir = self.settings.get('LastDirectory') + savedDir = TheSettings.get('LastDirectory') if len(savedDir)==0 or not os.path.exists(savedDir): savedDir = ARMORY_HOME_DIR self.lastDirectory = savedDir - self.writeSetting('LastDirectory', savedDir) + TheSettings.set('LastDirectory', savedDir) self.setupBlockchainService_step1() - self.signalExecution.executeMethod([self.finalizeLoadWallets, []]) + TheSignalExecution.executeMethod(self.finalizeLoadWallets) ############################################################################# @@ -2018,7 +2003,7 @@ def getFileSave(self, title='Save Wallet File', \ ffilter=['Wallet files (*.wallet)'], \ defaultFilename=None): LOGDEBUG('getFileSave') - startPath = self.settings.get('LastDirectory') + startPath = TheSettings.get('LastDirectory') if len(startPath)==0 or not os.path.exists(startPath): startPath = ARMORY_HOME_DIR @@ -2048,7 +2033,7 @@ def getFileSave(self, title='Save Wallet File', \ fdir,fname = os.path.split(filePath) if fdir: - self.writeSetting('LastDirectory', fdir) + TheSettings.set('LastDirectory', fdir) return filePath @@ -2060,7 +2045,7 @@ def getFileLoad(self, title='Load Wallet File', \ LOGDEBUG('getFileLoad') if defaultDir is None: - defaultDir = self.settings.get('LastDirectory') + defaultDir = TheSettings.get('LastDirectory') if len(defaultDir)==0 or not os.path.exists(defaultDir): defaultDir = ARMORY_HOME_DIR @@ -2097,7 +2082,7 @@ def getFileLoad(self, title='Load Wallet File', \ filePath = pathList[0].strip('\'') - self.writeSetting('LastDirectory', os.path.split(filePath)[0]) + TheSettings.set('LastDirectory', os.path.split(filePath)[0]) return filePath ############################################################################## @@ -2105,8 +2090,8 @@ def getWltSetting(self, wltID, propName, defaultValue=''): # Sometimes we need to settings specific to individual wallets -- we will # prefix the settings name with the wltID. wltPropName = 'Wallet_%s_%s' % (wltID, propName) - if self.settings.hasSetting(wltPropName): - return self.settings.get(wltPropName) + if TheSettings.hasSetting(wltPropName): + return TheSettings.get(wltPropName) else: if not defaultValue=='': self.setWltSetting(wltID, propName, defaultValue) @@ -2115,7 +2100,7 @@ def getWltSetting(self, wltID, propName, defaultValue=''): ############################################################################# def setWltSetting(self, wltID, propName, value): wltPropName = 'Wallet_%s_%s' % (wltID, propName) - self.writeSetting(wltPropName, value) + TheSettings.set(wltPropName, value) ############################################################################# @@ -2269,16 +2254,6 @@ def getWalletForAddressString(self, addrStr): return wltID return '' - ############################################################################# - def getSettingOrSetDefault(self, settingName, defaultVal): - s = self.settings.getSettingOrSetDefault(settingName, defaultVal) - return s - - ############################################################################# - def writeSetting(self, settingName, val): - self.settings.set(settingName, val) - - # NB: armoryd has a similar function (Armory_Daemon::start()), and both share # common functionality in ArmoryUtils (finishLoadBlockchainCommon). If you @@ -2294,10 +2269,10 @@ def finishLoadBlockchainGUI(self): self.ledgerSize = len(self.combinedLedger) self.statusBar().showMessage(self.tr('Blockchain loaded, wallets sync\'d!'), 10000) - currSyncSuccess = self.getSettingOrSetDefault("SyncSuccessCount", 0) - self.writeSetting('SyncSuccessCount', min(currSyncSuccess+1, 10)) + currSyncSuccess = TheSettings.getSettingOrSetDefault("SyncSuccessCount", 0) + TheSettings.set('SyncSuccessCount', min(currSyncSuccess+1, 10)) - if self.getSettingOrSetDefault('NotifyBlkFinish',True): + if TheSettings.getSettingOrSetDefault('NotifyBlkFinish',True): reply,remember = MsgBoxWithDNAA(self, self, MSGBOX.Info, self.tr('Blockchain Loaded!'), self.tr('Blockchain loading is complete. ' 'Your balances and transaction history are now available ' @@ -2305,13 +2280,13 @@ def finishLoadBlockchainGUI(self): 'receive bitcoins.'), dnaaMsg=self.tr('Do not show me this notification again '), yesStr='OK') if remember==True: - self.writeSetting('NotifyBlkFinish',False) + TheSettings.set('NotifyBlkFinish',False) self.mainDisplayTabs.setCurrentIndex(self.MAINTABS.Ledger) self.netMode = NETWORKMODE.Full - self.settings.set('FailedLoadCount', 0) + TheSettings.set('FailedLoadCount', 0) # This will force the table to refresh with new data self.removeBootstrapDat() # if we got here, we're *really* done with it @@ -2424,10 +2399,10 @@ def getCommentForLockboxTx(self, lboxId, le): ############################################################################# def convertLedgerToTable(self, ledgerProto, showSentToSelfAmt=True, wltIDIn=None): - ledger = ledgerProto.le + ledgers = ledgerProto.ledger table2D = [] datefmt = self.getPreferredDateFormat() - for le in ledger: + for le in ledgers: if wltIDIn is None: wltID = le.id else: @@ -2467,10 +2442,10 @@ def convertLedgerToTable(self, ledgerProto, showSentToSelfAmt=True, wltIDIn=None row.append(nConf) # UnixTime (needed for sorting) - row.append(le.txTime) + row.append(le.tx_time) # Date - row.append(str(unixTimeToFormatStr(le.txTime, datefmt))) + row.append(str(unixTimeToFormatStr(le.tx_time, datefmt))) # TxDir (actually just the amt... use the sign of the amt to determine dir) row.append(coin2str(le.value, maxZeros=2)) @@ -2479,12 +2454,12 @@ def convertLedgerToTable(self, ledgerProto, showSentToSelfAmt=True, wltIDIn=None row.append(wltName) # Comment - if le.isRBF == True: - if le.value < 0 or le.isSentToSelf: + if le.rbf == True: + if le.value < 0 or le.sent_to_self: dispComment = self.tr("*Right click to bump fee* ") + dispComment else: dispComment = self.tr("*** RBF Flagged *** ") + dispComment - elif le.isChainedZC == True: + elif le.chained_zc == True: dispComment = self.tr("*** Chained ZC *** ") + dispComment row.append(dispComment) @@ -2502,14 +2477,14 @@ def convertLedgerToTable(self, ledgerProto, showSentToSelfAmt=True, wltIDIn=None row.append(binary_to_hex(le.hash)) # Is this a coinbase/generation transaction - row.append(le.isCoinbase) + row.append(le.coinbase) # Sent-to-self - row.append(le.isSentToSelf) + row.append(le.sent_to_self) # RBF and zc chain status - row.append(le.isRBF) - row.append(le.isChainedZC) + row.append(le.rbf) + row.append(le.chained_zc) # Finally, attach the row to the table table2D.append(row) @@ -2532,7 +2507,7 @@ def populateLedgerComboBox(self): if comboIdx < 0: raise except: - comboIdx = self.getSettingOrSetDefault('LastFilterState', 0) + comboIdx = TheSettings.getSettingOrSetDefault('LastFilterState', 0) self.comboWltSelect.clear() self.comboWltSelect.addItem( self.tr('My Wallets' )) @@ -2672,7 +2647,7 @@ def addWalletToApplication(self, newWallet, walletIsNew=False): self.walletIDSet.add(newWltID) self.walletIDList.append(newWltID) - newWallet.registerWallet(walletIsNew) + newWallet.register(walletIsNew) showByDefault = (determineWalletType(newWallet, self)[0] != WLTTYPES.WatchOnly) self.walletVisibleList.append(showByDefault) @@ -2896,9 +2871,9 @@ def notifyNewZeroConf(self, leVec): vlen = len(leVec) for i in range(0, vlen): - notifyIn = self.getSettingOrSetDefault('NotifyBtcIn', \ + notifyIn = TheSettings.getSettingOrSetDefault('NotifyBtcIn', \ not OS_MACOSX) - notifyOut = self.getSettingOrSetDefault('NotifyBtcOut', \ + notifyOut = TheSettings.getSettingOrSetDefault('NotifyBtcOut', \ not OS_MACOSX) le = leVec[i] @@ -2915,18 +2890,19 @@ def broadcastTransaction(self, pytx, dryRun=False): #DlgDispTxInfo(pytx, None, self, self).exec_() return else: - LOGRAWDATA(pytx.serialize(), logging.INFO) + rawTxData = pytx.serialize() + LOGRAWDATA(rawTxData, logging.INFO) LOGPPRINT(pytx, logging.INFO) newTxHash = binary_to_hex(pytx.getHash()) self.broadcasting[newTxHash] = pytx - try: - LOGINFO('Sending Tx, %s', newTxHash) - TheBridge.broadcastTx([pytx.serialize()]) - except: - QMessageBox.warning(self, self.tr('Broadcast failed'), self.tr( - 'The broadcast process failed unexpectedly. Report this error to ' - 'the development team if this issue occurs repeatedly'), QMessageBox.Ok) + #try: + LOGINFO('Sending Tx, %s', newTxHash) + TheBridge.service.broadcastTx(rawTxData) + #except: + # QMessageBox.warning(self, self.tr('Broadcast failed'), self.tr( + # 'The broadcast process failed unexpectedly. Report this error to ' + # 'the development team if this issue occurs repeatedly'), QMessageBox.Ok) ############################################################################# def zcBroadcastError(self, txHash, errorMsg): @@ -3139,9 +3115,9 @@ def showLedgerTx(self): pytx = None txHashBin = hex_to_binary(txHash) - txProto = TheBridge.getTxByHash(txHashBin) + txProto = TheBridge.service.getTxByHash(txHashBin) pytx = PyTx().unserialize(txProto.raw) - pytx.setRBF(txProto.isRBF) + pytx.setRBF(txProto.rbf) if pytx==None: QMessageBox.critical(self, self.tr('Invalid Tx'), self.tr( @@ -3657,14 +3633,14 @@ def openBitcoinOrg(): self.tr('Open Armory settings window to change Bitcoin software management')) - self.dashBtns[DASHBTNS.Browse][TTIP] = self.createToolTipWidget( self.tr( + self.dashBtns[DASHBTNS.Browse][TTIP] = createToolTipWidget( self.tr( 'Will open your default browser to https://bitcoin.org where you can ' 'download the latest version of Bitcoin Core, and get other information ' 'and links about Bitcoin, in general.')) - self.dashBtns[DASHBTNS.Settings][TTIP] = self.createToolTipWidget( self.tr( + self.dashBtns[DASHBTNS.Settings][TTIP] = createToolTipWidget( self.tr( 'Change Bitcoin Core/bitcoind management settings or point Armory to ' 'a non-standard Bitcoin installation')) - self.dashBtns[DASHBTNS.Close][TTIP] = self.createToolTipWidget( self.tr( + self.dashBtns[DASHBTNS.Close][TTIP] = createToolTipWidget( self.tr( 'Armory has detected a running Bitcoin Core or bitcoind instance and ' 'will force it to exit')) @@ -3720,12 +3696,6 @@ def closeExistingBitcoin(self): 'Attempted to kill the running Bitcoin Core/bitcoind instance, ' 'but it was not found.'), QMessageBox.Ok) - ############################################################################# - def getPercentageFinished(self, maxblk, lastblk): - curr = EstimateCumulativeBlockchainSize(lastblk) - maxb = EstimateCumulativeBlockchainSize(maxblk) - return float(curr)/float(maxb) - ############################################################################# def showShuttingDownMessage(self): self.isShuttingDown = True @@ -3890,9 +3860,9 @@ def updateSyncProgress(self): if sdmStr == 'NodeStatus_Syncing': - sdmPercent = self.nodeStatus.chainState.progressPct * 100 + sdmPercent = self.nodeStatus.chain_state.progress_pct * 100 self.lblTimeLeftSync.setText(\ - "%d blocks remaining" % self.nodeStatus.chainState.blocksLeft) + "%d blocks remaining" % self.nodeStatus.chain_state.blocks_left) elif sdmStr == 'NodeStatus_Initializing': sdmPercent = 0 @@ -4278,29 +4248,29 @@ def GetDashStateText(self, mgmtMode, state): def getSDMStateStr(self): sdmStr = "" - if self.nodeStatus == None or not self.nodeStatus.isValid: + if self.nodeStatus == None or not self.nodeStatus.is_valid: return "NodeStatus_Offline" - if self.nodeStatus.nodeState == NodeStatus_Offline: + if self.nodeStatus.node_state == NodeStatus_Offline: sdmStr = "NodeStatus_Offline" - if self.nodeStatus.rpcState == RpcStatus_Online or \ - self.nodeStatus.rpcState == RpcStatus_Error_28: + if self.nodeStatus.rpc_state == RpcStatus_Online or \ + self.nodeStatus.rpc_state == RpcStatus_Error_28: sdmStr = "NodeStatus_Initializing" else: sdmStr = "NodeStatus_Ready" - if self.nodeStatus.rpcState == RpcStatus_Disabled: + if self.nodeStatus.rpc_state == RpcStatus_Disabled: return sdmStr - if self.nodeStatus.rpcState != RpcStatus_Online: + if self.nodeStatus.rpc_state != RpcStatus_Online: sdmStr = "NodeStatus_Initializing" else: - if self.nodeStatus.chainStatus.chainState == ChainStatus_Unknown: + if self.nodeStatus.chain_status.chain_state == ChainStatus_Unknown: sdmStr = "NodeStatus_Initializing" - elif self.nodeStatus.chainStatus.chainState == ChainStatus_Syncing: + elif self.nodeStatus.chain_status.chain_state == ChainStatus_Syncing: sdmStr = "NodeStatus_Syncing" return sdmStr @@ -4557,30 +4527,6 @@ def setBtnFrameVisible(b, descr=''): self.lblBusy.setPixmap(QPixmap('./img/loadicon_%d.png' % \ (self.numHeartBeat%6))) - ############################################################################# - def createToolTipWidget(self, tiptext, iconSz=2): - """ - The is to signal to Qt that it should be interpretted as HTML/Rich - text even if no HTML tags are used. This appears to be necessary for Qt - to wrap the tooltip text - """ - fgColor = htmlColor('ToolTipQ') - lbl = QLabel('(?)' % (iconSz, fgColor)) - lbl.setMaximumWidth(int(relaxedSizeStr(lbl, '(?)')[0])) - - def setAllText(wself, txt): - def pressEv(ev): - QWhatsThis.showText(ev.globalPos(), txt, self) - wself.mousePressEvent = pressEv - wself.setToolTip('' + txt) - - # Calling setText on this widget will update both the tooltip and QWT - from types import MethodType - lbl.setText = MethodType(setAllText, lbl) - - lbl.setText(tiptext) - return lbl - ############################################################################# def createAddressEntryWidgets(self, parent, initString='', maxDetectLen=128, boldDetectParts=0, **cabbKWArgs): @@ -4694,10 +4640,10 @@ def updateWalletData(self): ############################################################################# def updateStatusBarText(self): if self.nodeStatus != None and \ - self.nodeStatus.nodeState == NodeStatus_Online and \ - self.nodeStatus.isValid: + self.nodeStatus.node_state == NodeStatus_Online and \ + self.nodeStatus.is_valid: - haveRPC = (self.nodeStatus.rpcState == RpcStatus_Online) + haveRPC = (self.nodeStatus.rpc_state == RpcStatus_Online) if haveRPC: self.lblArmoryStatus.setText(\ @@ -4719,8 +4665,8 @@ def getToolTipTextOnline(): self.lblArmoryStatus.setToolTipLambda(getToolTipTextOnline) elif self.nodeStatus == None or \ - self.nodeStatus.nodeState == NodeStatus_Offline or \ - not self.nodeStatus.isValid: + self.nodeStatus.node_state == NodeStatus_Offline or \ + not self.nodeStatus.is_valid: self.lblArmoryStatus.setText(\ self.tr('Node offline (%d blocks) ' % \ (htmlColor('TextRed'), TheBDM.getTopBlockHeight()))) @@ -4736,12 +4682,12 @@ def getToolTipTextOffline(): self.lblArmoryStatus.setToolTipLambda(getToolTipTextOffline) ############################################################################# - def handleCppNotification(self, action, args): + def handleCppNotification(self, action, *args): if action == FINISH_LOAD_BLOCKCHAIN_ACTION: #Blockchain just finished loading, finish initializing UI and render #the ledgers - self.nodeStatus = TheBridge.getNodeStatus() + self.nodeStatus = TheBridge.service.getNodeStatus() self.updateWalletData() for wltid in self.walletMap: self.walletMap[wltid].detectHighestUsedIndex() @@ -4784,8 +4730,8 @@ def handleCppNotification(self, action, args): self.createCombinedLedger() self.blkReceived = RightNow() - self.writeSetting('LastBlkRecvTime', self.blkReceived) - self.writeSetting('LastBlkRecv', TheBDM.getTopBlockHeight()) + TheSettings.set('LastBlkRecvTime', self.blkReceived) + TheSettings.set('LastBlkRecv', TheBDM.getTopBlockHeight()) if self.netMode==NETWORKMODE.Full: LOGINFO('Current block number: %d', TheBDM.getTopBlockHeight()) @@ -4916,19 +4862,18 @@ def handleCppNotification(self, action, args): elif action == NODESTATUS_UPDATE: prevStatus = None - if self.nodeStatus != None and self.nodeStatus.isValid: - prevStatus = self.nodeStatus.nodeStatus - + if self.nodeStatus != None and self.nodeStatus.is_valid: + prevStatus = self.nodeStatus.node_state self.nodeStatus = args[0] - if prevStatus != self.nodeStatus.nodeStatus: - if self.nodeStatus.nodeStatus == NodeStatus_Offline: + if prevStatus != self.nodeStatus.node_state: + if self.nodeStatus.node_state == NodeStatus_Offline: self.showTrayMsg(self.tr('Disconnected'), self.tr('Connection to Bitcoin Core ' 'client lost! Armory cannot send nor ' 'receive bitcoins until connection is ' 're-established.'), QSystemTrayIcon.Critical, 10000) - elif self.nodeStatus.nodeStatus == NodeStatus_Online: + elif self.nodeStatus.node_state == NodeStatus_Online: self.showTrayMsg(self.tr('Connected'), self.tr('Connection to Bitcoin Core ' 're-established'), \ QSystemTrayIcon.Information, 10000) @@ -5167,7 +5112,7 @@ def doTheSystemTrayThing(self): lname = lname[:17] + '...' wltName = self.tr('Lockbox %d-of-%d "%s" (%s)' % (M, N, lname, moneyID)) - if le.isSentToSelf: + if le.sent_to_self: # Used to display the sent-to-self amount, but if this is a lockbox # we only have a cppWallet, and the determineSentToSelfAmt() func # only operates on python wallets. Oh well, the user can double- @@ -5192,11 +5137,11 @@ def doTheSystemTrayThing(self): elif le.value < 0: try: recipStr = '' - for addr in le.scrAddrList: + for addr in le.scraddr: if pywlt.hasAddrString(addr): continue if len(recipStr)==0: - recipStr = TheBridge.getScrAddrForAddrStr(addr) + recipStr = TheBridge.utils.getScrAddrForAddrStr(addr) else: recipStr = self.tr('') @@ -5220,7 +5165,7 @@ def doTheSystemTrayThing(self): ############################################################################# def closeEvent(self, event=None): - moc = self.getSettingOrSetDefault('MinimizeOrClose', 'DontKnow') + moc = TheSettings.getSettingOrSetDefault('MinimizeOrClose', 'DontKnow') doClose, doMinimize = False, False if moc=='DontKnow': reply,remember = MsgBoxWithDNAA(self, self, MSGBOX.Question, self.tr('Minimize or Close'), \ @@ -5230,11 +5175,11 @@ def closeEvent(self, event=None): if reply==True: doMinimize = True if remember: - self.writeSetting('MinimizeOrClose', 'Minimize') + TheSettings.set('MinimizeOrClose', 'Minimize') else: doClose = True if remember: - self.writeSetting('MinimizeOrClose', 'Close') + TheSettings.set('MinimizeOrClose', 'Close') if doMinimize or moc=='Minimize': self.minimizeArmory() @@ -5261,10 +5206,10 @@ def closeForReal(self): try: # Save the main window geometry in the settings file try: - self.writeSetting('MainGeometry', self.saveGeometry().toHex()) - self.writeSetting('MainWalletCols', saveTableView(self.walletsView)) + TheSettings.set('MainGeometry', self.saveGeometry().toHex()) + TheSettings.set('MainWalletCols', saveTableView(self.walletsView)) if self.ledgerView: - self.writeSetting('MainLedgerCols', saveTableView(self.ledgerView)) + TheSettings.set('MainLedgerCols', saveTableView(self.ledgerView)) except Exception as e: print ("- failed to save main geometry -") print (e) @@ -5276,7 +5221,7 @@ def closeForReal(self): LOGINFO('BDM is safe for clean shutdown') TheBDM.shutdown() - TheBridge.shutdown() + TheBridge.service.shutdown() # Remove Temp Modules Directory if it exists: if self.tempModulesDirName: @@ -5287,7 +5232,7 @@ def closeForReal(self): LOGEXCEPT('Strange error during shutdown') LOGINFO('Attempting to close the main window!') - self.signalExecution.executeMethod([QAPP.quit]) + TheSignalExecution.executeMethod(QAPP.quit) ############################################################################# def checkForNegImports(self): @@ -5371,13 +5316,13 @@ def setupBlockchainService_step1(self): self.setDashboardDetails() return - TheBridge.setupDB() + TheBridge.service.setupDB() ############################################################################# def setupBlockchainService_step2(self): self.switchNetworkMode(NETWORKMODE.Full) - TheBridge.registerWallets() + TheBridge.service.registerWallets() ############################################################################# def setupBlockchainService_step3(self): @@ -5385,7 +5330,7 @@ def setupBlockchainService_step3(self): self.loadBlockchainIfNecessary() self.setDashboardDetails() - TheBridge.goOnline() + TheBridge.service.goOnline() ############################################################################# def setupLedgerViews(self): @@ -5400,7 +5345,8 @@ def setupLedgerViews(self): self.ledgerTable = [] self.ledgerModel = LedgerDispModelSimple(self.ledgerTable, self, self) - self.ledgerModel.setLedgerDelegateId(TheBridge.getLedgerDelegateIdForWallets()) + self.ledgerModel.setLedgerDelegateId( + TheBridge.service.getLedgerDelegateIdForWallets()) self.ledgerModel.setConvertLedgerMethod(self.convertLedgerToTable) self.frmLedgUpDown = QFrame() @@ -5509,7 +5455,7 @@ def setupLedgerViews(self): self.tabActivity.setLayout(ledgLayout) self.mainDisplayTabs.addTab(self.tabActivity, self.tr('Transactions')) - hexledgsz = self.settings.get('MainLedgerCols') + hexledgsz = TheSettings.get('MainLedgerCols') if len(hexledgsz)>0: restoreTableView(self.ledgerView, hexledgsz) self.ledgerView.setColumnWidth(LEDGERCOLS.NumConf, 20) @@ -5556,8 +5502,8 @@ def bumpFee(self, walletId, txHash): ############################################################################# def promptUser(self, promptID, promptType, verbose, wltID, state): - self.signalExecution.executeMethod(\ - [self.promptDialogSetup, [promptID, promptType, verbose, wltID, state]]) + TheSignalExecution.executeMethod(self.promptDialogSetup, + promptID, promptType, verbose, wltID, state) ############################################################################# def promptDialogSetup(self, promptID, promptType, verbose, wltID, state): @@ -5567,28 +5513,28 @@ def promptDialogSetup(self, promptID, promptType, verbose, wltID, state): on a Qt dialog), so we use it to manage the promptID map as well ''' - if state == ClientProto_pb2.UnlockPromptState.Value('start'): + if state == BridgeProto_pb2.UnlockPromptState.Value('start'): if promptID in self.promptMap: raise Exception("already have this prompt ID") - if promptType == ClientProto_pb2.UnlockPromptType.Value('decrypt'): + if promptType == BridgeProto_pb2.UnlockPromptType.Value('decrypt'): ppDlg = DlgUnlockWallet(\ promptID, wltID, self, self, verbose, False) - elif promptType == ClientProto_pb2.UnlockPromptType.Value('migrate'): + elif promptType == BridgeProto_pb2.UnlockPromptType.Value('migrate'): ppDlg = DlgMigrateWallet(\ promptID, wltID, verbose, self, self) self.promptMap[promptID] = ppDlg ppDlg.exec_() - elif state == ClientProto_pb2.UnlockPromptState.Value('cycle'): + elif state == BridgeProto_pb2.UnlockPromptState.Value('cycle'): if promptID in self.promptMap: ppDlg = self.promptMap[promptID] ppDlg.show() ppDlg.recycle() - elif state == ClientProto_pb2.UnlockPromptState.Value('stop'): + elif state == BridgeProto_pb2.UnlockPromptState.Value('stop'): if promptID in self.promptMap: ppDlg = self.promptMap[promptID] ppDlg.accept() @@ -5629,12 +5575,22 @@ def unregisterProgressCallback(self, id): # Will make this customizable QAPP.setFont(GETFONT('var')) + # Setup translations + translator = QTranslator(QAPP) + app_dir = "./" + try: + app_dir = os.path.dirname(os.path.realpath(__file__)) + except: + if OS_WINDOWS and getattr(sys, 'frozen', False): + app_dir = os.path.dirname(sys.executable) + translator.load(TheSettings.getGuiLanguage(), os.path.join(app_dir, "lang/")) + QAPP.installTranslator(translator) + #setup main dialog armoryMainWindow = ArmoryMainWindow(splashScreen=SPLASH) #start cppbridge - from armoryengine import ArmoryUtils - ArmoryUtils.startBridge(armoryMainWindow.networkReadyCallback) + TheBDM.startBridge(getBridgeArgList(), armoryMainWindow.networkReadyCallback) #show main dialog armoryMainWindow.show() diff --git a/armoryengine/ArmoryUtils.py b/armoryengine/ArmoryUtils.py index b2f7d8812..da28ddf6f 100755 --- a/armoryengine/ArmoryUtils.py +++ b/armoryengine/ArmoryUtils.py @@ -1,19 +1,17 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -################################################################################ -# -# Copyright (C) 2011-2015, Armory Technologies, Inc. -# Distributed under the GNU Affero General Public License (AGPL v3) -# See LICENSE or http://www.gnu.org/licenses/agpl.html -# -################################################################################ -# -# Project: Armory -# Author: Alan Reiner -# Website: www.bitcoinarmory.com -# Orig Date: 20 November, 2011 -# -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2023, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## + import ast import codecs from datetime import datetime @@ -319,9 +317,6 @@ class SignerException(Exception): pass DEFAULT_ADDR_TYPE= '' SUBDIR = 'testnet3' if USE_TESTNET else '' + 'regtest' if USE_REGTEST else '' -#settingsObject = SettingsFile(CLI_OPTIONS.settingsPath) - - if not CLI_OPTIONS.satoshiHome==DEFAULT: BTC_HOME_DIR = CLI_OPTIONS.satoshiHome if BTC_HOME_DIR.endswith('blocks'): @@ -1497,7 +1492,7 @@ def scrAddr_to_script(scraddr): def script_to_scrAddr(binScript): """ Convert a binary script to scrAddr string (used by BDM) """ from armoryengine.CppBridge import TheBridge - return TheBridge.getScrAddrForScript(binScript) + return TheBridge.scriptUtils.getScrAddrForScript(binScript) ################################################################################ def script_to_addrStr(binScript): @@ -1507,7 +1502,7 @@ def script_to_addrStr(binScript): ################################################################################ def scrAddr_to_addrStr(scrAddr): from armoryengine.CppBridge import TheBridge - return TheBridge.getAddrStrForScrAddr(scrAddr) + return TheBridge.scriptUtils.getAddrStrForScrAddr(scrAddr) ################################################################################ # We beat around the bush here, to make sure it goes through addrStr which @@ -1753,7 +1748,7 @@ def hash256(s): def hash160(s): """ RIPEMD160( SHA256( binaryStr ) ) """ from armoryengine.CppBridge import TheBridge - return TheBridge.getHash160(s) + return TheBridge.utils.getHash160(s) def HMAC(key, msg, hashfunc=sha512, hashsz=None): @@ -3107,100 +3102,6 @@ def wrappedFunc(*args, **kwargs): def emptyFunc(*args, **kwargs): return - -def EstimateCumulativeBlockchainSize(blkNum): - # I tried to make a "static" variable here so that - # the string wouldn't be parsed on every call, but - # I botched that, somehow. - # - # It doesn't *have to* be fast, but why not? - # Oh well.. - blksizefile = """ - 0 285 - 20160 4496226 - 40320 9329049 - 60480 16637208 - 80640 31572990 - 82656 33260320 - 84672 35330575 - 86688 36815335 - 88704 38386205 - 100800 60605119 - 102816 64795352 - 104832 68697265 - 108864 79339447 - 112896 92608525 - 116928 116560952 - 120960 140607929 - 124992 170059586 - 129024 217718109 - 133056 303977266 - 137088 405836779 - 141120 500934468 - 145152 593217668 - 149184 673064617 - 153216 745173386 - 157248 816675650 - 161280 886105443 - 165312 970660768 - 169344 1058290613 - 173376 1140721593 - 177408 1240616018 - 179424 1306862029 - 181440 1463634913 - 183456 1639027360 - 185472 1868851317 - 187488 2019397056 - 189504 2173291204 - 191520 2352873908 - 193536 2530862533 - 195552 2744361593 - 197568 2936684028 - 199584 3115432617 - 201600 3282437367 - 203616 3490737816 - 205632 3669806064 - 207648 3848901149 - 209664 4064972247 - 211680 4278148686 - 213696 4557787597 - 215712 4786120879 - 217728 5111707340 - 219744 5419128115 - 221760 5733907456 - 223776 6053668460 - 225792 6407870776 - 227808 6652067986 - 228534 6778529822 - 257568 10838081536 - 259542 11106516992 - 271827 12968787968 - 286296 15619588096 - 290715 16626221056 - 323285 24216006308 - """ - strList = [line.strip().split() for line in blksizefile.strip().split('\n')] - BLK_SIZE_LIST = [[int(x[0]), int(x[1])] for x in strList] - - if blkNum < BLK_SIZE_LIST[-1][0]: - # Interpolate - bprev,bcurr = None, None - for i,blkpair in enumerate(BLK_SIZE_LIST): - if blkNum < blkpair[0]: - b0,d0 = BLK_SIZE_LIST[i-1] - b1,d1 = blkpair - ratio = float(blkNum-b0)/float(b1-b0) - return int(ratio*d1 + (1-ratio)*d0) - raise ValueError('Interpolation failed for %d' % blkNum) - - else: - bend, dend = BLK_SIZE_LIST[-1] - bend2, dend2 = BLK_SIZE_LIST[-3] - rate = float(dend - dend2) / float(bend - bend2) # bytes per block - extraOnTop = (blkNum - bend) * rate - return dend+extraOnTop - - ################################################################################ # Function checks to see if a binary value that's passed in is a valid public # key. The incoming key may be binary or hex. The return value is a boolean @@ -3345,201 +3246,6 @@ def getLastBytesOfFile(filename, nBytes=500*1024): fin.seek(sz - nBytes) return fin.read() -################################################################################ -################################################################################ -class SettingsFile(object): - """ - This class could be replaced by the built-in QSettings in PyQt, except - that older versions of PyQt do not support the QSettings (or at least - I never figured it out). Easy enough to do it here - - All settings must populated with a simple datatype -- non-simple - datatypes should be broken down into pieces that are simple: numbers - and strings, or lists/tuples of them. - - Will write all the settings to file. Each line will look like: - SingleValueSetting1 | 3824.8 - SingleValueSetting2 | this is a string - Tuple Or List Obj 1 | 12 $ 43 $ 13 $ 33 - Tuple Or List Obj 2 | str1 $ another str - """ - - ############################################################################# - def __init__(self, path=None): - self.settingsPath = path - self.settingsMap = {} - if not path: - self.settingsPath = os.path.join(ARMORY_HOME_DIR, 'ArmorySettings.txt') - - LOGINFO('Using settings file: %s', self.settingsPath) - if os.path.exists(self.settingsPath): - self.loadSettingsFile(path) - - - - ############################################################################# - def pprint(self, nIndent=0): - indstr = indent*nIndent - print(indstr + 'Settings:') - for k,v in self.settingsMap.items(): - print(indstr + indent + k.ljust(15), v) - - - ############################################################################# - def hasSetting(self, name): - return name in self.settingsMap - - ############################################################################# - def set(self, name, value): - if isinstance(value, tuple): - self.settingsMap[name] = list(value) - else: - self.settingsMap[name] = value - self.writeSettingsFile() - - ############################################################################# - def extend(self, name, value): - """ Adds/converts setting to list, appends value to the end of it """ - if name not in self.settingsMap: - if isinstance(value, list): - self.set(name, value) - else: - self.set(name, [value]) - else: - origVal = self.get(name, expectList=True) - if isinstance(value, list): - origVal.extend(value) - else: - origVal.append(value) - self.settingsMap[name] = origVal - self.writeSettingsFile() - - ############################################################################# - def get(self, name, expectList=False): - if not self.hasSetting(name) or self.settingsMap[name]=='': - return ([] if expectList else '') - else: - val = self.settingsMap[name] - if expectList: - if isinstance(val, list): - return val - else: - return [val] - else: - return val - - ############################################################################# - def getAllSettings(self): - return self.settingsMap - - ############################################################################# - def getSettingOrSetDefault(self, name, defaultVal, expectList=False): - output = defaultVal - if self.hasSetting(name): - output = self.get(name) - else: - self.set(name, defaultVal) - - return output - - - - ############################################################################# - def delete(self, name): - if self.hasSetting(name): - del self.settingsMap[name] - self.writeSettingsFile() - - ############################################################################# - def writeSettingsFile(self, path=None): - if not path: - path = self.settingsPath - f = open(path, 'wb') - for key,val in self.settingsMap.items(): - try: - # Skip anything that throws an exception - from PySide2.QtCore import QByteArray - - valStr = '' - if isinstance(val, str): - valStr = val - elif isinstance(val, int) or \ - isinstance(val, float): - valStr = str(val) - elif isinstance(val, list) or \ - isinstance(val, tuple): - valStr = ' $ '.join([str(v) for v in val]) - elif isinstance(val, QByteArray) and \ - sys.version_info >= (3,0): - valStr = str(val.data(), encoding='utf-8') - else: - valStr = str(val) - f.write(key.ljust(36).encode('utf-8')) - f.write(b' | ') - if valStr: - f.write(valStr.encode('utf-8')) - f.write(b'\n') - except: - LOGEXCEPT('Invalid entry in SettingsFile... skipping') - f.close() - - - ############################################################################# - def loadSettingsFile(self, path=None): - if not path: - path = self.settingsPath - - if not os.path.exists(path): - raise FileExistsError('Settings file DNE:' + path) - - f = open(path, 'rb') - sdata = f.read() - f.close() - - # Automatically convert settings to numeric if possible - def castVal(v): - v = v.strip() - a,b = v.isdigit(), v.replace(b'.',b'').isdigit() - if a: - return int(v) - elif b: - return float(v) - else: - if v.lower()==b'true': - return True - elif v.lower()==b'false': - return False - else: - return toUnicode(v) - - - sdata = [line.strip() for line in sdata.split(b'\n')] - for line in sdata: - if len(line.strip())==0: - continue - - try: - key,vals = line.split(b'|')[0:2] - valList = [castVal(v) for v in vals.split(b'$')] - if len(valList)==1: - self.settingsMap[key.strip().decode('utf-8')] = valList[0] - else: - self.settingsMap[key.strip().decode('utf-8')] = valList - except: - LOGEXCEPT('Invalid setting in %s (skipping...)', path) - -# Grab language settings from settings file or from cli -LANGUAGES = ["da", "de", "en", "es", "el", "fr", "he", "hr", "id", "ru", "sv"] -if CLI_OPTIONS.language == "en": - langSetting = SettingsFile(SETTINGS_PATH).get('Language') -else: - langSetting = CLI_OPTIONS.language -if langSetting not in LANGUAGES: - LOGERROR("Unsupported language %s specified. Defaulting to English (en)", langSetting) - langSetting = "en" -GUI_LANGUAGE = "armory_" + langSetting + ".qm" -LOGINFO("Using Language: %s", langSetting) - # Random method for creating def touchFile(fname): try: @@ -3577,7 +3283,7 @@ def calcLockboxID(script=None, scraddr=None): ################################################################################ def getNameForAddrType(addrType): from armoryengine.CppBridge import TheBridge - return TheBridge.getNameForAddrType(addrType) + return TheBridge.utils.getNameForAddrType(addrType) ################################################################################ def getRandomHexits_NotSecure(count): @@ -3587,9 +3293,7 @@ def getRandomHexits_NotSecure(count): # bridge setup ################# -def startBridge(notifyReadyCB): - from armoryengine.CppBridge import TheBridge - +def getBridgeArgList(): #gather cli args for bridge bridgeArgs = [] @@ -3621,6 +3325,4 @@ def startBridge(notifyReadyCB): if len(argPair[1]) > 0: stringArgs += "=" stringArgs += argPair[1] - - #set bridge - TheBridge.start(stringArgs, notifyReadyCB) + return stringArgs \ No newline at end of file diff --git a/armoryengine/BDM.py b/armoryengine/BDM.py index 5ccd9a4c4..b48c1767a 100644 --- a/armoryengine/BDM.py +++ b/armoryengine/BDM.py @@ -15,23 +15,9 @@ from armoryengine.ArmoryUtils import * from armoryengine.Timer import TimeThisFunction from armoryengine.BinaryPacker import UINT64 -from armoryengine import ClientProto_pb2 -from armoryengine.ClientProto_pb2 import \ - OpaquePayloadType, UnlockPromptCallback - -BDMAction_Ready = 1 -BDMAction_NewBlock = 2 -BDMAction_ZC = 3 -BDMAction_InvalidatedZC = 4 -BDMAction_Refresh = 5 -BDMAction_Exited = 6 -BDMAction_ErrorMsg = 7 -BDMAction_NodeStatus = 8 -BDMAction_BDV_Error = 9 -DISCONNECTED_CALLBACK_ID = 0xff543ad8 +from armoryengine.CppBridge import ServerPush, TheBridge -CppBridge_Ready = 20 -CppBridge_Registered = 21 +DISCONNECTED_CALLBACK_ID = 0xff543ad8 BDMPhase_DBHeaders = 1 BDMPhase_OrganizingChain = 2 @@ -49,7 +35,7 @@ BDM_BLOCKCHAIN_READY = 'BlockChainReady' BDM_SCANNING = 'Scanning' -FINISH_LOAD_BLOCKCHAIN_ACTION = 'FinishLoadBlockchain' +FINISH_LOAD_BLOCKCHAIN_ACTION = 'FinishLoadBlockchain' NEW_ZC_ACTION = 'newZC' NEW_BLOCK_ACTION = 'newBlock' REFRESH_ACTION = 'refresh' @@ -61,6 +47,9 @@ BDV_ERROR = 'BDV_Error' BDV_DISCONNECTED = 'BDV_Disconnected' +CPP_BDM_NOTIF_ID = "bdm_callback" +CPP_PROGRESS_NOTIF_ID = "progress" + SETUP_STEP2 = 'setup_step1_done' SETUP_STEP3 = 'setup_step2_done' @@ -85,7 +74,14 @@ def inner(*args, **kwargs): return func(*args, **kwargs) return inner +################################################################################ +class BDMCallbackWrapper(ServerPush): + def __init__(self, callbackId, callbackFunc): + super().__init__(callbackId) + self.callbackFunc = callbackFunc + def parseProtoPacket(self, protoPacket): + self.callbackFunc(protoPacket) ################################################################################ class BlockDataManager(object): @@ -190,7 +186,7 @@ def setSatoshiDir(self, newBtcDir): return self.btcdir = newBtcDir - + ############################################################################# @ActLikeASingletonBDM def predictLoadTime(self): @@ -240,80 +236,58 @@ def bdmCallback(bdmSignal, args): ############################################################################# def pushNotification(self, notifProto): - try: - act = '' - arglist = [] - - # AOTODO replace with constants - action = notifProto.type - block = notifProto.height - - if action == BDMAction_Ready: - print('BDM is ready!') - act = FINISH_LOAD_BLOCKCHAIN_ACTION - TheBDM.topBlockHeight = block - TheBDM.setState(BDM_BLOCKCHAIN_READY) - - elif action == BDMAction_ZC: - act = NEW_ZC_ACTION - argLedgers = ClientProto_pb2.BridgeLedgers() - argLedgers.ParseFromString(notifProto.opaque[0]) - arglist = argLedgers.le - - elif action == BDMAction_NewBlock: - act = NEW_BLOCK_ACTION - arglist.append(block) - TheBDM.topBlockHeight = block - - elif action == BDMAction_Refresh: - act = REFRESH_ACTION - arglist = notifProto.ids - - elif action == BDMAction_Exited: - act = STOPPED_ACTION - - elif action == BDMAction_ErrorMsg: - act = WARNING_ACTION - argstr = Cpp.BtcUtils_cast_to_string(arg) - arglist.append(argstr) - - elif action == BDMAction_BDV_Error: - act = BDV_ERROR - argBdvError = Cpp.BDV_Error_Struct_cast_to_BDVErrorStruct(arg) - arglist.append(argBdvError) - - elif action == BDMAction_NodeStatus: - act = NODESTATUS_UPDATE - argNodeStatus = ClientProto_pb2.BridgeNodeStatus() - argNodeStatus.ParseFromString(notifProto.opaque[0]) - arglist.append(argNodeStatus) - - elif action == DISCONNECTED_CALLBACK_ID: - TheBDM.setState(BDM_OFFLINE) - act = BDV_DISCONNECTED - - - #setup notifs - elif action == CppBridge_Ready: - act = SETUP_STEP2 - elif action == CppBridge_Registered: - act = SETUP_STEP3 - - listenerList = self.getListenerList() - for cppNotificationListener in listenerList: - cppNotificationListener(act, arglist) - except: - LOGEXCEPT('Error in running callback') - print(sys.exc_info()) - raise + act = '' + arglist = [] + + # AOTODO replace with constants + if notifProto.HasField("ready"): + print('BDM is ready!') + act = FINISH_LOAD_BLOCKCHAIN_ACTION + TheBDM.topBlockHeight = notifProto.ready.height + TheBDM.setState(BDM_BLOCKCHAIN_READY) + + elif notifProto.HasField("zero_conf"): + act = NEW_ZC_ACTION + arglist = notifProto.zero_conf.ledger + + elif notifProto.HasField("new_block"): + act = NEW_BLOCK_ACTION + arglist.append(notifProto.new_block.height) + TheBDM.topBlockHeight = notifProto.new_block.height + + elif notifProto.HasField("refresh"): + act = REFRESH_ACTION + arglist = notifProto.refresh.id + + elif notifProto.HasField("error"): + act = WARNING_ACTION + arglist.append(notifProto.error) + + elif notifProto.HasField("node_status"): + act = NODESTATUS_UPDATE + arglist.append(notifProto.node_status) + + elif notifProto.HasField("disconnected"): + TheBDM.setState(BDM_OFFLINE) + act = BDV_DISCONNECTED + + #setup notifs + elif notifProto.HasField("setup_done"): + act = SETUP_STEP2 + elif notifProto.HasField("registered"): + act = SETUP_STEP3 + + listenerList = self.getListenerList() + for cppNotificationListener in listenerList: + cppNotificationListener(act, *arglist) ############################################################################# def reportProgress(self, notifProto): - phase = notifProto.phase - prog = notifProto.progress - seconds = notifProto.etaSec - progressNumeric = notifProto.progressNumeric - walletVec = notifProto.ids + phase = notifProto.progress.phase + prog = notifProto.progress.progress + seconds = notifProto.progress.eta_sec + progressNumeric = notifProto.progress.progress_numeric + walletVec = notifProto.progress.id try: if len(walletVec) == 0: @@ -358,6 +332,15 @@ def pushFromBridge(self, payloadType, payload, uniqueId, callerId): else: LOGWARN("Unknown prompt data type") + ############################################################################# + def startBridge(self, stringArgs, notifyReadyLbd): + pushNotifCallback = BDMCallbackWrapper( + CPP_BDM_NOTIF_ID, self.pushNotification) + reportProgressCallback = BDMCallbackWrapper( + CPP_PROGRESS_NOTIF_ID, self.reportProgress) + + TheBridge.start(stringArgs, notifyReadyLbd) + ################################################################################ # Make TheBDM reference the asyncrhonous BlockDataManager wrapper if we are # running diff --git a/armoryengine/CoinSelection.py b/armoryengine/CoinSelection.py index 1d05ae6e8..e229e68e8 100644 --- a/armoryengine/CoinSelection.py +++ b/armoryengine/CoinSelection.py @@ -88,15 +88,15 @@ def __init__(self, scrAddr=None, txHash=None, txoIdx=None, val=None, ############################################################################# def createFromBridgeUtxo(self, bridgeUtxo): - scrAddr= bridgeUtxo.scrAddr + scrAddr= bridgeUtxo.scraddr val = bridgeUtxo.value - conf = TheBDM.getTopBlockHeight() - bridgeUtxo.txHeight + 1 - txHash = bridgeUtxo.txHash - txHashStr = binary_to_hex(bridgeUtxo.txHash) - txoIdx = bridgeUtxo.txOutIndex + conf = TheBDM.getTopBlockHeight() - bridgeUtxo.tx_height + 1 + txHash = bridgeUtxo.tx_hash + txHashStr = binary_to_hex(bridgeUtxo.tx_hash) + txoIdx = bridgeUtxo.txout_index script = bridgeUtxo.script - txHeight = bridgeUtxo.txHeight - txIndex = bridgeUtxo.txIndex + txHeight = bridgeUtxo.tx_height + txIndex = bridgeUtxo.tx_index sequence = 2**32-1 self.initialize(scrAddr, txHash, txHashStr, txHeight, txIndex, diff --git a/armoryengine/CppBridge.py b/armoryengine/CppBridge.py index 5b6a0dd40..5f3d27eb0 100755 --- a/armoryengine/CppBridge.py +++ b/armoryengine/CppBridge.py @@ -1,6 +1,6 @@ ################################################################################ # # -# Copyright (C) 2019-2021, goatpig. # +# Copyright (C) 2019-2023, goatpig. # # Distributed under the MIT license # # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # @@ -8,17 +8,17 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +import os import errno import socket -from armoryengine import ClientProto_pb2 +from armoryengine import BridgeProto_pb2 from armoryengine.ArmoryUtils import LOGDEBUG, LOGERROR, hash256 -from armoryengine.BDM import TheBDM from armoryengine.BinaryPacker import BinaryPacker, \ UINT32, UINT8, BINARY_CHUNK, VAR_INT from struct import unpack import atexit import threading -import binascii +import base64 import subprocess from concurrent.futures import ThreadPoolExecutor @@ -27,21 +27,25 @@ from armoryengine.BIP15x import \ BIP15xConnection, AEAD_THRESHOLD_BEGIN, AEAD_Error, \ CHACHA20POLY1305MAXPACKETSIZE - -CPP_BDM_NOTIF_ID = 2**32 -1 -CPP_PROGRESS_NOTIF_ID = 2**32 -2 -CPP_PROMPT_USER_ID = 2**32 -3 BRIDGE_CLIENT_HEADER = 1 -################################################################################# +################################################################################ +## +#### Exceptions +## +################################################################################ class BridgeError(Exception): pass -################################################################################# +################################################################################ class BridgeSignerError(Exception): pass -################################################################################# +################################################################################ +## +#### Tools +## +################################################################################ class PyPromFut(object): ############################################################################# @@ -69,30 +73,43 @@ def getVal(self): ################################################################################ -class ArmoryBridge(object): +## +#### bridge socket +## +################################################################################ +class BridgeSocket(object): + recvLen = 4 ############################################################################# + ## setup def __init__(self): - self.blockTimeByHeightCache = {} - self.addrTypeStrByType = {} - self.bip15xConnection = BIP15xConnection(\ - self.sendToBridgeRaw) + self.idCounter = 0 + self.responseDict = {} + self.callbackDict = {} + self.bip15xConnection = BIP15xConnection(self.sendToBridgeRaw) + self.run = False + self.rwLock = None + + #### + def setCallback(self, key, func): + self.callbackDict[key] = func + + def unsetCallback(self, key): + del self.callbackDict[key] ############################################################################# + ## listen socket setup def start(self, stringArgs, notifyReadyLbd): self.bip15xConnection.setNotifyReadyLbd(notifyReadyLbd) self.run = True self.rwLock = threading.Lock() - self.idCounter = 0 - self.responseDict = {} - self.executor = ThreadPoolExecutor(max_workers=2) listenFut = self.executor.submit(self.listenOnBridge) #append gui pubkey to arg list and spawn bridge - stringArgs += " --uiPubKey=" + self.bip15xConnection.getPubkeyHex() + os.environ['SERVER_PUBKEY'] = self.bip15xConnection.getPubkeyHex() self.processFut = self.executor.submit(self.spawnBridge, stringArgs) #block until listen socket receives bridge connection @@ -104,12 +121,19 @@ def start(self, stringArgs, notifyReadyLbd): #initiate AEAD handshake (server has to start it) self.bip15xConnection.serverStartHandshake() - ############################################################################# + #### def stop(self): - self.listenSocket.close() + self.rwLock.acquire(True) + + self.run = False self.clientSocket.close() + self.listenSocket.close() + + self.rwLock.release() + self.clientFut.result() ############################################################################# + ## bridge management def listenOnBridge(self): #setup listener @@ -122,11 +146,12 @@ def listenOnBridge(self): clientSocket, clientIP = self.listenSocket.accept() return clientSocket - ############################################################################# + #### def spawnBridge(self, stringArgs): - subprocess.run(["./CppBridge", stringArgs]) + subprocess.run(["./build/CppBridge", stringArgs]) ############################################################################# + ## socket write def encryptPayload(self, clearText): if not self.bip15xConnection.encrypted(): raise AEAD_Error("channel is not encrypted") @@ -139,24 +164,23 @@ def encryptPayload(self, clearText): return cipherText - ############################################################################# - def sendToBridgeProto(self, msg, \ - needsReply=True, callback=None, cbArgs=[], \ - msgType = BRIDGE_CLIENT_HEADER): + #### + def sendToBridgeProto(self, msg, needsReply, + callbackFunc, callbackArgs, msgType): - msg.payloadId = self.idCounter + msg.reference_id = self.idCounter self.idCounter = self.idCounter + 1 payload = msg.SerializeToString() - result = self.sendToBridgeBinary(payload, msg.payloadId, \ - needsReply, callback, cbArgs, msgType) + result = self.sendToBridgeBinary(payload, msg.reference_id, + needsReply, callbackFunc, callbackArgs, msgType) if needsReply: return result - ############################################################################# - def sendToBridgeBinary(self, payload, payloadId, \ - needsReply=True, callback=None, cbArgs=[], \ + #### + def sendToBridgeBinary(self, payload, payloadId, + needsReply=True, callback=None, cbArgs=[], msgType = BRIDGE_CLIENT_HEADER): #grab id from msg counter @@ -178,15 +202,16 @@ def sendToBridgeBinary(self, payload, payloadId, \ #encrypt encryptedPayloads = self.encryptPayload(bp.getBinaryString()) - if needsReply: + if callback != None: + #set callable in response dict + wrapper = CallbackWrapper(callback, cbArgs) + self.responseDict[payloadId] = wrapper + + elif needsReply: #instantiate prom/future object and set in response dict fut = PyPromFut() self.responseDict[payloadId] = fut - elif callback != None: - #set callable in response dict - self.responseDict[payloadId] = [callback, cbArgs] - #send over the wire, may have 2 payloads if we triggered a rekey for p in encryptedPayloads: self.clientSocket.sendall(p) @@ -194,16 +219,17 @@ def sendToBridgeBinary(self, payload, payloadId, \ #return future to caller self.rwLock.release() - if needsReply: + if callback == None and needsReply: return fut - ############################################################################# + #### def sendToBridgeRaw(self, msg): self.rwLock.acquire(True) self.clientSocket.sendall(msg) self.rwLock.release() ############################################################################# + ## socket read def pollRecv(self, payloadSize): payload = bytearray() fullSize = payloadSize @@ -222,15 +248,14 @@ def pollRecv(self, payloadSize): return payload - ############################################################################# + #### def readBridgeSocket(self): - recvLen = 4 while self.run is True: #wait for data on the socket try: - response = self.clientSocket.recv(recvLen) + response = self.clientSocket.recv(self.recvLen) - if len(response) < recvLen: + if len(response) < self.recvLen: break except socket.error as e: err = e.args[0] @@ -284,802 +309,931 @@ def readBridgeSocket(self): raise BridgeError("Received user data before AEAD is ready") #grab packet id - fullPacket = response[5:] + fullPacket = response[1:] + + #deser protobuf reply + protoPayload = BridgeProto_pb2.Payload() + if not protoPayload.ParseFromString(fullPacket): + raise BridgeError("failed to parse proto payload") + + #payloads are either replies or callbacks + if protoPayload.HasField('reply'): + reply = protoPayload.reply + referenceId = reply.reference_id + + #lock and look for future object in response dict + self.rwLock.acquire(True) + if referenceId not in self.responseDict: + LOGWARN(f"unknown reply referenceId: {referenceId}") + self.rwLock.release() + continue - packetId = unpack(' 0: - return response.reply[0] + if reply.success == False: + raise BridgeError(f"error in getAddrStrForScrAddr: {reply.error}") else: - return "" + return reply.script_utils.address_string +################################################################################ +class BridgeSigner(ProtoWrapper): ############################################################################# - def getTxOutScriptForScrAddr(self, script): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getTxOutScriptForScrAddr - packet.byteArgs.append(script) + ## setup ## + def __init__(self): + super().__init__(TheBridge.bridgeSocket) + self.signerId = None - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + #### + def __del__(self): + self.cleanup() - response = ClientProto_pb2.ReplyBinary() - response.ParseFromString(socketResponse) + #### + def getPacket(self): + if self.signerId == None or not self.signerId: + raise BridgeSignerError("[BridgeSigner] missing signerId") - return response.reply[0] + packet = BridgeProto_pb2.Request() + packet.signer.id = self.signerId + return packet ############################################################################# - def getHeaderByHeight(self, height): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getHeaderByHeight - packet.intArgs.append(height) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + def initNew(self): + if self.signerId != None: + raise BridgeSignerError("[initNew] signer already has an id") - response = ClientProto_pb2.ReplyBinary() - response.ParseFromString(socketResponse) + packet = BridgeProto_pb2.Request() + packet.signer.id = "" + packet.signer.get_new = True - return response.reply[0] + fut = self.send(packet) + reply = fut.getVal() + self.signerId = reply.signer.signer_id ############################################################################# - def getScrAddrForScript(self, script): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getScrAddrForScript - packet.byteArgs.append(script) + def cleanup(self): + packet = self.getPacket() + packet.signer.cleanup = True - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + self.send(packet, False) + self.signerId = None - response = ClientProto_pb2.ReplyBinary() - response.ParseFromString(socketResponse) + ############################################################################# + def setVersion(self, version): + packet = self.getPacket() + packet.signer.set_version.version = version - return response.reply[0] + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + raise BridgeSignerError( + f"[setVersion] failed with error: {reply.error}") ############################################################################# - def getScrAddrForAddrStr(self, addrStr): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getScrAddrForAddrStr - packet.stringArgs.append(addrStr) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + def setLockTime(self, locktime): + packet = self.getPacket() + packet.signer.set_lock_time.lock_time = locktime - errorResponse = ClientProto_pb2.ReplyError() - errorResponse.ParseFromString(socketResponse) - if errorResponse.isError == False: - response = ClientProto_pb2.ReplyBinary() - response.ParseFromString(socketResponse) - return response.reply[0] - raise BridgeError("error in getScrAddrForAddrStr: " + errorResponse.error) + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + raise BridgeSignerError( + f"[setLockTime] failed with error: {reply.error}") ############################################################################# - def getAddrStrForScrAddr(self, scrAddr): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getAddrStrForScrAddr - packet.byteArgs.append(scrAddr) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + def addSpenderByOutpoint(self, hashVal, txoutid, seq): + packet = self.getPacket() + method = packet.signer.add_spender_by_outpoint + method.hash = hashVal + method.tx_out_id = txoutid + method.sequence = seq - errorResponse = ClientProto_pb2.ReplyError() - errorResponse.ParseFromString(socketResponse) - if errorResponse.isError == False: - response = ClientProto_pb2.ReplyStrings() - response.ParseFromString(socketResponse) - return response.reply[0] - raise BridgeError("error in getAddrStrForScrAddr: " + errorResponse.error) + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + raise BridgeSignerError( + f"[addSpenderByOutpoint] failed with error: {reply.error}") ############################################################################# - def initCoinSelectionInstance(self, wltId, height): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.setupNewCoinSelectionInstance - packet.stringArgs.append(wltId) - packet.intArgs.append(height) + def populateUtxo(self, hashVal, txoutid, value, script): + packet = self.getPacket() + method = packet.signer.populate_utxo + method.hash = hashVal + method.tx_out_id = txoutid + method.value = value + method.script = script - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + raise BridgeSignerError( + f"[addSpenderByOutpoint] failed with error: {reply.error}") - response = ClientProto_pb2.ReplyStrings() - response.ParseFromString(socketResponse) + ############################################################################# + def addSupportingTx(self, rawTxData): + packet = self.getPacket() + packet.signer.add_supporting_tx.raw_tx = rawTxData - return response.reply[0] + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + raise BridgeSignerError( + f"[addSupportingTx] failed with error: {reply.error}") ############################################################################# - def destroyCoinSelectionInstance(self, csId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.destroyCoinSelectionInstance - packet.stringArgs.append(csId) + def addRecipient(self, value, script): + packet = self.getPacket() + method = packet.signer.add_recipient + method.value = value + method.script = script - self.sendToBridgeProto(packet, False) + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + raise BridgeSignerError( + f"[addRecipient] failed with error: {reply.error}") ############################################################################# - def setCoinSelectionRecipient(self, csId, addrStr, value, recId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.setCoinSelectionRecipient - packet.stringArgs.append(csId) - packet.stringArgs.append(addrStr) - packet.intArgs.append(recId) - packet.longArgs.append(value) + def toTxSigCollect(self, ustxType): + packet = self.getPacket() + packet.signer.to_tx_sig_collect.ustx_type = ustxType - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + fut = self.send(packet) + reply = fut.getVal() + return reply.signer.tx_sig_collect - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) + ############################################################################# + def fromTxSigCollect(self, txSigCollect): + packet = self.getPacket() + packet.signer.from_tx_sig_collect.tx_sig_collect = txSigCollect - if response.ints[0] == 0: - raise BridgeError("setCoinSelectionRecipient failed") + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + raise BridgeSignerError( + f"[fromTxSigCollect] failed with error: {reply.error}") ############################################################################# - def resetCoinSelection(self, csId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.resetCoinSelection - packet.stringArgs.append(csId) + def resolve(self, wltId): + packet = self.getPacket() + packet.signer.resolve.wallet_id = wltId - self.sendToBridgeProto(packet, False) + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + raise BridgeSignerError(f"[resolve] failed with error: {reply.error}") ############################################################################# - def cs_SelectUTXOs(self, csId, fee, feePerByte, processFlags): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.cs_SelectUTXOs - packet.stringArgs.append(csId) - packet.longArgs.append(fee) - packet.floatArgs.append(feePerByte) - packet.intArgs.append(processFlags) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + def signTx(self, wltId, serverPushObj, callbackFunc, callbackArgs=[]): + packet = self.getPacket() + packet.signer.sign_tx.wallet_id = wltId + packet.signer.sign_tx.callback_id = serverPushObj.callbackId + self.send(packet, callback=callbackFunc, cbArgs=callbackArgs) - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) + ############################################################################# + def getSignedTx(self): + packet = self.getPacket() + packet.signer.get_signed_tx = True - if response.ints[0] == 0: - raise BridgeError("selectUTXOs failed") + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + return None + return reply.signer.tx_data ############################################################################# - def cs_getUtxoSelection(self, csId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.cs_getUtxoSelection - packet.stringArgs.append(csId) + def getUnsignedTx(self): + packet = self.getPacket() + packet.signer.get_unsigned_tx = True - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() + fut = self.send(packet) + reply = fut.getVal() + if reply.success == False: + return None + return reply.signer.tx_data - response = ClientProto_pb2.BridgeUtxoList() - response.ParseFromString(socketResponse) + ############################################################################# + def getSignedStateForInput(self, inputId): + packet = self.getPacket() + packet.signer.get_signed_state_for_input.input_id = inputId - return response.data + fut = self.send(packet) + reply = fut.getVal() + return reply.signer.input_signed_state ############################################################################# - def cs_getFlatFee(self, csId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.cs_getFlatFee - packet.stringArgs.append(csId) + def fromType(self): + packet = self.getPacket() + packet.signer.get_unsigned_tx = True - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - return response.longs[0] - - ############################################################################# - def cs_getFeeByte(self, csId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.cs_getFeeByte - packet.stringArgs.append(csId) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - return response.floats[0] - - ############################################################################# - def cs_getSizeEstimate(self, csId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.cs_getSizeEstimate - packet.stringArgs.append(csId) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - return response.longs[0] - - ############################################################################# - def cs_ProcessCustomUtxoList(self, csId, \ - utxoList, fee, feePerByte, processFlags): - - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.cs_ProcessCustomUtxoList - packet.stringArgs.append(csId) - packet.longArgs.append(fee) - packet.floatArgs.append(feePerByte) - packet.intArgs.append(processFlags) - - for utxo in utxoList: - bridgeUtxo = utxo.toBridgeUtxo() - packet.byteArgs.append(bridgeUtxo.SerializeToString()) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeError("ProcessCustomUtxoList failed") + fut = self.send(packet) + reply = fut.getVal() + return reply.signer.from_type ############################################################################# - def cs_getFeeForMaxVal(self, csId, feePerByte): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.cs_getFeeForMaxVal - packet.stringArgs.append(csId) - packet.floatArgs.append(feePerByte) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - return response.longs[0] - - ############################################################################# - def cs_getFeeForMaxValUtxoVector(self, csId, utxoList, feePerByte): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.cs_getFeeForMaxValUtxoVector - packet.stringArgs.append(csId) - packet.floatArgs.append(feePerByte) - - for utxo in utxoList: - bridgeUtxo = utxo.toBridgeUtxo() - packet.byteArgs.append(bridgeUtxo.SerializeToString()) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - return response.longs[0] - - ############################################################################# - def generateRandomHex(self, size): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.generateRandomHex - packet.intArgs.append(size) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyStrings() - response.ParseFromString(socketResponse) - - return response.reply[0] - - ############################################################################# - def createAddressBook(self, wltId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.createAddressBook - packet.stringArgs.append(wltId) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.BridgeAddressBook() - response.ParseFromString(socketResponse) - - return response.data - - ############################################################################# - def getUtxosForValue(self, wltId, value): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getUtxosForValue - packet.stringArgs.append(wltId) - packet.longArgs.append(value) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.BridgeUtxoList() - response.ParseFromString(socketResponse) - - return response.data - - ############################################################################# - def getSpendableZCList(self, wltId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getSpendableZCList - packet.stringArgs.append(wltId) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.BridgeUtxoList() - response.ParseFromString(socketResponse) - - return response.data - - ############################################################################# - def getRBFTxOutList(self, wltId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getRBFTxOutList - packet.stringArgs.append(wltId) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.BridgeUtxoList() - response.ParseFromString(socketResponse) - - return response.data - - ############################################################################# - def getNewAddress(self, wltId, addrType): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getNewAddress - packet.stringArgs.append(wltId) - packet.intArgs.append(addrType) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.WalletAsset() - response.ParseFromString(socketResponse) - - return response - - ############################################################################# - def getNewChangeAddr(self, wltId, addrType): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getNewChangeAddr - packet.stringArgs.append(wltId) - packet.intArgs.append(addrType) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.WalletAsset() - response.ParseFromString(socketResponse) - - return response - - ############################################################################# - def peekChangeAddress(self, wltId, addrType): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.peekChangeAddress - packet.stringArgs.append(wltId) - packet.intArgs.append(addrType) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.WalletAsset() - response.ParseFromString(socketResponse) - - return response - - ############################################################################# - def getHash160(self, data): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getHash160 - packet.byteArgs.append(data) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyBinary() - response.ParseFromString(socketResponse) - - return response.reply[0] - - ############################################################################# - def broadcastTx(self, rawTxList): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.broadcastTx - packet.byteArgs.extend(rawTxList) + def canLegacySerialize(self): + packet = self.getPacket() + packet.signer.can_legacy_serialize = True - self.sendToBridgeProto(packet, False) + fut = self.send(packet) + reply = fut.getVal() + return reply.success - ############################################################################# - def extendAddressPool(self, wltId, progressId, count, callback): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.extendAddressPool - packet.stringArgs.append(wltId) - packet.stringArgs.append(progressId) - packet.intArgs.append(count) - - self.sendToBridgeProto(packet, False, - self.finishExtendAddressPool, [callback]) +################################################################################ +class ArmoryBridge(object): ############################################################################# - def finishExtendAddressPool(self, socketResponse, args): - response = ClientProto_pb2.WalletData() - response.ParseFromString(socketResponse) + def __init__(self): + self.blockTimeByHeightCache = {} + self.bridgeSocket = BridgeSocket() - #fire callback - callbackThread = threading.Thread(\ - group=None, target=args[0], \ - name=None, args=[response], kwargs={}) - callbackThread.start() + self.service = BlockchainService(self.bridgeSocket) + self.utils = BlockchainUtils(self.bridgeSocket) + self.scriptUtils = ScriptUtils(self.bridgeSocket) ############################################################################# - def createWallet(self, addrPoolSize, passphrase, controlPassphrase, \ - shortLabel, longLabel, extraEntropy): - walletCreationStruct = ClientProto_pb2.BridgeCreateWalletStruct() - walletCreationStruct.lookup = addrPoolSize - walletCreationStruct.passphrase = passphrase - walletCreationStruct.controlPassphrase = controlPassphrase - walletCreationStruct.label = shortLabel - walletCreationStruct.description = longLabel - - if extraEntropy is not None: - walletCreationStruct.extraEntropy = extraEntropy - - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.createWallet - packet.byteArgs.append(walletCreationStruct.SerializeToString()) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyStrings() - response.ParseFromString(socketResponse) - - if len(response.reply) != 1: - raise BridgeError("string reply count mismatch") - - return response.reply[0] + def start(self, stringArgs, notifyReadyLbd): + self.bridgeSocket.start(stringArgs, notifyReadyLbd) ############################################################################# - def deleteWallet(self, wltId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.deleteWallet - packet.stringArgs.append(wltId) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - return response.ints[0] + def send(self, msg, needsReply=True, callback=None, cbArgs=[], + msgType=BRIDGE_CLIENT_HEADER): + self.bridgeSocket.sendToBridgeProto(msg, + needsReply, callback, cbArgs, msgType) ############################################################################# - def getWalletData(self, wltId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getWalletData - packet.stringArgs.append(wltId) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.WalletData() - response.ParseFromString(socketResponse) - - return response + def pushNotification(self, callbackData): + notifThread = threading.Thread(\ + group=None, target=TheBDM.pushNotification, \ + name=None, args=[callbackData], kwargs={}) + notifThread.start() ############################################################################# - def registerWallet(self, walletId, isNew): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.registerWallet - packet.stringArgs.append(walletId) - packet.intArgs.append(isNew) + def pushProgressNotification(self, data): + payload = BridgeProto_pb2.CppProgressCallbackMsg() + payload.ParseFromString(data) - self.sendToBridgeProto(packet, False) + TheBDM.reportProgress(payload) ############################################################################# def getBlockTimeByHeight(self, height): if height in self.blockTimeByHeightCache: return self.blockTimeByHeightCache[height] - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getBlockTimeByHeight + packet = BridgeProto_pb2.Request() + packet.method = BridgeProto_pb2.getBlockTimeByHeight packet.intArgs.append(height) fut = self.sendToBridgeProto(packet) socketResponse = fut.getVal() - response = ClientProto_pb2.ReplyNumbers() + response = BridgeProto_pb2.ReplyNumbers() response.ParseFromString(socketResponse) blockTime = response.ints[0] @@ -1090,132 +1244,26 @@ def getBlockTimeByHeight(self, height): self.blockTimeByHeightCache[height] = blockTime return blockTime - ############################################################################# - def createBackupStringForWalletCallback(self, socketResponse, args): - rootData = ClientProto_pb2.BridgeBackupString() - rootData.ParseFromString(socketResponse) - - callbackArgs = [rootData] - callbackThread = threading.Thread(\ - group=None, target=args[0], \ - name=None, args=callbackArgs, kwargs={}) - callbackThread.start() - - ############################################################################# - def createBackupStringForWallet(self, wltId, callback): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.createBackupStringForWallet - packet.stringArgs.append(wltId) - - callbackArgs = [callback] - self.sendToBridgeProto(\ - packet, False, self.createBackupStringForWalletCallback, callbackArgs) - ############################################################################# def restoreWallet(self, root, chaincode, sppass, callbackId): - opaquePayload = ClientProto_pb2.RestoreWalletPayload() + opaquePayload = BridgeProto_pb2.RestoreWalletPayload() opaquePayload.root.extend(root) opaquePayload.secondary.extend(chaincode) opaquePayload.spPass = sppass - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.methodWithCallback - packet.methodWithCallback = ClientProto_pb2.restoreWallet + packet = BridgeProto_pb2.Request() + packet.method = BridgeProto_pb2.methodWithCallback + packet.methodWithCallback = BridgeProto_pb2.restoreWallet packet.byteArgs.append(callbackId) packet.byteArgs.append(opaquePayload.SerializeToString()) self.sendToBridgeProto(packet, False) - ############################################################################# - def callbackFollowUp(self, payload, callbackId, callerId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.methodWithCallback - packet.methodWithCallback = ClientProto_pb2.followUp - - packet.byteArgs.append(callbackId) - packet.byteArgs.append(payload.SerializeToString()) - - packet.intArgs.append(callerId) - - self.sendToBridgeProto(packet, False) - - ############################################################################# - def getNameForAddrType(self, addrType): - if addrType in self.addrTypeStrByType: - return self.addrTypeStrByType[addrType] - - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getNameForAddrType - packet.intArgs.append(addrType) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyStrings() - response.ParseFromString(socketResponse) - - addrTypeStr = response.reply[0] - self.addrTypeStrByType[addrType] = addrTypeStr - return addrTypeStr - - ############################################################################# - def setAddressTypeFor(self, walletId, assetId, addrType): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.setAddressTypeFor - - packet.stringArgs.append(walletId) - packet.byteArgs.append(assetId) - packet.intArgs.append(addrType) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.WalletAsset() - response.ParseFromString(socketResponse) - return response - - ############################################################################# - def setComment(self, wltId, key, val): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.setComment - - packet.stringArgs.append(wltId) - packet.byteArgs.append(key) - packet.stringArgs.append(val) - - self.sendToBridgeProto(packet, False) - - ############################################################################# - def setWalletLabels(self, wltId, title, desc): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.setWalletLabels - - packet.stringArgs.append(wltId) - packet.stringArgs.append(title) - packet.stringArgs.append(desc) - - self.sendToBridgeProto(packet, False) - - ############################################################################# - def estimateFee(self, blocks, strat): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.estimateFee - packet.intArgs.append(blocks) - packet.stringArgs.append(strat) - - fut = self.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.BridgeFeeEstimate() - response.ParseFromString(socketResponse) - - return response - ############################################################################# def getHistoryForWalletSelection(self, wltIDList, order): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.getHistoryForWalletSelection + packet = BridgeProto_pb2.Request() + packet.method = BridgeProto_pb2.getHistoryForWalletSelection packet.stringArgs.append(order) for wltID in wltIDList: packet.stringArgs.append(wltID) @@ -1223,289 +1271,58 @@ def getHistoryForWalletSelection(self, wltIDList, order): fut = self.sendToBridgeProto(packet) socketResponse = fut.getVal() - response = ClientProto_pb2.BridgeLedgers() + response = BridgeProto_pb2.BridgeLedgers() response.ParseFromString(socketResponse) return response ################################################################################ -class BridgeSigner(object): - def __init__(self): - self.signerId = None - - def __del__(self): - self.cleanup() - - ############################################################################# - def initNew(self): - if self.signerId != None: - raise BridgeSignerError("initNew") - - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.initNewSigner - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyStrings() - response.ParseFromString(socketResponse) - - self.signerId = response.reply[0] - - ############################################################################# - def cleanup(self): - if self.signerId != None: - return - - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.destroySigner - packet.stringArgs.append(self.signerId) - - TheBridge.sendToBridgeProto(packet, False) - self.signerId = None - - ############################################################################# - def setVersion(self, version): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_SetVersion - packet.stringArgs.append(self.signerId) - packet.intArgs.append(version) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeSignerError("setVersion") - - ############################################################################# - def setLockTime(self, locktime): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_SetLockTime - packet.stringArgs.append(self.signerId) - packet.intArgs.append(locktime) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeSignerError("setLockTime") - - ############################################################################# - def addSpenderByOutpoint(self, hashVal, txoutid, seq, value): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_addSpenderByOutpoint - packet.stringArgs.append(self.signerId) - packet.byteArgs.append(hashVal) - packet.intArgs.append(txoutid) - packet.intArgs.append(seq) - packet.longArgs.append(value) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeSignerError("addSpenderByOutpoint") - - ############################################################################# - def populateUtxo(self, hashVal, txoutid, value, script): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_populateUtxo - packet.stringArgs.append(self.signerId) - packet.byteArgs.append(hashVal) - packet.intArgs.append(txoutid) - packet.longArgs.append(value) - packet.byteArgs.append(script) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeSignerError("addSpenderByOutpoint") - - ############################################################################# - def addSupportingTx(self, rawTxData): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_addSupportingTx - packet.stringArgs.append(self.signerId) - packet.byteArgs.append(rawTxData) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeSignerError("addSpenderByOutpoint") - - ############################################################################# - def addRecipient(self, value, script): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_addRecipient - packet.stringArgs.append(self.signerId) - packet.byteArgs.append(script) - packet.longArgs.append(value) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeSignerError("addRecipient") - - ############################################################################# - def toTxSigCollect(self, ustxType): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_toTxSigCollect - packet.stringArgs.append(self.signerId) - packet.intArgs.append(ustxType) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyStrings() - response.ParseFromString(socketResponse) - - return response.reply[0] - - ############################################################################# - def fromTxSigCollect(self, txSigCollect): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_fromTxSigCollect - packet.stringArgs.append(self.signerId) - packet.stringArgs.append(txSigCollect) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeSignerError("fromTxSigCollect") - - ############################################################################# - def resolve(self, wltId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_resolve - packet.stringArgs.append(self.signerId) - packet.stringArgs.append(wltId) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - if response.ints[0] == 0: - raise BridgeSignerError("resolve") - - ############################################################################# - def signTx(self, wltId, callback): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_signTx - packet.stringArgs.append(self.signerId) - packet.stringArgs.append(wltId) - - callbackArgs = [callback] - TheBridge.sendToBridgeProto( - packet, False, self.signTxCallback, callbackArgs) - - ############################################################################# - def signTxCallback(self, socketResponse, args): - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - - callbackThread = threading.Thread(\ - group=None, target=args[0], \ - name=None, args=[], kwargs={}) +class CallbackWrapper(object): + def __init__(self, callbackFunc, callbackArgs=[]): + self.callbackFunc = callbackFunc + self.callbackArgs = callbackArgs + + def execute(self, replyObj): + callbackThread = threading.Thread( + group=None, target=self.callbackFunc, + name=None, args=[*self.callbackArgs, replyObj], kwargs={}) callbackThread.start() - ############################################################################# - def getSignedTx(self): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_getSignedTx - packet.stringArgs.append(self.signerId) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyBinary() - response.ParseFromString(socketResponse) - - return response.reply[0] - - ############################################################################# - def getUnsignedTx(self): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_getUnsignedTx - packet.stringArgs.append(self.signerId) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.ReplyBinary() - response.ParseFromString(socketResponse) - - return response.reply[0] - - - ############################################################################# - def getSignedStateForInput(self, inputId): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_getSignedStateForInput - packet.stringArgs.append(self.signerId) - packet.intArgs.append(inputId) - - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() - - response = ClientProto_pb2.BridgeInputSignedState() - response.ParseFromString(socketResponse) +################################################################################ +class ServerPush(ProtoWrapper): + def __init__(self, callbackId=""): + super().__init__(TheBridge.bridgeSocket) - return response + if len(callbackId) == 0: + self.callbackId = base64.b16encode(os.urandom(10)).decode('utf-8') + else: + self.callbackId = callbackId + self.bridgeSocket.setCallback(self.callbackId, self) - ############################################################################# - def fromType(self): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_fromType - packet.stringArgs.append(self.signerId) + self.refId = 0 + self.packet = None - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() + def parseProtoPacket(self, protoPacket): + raise Exception("override me") - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) + def process(self, protoPacket): + if protoPacket.HasField('cleanup'): + self.bridgeSocket.unsetCallback(self.callbackId) - return response.ints[0] + self.refId = protoPacket.reference_id + self.parseProtoPacket(protoPacket) - ############################################################################# - def canLegacySerialize(self): - packet = ClientProto_pb2.ClientCommand() - packet.method = ClientProto_pb2.signer_canLegacySerialize - packet.stringArgs.append(self.signerId) + def getNewPacket(self): + self.packet = BridgeProto_pb2.Request() + self.packet.callback_reply.reference_id = self.refId + self.refId = 0 + return self.packet.callback_reply - fut = TheBridge.sendToBridgeProto(packet) - socketResponse = fut.getVal() + def reply(self): + self.send(self.packet, needsReply=False) + self.packet = None - response = ClientProto_pb2.ReplyNumbers() - response.ParseFromString(socketResponse) - return bool(response.ints[0]) #### TheBridge = ArmoryBridge() diff --git a/armoryengine/PyBtcAddress.py b/armoryengine/PyBtcAddress.py index 5a8dce965..3d92d049d 100644 --- a/armoryengine/PyBtcAddress.py +++ b/armoryengine/PyBtcAddress.py @@ -148,17 +148,19 @@ def isAddrChainRoot(self): def loadFromProtobufPayload(self, payload): self.__init__() - self.prefixedHash = payload.prefixedHash - self.binPublicKey = payload.publicKey + self.prefixedHash = payload.prefixed_hash + self.binPublicKey = payload.public_key self.chainIndex = payload.id - self.assetId = payload.assetId + self.assetId = payload.asset_id self.isInitialized = True - self.addrType = payload.addrType - self.addressString = payload.addressString - - self.precursorScript = payload.precursorScript - self.isUsed = payload.isUsed - self.isChange = payload.isChange + self.addrType = payload.addr_type + self.addressString = payload.address_string + self.hasPrivKey = payload.has_priv_key + self.use_encryption = payload.use_encryption + + self.precursorScript = payload.precursor_script + self.isUsed = payload.is_used + self.isChange = payload.is_change ############################################################################# def getTxioCount(self): diff --git a/armoryengine/PyBtcWallet.py b/armoryengine/PyBtcWallet.py index 834a973be..49b091227 100644 --- a/armoryengine/PyBtcWallet.py +++ b/armoryengine/PyBtcWallet.py @@ -6,7 +6,7 @@ # Distributed under the GNU Affero General Public License (AGPL v3) # # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # -# Copyright (C) 2016-2021, goatpig # +# Copyright (C) 2016-2023, goatpig # # Distributed under the MIT license # # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # @@ -21,7 +21,7 @@ from armoryengine.BinaryUnpacker import * from armoryengine.Timer import Timer, TimeThisFunction from armoryengine.Decorators import singleEntrantMethod -from armoryengine.CppBridge import TheBridge +from armoryengine.CppBridge import TheBridge, BridgeWalletWrapper from armoryengine.PyBtcAddress import PyBtcAddress BLOCKCHAIN_READONLY = 0 @@ -182,7 +182,7 @@ class PyBtcWallet(object): """ ############################################################################# - def __init__(self): + def __init__(self, *, uniqueId=None, proto=None): self.fileTypeStr = '\xbaWALLET\x00' self.version = PYBTCWALLET_VERSION # (Major, Minor, Minor++, even-more-minor) self.eofByte = 0 @@ -216,13 +216,8 @@ def __init__(self): # Private key encryption details self.useEncryption = False - self.kdf = None - self.crypto = None - self.kdfKey = None - self.defaultKeyLifetime = 10 # seconds after unlock, that key is discarded - self.lockWalletAtTime = 0 # seconds after unlock, that key is discarded - self.isLocked = False - self.testedComputeTime=None + self.testedComputeTime = None + self.kdfMemoryReq = 0 # Deterministic wallet, need a root key. Though we can still import keys. # The unique ID contains the network byte (id[-1]) but is not intended to @@ -270,6 +265,13 @@ def __init__(self): self.balance_full = 0 self.txnCount = 0 + self.bridgeWalletObj = None + if uniqueId != None: + self.bridgeWalletObj = BridgeWalletWrapper(uniqueId) + elif proto != None: + self.loadFromProtobufPayload(proto) + self.bridgeWalletObj = BridgeWalletWrapper(self.uniqueIDB58) + ############################################################################# def isWltSigningAnyLockbox(self, lockboxList): for lockbox in lockboxList: @@ -361,12 +363,9 @@ def isRegistered(self): def getBalance(self, balType="Spendable"): if balType.lower() in ('spendable','spend'): return self.balance_spendable - #return self.cppWallet.getSpendableBalance(topBlockHeight, IGNOREZC) elif balType.lower() in ('unconfirmed','unconf'): - #return self.cppWallet.getUnconfirmedBalance(topBlockHeight, IGNOREZC) return self.balance_unconfirmed elif balType.lower() in ('total','ultimate','unspent','full'): - #return self.cppWallet.getFullBalance() return self.balance_full else: raise TypeError('Unknown balance type! "' + balType + '"') @@ -377,7 +376,7 @@ def getTxnCount(self): ############################################################################# def updateBalancesAndCount(self): - result = TheBridge.getBalanceAndCount(self.uniqueIDB58) + result = self.bridgeWalletObj.getBalanceAndCount() self.balance_full = result.full self.balance_spendable = result.spendable self.balance_unconfirmed = result.unconfirmed @@ -419,7 +418,7 @@ def getUTXOListForSpendVal(self, valToSpend = 2**64 - 1): if not self.doBlockchainSync==BLOCKCHAIN_DONOTUSE: from armoryengine.CoinSelection import PyUnspentTxOut - utxos = TheBridge.getUtxosForValue(self.uniqueIDB58, valToSpend) + utxos = self.bridgeWalletObj.getUtxosForValue(self.uniqueIDB58, valToSpend) utxoList = [] for i in range(len(utxos)): utxoList.append(PyUnspentTxOut().createFromBridgeUtxo(utxos[i])) @@ -445,10 +444,10 @@ def getFullUTXOList(self): if not self.doBlockchainSync==BLOCKCHAIN_DONOTUSE: #calling this with no value argument will return the full UTXO list from armoryengine.CoinSelection import PyUnspentTxOut - utxos = TheBridge.getUtxosForValue(self.uniqueIDB58, 2**64 - 1) + utxos = self.bridgeWalletObj.getUtxosForValue(2**64 - 1) utxoList = [] - for i in range(len(utxos)): - utxoList.append(PyUnspentTxOut().createFromBridgeUtxo(utxos[i])) + for i in range(len(utxos.utxo)): + utxoList.append(PyUnspentTxOut().createFromBridgeUtxo(utxos.utxo[i])) return utxoList else: LOGERROR('***Blockchain is not available for accessing wallet-tx data') @@ -460,10 +459,10 @@ def getZCUTXOList(self): #return full set of unspent ZC outputs if not self.doBlockchainSync==BLOCKCHAIN_DONOTUSE: from armoryengine.CoinSelection import PyUnspentTxOut - utxos = TheBridge.getSpendableZCList(self.uniqueIDB58) + utxos = self.bridgeWalletObj.getSpendableZCList() utxoList = [] - for i in range(len(utxos)): - utxoList.append(PyUnspentTxOut().createFromBridgeUtxo(utxos[i])) + for i in range(len(utxos.utxo)): + utxoList.append(PyUnspentTxOut().createFromBridgeUtxo(utxos.utxo[i])) return utxoList else: LOGERROR('***Blockchain is not available for accessing wallet-tx data') @@ -475,10 +474,10 @@ def getRBFTxOutList(self): #return full set of unspent ZC outputs if not self.doBlockchainSync==BLOCKCHAIN_DONOTUSE: from armoryengine.CoinSelection import PyUnspentTxOut - utxos = TheBridge.getRBFTxOutList(self.uniqueIDB58) + utxos = self.bridgeWalletObj.getRBFTxOutList() utxoList = [] - for i in range(len(utxos)): - utxoList.append(PyUnspentTxOut().createFromBridgeUtxo(utxos[i])) + for i in range(len(utxos.utxo)): + utxoList.append(PyUnspentTxOut().createFromBridgeUtxo(utxos.utxo[i])) return utxoList else: LOGERROR('***Blockchain is not available for accessing wallet-tx data') @@ -505,7 +504,8 @@ def hasAddrString(self, addrStr): return addrStr in self.addrByString ############################################################################# - def createNewWallet(self, passphrase=None, \ + @staticmethod + def createNewWallet(passphrase=None, \ kdfTargSec=DEFAULT_COMPUTE_TIME_TARGET, kdfMaxMem=DEFAULT_MAXMEM_LIMIT, \ shortLabel='', longLabel='', extraEntropy=None): @@ -538,24 +538,26 @@ def createNewWallet(self, passphrase=None, \ LOGINFO('***Creating new deterministic wallet') #create cpp wallet - walletId = TheBridge.createWallet(\ - self.addrPoolSize, \ - passphrase, "", \ - #kdfTargSec, kdfMaxMem, \ + walletId = TheBridge.utils.createWallet( + self.addrPoolSize, + passphrase, "", + #kdfTargSec, kdfMaxMem, shortLabel, longLabel, extraEntropy) - self.loadFromBridge(walletId) - return self + return PyBtcWallet().loadFromBridge(walletId) ############################################################################# + @staticmethod def loadFromBridge(self, walletId): - walletProto = TheBridge.getWalletData(walletId) - self.loadFromProtobufPayload(walletProto) + wallet = PyBtcWallet(uniqueId=walletId) + walletProto = wallet.bridgeWalletObj.getData() + wallet.loadFromProtobufPayload(walletProto) + return wallet ############################################################################# def peekChangeAddr(self, addrType=AddressEntryType_Default): - newAddrProto = TheBridge.getNewAddress(self.uniqueIDB58, addrType) + newAddrProto = self.bridgeWalletObj.getNewAddress(addrType) newAddrObj = PyBtcAddress() newAddrObj.loadFromProtobufPayload(newAddrProto) @@ -574,7 +576,7 @@ def addAddress(self, addrObj): ############################################################################# def getNewChangeAddr(self, addrType=AddressEntryType_Default): - newAddrProto = TheBridge.getNewAddress(self.uniqueIDB58, addrType) + newAddrProto = self.bridgeWalletObj.getNewAddress(addrType) newAddrObj = PyBtcAddress() newAddrObj.loadFromProtobufPayload(newAddrProto) @@ -583,7 +585,7 @@ def getNewChangeAddr(self, addrType=AddressEntryType_Default): ############################################################################# def getNextUnusedAddress(self, addrType=AddressEntryType_Default): - newAddrProto = TheBridge.getNewAddress(self.uniqueIDB58, addrType) + newAddrProto = self.bridgeWalletObj.getNewAddress(addrType) newAddrObj = PyBtcAddress() newAddrObj.loadFromProtobufPayload(newAddrProto) @@ -598,7 +600,6 @@ def getHighestUsedIndex(self): """ return self.highestUsedChainIndex - ############################################################################# def getHighestComputedIndex(self): """ @@ -626,8 +627,7 @@ def detectHighestUsedIndex(self): highest address used. """ - highestIndex = TheBridge.getHighestUsedIndex(self.uniqueIDB58) - + highestIndex = self.bridgeWalletObj.getHighestUsedIndex() if highestIndex > self.highestUsedChainIndex: self.highestUsedChainIndex = highestIndex @@ -1100,7 +1100,7 @@ def setComment(self, hashVal, newComment): """ self.commentsMap[hashVal] = newComment - TheBridge.setComment(self.uniqueIDB58, hashVal, newComment) + self.bridgeWalletObj.setComment(hashVal, newComment) ############################################################################# def getAddrCommentIfAvail(self, txHash): @@ -1139,7 +1139,7 @@ def getAddrCommentFromLe(self, le): # If we haven't extracted relevant addresses for this tx, yet -- do it txHash = le.hash if txHash not in self.txAddrMap: - self.txAddrMap[txHash] = le.scrAddrList + self.txAddrMap[txHash] = le.scraddr addrComments = [] for a160 in self.txAddrMap[txHash]: @@ -1165,12 +1165,11 @@ def getCommentForLE(self, le): return comment ############################################################################# - def setWalletLabels(self, lshort, llong=''): + def setLabels(self, lshort, llong=''): self.labelName = lshort self.labelDescr = llong - TheBridge.setWalletLabels(self.uniqueIDB58, - self.labelName, self.labelDescr) + self.bridgeWalletObj.setLabels(self.labelName, self.labelDescr) ############################################################################# def deleteImportedAddress(self, addr160): @@ -1209,7 +1208,7 @@ def deleteImportedAddress(self, addr160): self.cppWallet.removeAddressBulk([Hash160ToScrAddr(addr160)]) self.readWalletFile(wltPath) self.cppWallet = passCppWallet - self.registerWallet(False) + self.register(False) ############################################################################# def importExternalAddressBatch(self, privKeyList): @@ -1407,10 +1406,10 @@ def getAddrTotalTxnCount(self, addrHash): ############################################################################### def getAddrDataFromDB(self): - result = TheBridge.getAddrCombinedList(self.uniqueIDB58) + result = self.bridgeWalletObj.getAddrCombinedList() #update addr map - for addrProto in result.updatedAssets: + for addrProto in result.updated_asset: addrObj = PyBtcAddress() addrObj.loadFromProtobufPayload(addrProto) @@ -1418,16 +1417,16 @@ def getAddrDataFromDB(self): self.addrMap[addrHash] = addrObj #update balances and txio count - for i in range(0, len(result.ids)): - addrCombinedData = result.data[i] - addr = result.ids[i] + for i in range(0, len(result.balance)): + addrCombinedData = result.balance[i] + addr = addrCombinedData.id if addr in self.addrMap: addrObj = self.addrMap[addr] - addrObj.fullBalance = addrCombinedData.full - addrObj.spendableBalance = addrCombinedData.spendable - addrObj.unconfirmedBalance = addrCombinedData.unconfirmed - addrObj.txioCount = addrCombinedData.count + addrObj.fullBalance = addrCombinedData.balance.full + addrObj.spendableBalance = addrCombinedData.balance.spendable + addrObj.unconfirmedBalance = addrCombinedData.balance.unconfirmed + addrObj.txioCount = addrCombinedData.balance.count else: print ("[getAddrDataFromDB] missing address " + addr.hex()) @@ -1594,14 +1593,16 @@ def loadFromProtobufPayload(self, payload): self.labelName = payload.label self.labelDescr = payload.desc - self.lastComputedChainIndex = payload.lookupCount - self.highestUsedChainIndex = payload.useCount - self.watchingOnly = payload.watchingOnly - self.addressTypes = payload.addressTypes - self.defaultAddressType = payload.defaultAddressType + self.useEncryption = payload.use_encryption + self.lastComputedChainIndex = payload.lookup_count + self.highestUsedChainIndex = payload.use_count + self.watchingOnly = payload.watching_only + self.addressTypes = payload.address_type + self.defaultAddressType = payload.default_address_type + self.kdfMemoryReq = payload.kdf_mem_req #addrMap and chainIndexMap - for addr in payload.assets: + for addr in payload.address_data: addrObj = PyBtcAddress(self) addrObj.loadFromProtobufPayload(addr) self.addAddress(addrObj) @@ -1627,18 +1628,23 @@ def fillAddressPool(self, numPool, progressId, callback=None): def completeProcess(*args): self.loadFromProtobufPayload(*args) - callback() + if callback: + callback() - TheBridge.extendAddressPool(\ - self.uniqueIDB58, progressId, numPool, completeProcess) + self.bridgeWalletObj.extendAddressPool( + progressId, numPool, completeProcess) ############################################################################# - def registerWallet(self, isNew): - TheBridge.registerWallet(self.uniqueIDB58, isNew) + def register(self, isNew): + TheBridge.service.registerWallet(self.uniqueIDB58, isNew) ############################################################################# - def createBackupString(self, callback): - return TheBridge.createBackupStringForWallet(self.uniqueIDB58, callback) + def createBackupString(self, unlockHandler, callback): + def callbackHandler(callbackFunc, reply): + callbackFunc(reply) + + return self.bridgeWalletObj.createBackupStringForWallet( + unlockHandler, callbackHandler, [callback]) ############################################################################# def getAddressTypes(self): @@ -1661,10 +1667,23 @@ def setAddressTypeFor(self, addrObj, addrType): raise Exception(\ "[PyBtcWallet::setAddressTypeFor] inneligible address type") - protoAddr = TheBridge.setAddressTypeFor(\ - self.uniqueIDB58, addrObj.assetId, addrType) + protoAddr = self.bridgeWalletObj.setAddressTypeFor( + addrObj.assetId, addrType) addrObj.loadFromProtobufPayload(protoAddr) + ############################################################################# + def initCoinSelectionInstance(self, height): + return self.bridgeWalletObj.initCoinSelectionInstance(height) + + def createAddressBook(self): + return self.bridgeWalletObj.createAddressBook() + + def getLedgerDelegateIdForScrAddr(self, scrAddr): + return self.bridgeWalletObj.getLedgerDelegateIdForScrAddr(scrAddr) + + def getKdfMemoryReqtBytes(self): + return self.kdfMemoryReq + ############################################################################### def getSuffixedPath(walletPath, nameSuffix): fpath = walletPath diff --git a/armoryengine/Settings.py b/armoryengine/Settings.py new file mode 100644 index 000000000..5875ce54f --- /dev/null +++ b/armoryengine/Settings.py @@ -0,0 +1,208 @@ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2023, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## +import os +import sys + +from armoryengine.ArmoryUtils import ARMORY_HOME_DIR, LOGINFO, \ + LOGERROR, LOGEXCEPT, CLI_OPTIONS, SETTINGS_PATH, toUnicode + + +LANGUAGES = ["da", "de", "en", "es", "el", "fr", "he", "hr", "id", "ru", "sv"] +################################################################################ +class SettingsFile(object): + """ + This class could be replaced by the built-in QSettings in PyQt, except + that older versions of PyQt do not support the QSettings (or at least + I never figured it out). Easy enough to do it here + + All settings must populated with a simple datatype -- non-simple + datatypes should be broken down into pieces that are simple: numbers + and strings, or lists/tuples of them. + + Will write all the settings to file. Each line will look like: + SingleValueSetting1 | 3824.8 + SingleValueSetting2 | this is a string + Tuple Or List Obj 1 | 12 $ 43 $ 13 $ 33 + Tuple Or List Obj 2 | str1 $ another str + """ + + ############################################################################# + def __init__(self, path=None): + self.settingsPath = path + self.settingsMap = {} + if not path: + self.settingsPath = os.path.join(ARMORY_HOME_DIR, 'ArmorySettings.txt') + + LOGINFO('Using settings file: %s', self.settingsPath) + if os.path.exists(self.settingsPath): + self.loadSettingsFile(path) + + ############################################################################# + def pprint(self, nIndent=0): + indstr = indent*nIndent + print(indstr + 'Settings:') + for k,v in self.settingsMap.items(): + print(indstr + indent + k.ljust(15), v) + + ############################################################################# + def hasSetting(self, name): + return name in self.settingsMap + + ############################################################################# + def set(self, name, value): + if isinstance(value, tuple): + self.settingsMap[name] = list(value) + else: + self.settingsMap[name] = value + self.writeSettingsFile() + + ############################################################################# + def extend(self, name, value): + """ Adds/converts setting to list, appends value to the end of it """ + if name not in self.settingsMap: + if isinstance(value, list): + self.set(name, value) + else: + self.set(name, [value]) + else: + origVal = self.get(name, expectList=True) + if isinstance(value, list): + origVal.extend(value) + else: + origVal.append(value) + self.settingsMap[name] = origVal + self.writeSettingsFile() + + ############################################################################# + def get(self, name, expectList=False): + if not self.hasSetting(name) or self.settingsMap[name]=='': + return ([] if expectList else '') + else: + val = self.settingsMap[name] + if expectList: + if isinstance(val, list): + return val + else: + return [val] + else: + return val + + ############################################################################# + def getAllSettings(self): + return self.settingsMap + + ############################################################################# + def getSettingOrSetDefault(self, name, defaultVal, expectList=False): + output = defaultVal + if self.hasSetting(name): + output = self.get(name) + else: + self.set(name, defaultVal) + + return output + + ############################################################################# + def delete(self, name): + if self.hasSetting(name): + del self.settingsMap[name] + self.writeSettingsFile() + + ############################################################################# + def writeSettingsFile(self, path=None): + if not path: + path = self.settingsPath + f = open(path, 'wb') + for key,val in self.settingsMap.items(): + try: + # Skip anything that throws an exception + from PySide2.QtCore import QByteArray + + valStr = '' + if isinstance(val, str): + valStr = val + elif isinstance(val, int) or \ + isinstance(val, float): + valStr = str(val) + elif isinstance(val, list) or \ + isinstance(val, tuple): + valStr = ' $ '.join([str(v) for v in val]) + elif isinstance(val, QByteArray) and \ + sys.version_info >= (3,0): + valStr = str(val.data(), encoding='utf-8') + else: + valStr = str(val) + f.write(key.ljust(36).encode('utf-8')) + f.write(b' | ') + if valStr: + f.write(valStr.encode('utf-8')) + f.write(b'\n') + except: + LOGEXCEPT('Invalid entry in SettingsFile... skipping') + f.close() + + ############################################################################# + def loadSettingsFile(self, path=None): + if not path: + path = self.settingsPath + + if not os.path.exists(path): + raise FileExistsError('Settings file DNE:' + path) + + f = open(path, 'rb') + sdata = f.read() + f.close() + + # Automatically convert settings to numeric if possible + def castVal(v): + v = v.strip() + a,b = v.isdigit(), v.replace(b'.',b'').isdigit() + if a: + return int(v) + elif b: + return float(v) + else: + if v.lower()==b'true': + return True + elif v.lower()==b'false': + return False + else: + return toUnicode(v) + + + sdata = [line.strip() for line in sdata.split(b'\n')] + for line in sdata: + if len(line.strip())==0: + continue + + try: + key,vals = line.split(b'|')[0:2] + valList = [castVal(v) for v in vals.split(b'$')] + if len(valList)==1: + self.settingsMap[key.strip().decode('utf-8')] = valList[0] + else: + self.settingsMap[key.strip().decode('utf-8')] = valList + except: + LOGEXCEPT('Invalid setting in %s (skipping...)', path) + + ################################################################################ + def getGuiLanguage(self): + if CLI_OPTIONS.language == "en": + langSetting = self.get('Language') + else: + langSetting = CLI_OPTIONS.language + if langSetting not in LANGUAGES: + LOGERROR("Unsupported language %s specified. Defaulting to English (en)", langSetting) + langSetting = "en" + LOGINFO("Using Language: %s", langSetting) + return "armory_" + langSetting + ".qm" + +TheSettings = SettingsFile(SETTINGS_PATH) diff --git a/armoryengine/Transaction.py b/armoryengine/Transaction.py index 46b9ad673..a519ed724 100644 --- a/armoryengine/Transaction.py +++ b/armoryengine/Transaction.py @@ -21,6 +21,7 @@ from armoryengine.BDM import TheBDM, BDM_BLOCKCHAIN_READY from armoryengine.Script import convertScriptToOpStrings +from qtdialogs.DlgUnlockWallet import UnlockWalletHandler UNSIGNED_TX_VERSION = 2 @@ -45,8 +46,8 @@ class InputSignedStatusObject(object): def __init__(self, protoData): self.proto = protoData self.pubKeyMap = {} - for pubKeyPair in protoData.signStateList: - self.pubKeyMap[pubKeyPair.pubKey] = pubKeyPair.hasSig + for pubKeyPair in protoData.sign_state: + self.pubKeyMap[pubKeyPair.pub_key] = pubKeyPair.has_sig ############################################################################# def isSignedForPubKey(self, pubkey): @@ -83,8 +84,8 @@ def setLockTime(self, locktime): self.signer.setLockTime(locktime) ############################################################################# - def addSpenderByOutpoint(self, hashVal, index, seq, value): - self.signer.addSpenderByOutpoint(hashVal, index, seq, value) + def addSpenderByOutpoint(self, hashVal, index, seq): + self.signer.addSpenderByOutpoint(hashVal, index, seq) ############################################################################# def populateUtxo(self, hashVal, index, value, script): @@ -107,13 +108,17 @@ def fromTxSigCollect(self, txSigCollect): self.signer.fromTxSigCollect(txSigCollect) ############################################################################# - def signTx(self, wltId, callback): - self.signer.signTx(wltId, callback) + def signTx(self, wltId, callback, parentDlg): + def callbackHandler(callbackFunc, reply): + callbackFunc(reply.success) + + unlockHandler = UnlockWalletHandler(wltId, "Sign Transaction", parentDlg) + self.signer.signTx(wltId, unlockHandler, callbackHandler, [callback]) ############################################################################# def getSignedTx(self): rawSignedTx = self.signer.getSignedTx() - if len(rawSignedTx) == 0: + if rawSignedTx == None: raise SignatureError() signedTx = PyTx() @@ -123,7 +128,7 @@ def getSignedTx(self): ############################################################################# def getUnsignedTx(self): rawTx = self.signer.getUnsignedTx() - if len(rawTx) == 0: + if rawTx == None: raise SignatureError() pytx = PyTx() @@ -203,7 +208,7 @@ def getHash160ListFromMultisigScrAddr(scrAddr): ################################################################################ # These two methods are just easier-to-type wrappers around the C++ methods def getTxOutScriptType(script): - return TheBridge.getTxOutScriptType(script) + return TheBridge.scriptUtils.getTxOutScriptType(script) ################################################################################ # These two methods are just easier-to-type wrappers around the C++ methods @@ -229,7 +234,7 @@ def getTxInScriptType(txinObj): """ script = txinObj.binScript prevTx = txinObj.outpoint.txHash - return TheBridge.getTxInScriptType(script, prevTx) + return TheBridge.scriptUtils.getTxInScriptType(script, prevTx) ################################################################################ def getTxInP2SHScriptType(txinObj): @@ -244,7 +249,7 @@ def getTxInP2SHScriptType(txinObj): if not scrType==CPP_TXIN_SPENDP2SH: return None - lastPush = TheBridge.getLastPushDataInScript(txinObj.binScript) + lastPush = TheBridge.scriptUtils.getLastPushDataInScript(txinObj.binScript) return getTxOutScriptType(lastPush) @@ -253,8 +258,8 @@ def getTxInP2SHScriptType(txinObj): def TxInExtractAddrStrIfAvail(txinObj): rawScript = txinObj.binScript prevTxHash = txinObj.outpoint.txHash - scrType = TheBridge.getTxInScriptType(rawScript, prevTxHash) - lastPush = TheBridge.getLastPushDataInScript(rawScript) + scrType = TheBridge.scriptUtils.getTxInScriptType(rawScript, prevTxHash) + lastPush = TheBridge.scriptUtils.getLastPushDataInScript(rawScript) if scrType in [CPP_TXIN_STDUNCOMPR, CPP_TXIN_STDCOMPR]: return hash160_to_addrStr( hash160(lastPush) ) @@ -1423,13 +1428,10 @@ def unserialize(self, rawBinaryData, skipMagicCheck=False): ############################################################################# def evaluateSigningStatus(self, signerObj): inputSignStatus = signerObj.getSignedStateForInput(self.inputID) - signStatus = InputSigningStatus() signStatus.M = inputSignStatus.proto.m signStatus.N = inputSignStatus.proto.n - - signStatus.statusM = [TXIN_SIGSTAT.NO_SIGNATURE]*signStatus.M signStatus.statusN = [TXIN_SIGSTAT.NO_SIGNATURE]*signStatus.N @@ -1897,11 +1899,15 @@ def setupSigner(self): self.signer.setLockTime(self.lockTime) for txin in self.pytxObj.inputs: - self.signer.addSpenderByOutpoint(\ - txin.outpoint.txHash, txin.outpoint.txOutIndex, \ - txin.intSeq, txin.outpointValue) - self.signer.populateUtxo(txin.outpoint.txHash, txin.outpoint.txOutIndex, \ - txin.outpointValue, txin.binScript) + self.signer.addSpenderByOutpoint( + txin.outpoint.txHash, + txin.outpoint.txOutIndex, + txin.intSeq) + self.signer.populateUtxo( + txin.outpoint.txHash, + txin.outpoint.txOutIndex, + txin.outpointValue, + txin.binScript) if txin.supportTx != None: self.signer.addSupportingTx(txin.supportTx) @@ -2008,8 +2014,8 @@ def createFromPyTx(self, pytx, pubKeyMap=None, txMap=None, p2shMap=None): 'in supplied txMap') pyPrevTx = txMap[txhash].copy() elif TheBDM.getState()==BDM_BLOCKCHAIN_READY: - txRaw = TheBridge.getTxByHash(txhash) - if not txRaw.isValid: + txRaw = TheBridge.service.getTxByHash(txhash) + if not txRaw: raise InvalidHashError('Could not find the referenced tx') pyPrevTx = PyTx().unserialize(txRaw.raw) else: @@ -2463,174 +2469,14 @@ def resolveSigner(self, wltId): self.signer.resolve(wltId) ############################################################################# - def signTx(self, wltId, callback): - self.signer.signTx(wltId, callback) - -################################################################################ -# This is intended only for lists of unsignedTxInputs that have all unlocked -# signing keys in the signAddrObjMap. Map is all [scrAddr, PyBtcAddress] pairs. -# -# This method is intended for sweep transaction where a bundle of private keys -# were provided. -def PyCreateAndSignTx(ustxiList, dtxoList, sbdPrivKeyMap, hashcode=1, DetSign=True): - ustx = UnsignedTransaction().createFromUnsignedTxIO(ustxiList, dtxoList) - - for ustxiIndex in range(len(ustx.ustxInputs)): - for scrAddr in ustx.ustxInputs[ustxiIndex].scrAddrs: - sbdPriv = sbdPrivKeyMap.get(scrAddr) - if sbdPriv is None: - raise SignatureError('Supplied key map cannot sign all inputs') - ustx.createAndInsertSignatureForInput(ustxiIndex, sbdPriv, hashcode, - DetSign) - - # Make sure everything was good. - if not ustx.verifySigsAllInputs(): - raise SignatureError('Not all signatures are present or valid') - - return ustx.getSignedPyTx(doVerifySigs=False) # already checked them - - - -################################################################################ -# NOTE: This method was actually used to create the Blockchain-reorg unit- -# test, and hence why coinbase transactions are supported. However, -# for normal transactions supported by PyBtcEngine, this support is -# unnecessary. -# -# Additionally, this method both creates and signs the tx: however -# PyBtcEngine employs TxDistProposals which require the construction -# and signing to be two separate steps. This method is not suited -# for most of the armoryengine CONOPS. -# -# On the other hand, this method DOES work, and there is no reason -# not to use it if you already have PyBtcAddress-w-PrivKeys avail -# and have a list of inputs and outputs as described below. -# -# This method will take an already-selected set of TxOuts, along with -# PyBtcAddress objects containing necessary the private keys -# -# Src TxIn ~ {PyBtcAddr, PrevTx, PrevTxOutIdx} -OR- -# {MultiSigLockbox, PrevTx, PrevTxOutIdx, [PyBtcAddress], isP2SH} -# -OR- COINBASE = -1 (dst must not be multisig) -# If src is multisig, PyBtcAddress list is the signing prv keys -# Dst TxOut ~ {PyBtcAddr, value} -OR- {MultiSigLockbox, value, isP2SH} -# -# Of course, we usually don't have the private keys of the dst addrs... -# -def PyCreateAndSignTx_old(srcTxOuts, dstAddrsVals): - # This needs to support multisig. Perhaps the funct should just be moved.... - newTx = PyTx() - newTx.version = 1 - newTx.lockTime = 0 - newTx.inputs = [] - newTx.outputs = [] - - numInputs = len(srcTxOuts) - numOutputs = len(dstAddrsVals) - - coinbaseTx = False - if numInputs==1 and srcTxOuts[0] == -1: - coinbaseTx = True - - ############################# - # Fill in TxOuts first - for i in range(numOutputs): - txout = PyTxOut() - txout.value = dstAddrsVals[i][1] - dst = dstAddrsVals[i][0] - if type(dst) is not MultiSigLockbox: - if(coinbaseTx): - txout.binScript = pubkey_to_p2pk_script(dst.binPublicKey65.toBinStr()) - else: - txout.binScript = hash160_to_p2pkhash_script(dst.getAddr160()) - else: - dstMultiP2SH = dstAddrsVals[i][2] - if not dstMultiP2SH: - txout.binScript = dst.binScript - else: - txout.binScript = script_to_p2sh_script(dst.binScript) - - newTx.outputs.append(txout) - - ############################# - # Create temp TxIns with blank scripts - for i in range(numInputs): - txin = PyTxIn() - txin.outpoint = PyOutPoint() - if(coinbaseTx): - txin.outpoint.txHash = '\x00'*32 - txin.outpoint.txOutIndex = binary_to_int('\xff'*4) - else: - txin.outpoint.txHash = hash256(srcTxOuts[i][1].serialize()) - txin.outpoint.txOutIndex = srcTxOuts[i][2] - txin.binScript = '' - txin.intSeq = 2**32-1 - newTx.inputs.append(txin) - - ############################# - # Now we apply the ultra-complicated signature procedure - # We need a copy of the Tx with all the txin scripts blanked out - txCopySerialized = newTx.serialize() - for i in range(numInputs): - if coinbaseTx: - pass - else: - # Only implemented one type of hashing: SIGHASH_ALL - hashType = 1 # SIGHASH_ALL - hashCode1 = int_to_binary(1, widthBytes=1) - hashCode4 = int_to_binary(1, widthBytes=4) - - txCopy = PyTx().unserialize(txCopySerialized) - txoutIdx = srcTxOuts[i][2] - prevTxOut = srcTxOuts[i][1].outputs[txoutIdx] - binToSign = '' - src = srcTxOuts[i][0] - - # Copy the script of the TxOut we're spending, into the txIn script - txCopy.inputs[i].binScript = prevTxOut.binScript - preHashMsg = txCopy.serialize() + hashCode4 - - if type(src) is not MultiSigLockbox: - assert(src.hasPrivKey()) - - # Create the sig, and use deterministic signing. - signature = src.generateDERSignature(preHashMsg, DetSign=ENABLE_DETSIGN) - - # If we are spending a Coinbase-TxOut, only need sig, no pubkey - # Don't forget to tack on the one-byte hashcode and consider it part of sig - if len(prevTxOut.binScript) > 30: - sigLenInBinary = int_to_binary(len(signature) + 1) - newTx.inputs[i].binScript = sigLenInBinary + signature + hashCode1 - else: - pubkey = src.binPublicKey65.toBinStr() - sigLenInBinary = int_to_binary(len(signature) + 1) - pubkeyLenInBinary = int_to_binary(len(pubkey) ) - newTx.inputs[i].binScript = sigLenInBinary + signature + hashCode1 + \ - pubkeyLenInBinary + pubkey - - else: - newTx.inputs[i].binScript = int_to_binary(OP_0) - - for nxtAddr in srcTxOuts[i][3]: - assert(nxtAddr.hasPrivKey()) - signature = nxtAddr.generateDERSignature(preHashMsg, DetSign=ENABLE_DETSIGN) - sigLenInBinary = int_to_binary(len(signature) + 1) - newTx.inputs[i].binScript += sigLenInBinary + signature + hashCode1 - - srcMultiP2SH = srcTxOuts[i][4] - if srcMultiP2SH: - newTx.inputs[i].binScript += src.binScript - - ############################# - # Finally, our tx is complete! - return newTx - + def signTx(self, wltId, callback, parentDlg=None): + self.signer.signTx(wltId, callback, parentDlg) ############################################################################# def getFeeForTx(txHash): if TheBDM.getState()==BDM_BLOCKCHAIN_READY: try: - tx = PyTx().unserialize(TheBridge.getTxByHash(txHash).raw) + tx = PyTx().unserialize(TheBridge.service.getTxByHash(txHash).raw) if not tx.isInitialized(): LOGERROR('Attempted to get fee for tx we don\'t have...? %s', \ binary_to_hex(txHash,BIGENDIAN)) @@ -2662,11 +2508,12 @@ def determineSentToSelfAmt(le, wlt): creative with this tx, this may not actually work. """ amt = 0 - if le.isSentToSelf: - txProto = TheBridge.getTxByHash(le.hash) - pytx = PyTx().unserialize(txProto.raw) - if not pytx.isInitialized(): + if le.sent_to_senf: + txProto = TheBridge.service.getTxByHash(le.hash) + if txProto == None: return (0, 0) + + pytx = PyTx().unserialize(txProto.raw) if pytx.getNumTxOut()==1: return (pytx.outputs[0].getValue(), -1) maxChainIndex = -5 diff --git a/armoryengine/UserAddressUtils.py b/armoryengine/UserAddressUtils.py index 82ba0164e..897c42b62 100755 --- a/armoryengine/UserAddressUtils.py +++ b/armoryengine/UserAddressUtils.py @@ -85,8 +85,8 @@ def getWltIDForScrAddr(scrAddr, walletMap): scrAddr = script_to_scrAddr(outScript) wltID = getWltIDForScrAddr(a160, wltMap) else: - scrAddr = TheBridge.getScrAddrForAddrStr(userStr) - outScript = TheBridge.getTxOutScriptForScrAddr(scrAddr) + scrAddr = TheBridge.utils.getScrAddrForAddrStr(userStr) + outScript = TheBridge.scriptUtils.getTxOutScriptForScrAddr(scrAddr) hasAddrInIt = True # Check if it's a wallet scrAddr @@ -106,7 +106,7 @@ def getWltIDForScrAddr(scrAddr, walletMap): 'LboxID': lboxID, 'ShowID': hasAddrInIt, 'IsBech32' : isBech32} - except: + except Exception as e: #LOGEXCEPT('Invalid user string entered') return {'Script': None, 'WltID': None, @@ -276,10 +276,10 @@ def truncateStr(theStr, maxLen): dispStr = '' if scriptType == CPP_TXOUT_P2WPKH or scriptType == CPP_TXOUT_P2WSH: try: - dispStr = TheBridge.getAddrStrForScrAddr(binScript) + dispStr = TheBridge.scriptUtils.getAddrStrForScrAddr(binScript) except BridgeError as e: - scrAddr = TheBridge.getScrAddrForScript(binScript) - dispStr = TheBridge.getAddrStrForScrAddr(scrAddr) + scrAddr = TheBridge.scriptUtils.getScrAddrForScript(binScript) + dispStr = TheBridge.scriptUtils.getAddrStrForScrAddr(scrAddr) addrStr = dispStr elif scriptType in CPP_TXOUT_HAS_ADDRSTR: addrStr = script_to_addrStr(binScript) diff --git a/armorymodels.py b/armorymodels.py index dd6a39dd5..0dc3744ea 100755 --- a/armorymodels.py +++ b/armorymodels.py @@ -19,7 +19,7 @@ Signal, QSortFilterProxyModel from PySide2.QtWidgets import QStyle, QApplication, QCalendarWidget, \ QLineEdit, QFrame, QGridLayout, QPushButton, QAbstractItemView, \ - QStyledItemDelegate, QTableView + QStyledItemDelegate, QTableView, QLabel from armoryengine.ArmoryUtils import enum, coin2str, binary_to_hex, \ @@ -442,24 +442,24 @@ def reset(self, hard=False): self.bottomPage.id = 1 self.bottomPage.table = [] - if self.topPage.id == 0 or len(self.topPage.table) == 0: + if self.topPage.id == 0 or not self.topPage.table: try: - self.topPage.rawData = TheBridge.getHistoryPageForDelegate(\ + self.topPage.rawData = TheBridge.service.getHistoryPageForDelegate( self.ledgerDelegateId, self.topPage.id) toTable = self.convertLedger(self.topPage.rawData) self.topPage.table = toTable except: pass - if self.currentPage.id == 0 or len(self.currentPage.table) == 0: - self.currentPage.rawData = TheBridge.getHistoryPageForDelegate(\ + if self.currentPage.id == 0 or not self.currentPage.table: + self.currentPage.rawData = TheBridge.service.getHistoryPageForDelegate( self.ledgerDelegateId, self.currentPage.id) toTable = self.convertLedger(self.currentPage.rawData) self.currentPage.table = toTable - if len(self.bottomPage.table) == 0: + if not self.bottomPage.table: try: - self.bottomPage.rawData = TheBridge.getHistoryPageForDelegate(\ + self.bottomPage.rawData = TheBridge.service.getHistoryPageForDelegate( self.ledgerDelegateId, self.bottomPage.id) toTable = self.convertLedger(self.bottomPage.rawData) self.bottomPage.table = toTable @@ -511,17 +511,17 @@ def filterRawData(rawDataList, filter): return None if self.bottomPage.rawData: - result = filterRawData(self.bottomPage.rawData.le, filter) + result = filterRawData(self.bottomPage.rawData.ledger, filter) if result: return result if self.currentPage.rawData: - result = filterRawData(self.currentPage.rawData.le, filter) + result = filterRawData(self.currentPage.rawData.ledger, filter) if result: return result if self.topPage.rawData: - result = filterRawData(self.topPage.rawData.le, filter) + result = filterRawData(self.topPage.rawData.ledger, filter) if result: return result @@ -1400,8 +1400,6 @@ def headerData(self, section, orientation, role=Qt.DisplayRole): class SentToAddrBookModel(QAbstractTableModel): def __init__(self, wltID, main): super(SentToAddrBookModel, self).__init__() - - self.wltID = wltID self.main = main self.wlt = self.main.walletMap[wltID] @@ -1411,18 +1409,18 @@ def __init__(self, wltID, main): # http://sourceforge.net/tracker/?func=detail&atid=101645&aid=3403085&group_id=1645 # Must use awkwardness to get around iterating a vector in # the python code... :( - addressBook = TheBridge.createAddressBook(wltID) - nabe = len(addressBook) + addressBook = self.wlt.createAddressBook() + nabe = len(addressBook.address) for i in range(nabe): - abe = addressBook[i] + abe = addressBook.address[i] - scrAddr = abe.scrAddr + scrAddr = abe.scraddr try: addr160 = addrStr_to_hash160(scrAddr_to_addrStr(scrAddr))[1] # Only grab addresses that are not in any of your Armory wallets if not self.main.getWalletForAddrHash(addr160): - txHashList = abe.txHashes + txHashList = abe.tx_hash self.addrBook.append( [scrAddr, txHashList] ) except Exception as e: # This is not necessarily an error. It could be a lock box LOGERROR(str(e)) diff --git a/configure.ac b/configure.ac index 52a05e721..34eea4f78 100755 --- a/configure.ac +++ b/configure.ac @@ -80,8 +80,8 @@ AC_CONFIG_MACRO_DIR([build-aux/m4]) m4_include([build-aux/m4/ax_check_compile_flag.m4]) m4_include([build-aux/m4/ax_cxx_compile_stdcxx.m4]) -#check for c++11 -AX_CXX_COMPILE_STDCXX([11], [noext], [mandatory], [nodefault]) +#check for c++14 +AX_CXX_COMPILE_STDCXX([14], [noext], [mandatory], [nodefault]) # Make the compilation flags quiet unless V=1 is used. m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) diff --git a/cppForSwig/ArmoryBackups.cpp b/cppForSwig/ArmoryBackups.cpp index 7e186b7eb..ad0e55390 100644 --- a/cppForSwig/ArmoryBackups.cpp +++ b/cppForSwig/ArmoryBackups.cpp @@ -1055,7 +1055,7 @@ shared_ptr Helpers::restoreFromBackup( { //prompt caller on decrypt error and return callerPrompt(RestorePromptType::DecryptError, {}, promptDummy); - throw RestoreUserException("invalid SP pass"); + throw RestoreUserException("invalid SP pass"); } } @@ -1098,7 +1098,7 @@ shared_ptr Helpers::restoreFromBackup( case BackupType::Armory135: { /*legacy armory wallet*/ - + auto id = SecureBinaryData::fromString( computeWalletId(primaryData.data_, secondaryData.data_)); if (!callerPrompt(RestorePromptType::Id, checksumIndexes, id)) diff --git a/cppForSwig/ArmoryConfig.cpp b/cppForSwig/ArmoryConfig.cpp index 16354f610..c704df38b 100755 --- a/cppForSwig/ArmoryConfig.cpp +++ b/cppForSwig/ArmoryConfig.cpp @@ -131,6 +131,7 @@ const string& Armory::Config::getDataDir() void Armory::Config::parseArgs(int argc, char* argv[], ProcessType procType) { vector lines; + lines.reserve(argc); for (int i=1; i #include "BridgeSocket.h" #include "CppBridge.h" @@ -16,14 +17,25 @@ int main(int argc, char* argv[]) startupBIP151CTX(); startupBIP150CTX(4); + unsigned count = argc + 1; + auto args = new char*[count]; + for (int i=0; i( @@ -32,8 +44,6 @@ int main(int argc, char* argv[]) Armory::Config::NetworkSettings::oneWayAuth(), Armory::Config::NetworkSettings::isOffline()); - bridge->startThreads(); - //setup the socket auto sockPtr = std::make_shared( "127.0.0.1", "46122", bridge); @@ -56,8 +66,6 @@ int main(int argc, char* argv[]) //block main thread till socket dies sockPtr->blockUntilClosed(); - bridge->stopThreads(); - //done LOGINFO << "exiting"; diff --git a/cppForSwig/BridgeAPI/BridgeSocket.cpp b/cppForSwig/BridgeAPI/BridgeSocket.cpp index 371dfcc8d..8e99dc554 100755 --- a/cppForSwig/BridgeAPI/BridgeSocket.cpp +++ b/cppForSwig/BridgeAPI/BridgeSocket.cpp @@ -328,21 +328,18 @@ void WritePayload_Bridge::serialize(std::vector& data) return; auto msgSize = message_->ByteSizeLong(); - data.resize(msgSize + 9 + POLY1305MACLEN); + data.resize(msgSize + 5 + POLY1305MACLEN); //set packet size - uint32_t sizeVal = msgSize + 5; + uint32_t sizeVal = msgSize + 1; memcpy(&data[0], &sizeVal, sizeof(uint32_t)); - //set id - memcpy(&data[5], &id_, sizeof(uint32_t)); - //serialize protobuf message - message_->SerializeToArray(&data[9], msgSize); + message_->SerializeToArray(&data[5], msgSize); } //////////////////////////////////////////////////////////////////////////////// size_t WritePayload_Bridge::getSerializedSize(void) const { - return message_->ByteSizeLong() + 9 + POLY1305MACLEN; + return message_->ByteSizeLong() + 5 + POLY1305MACLEN; } diff --git a/cppForSwig/BridgeAPI/CppBridge.cpp b/cppForSwig/BridgeAPI/CppBridge.cpp index ae94c4556..cddf22c65 100755 --- a/cppForSwig/BridgeAPI/CppBridge.cpp +++ b/cppForSwig/BridgeAPI/CppBridge.cpp @@ -45,30 +45,7 @@ CppBridge::CppBridge(const string& path, const string& dbAddr, const string& dbPort, bool oneWayAuth, bool offline) : path_(path), dbAddr_(dbAddr), dbPort_(dbPort), dbOneWayAuth_(oneWayAuth), dbOffline_(offline) -{ - commandWithCallbackQueue_ = make_shared< - Armory::Threading::BlockingQueue>(); -} - -//////////////////////////////////////////////////////////////////////////////// -void CppBridge::startThreads() -{ - auto commandLbd = [this]() - { - this->processCommandWithCallbackThread(); - }; - - commandWithCallbackProcessThread_ = thread(commandLbd); -} - -//////////////////////////////////////////////////////////////////////////////// -void CppBridge::stopThreads() -{ - commandWithCallbackQueue_->terminate(); - - if (commandWithCallbackProcessThread_.joinable()) - commandWithCallbackProcessThread_.join(); -} +{} //////////////////////////////////////////////////////////////////////////////// bool CppBridge::processData(BinaryDataRef socketData) @@ -76,119 +53,6 @@ bool CppBridge::processData(BinaryDataRef socketData) return ProtobufCommandParser::processData(this, socketData); } -//////////////////////////////////////////////////////////////////////////////// -void CppBridge::queueCommandWithCallback(BridgeProto::Request msg) -{ - commandWithCallbackQueue_->push_back(move(msg)); -} - -//////////////////////////////////////////////////////////////////////////////// -void CppBridge::processCommandWithCallbackThread() -{ - /*** - This class of methods needs to interact several times with the user in the - course of their lifetime. A dedicated callback object to keep track of the - methods running thread and set of callbacks awaiting a return. - ***/ - - while (true) - { - BridgeProto::Request msg; - try - { - msg = move(commandWithCallbackQueue_->pop_front()); - } - catch (const StopBlockingLoop&) - { - break; - } - - /*BinaryDataRef opaqueRef; - if (msg.byteargs_size() < 2) - { - //msg has to carry a callback id - if (msg.byteargs_size() == 0) - throw runtime_error("malformed command"); - } - else - { - //grab opaque data - opaqueRef.setRef(msg.byteargs(1)); - } - - //grab callback id - BinaryDataRef callbackId; - callbackId.setRef(msg.byteargs(0)); - - auto getCallbackHandler = [this, &callbackId]()-> - shared_ptr - { - //grab the callback handler, add to map if missing - auto iter = callbackHandlerMap_.find(callbackId); - if (iter == callbackHandlerMap_.end()) - { - auto insertIter = callbackHandlerMap_.emplace( - callbackId, make_shared( - callbackId, commandWithCallbackQueue_)); - - iter = insertIter.first; - } - - return iter->second; - }; - - auto deleteCallbackHandler = [this, &callbackId]() - { - callbackHandlerMap_.erase(callbackId); - }; - - //process the commands - try - { - switch (msg.method_case()) - { - case MethodsWithCallback::followUp: - { - //this is a reply to an existing callback - if (msg.intargs_size() == 0) - throw runtime_error("missing callback arguments"); - - auto handler = getCallbackHandler(); - handler->processCallbackReply(msg.intargs(0), opaqueRef); - break; - } - - case MethodsWithCallback::cleanup: - { - //caller is done, cleanup callbacks entry from map - deleteCallbackHandler(); - break; - } - - //Entry point to the methods, they will populate their respective - //callbacks object with lambdas to process the returned values - case MethodsWithCallback::restoreWallet: - { - auto handler = getCallbackHandler(); - restoreWallet(opaqueRef, handler); - break; - } - - default: - throw runtime_error("unknown command"); - } - } - catch (const exception& e) - { - //make sure to cleanup the callback map entry on throws - deleteCallbackHandler(); - - //rethrow so that the caller can handle the error - throw e; - }*/ - } -} - //////////////////////////////////////////////////////////////////////////////// void CppBridge::writeToClient(BridgePayload msgPtr) const { @@ -198,41 +62,28 @@ void CppBridge::writeToClient(BridgePayload msgPtr) const } //////////////////////////////////////////////////////////////////////////////// -PassphraseLambda CppBridge::createPassphrasePrompt(UnlockPromptType promptType) +void CppBridge::callbackWriter(ServerPushWrapper& wrapper) { - unique_lock lock(passPromptMutex_); - auto&& id = fortuna_.generateRandom(6).toHexStr(); - auto passPromptObj = make_shared(id, writeLambda_); - - promptMap_.insert(make_pair(id, passPromptObj)); - return passPromptObj->getLambda(promptType); + setCallbackHandler(wrapper); + writeToClient(move(wrapper.payload)); } //////////////////////////////////////////////////////////////////////////////// -bool CppBridge::returnPassphrase( - const string& promptId, const string& passphrase) -{ - unique_lock lock(passPromptMutex_); - auto iter = promptMap_.find(promptId); - if (iter == promptMap_.end()) - return false; - - iter->second->setReply(passphrase); - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -void CppBridge::loadWallets(unsigned id) +void CppBridge::loadWallets(const string& callbackId, unsigned referenceId) { if (wltManager_ != nullptr) return; - auto thrLbd = [this, id](void)->void + auto thrLbd = [this, callbackId, referenceId](void)->void { - auto lbd = createPassphrasePrompt(UnlockPromptType::migrate); + auto passPromptObj = make_shared( + callbackId, [this](ServerPushWrapper wrapper){ + this->callbackWriter(wrapper); + }); + auto lbd = passPromptObj->getLambda(); wltManager_ = make_shared(path_, lbd); auto response = createWalletsPacket(); - response->mutable_reply()->set_reference_id(id); + response->mutable_reply()->set_reference_id(referenceId); writeToClient(move(response)); }; @@ -241,6 +92,15 @@ void CppBridge::loadWallets(unsigned id) thr.detach(); } +//////////////////////////////////////////////////////////////////////////////// +WalletPtr CppBridge::getWalletPtr(const string& wltId) const +{ + auto wai = WalletAccountIdentifier::deserialize(wltId); + auto wltContainer = wltManager_->getWalletContainer( + wai.walletId, wai.accountId); + return wltContainer->getWalletPtr(); +} + //////////////////////////////////////////////////////////////////////////////// BridgePayload CppBridge::createWalletsPacket() { @@ -397,15 +257,20 @@ void CppBridge::registerWallet(const string& id, bool isNew) } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::createBackupStringForWallet( - const string& id, unsigned msgId) +void CppBridge::createBackupStringForWallet(const string& waaId, + const string& callbackId, unsigned msgId) { - auto passLbd = createPassphrasePrompt(UnlockPromptType::decrypt); - auto wai = WalletAccountIdentifier::deserialize(id); + auto wai = WalletAccountIdentifier::deserialize(waaId); const auto& walletId = wai.walletId; - auto backupStringLbd = [this, walletId, msgId, passLbd]()->void + auto backupStringLbd = [this, walletId, msgId, callbackId]()->void { + auto passPromptObj = make_shared( + callbackId, [this](ServerPushWrapper wrapper){ + this->callbackWriter(wrapper); + }); + auto lbd = passPromptObj->getLambda(); + Armory::Backups::WalletBackup backupData; try { @@ -413,13 +278,13 @@ void CppBridge::createBackupStringForWallet( auto wltContainer = wltManager_->getWalletContainer(walletId); //grab root - backupData = move(wltContainer->getBackupStrings(passLbd)); + backupData = move(wltContainer->getBackupStrings(lbd)); } catch (const exception&) {} //wind down passphrase prompt - passLbd({BridgePassphrasePrompt::concludeKey}); + passPromptObj->cleanup(); auto payload = make_unique(); auto reply = payload->mutable_reply(); @@ -463,8 +328,7 @@ void CppBridge::createBackupStringForWallet( } //////////////////////////////////////////////////////////////////////////////// -void CppBridge::restoreWallet( - const BinaryDataRef& msgRef, shared_ptr handler) +void CppBridge::restoreWallet(const BinaryDataRef& msgRef) { /* Needs 2 lines for the root, possibly another 2 for the chaincode, possibly @@ -482,9 +346,10 @@ void CppBridge::restoreWallet( throw runtime_error("[restoreWallet] invalid root lines count"); // - auto restoreLbd = [this, handler](RestoreWalletPayload msg) + #if 0 + auto restoreLbd = [this](RestoreWalletPayload msg) { - auto createCallbackMessage = [handler]( + auto createCallbackMessage = []( int promptType, const vector chkResults, SecureBinaryData& extra)->unique_ptr @@ -628,8 +493,9 @@ void CppBridge::restoreWallet( handler->flagForCleanup(); }; + #endif - handler->methodThr_ = thread(restoreLbd, move(msg)); + //handler->methodThr_ = thread(restoreLbd, move(msg)); } //////////////////////////////////////////////////////////////////////////////// @@ -824,7 +690,8 @@ BridgePayload CppBridge::getAddrCombinedList(const string& id) for (auto& addrPair : updatedMap) { auto newAsset = aabData->add_updated_asset(); - CppToProto::addr(newAsset, addrPair.second, accPtr); + CppToProto::addr(newAsset, addrPair.second, accPtr, + wltContainer->getDefaultEncryptionKeyId()); } reply->set_success(true); @@ -842,9 +709,8 @@ BridgePayload CppBridge::getHighestUsedIndex(const string& id) auto reply = payload->mutable_reply(); reply->set_success(true); - auto index = reply->mutable_wallet()->mutable_highest_used_index(); - index->set_index(wltContainer->getHighestUsedIndex()); - + reply->mutable_wallet()->set_highest_used_index( + wltContainer->getHighestUsedIndex()); return payload; } @@ -930,11 +796,11 @@ void CppBridge::extendAddressPool(const string& wltId, } //////////////////////////////////////////////////////////////////////////////// -string CppBridge::createWallet(const Utils::CreateWallet& msg) +string CppBridge::createWallet( + const Utils::CreateWalletStruct& createWalletProto) { if (wltManager_ == nullptr) throw runtime_error("wallet manager is not initialized"); - const auto& createWalletProto = msg.wallet_struct(); //extra entropy SecureBinaryData extraEntropy; @@ -1013,8 +879,9 @@ BridgePayload CppBridge::getNewAddress(const string& id, unsigned type) auto reply = payload->mutable_reply(); reply->set_success(true); - auto addrProto = reply->mutable_wallet()->mutable_asset(); - CppToProto::addr(addrProto, addrPtr, accPtr); + auto addrProto = reply->mutable_wallet()->mutable_address_data(); + CppToProto::addr(addrProto, addrPtr, accPtr, + wltContainer->getDefaultEncryptionKeyId()); return payload; } @@ -1033,8 +900,9 @@ BridgePayload CppBridge::getChangeAddress(const string& id, unsigned type) auto reply = payload->mutable_reply(); reply->set_success(true); - auto addrProto = reply->mutable_wallet()->mutable_asset(); - CppToProto::addr(addrProto, addrPtr, accPtr); + auto addrProto = reply->mutable_wallet()->mutable_address_data(); + CppToProto::addr(addrProto, addrPtr, accPtr, + wltContainer->getDefaultEncryptionKeyId()); return payload; } @@ -1053,8 +921,9 @@ BridgePayload CppBridge::peekChangeAddress(const string& id, unsigned type) auto reply = payload->mutable_reply(); reply->set_success(true); - auto addrProto = reply->mutable_wallet()->mutable_asset(); - CppToProto::addr(addrProto, addrPtr, accPtr); + auto addrProto = reply->mutable_wallet()->mutable_address_data(); + CppToProto::addr(addrProto, addrPtr, accPtr, + wltContainer->getDefaultEncryptionKeyId()); return payload; } @@ -1110,8 +979,7 @@ BridgePayload CppBridge::getTxInScriptType( auto reply = payload->mutable_reply(); reply->set_success(true); - auto scriptType = reply->mutable_script_utils()->mutable_txin_script_type(); - scriptType->set_script_type(typeInt); + reply->mutable_script_utils()->set_txin_script_type(typeInt); return payload; } @@ -1124,8 +992,7 @@ BridgePayload CppBridge::getTxOutScriptType(const BinaryData& script) const auto reply = payload->mutable_reply(); reply->set_success(true); - auto scriptType = reply->mutable_script_utils()->mutable_txout_script_type(); - scriptType->set_script_type(typeInt); + reply->mutable_script_utils()->set_txout_script_type(typeInt); return payload; } @@ -1138,8 +1005,9 @@ BridgePayload CppBridge::getScrAddrForScript( auto payload = make_unique(); auto reply = payload->mutable_reply(); reply->set_success(true); - auto scrAddr = reply->mutable_script_utils()->mutable_scraddr(); - scrAddr->set_scraddr(resultBd.toCharPtr(), resultBd.getSize()); + + reply->mutable_script_utils()->set_scraddr( + resultBd.toCharPtr(), resultBd.getSize()); return payload; } @@ -1153,8 +1021,8 @@ BridgePayload CppBridge::getScrAddrForAddrStr(const string& addrStr) const { auto resultBd = BtcUtils::getScrAddrForAddrStr(addrStr); reply->set_success(true); - auto scrAddr = reply->mutable_script_utils()->mutable_scraddr(); - scrAddr->set_scraddr(resultBd.toCharPtr(), resultBd.getSize()); + reply->mutable_utils()->set_scraddr( + resultBd.toCharPtr(), resultBd.getSize()); } catch (const exception& e) { @@ -1178,8 +1046,8 @@ BridgePayload CppBridge::getLastPushDataInScript(const BinaryData& script) const else { reply->set_success(true); - auto pushData = reply->mutable_script_utils()->mutable_push_data(); - pushData->set_data(result.getCharPtr(), result.getSize()); + reply->mutable_script_utils()->set_push_data( + result.getCharPtr(), result.getSize()); } return payload; } @@ -1193,8 +1061,7 @@ BridgePayload CppBridge::getHash160(const BinaryDataRef& dataRef) const auto reply = payload->mutable_reply(); reply->set_success(true); - auto hashMsg = reply->mutable_utils()->mutable_hash(); - hashMsg->set_data(hash.getCharPtr(), hash.getSize()); + reply->mutable_utils()->set_hash(hash.getCharPtr(), hash.getSize()); return payload; } @@ -1207,8 +1074,8 @@ BridgePayload CppBridge::getTxOutScriptForScrAddr(const BinaryData& script) cons auto reply = payload->mutable_reply(); reply->set_success(true); - auto scriptReply = reply->mutable_script_utils()->mutable_script_data(); - scriptReply->set_data(resultBd.toCharPtr(), resultBd.getSize()); + reply->mutable_script_utils()->set_script_data( + resultBd.toCharPtr(), resultBd.getSize()); return payload; } @@ -1223,12 +1090,10 @@ BridgePayload CppBridge::getAddrStrForScrAddr(const BinaryData& script) const auto reply = payload->mutable_reply(); reply->set_success(true); auto scriptUtilsReply = reply->mutable_script_utils(); - auto addr = scriptUtilsReply->mutable_address_string(); - addr->set_address(addrStr); + scriptUtilsReply->set_address_string(addrStr); } catch (const exception& e) { - auto payload = make_unique(); auto reply = payload->mutable_reply(); reply->set_success(false); reply->set_error(e.what()); @@ -1309,9 +1174,9 @@ BridgePayload CppBridge::setAddressTypeFor( wai.walletId, wai.accountId); auto wltPtr = wltContainer->getWalletPtr(); - BinaryDataRef bdr; bdr.setRef(assetIdStr); + auto idRef = BinaryDataRef::fromString(assetIdStr); auto assetId = Armory::Wallets::AssetId::deserializeKey( - bdr, PROTO_ASSETID_PREFIX); + idRef, PROTO_ASSETID_PREFIX); //set address type in wallet wltPtr->updateAddressEntryType(assetId, (AddressEntryType)addrType); @@ -1325,8 +1190,9 @@ BridgePayload CppBridge::setAddressTypeFor( auto reply = payload->mutable_reply(); reply->set_success(true); - auto addrProto = reply->mutable_wallet()->mutable_asset(); - CppToProto::addr(addrProto, addrPtr, accPtr); + auto addrProto = reply->mutable_wallet()->mutable_address_data(); + CppToProto::addr(addrProto, addrPtr, accPtr, + wltContainer->getDefaultEncryptionKeyId()); return payload; } @@ -1342,9 +1208,8 @@ void CppBridge::getHeaderByHeight(unsigned height, unsigned msgId) reply->set_success(true); reply->set_reference_id(msgId); - auto headerData = reply->mutable_service()->mutable_header_data(); - headerData->set_data(headerRaw.getCharPtr(), headerRaw.getSize()); - + reply->mutable_service()->set_header_data( + headerRaw.getCharPtr(), headerRaw.getSize()); this->writeToClient(move(payload)); }; @@ -1390,10 +1255,7 @@ void CppBridge::setupNewCoinSelectionInstance(const string& id, reply->set_success(true); reply->set_reference_id(msgId); - auto walletReply = reply->mutable_wallet(); - auto csReply = walletReply->mutable_coin_selection_id(); - csReply->set_id(csId); - + reply->mutable_wallet()->set_coin_selection_id(csId); this->writeToClient(move(payload)); }; @@ -1486,6 +1348,7 @@ void CppBridge::getUtxosForValue(const string& id, auto payload = make_unique(); auto reply = payload->mutable_reply(); reply->set_success(true); + reply->set_reference_id(msgId); auto utxoList = reply->mutable_wallet()->mutable_utxo_list(); for (auto& utxo : utxoVec) @@ -1513,6 +1376,7 @@ void CppBridge::getSpendableZCList(const string& id, unsigned msgId) auto payload = make_unique(); auto reply = payload->mutable_reply(); reply->set_success(true); + reply->set_reference_id(msgId); auto utxoList = reply->mutable_wallet()->mutable_utxo_list(); for(auto& utxo : utxoVec) @@ -1559,14 +1423,19 @@ void CppBridge::getRBFTxOutList(const string& id, unsigned msgId) BridgePayload CppBridge::initNewSigner() { auto id = fortuna_.generateRandom(6).toHexStr(); - signerMap_.emplace(make_pair(id, make_shared())); + signerMap_.emplace(make_pair(id, + make_shared( + [this](const string& wltId)->auto { + return this->getWalletPtr(wltId); }, + [this](ServerPushWrapper wrapper) { + callbackWriter(wrapper); + }) + )); auto payload = make_unique(); auto reply = payload->mutable_reply(); reply->set_success(true); - - auto signerId = reply->mutable_signer()->mutable_signer_id(); - signerId->set_id(id); + reply->mutable_signer()->set_signer_id(id); return payload; } @@ -1586,120 +1455,6 @@ shared_ptr CppBridge::signerInstance( return iter->second; } -//////////////////////////////////////////////////////////////////////////////// -void CppBridge::signer_signTx( - const string& id, const string& wltId, unsigned msgId) -{ - //grab signer - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - auto signerPtr = iter->second; - - //grab wallet - auto wai = WalletAccountIdentifier::deserialize(wltId); - auto wltContainer = wltManager_->getWalletContainer( - wai.walletId, wai.accountId); - auto wltPtr = wltContainer->getWalletPtr(); - - auto passLbd = createPassphrasePrompt(UnlockPromptType::decrypt); - - //instantiate and set resolver feed - auto signLbd = [wltPtr, signerPtr, passLbd, msgId, this](void)->void - { - bool success = true; - try - { - auto wltSingle = dynamic_pointer_cast(wltPtr); - auto feed = make_shared(wltSingle); - - signerPtr->signer_.resetFeed(); - signerPtr->signer_.setFeed(feed); - - //create & set wallet lambda - wltPtr->setPassphrasePromptLambda(passLbd); - - //lock wallet - auto lock = wltPtr->lockDecryptedContainer(); - - //sign - signerPtr->signer_.sign(); - } - catch (exception&) - { - success = false; - } - - //signal Python that we're done - auto payload = make_unique(); - auto reply = payload->mutable_reply(); - reply->set_success(success); - reply->set_reference_id(msgId); - this->writeToClient(move(payload)); - - //wind down passphrase prompt - passLbd({BridgePassphrasePrompt::concludeKey}); - }; - - thread thr(signLbd); - if (thr.joinable()) - thr.detach(); -} - -//////////////////////////////////////////////////////////////////////////////// -bool CppBridge::signer_resolve( - const string& sId, const string& wltId) const -{ - //grab signer - auto iter = signerMap_.find(sId); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - auto& signer = iter->second->signer_; - - //grab wallet - auto wai = WalletAccountIdentifier::deserialize(wltId); - auto wltContainer = wltManager_->getWalletContainer( - wai.walletId, wai.accountId); - auto wltPtr = wltContainer->getWalletPtr(); - - //get wallet feed - auto wltSingle = dynamic_pointer_cast(wltPtr); - auto feed = make_shared(wltSingle); - - //set feed & resolve - signer.resetFeed(); - signer.setFeed(feed); - signer.resolvePublicData(); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -BridgePayload CppBridge::getSignedStateForInput( - const string& id, unsigned inputId) -{ - auto iter = signerMap_.find(id); - if (iter == signerMap_.end()) - throw runtime_error("invalid signer id"); - - if (iter->second->signState_ == nullptr) - { - iter->second->signState_ = make_unique( - iter->second->signer_.evaluateSignedState()); - } - - const auto signState = iter->second->signState_.get(); - auto payload = make_unique(); - auto reply = payload->mutable_reply(); - - auto signStateInput = signState->getSignedStateForInput(inputId); - auto inputState = reply->mutable_signer()->mutable_input_signed_state(); - CppToProto::signatureState(inputState, signStateInput); - - reply->set_success(true); - return payload; -} - //////////////////////////////////////////////////////////////////////////////// void CppBridge::broadcastTx(const vector& rawTxVec) { @@ -1728,9 +1483,7 @@ void CppBridge::getBlockTimeByHeight(uint32_t height, uint32_t msgId) const reply->set_success(true); reply->set_reference_id(msgId); - auto blockTime = reply->mutable_service()->mutable_block_time(); - blockTime->set_timestamp(timestamp); - + reply->mutable_service()->set_block_time(timestamp); this->writeToClient(move(payload)); }; @@ -1753,8 +1506,8 @@ void CppBridge::estimateFee(uint32_t blocks, result->set_success(true); auto feeMsg = result->mutable_service()->mutable_fee_estimate(); - feeMsg->set_feebyte(feeData.val_); - feeMsg->set_smartfee(feeData.isSmart_); + feeMsg->set_fee_byte(feeData.val_); + feeMsg->set_smart_fee(feeData.isSmart_); } catch (const ClientMessageError& e) { @@ -1768,6 +1521,31 @@ void CppBridge::estimateFee(uint32_t blocks, bdvPtr_->estimateFee(blocks, strat, callback); } +//////////////////////////////////////////////////////////////////////////////// +void CppBridge::setCallbackHandler(ServerPushWrapper& wrapper) +{ + if (wrapper.referenceId == 0 || wrapper.handler == nullptr) + return; + + unique_lock lock(callbackHandlerMu_); + auto result = callbackHandlers_.emplace( + wrapper.referenceId, move(wrapper.handler)); + if (!result.second) + throw runtime_error("handler collision"); +} + +CallbackHandler CppBridge::getCallbackHandler(uint32_t id) +{ + unique_lock lock(callbackHandlerMu_); + auto handlerIter = callbackHandlers_.find(id); + if (handlerIter == callbackHandlers_.end()) + throw runtime_error("missing handler"); + + auto handler = move(handlerIter->second); + callbackHandlers_.erase(handlerIter); + return handler; +} + //////////////////////////////////////////////////////////////////////////////// //// //// BridgeCallback @@ -1778,22 +1556,20 @@ void BridgeCallback::waitOnId(const string& id) string currentId; while(true) { - { - if (currentId == id) - return; - - unique_lock lock(idMutex_); - auto iter = validIds_.find(id); - if (*iter == id) - { - validIds_.erase(iter); - return; - } + if (currentId == id) + return; - validIds_.insert(currentId); - currentId.clear(); + unique_lock lock(idMutex_); + auto iter = validIds_.find(id); + if (*iter == id) + { + validIds_.erase(iter); + return; } + validIds_.insert(currentId); + currentId.clear(); + //TODO: implement queue wake up logic currentId = move(idQueue_.pop_front()); } @@ -1914,7 +1690,7 @@ void BridgeCallback::progress( progressMsg->set_phase((uint32_t)phase); progressMsg->set_progress(progress); - progressMsg->set_etasec(secondsRem); + progressMsg->set_eta_sec(secondsRem); progressMsg->set_progress_numeric(progressNumeric); for (auto& id : walletIdVec) @@ -1995,49 +1771,110 @@ void BridgeCallback::disconnected() //////////////////////////////////////////////////////////////////////////////// //// -//// MethodCallbacksHandler +//// CppBridgeSignerStruct //// //////////////////////////////////////////////////////////////////////////////// -void MethodCallbacksHandler::processCallbackReply( - unsigned callbackId, BinaryDataRef& dataRef) +CppBridgeSignerStruct::CppBridgeSignerStruct( + function getWalletFunc, + function writeFunc) : + getWalletFunc_(getWalletFunc), writeFunc_(writeFunc) +{} + +//////////////////////////////////////////////////////////////////////////////// +void CppBridgeSignerStruct::signTx(const string& wltId, + const string& callbackId, unsigned referenceId) { - auto iter = callbacks_.find(callbackId); - if (iter == callbacks_.end()) - return; //ignore unknown callbacks ids + //grab wallet + auto wltPtr = getWalletFunc_(wltId); + + //run signature process in its own thread, as it's an async process + auto signLbd = [this, wltPtr, callbackId, referenceId](void)->void + { + bool success = true; + + //create passphrase lambda + auto passPromptObj = make_shared( + callbackId, writeFunc_); + auto passLbd = passPromptObj->getLambda(); + + try + { + //cast wallet & create resolver + auto wltSingle = dynamic_pointer_cast(wltPtr); + auto feed = make_shared(wltSingle); + + //set resolver + signer_.resetFeed(); + signer_.setFeed(feed); + + //create & set passphrase lambda + wltPtr->setPassphrasePromptLambda(passLbd); + + //lock decryption container + auto lock = wltPtr->lockDecryptedContainer(); + + //sign, this will prompt the passphrase lambda on demand + signer_.sign(); + } + catch (const exception&) + { + success = false; + } + catch (...) + { + LOGINFO << "false catch"; + } + + //send reply to caller + auto protoMsg = make_unique(); + auto reply = protoMsg->mutable_reply(); + reply->set_success(success); + reply->set_reference_id(referenceId); - auto callbackLbd = move(iter->second); - callbacks_.erase(iter); - callbackLbd(dataRef); + ServerPushWrapper wrapper{ 0, nullptr, move(protoMsg) }; + writeFunc_(move(wrapper)); + + //wind down passphrase prompt + passPromptObj->cleanup(); + }; + + thread thr(signLbd); + if (thr.joinable()) + thr.detach(); } //////////////////////////////////////////////////////////////////////////////// -void MethodCallbacksHandler::flagForCleanup() +bool CppBridgeSignerStruct::resolve(const string& wltId) { - /* - Mock a shutdown message and queue it up. This message will trigger the - deletion of this callback handler from the callback map; - */ - if (parentCommandQueue_ == nullptr) - return; + //grab wallet + auto wltPtr = getWalletFunc_(wltId); - BridgeProto::Request msg; + //get wallet feed + auto wltSingle = dynamic_pointer_cast(wltPtr); + auto feed = make_shared(wltSingle); - /*msg.set_method(Methods::methodWithCallback); - msg.set_methodwithcallback(MethodsWithCallback::cleanup); - msg.add_byteargs(id_.toCharPtr(), id_.getSize());*/ + //set feed & resolve + signer_.resetFeed(); + signer_.setFeed(feed); + signer_.resolvePublicData(); - parentCommandQueue_->push_back(move(msg)); - parentCommandQueue_ = nullptr; + return true; } //////////////////////////////////////////////////////////////////////////////// -unsigned MethodCallbacksHandler::addCallback( - const function& callback) +BridgePayload CppBridgeSignerStruct::getSignedStateForInput(unsigned inputId) { - /* - This method isn't thread safe. - */ - auto id = counter_++; - callbacks_.emplace(id, callback); - return id; -} \ No newline at end of file + if (signState_ == nullptr) + signState_ = make_unique(signer_.evaluateSignedState()); + + const auto signState = signState_.get(); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + + auto signStateInput = signState->getSignedStateForInput(inputId); + auto inputState = reply->mutable_signer()->mutable_input_signed_state(); + CppToProto::signatureState(inputState, signStateInput); + + reply->set_success(true); + return payload; +} diff --git a/cppForSwig/BridgeAPI/CppBridge.h b/cppForSwig/BridgeAPI/CppBridge.h index 70c4f9808..2a09385de 100755 --- a/cppForSwig/BridgeAPI/CppBridge.h +++ b/cppForSwig/BridgeAPI/CppBridge.h @@ -15,11 +15,18 @@ #include "../protobuf/BridgeProto.pb.h" #include "../AsyncClient.h" +namespace BridgeProto +{ + class CallbackReply; +}; + namespace Armory { namespace Bridge { struct WritePayload_Bridge; + struct ServerPushWrapper; + using BridgePayload = std::unique_ptr; ////////////////////////////////////////////////////////////////////////// typedef std::function signState_; - }; - - ////////////////////////////////////////////////////////////////////////// - using CommandQueue = std::shared_ptr>; - - //// - class CppBridge; - - //// - class MethodCallbacksHandler + using WalletPtr = std::shared_ptr; + class CppBridgeSignerStruct { - friend class CppBridge; - private: - unsigned counter_ = 0; - const BinaryData id_; - std::thread methodThr_; - std::map> callbacks_; - - CommandQueue parentCommandQueue_; + std::unique_ptr signState_{}; + const std::function getWalletFunc_; + const std::function writeFunc_; public: - MethodCallbacksHandler(const BinaryData& id, CommandQueue queue) : - id_(id), parentCommandQueue_(queue) - {} + Armory::Signer::Signer signer_{}; - ~MethodCallbacksHandler(void) - { - flagForCleanup(); - if (methodThr_.joinable()) - methodThr_.join(); - } - - const BinaryData& id(void) const { return id_; } - unsigned addCallback(const std::function&); + public: + CppBridgeSignerStruct(std::function, + std::function); - //startThread - void flagForCleanup(void); - void processCallbackReply(unsigned, BinaryDataRef&); + void signTx(const std::string&, const std::string&, unsigned); + bool resolve(const std::string&); + BridgePayload getSignedStateForInput(unsigned); }; ////////////////////////////////////////////////////////////////////////// + using CallbackHandler = std::function; struct ProtobufCommandParser; - using BridgePayload = std::unique_ptr; class CppBridge { @@ -142,29 +123,18 @@ namespace Armory PRNG_Fortuna fortuna_; - std::mutex passPromptMutex_; - std::map> promptMap_; - std::function)> writeLambda_; const bool dbOneWayAuth_; const bool dbOffline_; - std::map> - callbackHandlerMap_; - CommandQueue commandWithCallbackQueue_; - - std::thread commandWithCallbackProcessThread_; + std::mutex callbackHandlerMu_; + std::map callbackHandlers_; private: - //commands with callback - void queueCommandWithCallback(BridgeProto::Request); - void processCommandWithCallbackThread(void); - //wallet setup - void loadWallets(unsigned id); + void loadWallets(const std::string&, unsigned); BridgePayload createWalletsPacket(void); bool deleteWallet(const std::string&); BridgePayload getWalletPacket(const std::string&) const; @@ -187,10 +157,10 @@ namespace Armory BridgePayload getNewAddress(const std::string&, unsigned); BridgePayload getChangeAddress(const std::string&, unsigned); BridgePayload peekChangeAddress(const std::string&, unsigned); - std::string createWallet(const BridgeProto::Utils::CreateWallet&); - void createBackupStringForWallet(const std::string&, unsigned); - void restoreWallet(const BinaryDataRef&, - std::shared_ptr); + std::string createWallet(const BridgeProto::Utils::CreateWalletStruct&); + void createBackupStringForWallet(const std::string&, + const std::string&, unsigned); + void restoreWallet(const BinaryDataRef&); //ledgers const std::string& getLedgerDelegateIdForWallets(void); @@ -224,13 +194,9 @@ namespace Armory //signer BridgePayload initNewSigner(void); void destroySigner(const std::string&); - std::shared_ptr - signerInstance(const std::string&) const; - - void signer_signTx(const std::string&, const std::string&, unsigned); - BridgePayload getSignedStateForInput( - const std::string&, unsigned); - bool signer_resolve(const std::string&, const std::string&) const; + std::shared_ptr signerInstance( + const std::string&) const; + WalletPtr getWalletPtr(const std::string&) const; //utils BridgePayload getTxInScriptType( @@ -249,10 +215,10 @@ namespace Armory void getBlockTimeByHeight(uint32_t, uint32_t) const; void estimateFee(uint32_t, const std::string&, uint32_t) const; - //passphrase prompt - PassphraseLambda createPassphrasePrompt( - ::BridgeProto::UnlockPromptType); - bool returnPassphrase(const std::string&, const std::string&); + //custom callback handlers + void callbackWriter(ServerPushWrapper&); + void setCallbackHandler(ServerPushWrapper&); + CallbackHandler getCallbackHandler(uint32_t); public: CppBridge(const std::string&, const std::string&, @@ -266,9 +232,6 @@ namespace Armory { writeLambda_ = lbd; } - - void startThreads(void); - void stopThreads(void); }; }; //namespace Bridge }; //namespace Armory diff --git a/cppForSwig/BridgeAPI/PassphrasePrompt.cpp b/cppForSwig/BridgeAPI/PassphrasePrompt.cpp index 261c6370e..4d9c48f73 100644 --- a/cppForSwig/BridgeAPI/PassphrasePrompt.cpp +++ b/cppForSwig/BridgeAPI/PassphrasePrompt.cpp @@ -6,105 +6,100 @@ // // //////////////////////////////////////////////////////////////////////////////// +#include "log.h" +#include "../protobuf/BridgeProto.pb.h" #include "PassphrasePrompt.h" -#include "BridgeSocket.h" using namespace Armory::Bridge; using namespace std; using namespace BridgeProto; +uint32_t BridgePassphrasePrompt::referenceCounter_ = 1; + //////////////////////////////////////////////////////////////////////////////// //// //// BridgePassphrasePrompt //// //////////////////////////////////////////////////////////////////////////////// -const Armory::Wallets::EncryptionKeyId - BridgePassphrasePrompt::concludeKey("concludePrompt"); +BridgePassphrasePrompt::BridgePassphrasePrompt(const std::string& id, + std::function func) : + promptId_(id), writeFunc_(move(func)) +{} -PassphraseLambda BridgePassphrasePrompt::getLambda(UnlockPromptType type) +//////////////////////////////////////////////////////////////////////////////// +SecureBinaryData BridgePassphrasePrompt::processFeedRequest( + const set& ids) { - auto lbd = [this, type] - (const set& ids) - ->SecureBinaryData + if (ids.empty()) { - UnlockPromptState promptState = UnlockPromptState::cycle; - if (encryptionKeyIds_.empty()) - { - if (ids.empty()) - throw runtime_error("malformed command"); + //exit condition + cleanup(); + return {}; + } - encryptionKeyIds_ = ids; - promptState = UnlockPromptState::start; - } - - //cycle the promise & future - promPtr_ = make_unique>(); - futPtr_ = make_unique>( - promPtr_->get_future()); + //cycle the promise & future + auto promPtr = make_shared>(); + auto fut = promPtr->get_future(); - //create protobuf payload - UnlockPromptCallback opaque; - opaque.set_promptid(promptId_); - opaque.set_prompttype(type); + auto refId = referenceCounter_++; - switch (type) - { - case UnlockPromptType::decrypt: - { - opaque.set_verbose("Unlock Wallet"); - break; - } + //create protobuf payload + auto protoPtr = make_unique(); + auto pushPtr = protoPtr->mutable_callback(); + pushPtr->set_callback_id(promptId_); + pushPtr->set_reference_id(refId); - case UnlockPromptType::migrate: - { - opaque.set_verbose("Migrate Wallet"); - break; - } + auto unlockPtr = pushPtr->mutable_unlock_request(); + for (const auto& id : ids) + unlockPtr->add_encryption_key_ids(id.toHexStr()); - default: - opaque.set_verbose("undefined prompt type"); + //reply handler + auto replyHandler = [promPtr](const CallbackReply& reply)->bool + { + if (!reply.success() || + reply.reply_payload_case() != CallbackReply::kPassphrase) + { + promPtr->set_exception(make_exception_ptr(runtime_error(""))); } - - bool exit = false; - if (!ids.empty()) + else { - auto iter = ids.begin(); - if (*iter == concludeKey) - { - promptState = UnlockPromptState::stop; - exit = true; - } - - opaque.set_walletid(iter->toHexStr()); + promPtr->set_value(SecureBinaryData::fromString(reply.passphrase())); } - opaque.set_state(promptState); - - auto msg = make_unique(); - msg->set_payloadtype(OpaquePayloadType::prompt); - - string serializedOpaqueData; - opaque.SerializeToString(&serializedOpaqueData); - msg->set_payload(serializedOpaqueData); - - //push over socket - auto payload = make_unique(); - payload->message_ = move(msg); - writeLambda_(move(payload)); + return true; + }; - if (exit) - return {}; + //push over socket + ServerPushWrapper wrapper{ refId, replyHandler, move(protoPtr) }; + writeFunc_(move(wrapper)); - //wait on future - return futPtr_->get(); - }; + //wait on future + try + { + return fut.get(); + } + catch (const exception&) + { + LOGINFO << "cancelled wallet unlock"; + return {}; + } +} - return lbd; +//////////////////////////////////////////////////////////////////////////////// +void BridgePassphrasePrompt::cleanup() +{ + auto protoPtr = make_unique(); + auto pushPtr = protoPtr->mutable_callback(); + pushPtr->set_callback_id(promptId_); + pushPtr->set_cleanup(true); + writeFunc_(ServerPushWrapper{0, nullptr, move(protoPtr)}); } //////////////////////////////////////////////////////////////////////////////// -void BridgePassphrasePrompt::setReply(const string& passphrase) +PassphraseLambda BridgePassphrasePrompt::getLambda() { - auto&& passSBD = SecureBinaryData::fromString(passphrase); - promPtr_->set_value(passSBD); + return [this](const set& ids)->SecureBinaryData + { + return processFeedRequest(ids); + }; } diff --git a/cppForSwig/BridgeAPI/PassphrasePrompt.h b/cppForSwig/BridgeAPI/PassphrasePrompt.h index 26de981d8..51704c907 100644 --- a/cppForSwig/BridgeAPI/PassphrasePrompt.h +++ b/cppForSwig/BridgeAPI/PassphrasePrompt.h @@ -11,41 +11,48 @@ #include -#include "../protobuf/BridgeProto.pb.h" #include "../Wallets/WalletIdTypes.h" #include "../Wallets/PassphraseLambda.h" -#define BRIDGE_CALLBACK_PROMPTUSER "prompt" +namespace BridgeProto +{ + class Payload; + class CallbackReply; +}; namespace Armory { namespace Bridge { - struct WritePayload_Bridge; + using CallbackHandler = std::function; + + ////////////////////////////////////////////////////////////////////////// + struct ServerPushWrapper + { + const uint32_t referenceId; + CallbackHandler handler = nullptr; + std::unique_ptr payload; + }; ////////////////////////////////////////////////////////////////////////// class BridgePassphrasePrompt { - private: - std::unique_ptr> promPtr_; - std::unique_ptr> futPtr_; + static uint32_t referenceCounter_; + private: const std::string promptId_; - std::function)> writeLambda_; - - std::set encryptionKeyIds_; + std::function writeFunc_; - public: - static const Wallets::EncryptionKeyId concludeKey; + private: + SecureBinaryData processFeedRequest( + const std::set&); public: - BridgePassphrasePrompt(const std::string& id, - std::function)> lbd) : - promptId_(id), writeLambda_(lbd) - {} + BridgePassphrasePrompt(const std::string&, + std::function); - PassphraseLambda getLambda(::BridgeProto::UnlockPromptType); - void setReply(const std::string&); + PassphraseLambda getLambda(); + void cleanup(void); }; }; //namespace Bridge }; //namespace Armory diff --git a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp index 0f7d53efa..190964fdc 100644 --- a/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp +++ b/cppForSwig/BridgeAPI/ProtobufCommandParser.cpp @@ -33,7 +33,8 @@ bool ProtobufCommandParser::processBlockchainServiceCommands(CppBridge* bridge, case BridgeProto::BlockchainService::kLoadWallets: { - bridge->loadWallets(referenceId); + bridge->loadWallets(msg.load_wallets().callback_id(), + referenceId); break; } @@ -83,9 +84,7 @@ bool ProtobufCommandParser::processBlockchainServiceCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto service = reply->mutable_service(); - auto idReply = service->mutable_ledger_delegate_id(); - idReply->set_id(delegateId); + reply->mutable_service()->set_ledger_delegate_id(delegateId); response = move(payload); break; } @@ -190,7 +189,8 @@ bool ProtobufCommandParser::processWalletCommands(CppBridge* bridge, { case BridgeProto::Wallet::kCreateBackupString: { - bridge->createBackupStringForWallet(msg.id(), referenceId); + bridge->createBackupStringForWallet(msg.id(), + msg.create_backup_string().callback_id(), referenceId); break; } @@ -198,16 +198,14 @@ bool ProtobufCommandParser::processWalletCommands(CppBridge* bridge, { auto addrHashRef = BinaryDataRef::fromString( msg.get_ledger_delegate_id_for_scraddr().hash()); - auto& delegateId = bridge->getLedgerDelegateIdForScrAddr( + const auto& delegateId = bridge->getLedgerDelegateIdForScrAddr( msg.id(), addrHashRef); auto payload = make_unique(); auto reply = payload->mutable_reply(); reply->set_success(true); - auto walletMsg = reply->mutable_wallet(); - auto delegateMsg = walletMsg->mutable_ledger_delegate_id(); - delegateMsg->set_id(delegateId); + reply->mutable_wallet()->set_ledger_delegate_id(delegateId); response = move(payload); break; } @@ -265,7 +263,7 @@ bool ProtobufCommandParser::processWalletCommands(CppBridge* bridge, { const auto& setAddrMsg = msg.set_address_type_for(); response = bridge->setAddressTypeFor( - msg.id(), setAddrMsg.address(), setAddrMsg.address_type()); + msg.id(), setAddrMsg.asset_id(), setAddrMsg.address_type()); response->mutable_reply()->set_reference_id(referenceId); break; } @@ -430,9 +428,7 @@ bool ProtobufCommandParser::processCoinSelectionCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto cs = reply->mutable_coin_selection(); - auto fee = cs->mutable_flat_fee(); - fee->set_fee(flatFee); + reply->mutable_coin_selection()->set_flat_fee(flatFee); response = move(payload); break; } @@ -445,9 +441,7 @@ bool ProtobufCommandParser::processCoinSelectionCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto cs = reply->mutable_coin_selection(); - auto fee = cs->mutable_fee_byte(); - fee->set_fee(feeByte); + reply->mutable_coin_selection()->set_fee_byte(feeByte); response = move(payload); break; } @@ -460,9 +454,7 @@ bool ProtobufCommandParser::processCoinSelectionCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto cs = reply->mutable_coin_selection(); - auto size = cs->mutable_size_estimate(); - size->set_size(sizeEstimate); + reply->mutable_coin_selection()->set_size_estimate(sizeEstimate); response = move(payload); break; } @@ -548,9 +540,7 @@ bool ProtobufCommandParser::processCoinSelectionCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto cs = reply->mutable_coin_selection(); - auto fee = cs->mutable_flat_fee(); - fee->set_fee(flatFee); + reply->mutable_coin_selection()->set_flat_fee(flatFee); response = move(payload); break; } @@ -572,6 +562,15 @@ bool ProtobufCommandParser::processSignerCommands(CppBridge* bridge, auto signer = bridge->signerInstance(msg.id()); if (signer == nullptr) { + if (msg.id().empty()) + { + auto response = bridge->initNewSigner(); + response->mutable_reply()->set_reference_id(referenceId); + + bridge->writeToClient(move(response)); + return true; + } + auto payload = make_unique(); auto reply = payload->mutable_reply(); reply->set_success(false); @@ -586,9 +585,7 @@ bool ProtobufCommandParser::processSignerCommands(CppBridge* bridge, { case BridgeProto::Signer::kGetNew: { - response = bridge->initNewSigner(); - response->mutable_reply()->set_reference_id(referenceId); - break; + throw runtime_error("invalid get_new request"); } case BridgeProto::Signer::kCleanup: @@ -687,10 +684,7 @@ bool ProtobufCommandParser::processSignerCommands(CppBridge* bridge, auto payload = make_unique(); auto reply = payload->mutable_reply(); reply->set_success(true); - - auto sigCollectReply = - reply->mutable_signer()->mutable_tx_sig_collect(); - sigCollectReply->set_data(txSigCollect); + reply->mutable_signer()->set_tx_sig_collect(txSigCollect); response = move(payload); break; } @@ -709,55 +703,54 @@ bool ProtobufCommandParser::processSignerCommands(CppBridge* bridge, case BridgeProto::Signer::kSignTx: { - bridge->signer_signTx(msg.id(), - msg.sign_tx().wallet_id(), referenceId); + signer->signTx(msg.sign_tx().wallet_id(), + msg.sign_tx().callback_id(), referenceId); break; } case BridgeProto::Signer::kGetSignedTx: { - BinaryDataRef data; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); try { - data = signer->signer_.serializeSignedTx(); + auto data = signer->signer_.serializeSignedTx(); + reply->mutable_signer()->set_tx_data( + data.toCharPtr(), data.getSize()); + reply->set_success(true); } catch (const exception&) - {} - - auto payload = make_unique(); - auto reply = payload->mutable_reply(); - reply->set_success(true); + { + reply->set_success(false); + } - auto txData = reply->mutable_signer()->mutable_tx_data(); - txData->set_data(data.toCharPtr(), data.getSize()); response = move(payload); break; } case BridgeProto::Signer::kGetUnsignedTx: { - BinaryDataRef data; + auto payload = make_unique(); + auto reply = payload->mutable_reply(); try { - data = signer->signer_.serializeUnsignedTx(); + auto data = signer->signer_.serializeUnsignedTx(); + reply->mutable_signer()->set_tx_data( + data.toCharPtr(), data.getSize()); + reply->set_success(true); } catch (const exception&) - {} - - auto payload = make_unique(); - auto reply = payload->mutable_reply(); - reply->set_success(true); + { + reply->set_success(false); + } - auto txData = reply->mutable_signer()->mutable_tx_data(); - txData->set_data(data.toCharPtr(), data.getSize()); response = move(payload); break; } case BridgeProto::Signer::kResolve: { - auto result = bridge->signer_resolve(msg.id(), - msg.resolve().wallet_id()); + auto result = signer->resolve(msg.resolve().wallet_id()); auto payload = make_unique(); auto reply = payload->mutable_reply(); @@ -768,8 +761,8 @@ bool ProtobufCommandParser::processSignerCommands(CppBridge* bridge, case BridgeProto::Signer::kGetSignedStateForInput: { - response = bridge->getSignedStateForInput( - msg.id(), msg.get_signed_state_for_input().input_id()); + response = signer->getSignedStateForInput( + msg.get_signed_state_for_input().input_id()); break; } @@ -781,9 +774,7 @@ bool ProtobufCommandParser::processSignerCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto signer = reply->mutable_signer(); - auto type = signer->mutable_from_type(); - type->set_type((int)result); + reply->mutable_signer()->set_from_type((int)result); response = move(payload); break; } @@ -822,8 +813,7 @@ bool ProtobufCommandParser::processUtilsCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto idMsg = reply->mutable_utils()->mutable_wallet_id(); - idMsg->set_id(wltId); + reply->mutable_utils()->set_wallet_id(wltId); response = move(payload); break; } @@ -837,8 +827,7 @@ bool ProtobufCommandParser::processUtilsCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto hexMsg = reply->mutable_utils()->mutable_random_hex(); - hexMsg->set_data(str); + reply->mutable_utils()->set_random_hex(str); response = move(payload); break; } @@ -848,7 +837,6 @@ bool ProtobufCommandParser::processUtilsCommands(CppBridge* bridge, const auto& getHashMsg = msg.get_hash_160(); const auto& data = BinaryDataRef::fromString(getHashMsg.data()); response = bridge->getHash160(data); - response->mutable_reply()->set_reference_id(referenceId); break; } @@ -856,7 +844,6 @@ bool ProtobufCommandParser::processUtilsCommands(CppBridge* bridge, { response = bridge->getScrAddrForAddrStr( msg.get_scraddr_for_addrstr().address()); - response->mutable_reply()->set_reference_id(referenceId); break; } @@ -869,8 +856,7 @@ bool ProtobufCommandParser::processUtilsCommands(CppBridge* bridge, auto reply = payload->mutable_reply(); reply->set_success(true); - auto nameReply = reply->mutable_utils()->mutable_address_type_name(); - nameReply->set_name(typeName); + reply->mutable_utils()->set_address_type_name(typeName); response = move(payload); break; } @@ -944,10 +930,18 @@ bool ProtobufCommandParser::processScriptUtilsCommands(CppBridge* bridge, } //////////////////////////////////////////////////////////////////////////////// -bool ProtobufCommandParser::processMethodsWithCallback(CppBridge*, - unsigned, const BridgeProto::MethodsWithCallback&) +bool ProtobufCommandParser::processCallbackReply(CppBridge* bridge, + const BridgeProto::CallbackReply& msg) { - return true; + try + { + auto handler = bridge->getCallbackHandler(msg.reference_id()); + return handler(msg); + } + catch (const std::runtime_error&) + { + return false; + } } //////////////////////////////////////////////////////////////////////////////// @@ -976,42 +970,19 @@ bool ProtobufCommandParser::processData( return processUtilsCommands(bridge, id, msg.utils()); case BridgeProto::Request::kScriptUtils: return processScriptUtilsCommands(bridge, id, msg.script_utils()); - case BridgeProto::Request::kCallback: - return processMethodsWithCallback(bridge, id, msg.callback()); - } - - return true; + case BridgeProto::Request::kCallbackReply: + return processCallbackReply(bridge, msg.callback_reply()); - /*case Methods::methodWithCallback: - { - try + case BridgeProto::Request::METHOD_NOT_SET: { - bridge->queueCommandWithCallback(move(msg)); - } - catch (const exception& e) - { - LOGERR << "[methodWithCallback] " << e.what(); - auto errMsg = make_unique(); - errMsg->set_iserror(true); - errMsg->set_error(e.what()); - - response = move(errMsg); + auto payload = make_unique(); + auto reply = payload->mutable_reply(); + reply->set_success(false); + reply->set_reference_id(id); + bridge->writeToClient(move(payload)); + return false; } + } - break; - }*/ - - - /*case Methods::returnPassphrase: - { - if (msg.stringargs_size() != 2) - throw runtime_error("invalid command: returnPassphrase"); - - auto result = bridge->returnPassphrase(msg.stringargs(0), msg.stringargs(1)); - - auto resultProto = make_unique(); - resultProto->add_ints(result); - response = move(resultProto); - break; - }*/ + return true; } diff --git a/cppForSwig/BridgeAPI/ProtobufCommandParser.h b/cppForSwig/BridgeAPI/ProtobufCommandParser.h index 8519868fb..6084406a6 100644 --- a/cppForSwig/BridgeAPI/ProtobufCommandParser.h +++ b/cppForSwig/BridgeAPI/ProtobufCommandParser.h @@ -19,7 +19,7 @@ namespace BridgeProto class Signer; class Utils; class ScriptUtils; - class MethodsWithCallback; + class CallbackReply; }; namespace Armory @@ -43,8 +43,8 @@ namespace Armory const BridgeProto::Utils&); static bool processScriptUtilsCommands(CppBridge*, unsigned, const BridgeProto::ScriptUtils&); - static bool processMethodsWithCallback(CppBridge*, unsigned, - const BridgeProto::MethodsWithCallback&); + static bool processCallbackReply(CppBridge*, + const BridgeProto::CallbackReply&); public: static bool processData(CppBridge*, BinaryDataRef); diff --git a/cppForSwig/BridgeAPI/ProtobufConversions.cpp b/cppForSwig/BridgeAPI/ProtobufConversions.cpp index 17e85055d..91343e5d3 100644 --- a/cppForSwig/BridgeAPI/ProtobufConversions.cpp +++ b/cppForSwig/BridgeAPI/ProtobufConversions.cpp @@ -8,6 +8,7 @@ #include "ProtobufConversions.h" #include "../Wallets/WalletIdTypes.h" +#include "../Wallets/AssetEncryption.h" #include "DBClientClasses.h" #include "TxEvalState.h" @@ -50,8 +51,9 @@ void CppToProto::ledger(Ledger* ledgerProto, } //////////////////////////////////////////////////////////////////////////////// -void CppToProto::addr(WalletReply::Asset* assetPtr, - shared_ptr addrPtr, shared_ptr accPtr) +bool CppToProto::addr(WalletReply::AddressData* assetPtr, + shared_ptr addrPtr, shared_ptr accPtr, + const EncryptionKeyId& defaultEncryptionKeyId) { if (accPtr == nullptr) throw runtime_error("[CppToProto::addr] null acc ptr"); @@ -65,16 +67,22 @@ void CppToProto::addr(WalletReply::Asset* assetPtr, //address type & pubkey BinaryDataRef pubKeyRef; + std::shared_ptr addrWithAssetPtr = nullptr; uint32_t addrType = (uint32_t)addrPtr->getType(); + auto addrNested = dynamic_pointer_cast(addrPtr); if (addrNested != nullptr) { addrType |= (uint32_t)addrNested->getPredecessor()->getType(); - pubKeyRef = addrNested->getPredecessor()->getPreimage().getRef(); + auto pred = addrNested->getPredecessor(); + pubKeyRef = pred->getPreimage().getRef(); + + addrWithAssetPtr = dynamic_pointer_cast(pred); } else { pubKeyRef = addrPtr->getPreimage().getRef(); + addrWithAssetPtr = dynamic_pointer_cast(addrPtr); } assetPtr->set_addr_type(addrType); @@ -96,12 +104,43 @@ void CppToProto::addr(WalletReply::Asset* assetPtr, bool isChange = accPtr->isAssetChange(addrPtr->getID()); assetPtr->set_is_change(isChange); + //priv key & encryption status + bool isLocked = false; + bool hasPrivKey = false; + if (addrWithAssetPtr != nullptr) + { + auto theAsset = addrWithAssetPtr->getAsset(); + if (theAsset != nullptr) + { + if (theAsset->hasPrivateKey()) + { + hasPrivKey = true; + try + { + //the privkey is considered locked if it's encrypted by + //something else than the default encryption key, which + //lays in clear text in the wallet header + auto encryptionKeyId = theAsset->getPrivateEncryptionKeyId(); + isLocked = (encryptionKeyId != defaultEncryptionKeyId); + } + catch (const runtime_error&) + { + //nothing to do, address has no encryption key + } + } + } + } + assetPtr->set_has_priv_key(hasPrivKey); + assetPtr->set_use_encryption(isLocked); + //precursor, if any if (addrNested == nullptr) - return; + return isLocked; auto& precursor = addrNested->getPredecessor()->getScript(); assetPtr->set_precursor_script(precursor.getCharPtr(), precursor.getSize()); + + return isLocked; } //////////////////////////////////////////////////////////////////////////////// @@ -136,12 +175,25 @@ void CppToProto::wallet(WalletReply::WalletData* wltProto, //address map auto addrMap = accPtr->getUsedAddressMap(); + bool useEncryption = true; for (auto& addrPair : addrMap) { - auto assetPtr = wltProto->add_asset(); - CppToProto::addr(assetPtr, addrPair.second, accPtr); + auto assetPtr = wltProto->add_address_data(); + useEncryption &= CppToProto::addr(assetPtr, addrPair.second, accPtr, + wltPtr->getDefaultEncryptionKeyId()); } + //encryption info + wltProto->set_use_encryption(useEncryption); + + uint32_t kdfMem = 0; + auto kdfPtr = wltPtr->getDefaultKdf(); + auto kdfRomix = dynamic_pointer_cast< + Encryption::KeyDerivationFunction_Romix>(kdfPtr); + if (kdfRomix != nullptr) + kdfMem = kdfRomix->memTarget(); + wltProto->set_kdf_mem_req(kdfMem); + //labels wltProto->set_label(wltPtr->getLabel()); wltProto->set_desc(wltPtr->getDescription()); @@ -199,17 +251,17 @@ void CppToProto::signatureState( SignerReply::InputSignedState* ssProto, const Armory::Signer::TxInEvalState& ssCpp) { - ssProto->set_isvalid(ssCpp.isValid()); + ssProto->set_is_valid(ssCpp.isValid()); ssProto->set_m(ssCpp.getM()); ssProto->set_n(ssCpp.getN()); - ssProto->set_sigcount(ssCpp.getSigCount()); + ssProto->set_sig_count(ssCpp.getSigCount()); const auto& pubKeyMap = ssCpp.getPubKeyMap(); for (auto& pubKeyPair : pubKeyMap) { - auto keyData = ssProto->add_signstatelist(); - keyData->set_pubkey( + auto keyData = ssProto->add_sign_state(); + keyData->set_pub_key( pubKeyPair.first.getCharPtr(), pubKeyPair.first.getSize()); - keyData->set_hassig(pubKeyPair.second); + keyData->set_has_sig(pubKeyPair.second); } } diff --git a/cppForSwig/BridgeAPI/ProtobufConversions.h b/cppForSwig/BridgeAPI/ProtobufConversions.h index 0c0ed490c..7e03ba03c 100644 --- a/cppForSwig/BridgeAPI/ProtobufConversions.h +++ b/cppForSwig/BridgeAPI/ProtobufConversions.h @@ -37,6 +37,7 @@ namespace Armory { class AddressAccountId; class AssetWallet; + class EncryptionKeyId; }; namespace Signer @@ -53,10 +54,11 @@ namespace Armory BridgeProto::Ledger*, const DBClientClasses::LedgerEntry&); - static void addr( - BridgeProto::WalletReply::Asset*, + static bool addr( + BridgeProto::WalletReply::AddressData*, std::shared_ptr, - std::shared_ptr); + std::shared_ptr, + const Wallets::EncryptionKeyId&); static void wallet( BridgeProto::WalletReply::WalletData*, diff --git a/cppForSwig/BridgeAPI/WalletManager.cpp b/cppForSwig/BridgeAPI/WalletManager.cpp index c5c3da4bc..080aaebb0 100644 --- a/cppForSwig/BridgeAPI/WalletManager.cpp +++ b/cppForSwig/BridgeAPI/WalletManager.cpp @@ -675,6 +675,11 @@ void WalletContainer::setLabels(const string& title, const string& desc) wallet_->setDescription(desc); } +//////////////////////////////////////////////////////////////////////////////// +const EncryptionKeyId& WalletContainer::getDefaultEncryptionKeyId() const +{ + return wallet_->getDefaultEncryptionKeyId(); +} //////////////////////////////////////////////////////////////////////////////// //// @@ -876,7 +881,7 @@ shared_ptr Armory135Header::migrate( { //decrypt lbd auto decryptPrivKey = [this, &privKeyPass]( - const PassphraseLambda& passLbd, + const PassphraseLambda& passLbd, const Armory135Address& rootAddrObj)->SecureBinaryData { set idSet = { BinaryData::fromString(walletID_) }; @@ -912,7 +917,8 @@ shared_ptr Armory135Header::migrate( decryptedRoot = move(decryptPrivKey(passLbd, rootAddrObj)); } - passLbd({Armory::Bridge::BridgePassphrasePrompt::concludeKey}); + //cleanup + passLbd({}); } //create wallet diff --git a/cppForSwig/BridgeAPI/WalletManager.h b/cppForSwig/BridgeAPI/WalletManager.h index 06fa3d102..02402ac15 100644 --- a/cppForSwig/BridgeAPI/WalletManager.h +++ b/cppForSwig/BridgeAPI/WalletManager.h @@ -41,6 +41,7 @@ namespace Armory { class AddressAccountId; class AssetWallet; + class EncryptionKeyId; }; }; @@ -218,6 +219,8 @@ class WalletContainer void setComment(const std::string&, const std::string&); void setLabels(const std::string&, const std::string&); + + const Armory::Wallets::EncryptionKeyId& getDefaultEncryptionKeyId() const; }; //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/BtcUtils.cpp b/cppForSwig/BtcUtils.cpp index 9deb87ae2..9858fba7a 100644 --- a/cppForSwig/BtcUtils.cpp +++ b/cppForSwig/BtcUtils.cpp @@ -277,7 +277,7 @@ BinaryData BtcUtils::getScrAddrForAddrStr(const string& addrStr) { auto scrAddrPair = BtcUtils::segWitAddressToScrAddr(addrStr); if (scrAddrPair.second != 0) - throw runtime_error("[createRecipient] unsupported sw version"); + throw runtime_error("[getScrAddrForAddrStr] unsupported sw version"); switch (scrAddrPair.first.getSize()) { diff --git a/cppForSwig/BtcUtils.h b/cppForSwig/BtcUtils.h index d859a9753..d28d18009 100644 --- a/cppForSwig/BtcUtils.h +++ b/cppForSwig/BtcUtils.h @@ -1739,7 +1739,7 @@ class BtcUtils { //sanity checks if (b58.size() == 0) - throw std::range_error("empty BinaryData"); + throw std::range_error("empty b58 string"); size_t size = b58.size(); BinaryData result(size); diff --git a/cppForSwig/Signer/Signer.cpp b/cppForSwig/Signer/Signer.cpp index 1e72baf7a..de53ce9f7 100644 --- a/cppForSwig/Signer/Signer.cpp +++ b/cppForSwig/Signer/Signer.cpp @@ -1520,15 +1520,10 @@ void ScriptSpender::sign(shared_ptr proxy) } }; - try - { - signStack(legacyStack_, false); - signStack(witnessStack_, true); + signStack(legacyStack_, false); + signStack(witnessStack_, true); - processStacks(); - } - catch (const exception&) - {} + processStacks(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/Wallets/AssetEncryption.cpp b/cppForSwig/Wallets/AssetEncryption.cpp index ac4c34357..f1d035bbe 100644 --- a/cppForSwig/Wallets/AssetEncryption.cpp +++ b/cppForSwig/Wallets/AssetEncryption.cpp @@ -151,6 +151,12 @@ bool KeyDerivationFunction_Romix::isSame(KeyDerivationFunction* const kdf) const salt_ == kdfromix->salt_; } +//////////////////////////////////////////////////////////////////////////////// +unsigned KeyDerivationFunction_Romix::memTarget() const +{ + return memTarget_; +} + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //// Cipher @@ -623,6 +629,20 @@ const SecureBinaryData& ClearTextEncryptionKey::getDerivedKey( EncryptedAssetData::~EncryptedAssetData() {} +//////////////////////////////////////////////////////////////////////////////// +bool EncryptedAssetData::hasData() const +{ + return cipherData_ != nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +const CipherData* EncryptedAssetData::getCipherDataPtr() const +{ + if (!hasData()) + throw runtime_error("no cypher data"); + return cipherData_.get(); +} + //////////////////////////////////////////////////////////////////////////////// unique_ptr EncryptedAssetData::decrypt( const SecureBinaryData& key) const diff --git a/cppForSwig/Wallets/AssetEncryption.h b/cppForSwig/Wallets/AssetEncryption.h index 6b688e622..6929fd9d4 100644 --- a/cppForSwig/Wallets/AssetEncryption.h +++ b/cppForSwig/Wallets/AssetEncryption.h @@ -97,6 +97,7 @@ namespace Armory bool isSame(KeyDerivationFunction* const) const; BinaryData serialize(void) const; const BinaryData& getId(void) const; + unsigned memTarget(void) const; }; /////////////////////////////////////////////////////////////////////// @@ -397,15 +398,8 @@ namespace Armory const EncryptionKeyId& getEncryptionKeyId(void) const; const BinaryData& getKdfId(void) const; - bool hasData(void) const - { - return cipherData_ != nullptr; - } - - const CipherData* getCipherDataPtr() const - { - return cipherData_.get(); - } + bool hasData(void) const; + const CipherData* getCipherDataPtr(void) const; }; }; //namespace Encryption diff --git a/cppForSwig/Wallets/DecryptedDataContainer.cpp b/cppForSwig/Wallets/DecryptedDataContainer.cpp index 18736dd30..16cd45e82 100644 --- a/cppForSwig/Wallets/DecryptedDataContainer.cpp +++ b/cppForSwig/Wallets/DecryptedDataContainer.cpp @@ -72,6 +72,30 @@ void DecryptedDataContainer::lockOther( otherLocks_.push_back(OtherLockedContainer(other)); } +//////////////////////////////////////////////////////////////////////////////// +void DecryptedDataContainer::addKdf( + std::shared_ptr kdfPtr) +{ + kdfMap_.insert(std::make_pair(kdfPtr->getId(), kdfPtr)); +} + +//////////////////////////////////////////////////////////////////////////////// +std::shared_ptr DecryptedDataContainer::getKdf( + const SecureBinaryData& kdfId) const +{ + auto iter = kdfMap_.find(kdfId); + if (iter == kdfMap_.end()) + return nullptr; + return iter->second; +} + +//////////////////////////////////////////////////////////////////////////////// +void DecryptedDataContainer::addEncryptionKey( + std::shared_ptr keyPtr) +{ + encryptedKeys_.insert(std::make_pair(keyPtr->getId(), keyPtr)); +} + //////////////////////////////////////////////////////////////////////////////// unique_ptr DecryptedDataContainer::deriveEncryptionKey( unique_ptr decrKey, @@ -249,12 +273,12 @@ EncryptionKeyId DecryptedDataContainer::populateEncryptionKey( { /* This method looks for existing encryption keys in the container. It will - return the clear text encryption key if present, or populate the + return if the clear text encryption key is present, or populate the container until it cannot find precursors (an encryption key may be encrypted by another encryption key). At which point, it will prompt the user for a passphrase. - keyMap: for all eligible key|kdf pairs. These are listed by + keyMap: for all eligible {key, kdf} pairs. These are listed by the encrypted data object that you're looking to decrypt. Returns the id of the key from the keyMap used for decryption. @@ -857,4 +881,4 @@ void DecryptedDataContainer::eraseEncryptionKey( shared_ptr sharedTx(move(tx)); deleteFromDisk(sharedTx, temp_key); } -} \ No newline at end of file +} diff --git a/cppForSwig/Wallets/DecryptedDataContainer.h b/cppForSwig/Wallets/DecryptedDataContainer.h index 8e2e908e5..52ab76627 100644 --- a/cppForSwig/Wallets/DecryptedDataContainer.h +++ b/cppForSwig/Wallets/DecryptedDataContainer.h @@ -129,6 +129,7 @@ namespace Armory void initAfterLock(void); void cleanUpBeforeUnlock(void); + public: const EncryptionKeyId& getDefaultEncryptionKeyId(void) const { return defaultEncryptionKeyId_; @@ -157,15 +158,10 @@ namespace Armory EncryptionKeyId populateEncryptionKey( const std::map&); - void addKdf(std::shared_ptr kdfPtr) - { - kdfMap_.insert(std::make_pair(kdfPtr->getId(), kdfPtr)); - } - - void addEncryptionKey(std::shared_ptr keyPtr) - { - encryptedKeys_.insert(std::make_pair(keyPtr->getId(), keyPtr)); - } + void addKdf(std::shared_ptr); + std::shared_ptr getKdf( + const SecureBinaryData&) const; + void addEncryptionKey(std::shared_ptr); void updateOnDisk(void); void updateOnDisk(std::unique_ptr); diff --git a/cppForSwig/Wallets/Wallets.cpp b/cppForSwig/Wallets/Wallets.cpp index 4562d69dc..a448c337a 100644 --- a/cppForSwig/Wallets/Wallets.cpp +++ b/cppForSwig/Wallets/Wallets.cpp @@ -565,6 +565,22 @@ const Armory::Wallets::AddressAccountId& AssetWallet::getMainAccountID() const return mainAccount_; } +//////////////////////////////////////////////////////////////////////////////// +const EncryptionKeyId& AssetWallet::getDefaultEncryptionKeyId() const +{ + if (decryptedData_ == nullptr) + throw WalletException("[getDefaultEncryptionKeyId] unexpected error"); + + return decryptedData_->getDefaultEncryptionKeyId(); +} + +//////////////////////////////////////////////////////////////////////////////// +std::shared_ptr + AssetWallet::getDefaultKdf() const +{ + return decryptedData_->getKdf(decryptedData_->getDefaultKdfId()); +} + //////////////////////////////////////////////////////////////////////////////// shared_ptr AssetWallet::getAccountForID( const AddressAccountId& id) const diff --git a/cppForSwig/Wallets/Wallets.h b/cppForSwig/Wallets/Wallets.h index ce1e18e3c..8c47f3733 100644 --- a/cppForSwig/Wallets/Wallets.h +++ b/cppForSwig/Wallets/Wallets.h @@ -45,7 +45,7 @@ namespace Armory namespace IO { class WalletDBInterface; - class WalletHeader; + struct WalletHeader; }; ////////////////////////////////////////////////////////////////////////// @@ -211,6 +211,9 @@ namespace Armory void deleteComment(const BinaryData&); const AddressAccountId& getMainAccountID(void) const; + const EncryptionKeyId& getDefaultEncryptionKeyId(void) const; + std::shared_ptr + getDefaultKdf(void) const; void setLabel(const std::string&); void setDescription(const std::string&); diff --git a/cppForSwig/gtest/SupernodeTests.cpp b/cppForSwig/gtest/SupernodeTests.cpp index 7689d6313..94f1296d7 100644 --- a/cppForSwig/gtest/SupernodeTests.cpp +++ b/cppForSwig/gtest/SupernodeTests.cpp @@ -2243,7 +2243,7 @@ class WebSocketTests : public ::testing::Test }; //////////////////////////////////////////////////////////////////////////////// -TEST_F(WebSocketTests, WebSocketStack_ParallelAsync) +TEST_F(WebSocketTests, DISABLED_WebSocketStack_ParallelAsync) { // TestUtils::setBlocks({ "0", "1", "2", "3", "4", "5" }, blk0dat_); diff --git a/cppForSwig/gtest/WalletTests.cpp b/cppForSwig/gtest/WalletTests.cpp index 066756bba..2f487bc38 100644 --- a/cppForSwig/gtest/WalletTests.cpp +++ b/cppForSwig/gtest/WalletTests.cpp @@ -9049,8 +9049,8 @@ TEST_F(BackupTests, Easy16_AutoRepair) try { auto userPrompt = [&wltID, &decoded, &succesfulRepairs]( - Armory::Backups::RestorePromptType promptType, - const vector& chksumIndexes, + Armory::Backups::RestorePromptType promptType, + const vector& chksumIndexes, SecureBinaryData& extra)->bool { switch (promptType) diff --git a/cppForSwig/protobuf/BridgeProto.proto b/cppForSwig/protobuf/BridgeProto.proto index 49e084680..04ada3972 100644 --- a/cppForSwig/protobuf/BridgeProto.proto +++ b/cppForSwig/protobuf/BridgeProto.proto @@ -2,34 +2,6 @@ syntax = "proto2"; package BridgeProto; -//////////////////////////////////////////////////////////////////////////////// -// -//// weird shit that needs to go -// -//////////////////////////////////////////////////////////////////////////////// -// Unlock prompt -enum UnlockPromptState -{ - start = 1; - stop = 2; - cycle = 3; -} - -enum UnlockPromptType -{ - decrypt = 1; - migrate = 2; -} - -message UnlockPromptCallback -{ - required bytes promptID = 1; - required UnlockPromptType promptType = 2; - required string verbose = 3; - required UnlockPromptState state = 4; - optional string walletID = 5; -} - //////////////////////////////////////////////////////////////////////////////// // RestoreWallet messages message RestoreWalletPayload @@ -67,23 +39,6 @@ message RestoreReply optional bytes extra = 2; } -//////////////////////////////////////////////////////////////////////////////// -// Opaque payloads -enum OpaquePayloadType -{ - prompt = 1; - commandWithCallback = 2; -} - -message OpaquePayload -{ - required OpaquePayloadType payloadType = 1; - optional bytes uniqueId = 2; - optional uint32 intId = 3; - - optional bytes payload = 10; -} - //////////////////////////////////////////////////////////////////////////////// // //// Data types @@ -143,7 +98,7 @@ message Utxo // //////////////////////////////////////////////////////////////////////////////// -message Callback +message CallbackPush { message Ready { required uint32 height = 1; @@ -166,7 +121,7 @@ message Callback message Progress { optional uint32 phase = 1; required float progress = 2; - optional uint32 etaSec = 3; + optional uint32 eta_sec = 3; required uint32 progress_numeric = 4; repeated string id = 10; @@ -177,9 +132,15 @@ message Callback } //// - required string callback_id = 1; + message UnlockRequest { + repeated string encryption_key_ids = 1; + } - oneof payload { + //// + required string callback_id = 1; + optional uint32 reference_id = 2; + + oneof push_payload { Ready ready = 10; SetupDone setup_done = 11; Registered registered = 12; @@ -190,6 +151,18 @@ message Callback NodeStatus node_status = 21; ZeroConf zero_conf = 22; string error = 30; + bool cleanup = 40; + UnlockRequest unlock_request = 41; + } +} + +message CallbackReply +{ + required bool success = 1; + required uint32 reference_id = 2; + + oneof reply_payload { + string passphrase = 10; } } @@ -200,13 +173,9 @@ message Callback //////////////////////////////////////////////////////////////////////////////// message BlockchainService { - message Shutdown{} - message SetupDB{} - message GoOnline{} - message GetNodeStatus{} - message LoadWallets{} - - message RegisterWallets{} + message LoadWallets { + required string callback_id = 1; + } message RegisterWallet { required string id = 1; required bool is_new = 2; @@ -229,7 +198,6 @@ message BlockchainService required string strat = 2; } - message GetLedgerDelegateIdForWallets{} message UpdateWalletsLedgerFilter { repeated string wallet_id = 1; } @@ -246,22 +214,21 @@ message BlockchainService //// oneof method { - Shutdown shutdown = 1; - SetupDB setup_db = 2; - GoOnline go_online = 3; - GetNodeStatus get_node_status = 4; - LoadWallets load_wallets = 5; - - RegisterWallets register_wallets = 6; - RegisterWallet register_wallet = 7; - - BroadcastTx broadcast_tx = 8; - GetTxByHash get_tx_by_hash = 9; - GetHeaderByHeight get_header_by_height = 10; - GetBlockTimeByHeight get_block_time_by_height = 11; - EstimateFee estimate_fee = 12; - - GetLedgerDelegateIdForWallets get_ledger_delegate_id_for_wallets = 20; + bool shutdown = 1; + bool setup_db = 2; + bool go_online = 3; + bool get_node_status = 4; + LoadWallets load_wallets = 5; + bool register_wallets = 6; + + RegisterWallet register_wallet = 10; + BroadcastTx broadcast_tx = 11; + GetTxByHash get_tx_by_hash = 12; + GetHeaderByHeight get_header_by_height = 13; + GetBlockTimeByHeight get_block_time_by_height = 14; + EstimateFee estimate_fee = 15; + + bool get_ledger_delegate_id_for_wallets = 20; UpdateWalletsLedgerFilter update_wallets_ledger_filter = 21; GetHistoryPageForDelegate get_history_page_for_delegate = 22; GetHistoryForWalletSelection get_history_for_wallet_selection = 23; @@ -280,37 +247,26 @@ message Wallet required uint32 type = 1; } - message GetHighestUsedIndex{} message ExtendAddressPool { required uint32 count = 1; required string callback_id = 2; } - message CreateBackupString{} - message Delete{} - message GetData{} - - message GetAddrCombinedList{} message SetAddressTypeFor { - required string address = 1; + required bytes asset_id = 1; required uint32 address_type = 2; } message GetLedgerDelegateIdForScrAddr { required bytes hash = 1; } - message GetBalanceAndCount{} - message SetupNewCoinSelectionInstance{ + message SetupNewCoinSelectionInstance { required uint32 height = 1; } message GetUtxosForValue { required uint64 value = 1; } - message GetSpendableZCList{} - message GetRBFTxOutList{} - - message CreateAddressBook{} message SetComment { required bytes hash_key = 1; @@ -321,6 +277,10 @@ message Wallet required string description = 2; } + message CreateBackupString { + required string callback_id = 1; + } + //// required string id = 1; oneof method { @@ -328,35 +288,32 @@ message Wallet GetChangeAddress get_change_address = 3; PeekChangeAddress peek_change_address = 4; - GetHighestUsedIndex get_highest_used_index = 10; + bool get_highest_used_index = 10; ExtendAddressPool extend_address_pool = 11; CreateBackupString create_backup_string = 20; - Delete delete = 21; - GetData get_data = 22; + bool delete = 21; + bool get_data = 22; - GetAddrCombinedList get_addr_combined_list = 30; + bool get_addr_combined_list = 30; SetAddressTypeFor set_address_type_for = 31; GetLedgerDelegateIdForScrAddr get_ledger_delegate_id_for_scraddr = 40; - GetBalanceAndCount get_balance_and_count = 41; + bool get_balance_and_count = 41; SetupNewCoinSelectionInstance setup_new_coin_selection_instance = 50; GetUtxosForValue get_utxos_for_value = 51; - GetSpendableZCList get_spendable_zc_list = 52; - GetRBFTxOutList get_rbf_txout_list = 53; + bool get_spendable_zc_list = 52; + bool get_rbf_txout_list = 53; - CreateAddressBook create_address_book = 60; - SetComment set_comment = 61; - SetLabels set_labels = 62; + bool create_address_book = 60; + SetComment set_comment = 61; + SetLabels set_labels = 62; } } message CoinSelection { - message Cleanup{} - message Reset{} - message SetRecipient { required string address = 1; required uint64 value = 2; @@ -371,11 +328,6 @@ message CoinSelection } } - message GetUtxoSelection{} - message GetFlatFee{} - message GetFeeByte{} - message GetSizeEstimate{} - message ProcessCustomUtxoList { repeated Utxo utxos = 1; required uint32 flags = 2; @@ -393,16 +345,16 @@ message CoinSelection //// required string id = 1; oneof method { - Cleanup cleanup = 2; - Reset reset = 3; + bool cleanup = 2; + bool reset = 3; SetRecipient set_recipient = 4; SelectUTXOs select_utxos = 5; - GetUtxoSelection get_utxo_selection = 6; - GetFlatFee get_flat_fee = 7; - GetFeeByte get_fee_byte = 8; - GetSizeEstimate get_size_estimate = 9; + bool get_utxo_selection = 6; + bool get_flat_fee = 7; + bool get_fee_byte = 8; + bool get_size_estimate = 9; ProcessCustomUtxoList process_custom_utxo_list = 10; GetFeeForMaxVal get_fee_for_max_val = 11; @@ -411,9 +363,6 @@ message CoinSelection message Signer { - message GetNew{} - message Cleanup{} - message SetVersion { required uint32 version = 1; } @@ -448,9 +397,8 @@ message Signer message SignTx { required string wallet_id = 1; + required string callback_id = 2; } - message GetSignedTx{} - message GetUnsignedTx{} message GetSignedStateForInput { required uint32 input_id = 1; } @@ -462,35 +410,32 @@ message Signer required bytes raw_tx = 1; } - message FromType{} - message CanLegacySerialize{} - //// required string id = 1; oneof method { - GetNew get_new = 2; - Cleanup cleanup = 3; + bool get_new = 2; + bool cleanup = 3; - SetVersion set_version = 4; - SetLockTime set_lock_time = 5; + SetVersion set_version = 4; + SetLockTime set_lock_time = 5; - AddSpenderByOutpoint add_spender_by_outpoint = 6; - PopulateUtxo populate_utxo = 7; - AddRecipient add_recipient = 8; + AddSpenderByOutpoint add_spender_by_outpoint = 6; + PopulateUtxo populate_utxo = 7; + AddRecipient add_recipient = 8; - ToTxSigCollect to_tx_sig_collect = 9; - FromTxSigCollect from_tx_sig_collect = 10; + ToTxSigCollect to_tx_sig_collect = 9; + FromTxSigCollect from_tx_sig_collect = 10; SignTx sign_tx = 11; - GetSignedTx get_signed_tx = 12; - GetUnsignedTx get_unsigned_tx = 13; + bool get_signed_tx = 12; + bool get_unsigned_tx = 13; GetSignedStateForInput get_signed_state_for_input = 14; - Resolve resolve = 15; - AddSupportingTx add_supporting_tx = 16; + Resolve resolve = 15; + AddSupportingTx add_supporting_tx = 16; - FromType from_type = 17; - CanLegacySerialize can_legacy_serialize = 18; + bool from_type = 17; + bool can_legacy_serialize = 18; } } @@ -521,10 +466,6 @@ message Utils optional string description = 31; } - message CreateWallet { - required CreateWalletStruct wallet_struct = 1; - } - oneof method { GenerateRandomHex generate_random_hex = 1; GetHash160 get_hash_160 = 2; @@ -532,7 +473,7 @@ message Utils GetScrAddrForAddrStr get_scraddr_for_addrstr = 3; GetNameForAddrType get_name_for_addr_type = 4; - CreateWallet create_wallet = 5; + CreateWalletStruct create_wallet = 5; } } @@ -541,35 +482,20 @@ message ScriptUtils message GetTxInScriptType { required bytes hash = 1; } - message GetTxOutScriptType {} - - message GetScrAddrForScript{} - message GetLastPushDataInScript{} - message GetTxOutScriptForScrAddr{} - message GetAddrStrForScrAddr{} //// required bytes script = 1; oneof method { - GetTxInScriptType get_txin_script_type = 2; - GetTxOutScriptType get_txout_script_type = 3; + GetTxInScriptType get_txin_script_type = 2; + bool get_txout_script_type = 3; - GetScrAddrForScript get_scraddr_for_script = 10; - GetLastPushDataInScript get_last_push_data_in_script = 11; - GetTxOutScriptForScrAddr get_txout_script_for_scraddr = 12; - GetAddrStrForScrAddr get_addrstr_for_scraddr = 13; + bool get_scraddr_for_script = 10; + bool get_last_push_data_in_script = 11; + bool get_txout_script_for_scraddr = 12; + bool get_addrstr_for_scraddr = 13; } } -message MethodsWithCallback -{ - optional int32 followUp = 1; - optional int32 cleanup = 2; - - //restoreWallet = 10; - //returnPassphrase = 22; -} - message Request { required uint32 reference_id = 1; @@ -581,7 +507,7 @@ message Request Signer signer = 23; Utils utils = 24; ScriptUtils script_utils = 25; - MethodsWithCallback callback = 26; + CallbackReply callback_reply = 30; } } @@ -592,22 +518,10 @@ message Request //////////////////////////////////////////////////////////////////////////////// message BlockchainServiceReply { - message BlockTime { - required uint32 timestamp = 1; - } - - message LedgerDelegateId { - required string id = 1; - } - - message HeaderData { - required bytes data = 1; - } - message FeeEstimate { - required float feeByte = 1; - required bool smartFee = 2; + required float fee_byte = 1; + required bool smart_fee = 2; } message LedgerHistory { @@ -625,9 +539,9 @@ message BlockchainServiceReply // oneof reply { - BlockTime block_time = 10; - LedgerDelegateId ledger_delegate_id = 11; - HeaderData header_data = 12; + uint32 block_time = 10; + string ledger_delegate_id = 11; + bytes header_data = 12; FeeEstimate fee_estimate = 13; LedgerHistory ledger_history = 14; Tx tx = 15; @@ -638,18 +552,6 @@ message BlockchainServiceReply //// message WalletReply { - message HighestUsedIndex { - required int32 index = 1; - } - - message CoinSelectionId { - required string id = 1; - } - - message LedgerDelegateId { - required string id = 1; - } - message UtxoList { repeated Utxo utxo = 1; } @@ -665,19 +567,21 @@ message WalletReply } //wallet data - message Asset + message AddressData { - required int32 id = 1; - required uint32 addr_type = 2; - required bool is_used = 3; - required bool is_change = 4; - required bytes asset_id = 5; + required int32 id = 1; + required uint32 addr_type = 2; + required bool is_used = 3; + required bool is_change = 4; + required bytes asset_id = 5; + required bool has_priv_key = 6; + required bool use_encryption = 7; required bytes prefixed_hash = 10; required bytes public_key = 11; optional bytes precursor_script = 12; - required string address_string = 20; + required string address_string = 20; } message WalletData @@ -688,11 +592,13 @@ message WalletReply required bool watching_only = 4; repeated uint32 address_type = 5; required uint32 default_address_type = 6; + required bool use_encryption = 7; + required uint32 kdf_mem_req = 8; optional string label = 10; optional string desc = 11; - repeated Asset asset = 20; + repeated AddressData address_data = 20; message Comment { @@ -725,27 +631,27 @@ message WalletReply message AddressAndBalanceData { repeated AddressBalanceData balance = 1; - repeated Asset updated_asset = 2; + repeated AddressData updated_asset = 2; } message BalanceAndCount { - required uint64 full = 1; - required uint64 spendable = 2; + required uint64 full = 1; + required uint64 spendable = 2; required uint64 unconfirmed = 3; - required uint64 count = 4; + required uint64 count = 4; } // oneof reply { - HighestUsedIndex highest_used_index = 10; - CoinSelectionId coin_selection_id = 11; - LedgerDelegateId ledger_delegate_id = 12; + int32 highest_used_index = 10; + string coin_selection_id = 11; + string ledger_delegate_id = 12; BalanceAndCount balance_and_count = 13; AddressAndBalanceData address_and_balance_data = 14; UtxoList utxo_list = 15; AddressBook address_book = 16; - Asset asset = 20; + AddressData address_data = 20; WalletData wallet_data = 21; MultipleWalletData multiple_wallets = 22; BackupString backup_string = 23; @@ -755,71 +661,43 @@ message WalletReply //// message CoinSelectionReply { - message FlatFee { - required uint64 fee = 1; - } - - message FeeByte { - required float fee = 1; - } - - message SizeEstimate { - required uint32 size = 1; - } - message UtxoList { repeated Utxo utxo = 1; } // oneof reply { - FlatFee flat_fee = 10; - FeeByte fee_byte = 11; - SizeEstimate size_estimate = 12; - UtxoList utxo_list = 13; + uint64 flat_fee = 10; + float fee_byte = 11; + uint32 size_estimate = 12; + UtxoList utxo_list = 13; } } //// message SignerReply { - message SignerId { - required string id = 1; - } - - message FromType { - required uint32 type = 1; - } - - message TxSigCollect { - required string data = 1; - } - - message TxData { - required bytes data = 1; - } - message InputSignedState { - required bool isValid = 1; - required uint32 m = 2; - required uint32 n = 3; + required bool is_valid = 1; + required uint32 m = 2; + required uint32 n = 3; - required uint32 sigCount = 10; - repeated PubKeySignatureState signStateList = 11; + required uint32 sig_count = 10; message PubKeySignatureState { - required bytes pubKey = 1; - required bool hasSig = 2; + required bytes pub_key = 1; + required bool has_sig = 2; } + repeated PubKeySignatureState sign_state = 11; } // oneof reply { - SignerId signer_id = 10; - FromType from_type = 11; - TxSigCollect tx_sig_collect = 12; - TxData tx_data = 13; + string signer_id = 10; + uint32 from_type = 11; + string tx_sig_collect = 12; + bytes tx_data = 13; InputSignedState input_signed_state = 14; } } @@ -827,66 +705,25 @@ message SignerReply //// message UtilsReply { - message RandomHex { - required string data = 1; - } - - message AddressTypeName { - required string name = 1; - } - - message WalletId { - required string id = 1; - } - - message Hash { - required bytes data = 1; - } - - // oneof reply { - RandomHex random_hex = 10; - AddressTypeName address_type_name = 11; - WalletId wallet_id = 12; - Hash hash = 13; + string random_hex = 10; + string address_type_name = 11; + string wallet_id = 12; + bytes hash = 13; + bytes scraddr = 14; } } //// message ScriptUtilsReply { - message TxInScriptType { - required uint32 script_type = 1; - } - - message TxOutScriptType { - required uint32 script_type = 1; - } - - message AddressString { - required string address = 1; - } - - message ScriptAddress { - required bytes scraddr = 1; - } - - message PushData { - required bytes data = 1; - } - - message ScriptData { - required bytes data = 1; - } - - // oneof reply { - TxInScriptType txin_script_type = 10; - TxOutScriptType txout_script_type = 11; - AddressString address_string = 12; - ScriptAddress scraddr = 13; - PushData push_data = 14; - ScriptData script_data = 15; + uint32 txin_script_type = 10; + uint32 txout_script_type = 11; + string address_string = 12; + bytes scraddr = 13; + bytes push_data = 14; + bytes script_data = 15; } } @@ -897,7 +734,7 @@ message Reply optional uint32 reference_id = 2; optional string error = 3; - oneof reply_type { + oneof reply_payload { BlockchainServiceReply service = 10; WalletReply wallet = 11; CoinSelectionReply coin_selection = 12; @@ -915,7 +752,7 @@ message Reply message Payload { oneof payload { - Reply reply = 1; - Callback callback = 2; + Reply reply = 1; + CallbackPush callback = 2; } } \ No newline at end of file diff --git a/qtdialogs/ArmoryDialog.py b/qtdialogs/ArmoryDialog.py index cd2777725..9a695b983 100644 --- a/qtdialogs/ArmoryDialog.py +++ b/qtdialogs/ArmoryDialog.py @@ -16,6 +16,7 @@ from qtdialogs.qtdefines import AddToRunningDialogsList, GETFONT from armoryengine.ArmoryUtils import USE_TESTNET, USE_REGTEST +from ui.QtExecuteSignal import TheSignalExecution ################################################################################ class ArmoryDialog(QDialog): @@ -29,8 +30,6 @@ def __init__(self, parent=None, main=None): self.parent = parent self.main = main - if self.main != None: - self.signalExecution = self.main.signalExecution #connect this dialog to the parent's close signal if self.parent is not None and hasattr(self.parent, 'closeSignal'): @@ -58,7 +57,7 @@ def reject(self): super(ArmoryDialog, self).reject() def executeMethod(self, _callable, *args): - self.signalExecution.executeMethod(_callable, *args) + TheSignalExecution.executeMethod(_callable, *args) def callLater(self, delay, _callable, *args): - self.signalExecution.callLater(delay, _callable, *args) + TheSignalExecution.callLater(delay, _callable, *args) diff --git a/qtdialogs/DlgAddressBook.py b/qtdialogs/DlgAddressBook.py index 9be8059f2..8a7aad078 100644 --- a/qtdialogs/DlgAddressBook.py +++ b/qtdialogs/DlgAddressBook.py @@ -21,11 +21,11 @@ from armoryengine.ArmoryUtils import DEFAULT_RECEIVE_TYPE, \ addrStr_to_hash160, P2SHBYTE from armoryengine.MultiSigUtils import isBareLockbox, isP2SHLockbox +from armoryengine.Settings import TheSettings -from qtdialogs.qtdialogs import STRETCH -from qtdialogs.qtdefines import QRichLabel, tightSizeStr, \ +from qtdialogs.qtdefines import QRichLabel, tightSizeStr, STRETCH, \ initialColResize, USERMODE, HLINE, makeHorizFrame, restoreTableView, \ - saveTableView + saveTableView, createToolTipWidget from qtdialogs.DlgSetComment import DlgSetComment from qtdialogs.ArmoryDialog import ArmoryDialog @@ -177,9 +177,9 @@ def toggleAddrType(addrtype): - ttipSendWlt = self.main.createToolTipWidget(\ + ttipSendWlt = createToolTipWidget(\ self.tr('The next unused address in that wallet will be calculated and selected. ')) - ttipSendAddr = self.main.createToolTipWidget(\ + ttipSendAddr = createToolTipWidget(\ self.tr('Addresses that are in other wallets you own are not showns.')) @@ -187,7 +187,7 @@ def toggleAddrType(addrtype): self.btnSelectWlt = QPushButton(self.tr('No Wallet Selected')) self.useBareMultiSigCheckBox = QCheckBox(self.tr('Use Bare Multi-Sig (No P2SH)')) self.useBareMultiSigCheckBox.setVisible(False) - self.ttipBareMS = self.main.createToolTipWidget( self.tr( + self.ttipBareMS = createToolTipWidget( self.tr( 'EXPERT OPTION: Do not check this box unless you know what it means ' 'and you need it! Forces Armory to exposes public ' 'keys to the blockchain before the funds are spent. ' @@ -252,10 +252,10 @@ def toggleAddrType(addrtype): self.setMinimumWidth(300) - hexgeom = self.main.settings.get('AddrBookGeometry') - wltgeom = self.main.settings.get('AddrBookWltTbl') - rxgeom = self.main.settings.get('AddrBookRxTbl') - txgeom = self.main.settings.get('AddrBookTxTbl') + hexgeom = TheSettings.get('AddrBookGeometry') + wltgeom = TheSettings.get('AddrBookWltTbl') + rxgeom = TheSettings.get('AddrBookRxTbl') + txgeom = TheSettings.get('AddrBookTxTbl') if len(hexgeom) > 0: if type(hexgeom) == str: geom = QByteArray(bytes.fromhex(hexgeom)) @@ -271,10 +271,10 @@ def toggleAddrType(addrtype): ############################################################################# def saveGeometrySettings(self): - self.main.writeSetting('AddrBookGeometry', self.saveGeometry().toHex()) - self.main.writeSetting('AddrBookWltTbl', saveTableView(self.wltDispView)) - self.main.writeSetting('AddrBookRxTbl', saveTableView(self.addrBookRxView)) - self.main.writeSetting('AddrBookTxTbl', saveTableView(self.addrBookTxView)) + TheSettings.set('AddrBookGeometry', self.saveGeometry().toHex()) + TheSettings.set('AddrBookWltTbl', saveTableView(self.wltDispView)) + TheSettings.set('AddrBookRxTbl', saveTableView(self.addrBookRxView)) + TheSettings.set('AddrBookTxTbl', saveTableView(self.addrBookTxView)) ############################################################################# def closeEvent(self, event): diff --git a/qtdialogs/DlgAddressInfo.py b/qtdialogs/DlgAddressInfo.py new file mode 100644 index 000000000..00bc277db --- /dev/null +++ b/qtdialogs/DlgAddressInfo.py @@ -0,0 +1,416 @@ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2023, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## +from PySide2.QtCore import Qt +from PySide2.QtWidgets import QApplication, QFrame, QGridLayout, \ + QPushButton, QTableView, QLabel, QVBoxLayout, QDialogButtonBox + +from armoryengine.ArmoryUtils import BIGENDIAN, LITTLEENDIAN, binary_to_hex +from armoryengine.Settings import TheSettings + +from armorymodels import LedgerDispModelSimple, LedgerDispDelegate, \ + LEDGERCOLS, GETFONT +from qtdialogs.qtdefines import USERMODE, STYLE_RAISED, STYLE_SUNKEN, \ + QRichLabel, HORIZONTAL, VERTICAL, tightSizeStr, tightSizeNChar, \ + makeLayoutFrame, makeHorizFrame, makeVertFrame, QLabelButton, \ + initialColResize, STRETCH, createToolTipWidget +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.QRCodeWidget import QRCodeWidget + +############################################################################## +class DlgAddressInfo(ArmoryDialog): + def __init__(self, wlt, addrObj, parent=None, main=None, mode=None): + super(DlgAddressInfo, self).__init__(parent, main) + + self.wlt = wlt + self.addrObj = addrObj + scrAddr = addrObj.getPrefixedAddr() + + self.ledgerTable = [] + + self.mode = mode + if mode == None: + if main == None: + self.mode = USERMODE.Standard + else: + self.mode = self.main.usermode + + + dlgLayout = QGridLayout() + addrStr = addrObj.getAddressString() + + frmInfo = QFrame() + frmInfo.setFrameStyle(STYLE_RAISED) + frmInfoLayout = QGridLayout() + + lbls = [] + + # Hash160 + if mode in (USERMODE.Advanced, USERMODE.Expert): + bin25 = base58_to_binary(addrStr) + lbls.append([]) + lbls[-1].append(createToolTipWidget(\ + self.tr('This is the computer-readable form of the address'))) + lbls[-1].append(QRichLabel(self.tr('Public Key Hash'))) + h160Str = binary_to_hex(bin25[1:-4]) + if mode == USERMODE.Expert: + network = binary_to_hex(bin25[:1 ]) + hash160 = binary_to_hex(bin25[ 1:-4 ]) + addrChk = binary_to_hex(bin25[ -4:]) + h160Str += self.tr('%s (Network: %s / Checksum: %s)' % (hash160, network, addrChk)) + lbls[-1].append(QLabel(h160Str)) + + + + lbls.append([]) + lbls[-1].append(QLabel('')) + lbls[-1].append(QRichLabel(self.tr('Wallet:'))) + lbls[-1].append(QLabel(self.wlt.labelName)) + + lbls.append([]) + lbls[-1].append(QLabel('')) + lbls[-1].append(QRichLabel(self.tr('Address:'))) + lbls[-1].append(QLabel(addrStr)) + + + lbls.append([]) + lbls[-1].append(createToolTipWidget(self.tr( + 'Address type is either Imported or Permanent. ' + 'Permanent ' + 'addresses are part of the base wallet, and are protected by printed ' + 'paper backups, regardless of when the backup was performed. ' + 'Imported addresses are only protected by digital backups, or manually ' + 'printing the individual keys list, and only if the wallet was backed up ' + 'after the keys were imported.'))) + + lbls[-1].append(QRichLabel(self.tr('Address Type:'))) + if self.addrObj.chainIndex == -2: + lbls[-1].append(QLabel(self.tr('Imported'))) + else: + lbls[-1].append(QLabel(self.tr('Permanent'))) + + # TODO: fix for BIP-32 + lbls.append([]) + lbls[-1].append(createToolTipWidget( + self.tr('The index of this address within the wallet.'))) + lbls[-1].append(QRichLabel(self.tr('Index:'))) + if self.addrObj.chainIndex > -1: + lbls[-1].append(QLabel(str(self.addrObj.chainIndex+1))) + else: + lbls[-1].append(QLabel(self.tr("Imported"))) + + + # Current Balance of address + lbls.append([]) + lbls[-1].append(createToolTipWidget(self.tr( + 'This is the current spendable balance of this address, ' + 'not including zero-confirmation transactions from others.'))) + lbls[-1].append(QRichLabel(self.tr('Current Balance'))) + try: + balCoin = addrObj.getSpendableBalance() + balStr = coin2str(balCoin, maxZeros=1) + if balCoin > 0: + goodColor = htmlColor('MoneyPos') + lbls[-1].append(QRichLabel(\ + '' + balStr.strip() + ' BTC')) + else: + lbls[-1].append(QRichLabel(balStr.strip() + ' BTC')) + except: + lbls[-1].append(QRichLabel("N/A")) + + + lbls.append([]) + lbls[-1].append(QLabel('')) + lbls[-1].append(QRichLabel(self.tr('Comment:'))) + if self.addrObj.chainIndex > -1: + lbls[-1].append(QLabel(str(wlt.commentsMap[scrAddr]) if scrAddr in wlt.commentsMap else '')) + else: + lbls[-1].append(QLabel('')) + + lbls.append([]) + lbls[-1].append(createToolTipWidget( + self.tr('The total number of transactions in which this address was involved'))) + lbls[-1].append(QRichLabel(self.tr('Transaction Count:'))) + #lbls[-1].append(QLabel(str(len(txHashes)))) + try: + txnCount = self.addrObj.getTxioCount() + lbls[-1].append(QLabel(str(txnCount))) + except: + lbls[-1].append(QLabel("N/A")) + + + for i in range(len(lbls)): + for j in range(1, 3): + lbls[i][j].setTextInteractionFlags(Qt.TextSelectableByMouse | \ + Qt.TextSelectableByKeyboard) + for j in range(3): + if (i, j) == (0, 2): + frmInfoLayout.addWidget(lbls[i][j], i, j, 1, 2) + else: + frmInfoLayout.addWidget(lbls[i][j], i, j, 1, 1) + + qrcode = QRCodeWidget(addrStr, 80, parent=self) + qrlbl = QRichLabel(self.tr('Double-click to expand')) + frmqr = makeVertFrame([qrcode, qrlbl]) + + frmInfoLayout.addWidget(frmqr, 0, 4, len(lbls), 1) + frmInfo.setLayout(frmInfoLayout) + dlgLayout.addWidget(frmInfo, 0, 0, 1, 1) + + + # ## Set up the address ledger + self.ledgerModel = LedgerDispModelSimple(self.ledgerTable, self, self.main) + delegateId = self.wlt.getLedgerDelegateIdForScrAddr(scrAddr) + self.ledgerModel.setLedgerDelegateId(delegateId) + + def ledgerToTableScrAddr(ledger): + return self.main.convertLedgerToTable( + ledger, wltIDIn=self.wlt.uniqueIDB58) + self.ledgerModel.setConvertLedgerMethod(ledgerToTableScrAddr) + + self.frmLedgUpDown = QFrame() + #self.ledgerView = ArmoryTableView(self, self.main, self.frmLedgUpDown) + self.ledgerView = QTableView(self.main) + self.ledgerView.setModel(self.ledgerModel) + self.ledgerView.setItemDelegate(LedgerDispDelegate(self)) + + self.ledgerView.hideColumn(LEDGERCOLS.isOther) + self.ledgerView.hideColumn(LEDGERCOLS.UnixTime) + self.ledgerView.hideColumn(LEDGERCOLS.WltID) + self.ledgerView.hideColumn(LEDGERCOLS.WltName) + self.ledgerView.hideColumn(LEDGERCOLS.TxHash) + self.ledgerView.hideColumn(LEDGERCOLS.isCoinbase) + self.ledgerView.hideColumn(LEDGERCOLS.toSelf) + self.ledgerView.hideColumn(LEDGERCOLS.optInRBF) + + self.ledgerView.setSelectionBehavior(QTableView.SelectRows) + self.ledgerView.setSelectionMode(QTableView.SingleSelection) + self.ledgerView.horizontalHeader().setStretchLastSection(True) + self.ledgerView.verticalHeader().setDefaultSectionSize(20) + self.ledgerView.verticalHeader().hide() + self.ledgerView.setMinimumWidth(650) + + dateWidth = tightSizeStr(self.ledgerView, '_9999-Dec-99 99:99pm__')[0] + initialColResize(self.ledgerView, [20, 0, dateWidth, 72, 0, 0.45, 0.3]) + + ttipLedger = createToolTipWidget(self.tr( + 'Unlike the wallet-level ledger, this table shows every ' + 'transaction input and output as a separate entry. ' + 'Therefore, there may be multiple entries for a single transaction, ' + 'which will happen if money was sent-to-self (explicitly, or as ' + 'the change-back-to-self address).')) + lblLedger = QLabel(self.tr('All Address Activity:')) + + lblstrip = makeLayoutFrame(HORIZONTAL, [lblLedger, ttipLedger, STRETCH]) + bottomRow = makeHorizFrame([STRETCH, self.frmLedgUpDown, STRETCH], condenseMargins=True) + frmLedger = makeLayoutFrame(VERTICAL, [lblstrip, self.ledgerView, bottomRow]) + dlgLayout.addWidget(frmLedger, 1, 0, 1, 1) + + + # Now add the right-hand-side option buttons + lbtnCopyAddr = QLabelButton(self.tr('Copy Address to Clipboard')) + lbtnViewKeys = QLabelButton(self.tr('View Address Keys')) + # lbtnSweepA = QLabelButton('Sweep Address') + lbtnDelete = QLabelButton(self.tr('Delete Address')) + + lbtnCopyAddr.linkActivated.connect(self.copyAddr) + lbtnViewKeys.linkActivated.connect(self.viewKeys) + lbtnDelete.linkActivated.connect(self.deleteAddr) + + optFrame = QFrame() + optFrame.setFrameStyle(STYLE_SUNKEN) + + hasPriv = self.addrObj.hasPrivKey + adv = (self.main.usermode in (USERMODE.Advanced, USERMODE.Expert)) + watch = self.wlt.watchingOnly + + + self.lblCopied = QRichLabel('') + self.lblCopied.setMinimumHeight(tightSizeNChar(self.lblCopied, 1)[1]) + + self.lblLedgerWarning = QRichLabel(self.tr( + 'NOTE: The ledger shows each transaction input and ' + 'output for this address. There are typically many ' + 'inputs and outputs for each transaction, therefore the entries ' + 'represent only partial transactions. Do not worry if these entries ' + 'do not look familiar.')) + + + optLayout = QVBoxLayout() + if True: optLayout.addWidget(lbtnCopyAddr) + if adv: optLayout.addWidget(lbtnViewKeys) + + if True: optLayout.addStretch() + if True: optLayout.addWidget(self.lblCopied) + + optLayout.addWidget(self.lblLedgerWarning) + + optLayout.addStretch() + optFrame.setLayout(optLayout) + + rightFrm = makeLayoutFrame(VERTICAL, [QLabel(self.tr('Available Actions:')), optFrame]) + dlgLayout.addWidget(rightFrm, 0, 1, 2, 1) + + btnGoBack = QPushButton(self.tr('<<< Go Back')) + btnGoBack.clicked.connect(self.reject) + + self.setLayout(dlgLayout) + self.setWindowTitle(self.tr('Address Information')) + + self.ledgerModel.reset() + + def copyAddr(self): + clipb = QApplication.clipboard() + clipb.clear() + clipb.setText(self.addrObj.getAddressString()) + self.lblCopied.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) + self.lblCopied.setText(self.tr('Copied!')) + + def makePaper(self): + pass + + def viewKeys(self): + ''' + if self.wlt.useEncryption and self.wlt.isLocked: + unlockdlg = DlgUnlockWallet(self.wlt, self, self.main, 'View Private Keys') + if not unlockdlg.exec_(): + QMessageBox.critical(self, self.tr('Wallet is Locked'), \ + self.tr('Key information will not include the private key data.'), \ + QMessageBox.Ok) + ''' + dlg = DlgShowKeys(self.addrObj, self.wlt, self, self.main) + dlg.exec_() + + def deleteAddr(self): + pass + +############################################################################# +class DlgShowKeys(ArmoryDialog): + def __init__(self, addr, wlt, parent=None, main=None): + super(DlgShowKeys, self).__init__(parent, main) + + self.addr = addr + self.wlt = wlt + self.scrAddr = self.addr.getPrefixedAddr() + + lblWarn = QRichLabel('') + plainPriv = False + ''' + if addr.binPrivKey32_Plain.getSize() > 0: + plainPriv = True + lblWarn = QRichLabel(self.tr( + 'Warning: the unencrypted private keys ' + 'for this address are shown below. They are "private" because ' + 'anyone who obtains them can spend the money held ' + 'by this address. Please protect this information the ' + 'same as you protect your wallet.' % htmlColor('TextWarn'))) + ''' + warnFrm = makeLayoutFrame(HORIZONTAL, [lblWarn]) + + endianness = TheSettings.getSettingOrSetDefault('PrefEndian', BIGENDIAN) + estr = 'BE' if endianness == BIGENDIAN else 'LE' + def formatBinData(binStr, endian=LITTLEENDIAN): + binHex = binary_to_hex(binStr) + if endian != LITTLEENDIAN: + binHex = hex_switchEndian(binHex) + binHexPieces = [binHex[i:i + 8] for i in range(0, len(binHex), 8)] + return ' '.join(binHexPieces) + + + lblDescr = QRichLabel(self.tr(f'Key Data for address: %s' % self.addr.getAddressString())) + + lbls = [] + + lbls.append([]) + binKey = b'' #self.addr.binPrivKey32_Plain.toBinStr() + lbls[-1].append(createToolTipWidget(self.tr( + 'The raw form of the private key for this address. It is ' + '32-bytes of randomly generated data'))) + lbls[-1].append(QRichLabel(self.tr('Private Key (hex,%s):' % estr))) + if not addr.hasPrivKey: + lbls[-1].append(QRichLabel(self.tr('[[ No Private Key in Watching-Only Wallet ]]'))) + elif plainPriv: + lbls[-1].append(QLabel(formatBinData(binKey))) + else: + lbls[-1].append(QRichLabel(self.tr('[[ ENCRYPTED ]]'))) + + if plainPriv: + lbls.append([]) + lbls[-1].append(createToolTipWidget(self.tr( + 'This is a more compact form of the private key, and includes ' + 'a checksum for error detection.'))) + lbls[-1].append(QRichLabel(self.tr('Private Key (Base58):'))) + b58Key = encodePrivKeyBase58(binKey) + lbls[-1].append(QLabel(' '.join([b58Key[i:i + 6] for i in range(0, len(b58Key), 6)]))) + + + + lbls.append([]) + lbls[-1].append(createToolTipWidget(self.tr( + 'The raw public key data. This is the X-coordinate of ' + 'the Elliptic-curve public key point.'))) + lbls[-1].append(QRichLabel(self.tr('Public Key X (%s):' % estr))) + lbls[-1].append(QRichLabel(formatBinData(self.addr.binPublicKey[1:1 + 32]))) + + + lbls.append([]) + lbls[-1].append(createToolTipWidget(self.tr( + 'The raw public key data. This is the Y-coordinate of ' + 'the Elliptic-curve public key point.'))) + lbls[-1].append(QRichLabel(self.tr('Public Key Y (%s):' % estr))) + lbls[-1].append(QRichLabel(formatBinData(self.addr.binPublicKey[1 + 32:1 + 32 + 32]))) + + + bin25 = self.addr.getPrefixedAddr() + network = binary_to_hex(bin25[:1 ]) + hash160 = binary_to_hex(bin25[ 1:-4 ]) + addrChk = binary_to_hex(bin25[ -4:]) + h160Str = self.tr('%s (Network: %s / Checksum: %s)' % (hash160, network, addrChk)) + + lbls.append([]) + lbls[-1].append(createToolTipWidget(\ + self.tr('This is the hexadecimal version if the address string'))) + lbls[-1].append(QRichLabel(self.tr('Public Key Hash:'))) + lbls[-1].append(QLabel(h160Str)) + + frmKeyData = QFrame() + frmKeyData.setFrameStyle(STYLE_RAISED) + frmKeyDataLayout = QGridLayout() + + + # Now set the label properties and jam them into an information frame + for row, lbl3 in enumerate(lbls): + lbl3[1].setFont(GETFONT('Var')) + lbl3[2].setFont(GETFONT('Fixed')) + lbl3[2].setTextInteractionFlags(Qt.TextSelectableByMouse | \ + Qt.TextSelectableByKeyboard) + lbl3[2].setWordWrap(False) + + for j in range(3): + frmKeyDataLayout.addWidget(lbl3[j], row, j) + + + frmKeyData.setLayout(frmKeyDataLayout) + + bbox = QDialogButtonBox(QDialogButtonBox.Ok) + bbox.accepted.connect(self.accept) + + + dlgLayout = QVBoxLayout() + dlgLayout.addWidget(lblWarn) + dlgLayout.addWidget(lblDescr) + dlgLayout.addWidget(frmKeyData) + dlgLayout.addWidget(bbox) + + + self.setLayout(dlgLayout) + self.setWindowTitle(self.tr('Address Key Information')) + diff --git a/qtdialogs/DlgBackupCenter.py b/qtdialogs/DlgBackupCenter.py index a0c3fd5f3..b1a7edccb 100644 --- a/qtdialogs/DlgBackupCenter.py +++ b/qtdialogs/DlgBackupCenter.py @@ -4,7 +4,7 @@ # Distributed under the GNU Affero General Public License (AGPL v3) # # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # -# Copyright (C) 2016-2022, goatpig # +# Copyright (C) 2016-2023, goatpig # # Distributed under the MIT license # # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # @@ -18,18 +18,19 @@ QGraphicsScene, QGraphicsView, QCheckBox, QComboBox, QGraphicsPixmapItem, \ QGraphicsLineItem, QGraphicsItem +from armoryengine.ArmoryUtils import toUnicode, USE_TESTNET, USE_REGTEST +from armorycolors import htmlColor + from ui.WalletFrames import WalletBackupFrame from ui.QrCodeMatrix import CreateQRMatrix from qtdialogs.qtdefines import makeHorizFrame, QRichLabel, \ makeVertFrame, QImageLabel, HLINE, GETFONT, STYLE_RAISED, tightSizeStr, \ - setLayoutStretch + setLayoutStretch, STRETCH, createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.qtdialogs import STRETCH -from qtdialogs.DlgUnlockWallet import DlgUnlockWallet +from qtdialogs.DlgUnlockWallet import UnlockWalletHandler +from qtdialogs.DlgRestore import OpenPaperBackupDialog -from armoryengine.ArmoryUtils import toUnicode, USE_TESTNET, USE_REGTEST -from armorycolors import htmlColor ################################################################################ class DlgBackupCenter(ArmoryDialog): @@ -97,7 +98,7 @@ def backupDigital(): self.accept() def backupPaper(): - OpenPaperBackupWindow('Single', self, self.main, self.wlt) + OpenPaperBackupDialog('Single', self, self.main, self.wlt) self.accept() def backupOther(): @@ -384,8 +385,6 @@ def paint(self, painter, option, widget=None): ################################################################################ class DlgPrintBackup(ArmoryDialog): - backupSetupSignal = Signal() - """ Open up a "Make Paper Backup" dialog, so the user can print out a hard copy of whatever data they need to recover their wallet should they lose @@ -414,11 +413,19 @@ def __init__(self, parent, main, wlt, printType='SingleSheet', \ self.backupData = None def resumeSetup(rootData): - self.backupData = rootData - self.backupSetupSignal.emit() + success = rootData.success + if not rootData.HasField("wallet") or \ + not rootData.wallet.HasField("backup_string"): + success = False - self.backupSetupSignal.connect(self.setup) - rootData = self.wlt.createBackupString(resumeSetup) + self.backupData = None + if success: + self.backupData = rootData.wallet.backup_string + self.executeMethod(self.setup) + + unlockHandler = UnlockWalletHandler(self.wlt.uniqueIDB58, + "Create Backup", self) + rootData = self.wlt.createBackupString(unlockHandler, resumeSetup) ### def setup(self): @@ -434,7 +441,7 @@ def setup(self): # A self-evident check of whether we need to print the chaincode. # If we derive the chaincode from the private key, and it matches # what's already in the wallet, we obviously don't need to print it! - self.noNeedChaincode = (len(self.backupData.chainclear) == 0) + self.noNeedChaincode = (len(self.backupData.chain_clear) == 0) # Save off imported addresses in case they need to be printed, too for a160, addr in self.wlt.addrMap.items(): @@ -482,7 +489,7 @@ def setup(self): if(self.doPrintFrag): self.chkSecurePrint.setChecked(self.fragData['Secure']) - self.ttipSecurePrint = self.main.createToolTipWidget(self.tr( + self.ttipSecurePrint = createToolTipWidget(self.tr( u'SecurePrint\u200b\u2122 encrypts your backup with a code displayed on ' 'the screen, so that no other devices on your network see the sensitive ' 'data when you send it to the printer. If you turn on ' @@ -495,7 +502,7 @@ def setup(self): u'encryption code on each printed backup page! Your SecurePrint\u200b\u2122 code is
' '%s. Your backup will not work ' 'if this code is lost!' % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ - self.backupData.sppass, htmlColor('TextWarn')))) + self.backupData.sp_pass, htmlColor('TextWarn')))) self.chkSecurePrint.clicked.connect(self.redrawBackup) @@ -557,7 +564,6 @@ def setup(self): self.setWindowIcon(QIcon('./img/printer_icon.png')) self.setWindowTitle('Print Wallet Backup') - # Apparently I can't programmatically scroll until after it's painted def scrollTop(): vbar = self.view.verticalScrollBar() @@ -592,9 +598,6 @@ def redrawBackup(self): self.showPageSelect(showPageCombo) self.view.update() - - - def clickImportChk(self): if self.numImportPages > 1 and self.chkImportPrint.isChecked(): ans = QMessageBox.warning(self, self.tr('Lots to Print!'), self.tr( @@ -615,7 +618,6 @@ def clickImportChk(self): self.comboPageNum.setCurrentIndex(0) self.redrawBackup() - def showPageSelect(self, doShow=True): MARGIN = self.scene.MARGIN_PIXELS bottomOfPage = self.scene.pageRect().height() + MARGIN @@ -638,9 +640,6 @@ def showPageSelect(self, doShow=True): self.comboPageNum.setVisible(doShow) self.lblPageMaxStr.setVisible(doShow) - - - def print_(self): LOGINFO('Printing!') self.printer = QPrinter(QPrinter.HighResolution) @@ -687,7 +686,6 @@ def print_(self): else: self.accept() - def cleanup(self): self.backupData = None @@ -913,11 +911,11 @@ def createPrintScene(self, printType, printData): else: # Single-sheet backup if doMask: - code12 = self.backupData.rootencr - code34 = self.backupData.chainencr + code12 = self.backupData.root_encr + code34 = self.backupData.chain_encr else: - code12 = self.backupData.rootclear - code34 = self.backupData.chainclear + code12 = self.backupData.root_clear + code34 = self.backupData.chain_clear Lines = [] @@ -1066,7 +1064,7 @@ def updateM(): self.createFragDisplay() self.scrollArea.setWidgetResizable(True) - self.ttipSecurePrint = self.main.createToolTipWidget(self.tr( + self.ttipSecurePrint = createToolTipWidget(self.tr( u'SecurePrint\u200b\u2122 encrypts your backup with a code displayed on ' 'the screen, so that no other devices or processes has access to the ' 'unencrypted private keys (either network devices when printing, or ' @@ -1078,8 +1076,8 @@ def updateM(): u'Your SecurePrint\u200b\u2122 code is
' '%s. ' 'All fragments for a given wallet use the ' - 'same code.' % (htmlColor('TextWarn'), htmlColor('TextBlue'), self.backupData.sppass, \ - htmlColor('TextWarn')))) + 'same code.' % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ + self.backupData.sp_pass, htmlColor('TextWarn')))) self.connect(self.chkSecurePrint, SIGNAL(CLICKED), self.clickChkSP) self.chkSecurePrint.setChecked(False) self.lblSecurePrint.setVisible(False) @@ -1338,7 +1336,7 @@ def clickSaveFrag(self, zindex): '

' 'The above code is case-sensitive!' \ % (htmlColor('TextWarn'), htmlColor('TextBlue'), \ - self.backupData.sppass)) + self.backupData.sp_pass)) QMessageBox.information(self, self.tr('Success'), qmsg, QMessageBox.Ok) diff --git a/qtdialogs/DlgChangePassphrase.py b/qtdialogs/DlgChangePassphrase.py index de20a64df..4e52b741f 100644 --- a/qtdialogs/DlgChangePassphrase.py +++ b/qtdialogs/DlgChangePassphrase.py @@ -20,7 +20,7 @@ from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.DlgPasswd3 import DlgPasswd3 -from qtdialogs.DlgSettings import MIN_PASSWD_WIDTH +from qtdialogs.qtdefines import MIN_PASSWD_WIDTH ################################################################################ class DlgChangePassphrase(ArmoryDialog): diff --git a/qtdialogs/DlgDispTxInfo.py b/qtdialogs/DlgDispTxInfo.py index f1943c503..0470f773a 100644 --- a/qtdialogs/DlgDispTxInfo.py +++ b/qtdialogs/DlgDispTxInfo.py @@ -31,12 +31,13 @@ from armoryengine.Block import PyBlockHeader from armoryengine.Script import convertScriptToOpStrings from armoryengine.CppBridge import TheBridge +from armoryengine.Settings import TheSettings from armorymodels import TxInDispModel, TxOutDispModel, TXINCOLS, TXOUTCOLS from qtdialogs.qtdefines import STYLE_RAISED, USERMODE, \ QRichLabel, relaxedSizeStr, GETFONT, tightSizeNChar, STYLE_SUNKEN, \ - HORIZONTAL, makeHorizFrame, initialColResize, makeLayoutFrame -from qtdialogs.qtdialogs import STRETCH + HORIZONTAL, makeHorizFrame, initialColResize, makeLayoutFrame, STRETCH, \ + createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog ################################################################################ @@ -290,7 +291,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ if ledgerEntry: txAmt = ledgerEntry.value - if ledgerEntry.isSentToSelf: + if ledgerEntry.sent_to_self: txdir = self.tr('Sent-to-Self') svPairDisp = [] if len(self.pytx.outputs)==1: @@ -349,13 +350,13 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ # Show the transaction ID, with the user's preferred endianness # I hate BE, but block-explorer requires it so it's probably a better default - endianness = self.main.getSettingOrSetDefault('PrefEndian', BIGENDIAN) + endianness = TheSettings.getSettingOrSetDefault('PrefEndian', BIGENDIAN) estr = '' if self.mode in (USERMODE.Advanced, USERMODE.Expert): estr = ' (BE)' if endianness == BIGENDIAN else ' (LE)' lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(self.tr('Unique identifier for this transaction'))) + lbls[-1].append(createToolTipWidget(self.tr('Unique identifier for this transaction'))) lbls[-1].append(QLabel(self.tr('Transaction ID' )+ estr + ':')) @@ -385,12 +386,12 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ if self.mode in (USERMODE.Expert,): # Add protocol version and locktime to the display lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(self.tr('Bitcoin Protocol Version Number'))) + lbls[-1].append(createToolTipWidget(self.tr('Bitcoin Protocol Version Number'))) lbls[-1].append(QLabel(self.tr('Tx Version:'))) lbls[-1].append(QLabel(str(self.pytx.version))) lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('The time at which this transaction becomes valid.'))) lbls[-1].append(QLabel(self.tr('Lock-Time:'))) if self.pytx.lockTime == 0: @@ -403,7 +404,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(self.tr('Comment stored for this transaction in this wallet'))) + lbls[-1].append(createToolTipWidget(self.tr('Comment stored for this transaction in this wallet'))) lbls[-1].append(QLabel(self.tr('User Comment:'))) try: txhash_bin = hex_to_binary(txHash, endOut=endianness) @@ -427,10 +428,10 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ if not self.data[FIELDS.Time] == None: lbls.append([]) if self.data[FIELDS.Blk] >= 2 ** 32 - 1: - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('The time that you computer first saw this transaction'))) else: - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('All transactions are eventually included in a "block." The ' 'time shown here is the time that the block entered the "blockchain."'))) lbls[-1].append(QLabel('Transaction Time:')) @@ -440,7 +441,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ nConf = 0 if self.data[FIELDS.Blk] >= 2 ** 32 - 1: lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('This transaction has not yet been included in a block. ' 'It usually takes 5-20 minutes for a transaction to get ' 'included in a block after the user hits the "Send" button.'))) @@ -451,7 +452,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ if not self.data[FIELDS.Idx] == None and self.mode == USERMODE.Expert: idxStr =' (Tx #%d)' % self.data[FIELDS.Idx] lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('Every transaction is eventually included in a "block" which ' 'is where the transaction is permanently recorded. A new block ' 'is produced approximately every 10 minutes.'))) @@ -460,7 +461,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ if TheBDM.getState() == BDM_BLOCKCHAIN_READY: nConf = TheBDM.getTopBlockHeight() - self.data[FIELDS.Blk] + 1 lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('The number of blocks that have been produced since ' 'this transaction entered the blockchain. A transaction ' 'with 6 or more confirmations is nearly impossible to reverse.'))) @@ -470,7 +471,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ isRBF = self.pytx.isRBF() if isRBF: lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('This transaction can be replaced by another transaction that ' 'spends the same inputs if the replacement transaction has ' 'a higher fee.'))) @@ -481,7 +482,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ if svPairDisp == None and precomputeAmt == None: # Couldn't determine recip/change outputs lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('Most transactions have at least a recipient output and a ' 'returned-change output. You do not have enough information ' 'to determine which is which, and so this fields shows the sum ' @@ -490,13 +491,13 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ lbls[-1].append(QLabel(coin2str(txAmt, maxZeros=1).strip() + ' BTC')) else: lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('Bitcoins were either sent or received, or sent-to-self'))) lbls[-1].append(QLabel('Transaction Direction:')) lbls[-1].append(QRichLabel(txdir)) lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('The value shown here is the net effect on your ' 'wallet, including transaction fee.'))) lbls[-1].append(QLabel('Transaction Amount:')) @@ -511,7 +512,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ txsize = str(self.data[FIELDS.TxSize]) txsize_str = self.tr("%s bytes" % txsize) lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('Size of the transaction in bytes'))) lbls[-1].append(QLabel(self.tr('Tx Size: '))) lbls[-1].append(QLabel(txsize_str)) @@ -519,7 +520,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ if not self.data[FIELDS.SumIn] == None: fee = self.data[FIELDS.SumIn] - self.data[FIELDS.SumOut] lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( + lbls[-1].append(createToolTipWidget( self.tr('Transaction fees go to users supplying the Bitcoin network with ' 'computing power for processing transactions and maintaining security.'))) lbls[-1].append(QLabel('Tx Fee Paid:')) @@ -557,7 +558,7 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ for i, sv in enumerate(svPairDisp): rlbls.append([]) if i == 0: - rlbls[-1].append(self.main.createToolTipWidget( + rlbls[-1].append(createToolTipWidget( self.tr('All outputs of the transaction excluding change-' 'back-to-sender outputs. If this list does not look ' 'correct, it is possible that the change-output was ' @@ -699,10 +700,10 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ ttipText += (self.tr('Each input is like an X amount dollar bill. Usually there are more inputs ' 'than necessary for the transaction, and there will be an extra ' 'output returning change to the sender')) - ttipInputs = self.main.createToolTipWidget(ttipText) + ttipInputs = createToolTipWidget(ttipText) lblOutputs = QLabel(self.tr('Transaction Outputs (Receiving addresses):')) - ttipOutputs = self.main.createToolTipWidget( + ttipOutputs = createToolTipWidget( self.tr('Shows all outputs, including other recipients ' 'of the same transaction, and change-back-to-sender outputs ' '(change outputs are displayed in light gray).')) @@ -939,16 +940,16 @@ def extractTxInfo(pytx, rcvTime=None): sumTxOut = sum([t[1] for t in txOutToList]) if TheBDM.getState() == BDM_BLOCKCHAIN_READY and hasTxHash: - txProto = TheBridge.getTxByHash(txHash) - if txProto.isValid: + txProto = TheBridge.service.getTxByHash(txHash) + if txProto is not None: hgt = txProto.height txWeight = pytx.getTxWeight() if hgt <= TheBDM.getTopBlockHeight(): header = PyBlockHeader() - header.unserialize(TheBridge.getHeaderByHeight(hgt)) + header.unserialize(TheBridge.service.getHeaderByHeight(hgt)) txTime = unixTimeToFormatStr(header.timestamp) txBlk = hgt - txIdx = txProto.txIndex + txIdx = txProto.tx_index txSize = pytx.getSize() else: if rcvTime == None: @@ -970,16 +971,16 @@ def extractTxInfo(pytx, rcvTime=None): txin = pytx.getTxIn(i) prevTxHash = txin.getOutPoint().txHash prevTxIndex = txin.getOutPoint().txOutIndex - prevTxRaw = TheBridge.getTxByHash(prevTxHash) - prevTx = PyTx().unserialize(prevTxRaw.raw) - if prevTx.isInitialized(): + prevTxRaw = TheBridge.service.getTxByHash(prevTxHash) + if prevTxRaw != None: + prevTx = PyTx().unserialize(prevTxRaw.raw) prevTxOut = prevTx.getTxOut(prevTxIndex) txinFromList[-1].append(prevTxOut.getScrAddressStr()) txinFromList[-1].append(prevTxOut.getValue()) if prevTx.isInitialized(): txinFromList[-1].append(prevTxRaw.height) txinFromList[-1].append(prevTxHash) - txinFromList[-1].append(prevTxRaw.txIndex) + txinFromList[-1].append(prevTxRaw.tx_index) txinFromList[-1].append(prevTxOut.getScript()) else: LOGERROR('How did we get a bad parent pointer? (extractTxInfo)') diff --git a/qtdialogs/DlgEULA.py b/qtdialogs/DlgEULA.py index 31d5c6d4a..0dcf20f9a 100644 --- a/qtdialogs/DlgEULA.py +++ b/qtdialogs/DlgEULA.py @@ -14,10 +14,9 @@ from PySide2.QtGui import QIcon from PySide2.QtWidgets import QVBoxLayout, QTextEdit, QCheckBox, QPushButton -from qtdialogs.qtdefines import GETFONT, tightSizeNChar, \ +from qtdialogs.qtdefines import GETFONT, tightSizeNChar, STRETCH, \ makeHorizFrame, makeVertFrame, QRichLabel from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.qtdialogs import STRETCH ############################################################################# class DlgEULA(ArmoryDialog): @@ -76,7 +75,7 @@ def reject(self): super(DlgEULA, self).reject() def accept(self): - self.main.writeSetting('Agreed_to_EULA', True) + TheSettings.set('Agreed_to_EULA', True) super(DlgEULA, self).accept() def toggleChkBox(self, isEnabled): diff --git a/qtdialogs/DlgExportTxHistory.py b/qtdialogs/DlgExportTxHistory.py index 5090561a6..929dd4c0b 100644 --- a/qtdialogs/DlgExportTxHistory.py +++ b/qtdialogs/DlgExportTxHistory.py @@ -18,7 +18,7 @@ IGNOREZC, RightNow, hex_to_binary, hex_switchEndian, FORMAT_SYMBOLS, \ DEFAULT_DATE_FORMAT from qtdialogs.qtdefines import QRichLabel, HLINE, STRETCH, WLTTYPES, \ - makeHorizFrame, determineWalletType + makeHorizFrame, determineWalletType, createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog from armorymodels import LEDGERCOLS @@ -71,7 +71,7 @@ def __init__(self, parent=None, main=None): self.edtDateFormat = QLineEdit() self.edtDateFormat.setText(fmt) - self.ttipFormatDescr = self.main.createToolTipWidget(ttipStr) + self.ttipFormatDescr = createToolTipWidget(ttipStr) self.lblDateExample = QRichLabel('', doWrap=False) self.connect(self.edtDateFormat, SIGNAL('textEdited(QString)'), self.doExampleDate) diff --git a/qtdialogs/DlgInflatedQR.py b/qtdialogs/DlgInflatedQR.py deleted file mode 100644 index 9248ba437..000000000 --- a/qtdialogs/DlgInflatedQR.py +++ /dev/null @@ -1,43 +0,0 @@ -############################################################################## -# # -# Copyright (C) 2011-2015, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -# Copyright (C) 2016-2022, goatpig # -# Distributed under the MIT license # -# See LICENSE-MIT or https://opensource.org/licenses/MIT # -# # -############################################################################## - -from PySide2.QtWidgets import QVBoxLayout - -from qtdialogs.qtdefines import QRichLabel -from qtdialogs.ArmoryDialog import ArmoryDialog - -# Create a very simple dialog and execute it -class DlgInflatedQR(ArmoryDialog): - def __init__(self, parent, dataToQR): - super(DlgInflatedQR, self).__init__(parent, parent.main) - - sz = QApplication.desktop().size() - w,h = sz.width(), sz.height() - qrSize = int(min(w,h)*0.8) - qrDisp = QRCodeWidget(dataToQR, prefSize=qrSize) - - def closeDlg(*args): - self.accept() - qrDisp.mouseDoubleClickEvent = closeDlg - self.mouseDoubleClickEvent = closeDlg - - lbl = QRichLabel(self.tr('Double-click or press ESC to close')) - lbl.setAlignment(Qt.AlignTop | Qt.AlignHCenter) - - frmQR = makeHorizFrame(['Stretch', qrDisp, 'Stretch']) - frmFull = makeVertFrame(['Stretch',frmQR, lbl, 'Stretch']) - - layout = QVBoxLayout() - layout.addWidget(frmFull) - - self.setLayout(layout) - self.showFullScreen() diff --git a/qtdialogs/DlgIntroMessage.py b/qtdialogs/DlgIntroMessage.py index 4011715e7..573420237 100644 --- a/qtdialogs/DlgIntroMessage.py +++ b/qtdialogs/DlgIntroMessage.py @@ -16,10 +16,8 @@ QGridLayout, QDialogButtonBox, QPushButton from qtdialogs.qtdefines import QRichLabel, GETFONT, \ - makeLayoutFrame, VERTICAL, HORIZONTAL - + makeLayoutFrame, VERTICAL, HORIZONTAL, STRETCH from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.qtdialogs import STRETCH ################################################################################ class DlgIntroMessage(ArmoryDialog): diff --git a/qtdialogs/DlgKeypoolSettings.py b/qtdialogs/DlgKeypoolSettings.py index 860463fbe..daf94d885 100644 --- a/qtdialogs/DlgKeypoolSettings.py +++ b/qtdialogs/DlgKeypoolSettings.py @@ -15,11 +15,11 @@ from armorycolors import htmlColor -from qtdialogs.qtdefines import relaxedSizeStr, \ +from qtdialogs.qtdefines import relaxedSizeStr, STRETCH, \ STYLE_SUNKEN, QRichLabel, makeHorizFrame, makeVertFrame from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.qtdialogs import STRETCH from qtdialogs.DlgProgress import DlgProgress +from ui.QtExecuteSignal import TheSignalExecution ################################################################################ class DlgKeypoolSettings(ArmoryDialog): @@ -132,7 +132,7 @@ def clickCompute(self): def completeCallback(): if self.main is None: return - self.main.signalExecution.executeMethod([self.completeCompute, []]) + TheSignalExecution.executeMethod(self.completeCompute) fillAddressPoolProgress.exec_(self.wlt.fillAddressPool, naddr, fillAddressPoolProgress.callbackId, completeCallback) diff --git a/qtdialogs/DlgMigrateWallet.py b/qtdialogs/DlgMigrateWallet.py index 276141757..c0de3d329 100644 --- a/qtdialogs/DlgMigrateWallet.py +++ b/qtdialogs/DlgMigrateWallet.py @@ -16,13 +16,9 @@ QDialogButtonBox, QGridLayout, QPushButton, QVBoxLayout, \ QSizePolicy, QRadioButton, QButtonGroup, QLayout -from qtdialogs.qtdefines import tightSizeStr, \ - makeHorizFrame -from qtdialogs.qtdialogs import MIN_PASSWD_WIDTH, LetterButton, \ - STRETCH - +from qtdialogs.qtdefines import tightSizeStr, STRETCH, \ + makeHorizFrame, MIN_PASSWD_WIDTH, LetterButton, createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog - from armoryengine.CppBridge import TheBridge ################################################################################ @@ -62,7 +58,7 @@ def __init__(self, promptId, wltID, verbose, parent=None, main=None): ##### Lower layout # Add scrambled keyboard (EN-US only) - ttipScramble = self.main.createToolTipWidget(\ + ttipScramble = createToolTipWidget(\ self.tr('Using a visual keyboard to enter your passphrase ' 'protects you against simple keyloggers. Scrambling ' 'makes it difficult to use, but prevents even loggers ' @@ -77,7 +73,7 @@ def __init__(self, promptId, wltID, verbose, parent=None, main=None): btngrp.addButton(self.rdoScrambleLite) btngrp.addButton(self.rdoScrambleFull) btngrp.setExclusive(True) - defaultScramble = self.main.getSettingOrSetDefault('ScrambleDefault', 0) + defaultScramble = TheSettings.getSettingOrSetDefault('ScrambleDefault', 0) if defaultScramble == 0: self.rdoScrambleNone.setChecked(True) elif defaultScramble == 1: @@ -96,7 +92,7 @@ def __init__(self, promptId, wltID, verbose, parent=None, main=None): self.frmKeyboard = QFrame() self.frmKeyboard.setLayout(self.layoutKeyboard) - showOSD = self.main.getSettingOrSetDefault('KeybdOSD', False) + showOSD = TheSettings.getSettingOrSetDefault('KeybdOSD', False) self.layoutLower = QGridLayout() self.layoutLower.addWidget(btnRowFrm , 0, 0) self.layoutLower.addWidget(self.frmKeyboard , 1, 0) diff --git a/qtdialogs/DlgNewAddress.py b/qtdialogs/DlgNewAddress.py index ecf909ffb..cd8f64ddb 100644 --- a/qtdialogs/DlgNewAddress.py +++ b/qtdialogs/DlgNewAddress.py @@ -20,10 +20,10 @@ from armoryengine.BDM import TheBDM, BDM_OFFLINE from armorycolors import Colors -from qtdialogs.qtdefines import determineWalletType, \ +from qtdialogs.qtdefines import determineWalletType, STRETCH, \ STYLE_RAISED, QRichLabel, tightSizeStr, makeHorizFrame, \ - makeVertFrame, WLTTYPES, tightSizeNChar, STYLE_SUNKEN, MSGBOX -from qtdialogs.qtdialogs import STRETCH + makeVertFrame, WLTTYPES, tightSizeNChar, STYLE_SUNKEN, MSGBOX, \ + createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.QRCodeWidget import QRCodeWidget from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA @@ -70,7 +70,7 @@ def openPaymentRequest(): btnLink.clicked.connect(openPaymentRequest) - tooltip1 = self.main.createToolTipWidget(self.tr( + tooltip1 = createToolTipWidget(self.tr( 'You can securely use this address as many times as you want. ' 'However, all people to whom you give this address will ' 'be able to see the number and amount of bitcoins ever ' diff --git a/qtdialogs/DlgOfflineTx.py b/qtdialogs/DlgOfflineTx.py index d9ff767fc..deed9a1af 100644 --- a/qtdialogs/DlgOfflineTx.py +++ b/qtdialogs/DlgOfflineTx.py @@ -21,11 +21,12 @@ from qtdialogs.qtdefines import QRichLabel, HORIZONTAL, \ STYLE_SUNKEN, GETFONT, STYLE_RAISED, VERTICAL, makeLayoutFrame, \ relaxedSizeNChar, determineWalletType, WLTTYPES, makeHorizFrame, \ - makeVertFrame, STYLE_PLAIN, HLINE, tightSizeNChar -from qtdialogs.qtdialogs import STRETCH + makeVertFrame, STYLE_PLAIN, HLINE, tightSizeNChar, STRETCH, \ + createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog -from ui.TxFramesOffline import SignBroadcastOfflineTxFrame, SignerSerializeTypeSelector +from ui.TxFramesOffline import SignBroadcastOfflineTxFrame, \ + SignerSerializeTypeSelector ################################################################################ class ReviewOfflineTxFrame(ArmoryDialog): @@ -36,14 +37,14 @@ def __init__(self, parent=None, main=None, initLabel=''): self.wlt = None self.lblDescr = QRichLabel('') - ttipDataIsSafe = self.main.createToolTipWidget(\ + ttipDataIsSafe = createToolTipWidget(\ self.tr('There is no security-sensitive information in this data below, so ' 'it is perfectly safe to copy-and-paste it into an ' 'email message, or save it to a borrowed USB key.')) btnSave = QPushButton(self.tr('Save as file...')) btnSave.clicked.connect(self.doSaveFile) - ttipSave = self.main.createToolTipWidget(\ + ttipSave = createToolTipWidget(\ self.tr('Save this data to a USB key or other device, to be transferred to ' 'a computer that contains the private keys for this wallet.')) @@ -52,7 +53,7 @@ def __init__(self, parent=None, main=None, initLabel=''): self.lblCopied = QRichLabel(' ') self.lblCopied.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - ttipCopy = self.main.createToolTipWidget(\ + ttipCopy = createToolTipWidget(\ self.tr('Copy the transaction data to the clipboard, so that it can be ' 'pasted into an email or a text document.')) @@ -191,13 +192,13 @@ def __init__(self, wlt, ustx, parent=None, main=None): doneButton = QPushButton(self.tr('Done')) doneButton.clicked.connect(self.accept) - ttipDone = self.main.createToolTipWidget(self.tr( + ttipDone = createToolTipWidget(self.tr( 'By clicking Done you will exit the offline transaction process for now. ' 'When you are ready to sign and/or broadcast the transaction, click the Offline ' 'Transactions button in the main window, then click the Sign and/or ' 'Broadcast Transaction button in the Select Offline Action dialog.')) - ttipContinue = self.main.createToolTipWidget(self.tr( + ttipContinue = createToolTipWidget(self.tr( 'By clicking Continue you will continue to the next step in the offline ' 'transaction process to sign and/or broadcast the transaction.')) diff --git a/qtdialogs/DlgPasswd3.py b/qtdialogs/DlgPasswd3.py index f34409d43..4d7fd9235 100644 --- a/qtdialogs/DlgPasswd3.py +++ b/qtdialogs/DlgPasswd3.py @@ -10,13 +10,13 @@ # # ############################################################################## -from PySide2.QtWidgets import QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QPushButton +from PySide2.QtWidgets import QDialogButtonBox, QGridLayout, QLabel, \ + QLineEdit, QPushButton from PySide2.QtGui import QPixmap from PySide2.QtCore import Qt, SIGNAL from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.DlgSettings import MIN_PASSWD_WIDTH -from qtdialogs.qtdefines import QRichLabel +from qtdialogs.qtdefines import QRichLabel, MIN_PASSWD_WIDTH class DlgPasswd3(ArmoryDialog): def __init__(self, parent=None, main=None): diff --git a/qtdialogs/DlgProgress.py b/qtdialogs/DlgProgress.py index c06a9f2c5..90ac2c5aa 100644 --- a/qtdialogs/DlgProgress.py +++ b/qtdialogs/DlgProgress.py @@ -17,12 +17,12 @@ from PySide2.QtWidgets import QPushButton, QVBoxLayout, QLabel, \ QProgressBar, QGridLayout -from qtdialogs.qtdialogs import STRETCH +from qtdialogs.qtdefines import STRETCH from qtdialogs.ArmoryDialog import ArmoryDialog from armoryengine.ArmoryUtils import PyBackgroundThread from armoryengine.BDM import BDMPhase_Completed -from ui.QtExecuteSignal import QtExecuteSignal +from ui.QtExecuteSignal import TheSignalExecution ############################################################################### class DlgProgress(ArmoryDialog): @@ -71,7 +71,7 @@ def __init__(self, parent=None, main=None, Interrupt=None, HBar=None, self.btnStop = None if main is not None: - self.main.signalExecution.executeMethod([self.setup, [parent]]) + TheSignalExecution.executeMethod(self.setup, [parent]) else: return @@ -89,7 +89,7 @@ def UpdateDlg(self, text=None, HBar=None, phase=None): if HBar is not None: self.hbarProgress.setValue(HBar) def Kill(self): - self.main.signalExecution.executeMethod([self.Exit, []]) + TheSignalExecution.executeMethod(self.Exit) def Exit(self): self.running = 0 @@ -106,8 +106,7 @@ def exec_(self, *args, **kwargs): exec_thread = PyBackgroundThread(self.exec_async, *args, **kwargs) exec_thread.start() - self.main.signalExecution.executeMethod( - [super(ArmoryDialog, self).exec_, []]) + TheSignalExecution.executeMethod(super(ArmoryDialog, self).exec_) exec_thread.join() if exec_thread.didThrowError(): diff --git a/qtdialogs/DlgRequestPayment.py b/qtdialogs/DlgRequestPayment.py index 9735808a5..6ff04c3af 100644 --- a/qtdialogs/DlgRequestPayment.py +++ b/qtdialogs/DlgRequestPayment.py @@ -51,7 +51,7 @@ def __init__(self, parent, main, recvAddr, amt=None, msg=''): # Link Text: self.edtLinkText = QLineEdit() defaultHex = binary_to_hex('Click here to pay for your order!') - savedHex = self.main.getSettingOrSetDefault('DefaultLinkText', defaultHex) + savedHex = TheSettings.getSettingOrSetDefault('DefaultLinkText', defaultHex) if savedHex.startswith('FFFFFFFF'): # An unfortunate hack until we change our settings storage mechanism # See comment in saveLinkText function for details @@ -233,7 +233,7 @@ def __init__(self, parent, main, recvAddr, amt=None, msg=''): self.callLater(1, self.periodicUpdate) - hexgeom = str(self.main.settings.get('PayReqestGeometry')) + hexgeom = str(self.main.settings.get('PayRequestGeometry')) if len(hexgeom) > 0: geom = QByteArray.fromHex(hexgeom) self.restoreGeometry(geom) @@ -254,12 +254,12 @@ def saveLinkText(self): # mistake it for meaningful data. We remove it upon reading # the value from the settings file. hexText = 'FFFFFFFF'+binary_to_hex(linktext) - self.main.writeSetting('DefaultLinkText', hexText) + TheSettings.set('DefaultLinkText', hexText) ############################################################################# def saveGeometrySettings(self): - self.main.writeSetting('PayReqestGeometry', self.saveGeometry().toHex()) + TheSettings.set('PayRequestGeometry', self.saveGeometry().toHex()) ############################################################################# def closeEvent(self, event): diff --git a/qtdialogs/DlgRestore.py b/qtdialogs/DlgRestore.py new file mode 100644 index 000000000..55120cc62 --- /dev/null +++ b/qtdialogs/DlgRestore.py @@ -0,0 +1,1619 @@ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2023, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## + +from PySide2.QtWidgets import QButtonGroup, QCheckBox, QDialogButtonBox, \ + QFrame, QGridLayout, QLabel, QLayout, QLineEdit, QMessageBox, \ + QPushButton, QRadioButton, QTabWidget, QVBoxLayout + +from armoryengine import BridgeProto_pb2 +from armoryengine.CppBridge import TheBridge +from armoryengine.ArmoryUtils import LOGERROR, UINT32_MAX, UINT8_MAX, \ + UNKNOWN +from armoryengine.BDM import TheBDM +from armoryengine.PyBtcWallet import PyBtcWallet +from ui.QtExecuteSignal import TheSignalExecution + +from qtdialogs.ArmoryDialog import ArmoryDialog +from qtdialogs.DlgChangePassphrase import DlgChangePassphrase +from qtdialogs.DlgReplaceWallet import DlgReplaceWallet +from qtdialogs.MsgBoxCustom import MsgBoxCustom +from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, \ + makeHorizFrame, makeVertFrame, MSGBOX, GETFONT, tightSizeStr, \ + AdvancedOptionsFrame + + +################################################################################ +# Create a special QLineEdit with a masked input +# Forces the cursor to start at position 0 whenever there is no input +class MaskedInputLineEdit(QLineEdit): + def __init__(self, inputMask): + super(MaskedInputLineEdit, self).__init__() + self.setInputMask(inputMask) + fixFont = GETFONT('Fix', 9) + self.setFont(fixFont) + self.setMinimumWidth(tightSizeStr(fixFont, inputMask)[0] + 10) + self.cursorPositionChanged.connect(self.controlCursor) + + def controlCursor(self, oldpos, newpos): + if newpos != 0 and len(str(self.text()).strip()) == 0: + self.setCursorPosition(0) + + +################################################################################ +class DlgRestoreSingle(ArmoryDialog): + ############################################################################# + def __init__(self, parent, main, thisIsATest=False, expectWltID=None): + super(DlgRestoreSingle, self).__init__(parent, main) + + self.newWltID = None + self.callbackId = None + self.thisIsATest = thisIsATest + self.testWltID = expectWltID + headerStr = '' + if thisIsATest: + lblDescr = QRichLabel(self.tr( + 'Test a Paper Backup ' + '

' + 'Use this window to test a single-sheet paper backup. If your ' + 'backup includes imported keys, those will not be covered by this test.')) + else: + lblDescr = QRichLabel(self.tr( + 'Restore a Wallet from Paper Backup ' + '

' + 'Use this window to restore a single-sheet paper backup. ' + 'If your backup includes extra pages with ' + 'imported keys, please restore the base wallet first, then ' + 'double-click the restored wallet and select "Import Private ' + 'Keys" from the right-hand menu.')) + + + lblType = QRichLabel(self.tr('Backup Type:'), doWrap=False) + + self.version135Button = QRadioButton(self.tr('Version 1.35 (4 lines)'), self) + self.version135aButton = QRadioButton(self.tr('Version 1.35a (4 lines Unencrypted)'), self) + self.version135aSPButton = QRadioButton(self.tr(u'Version 1.35a (4 lines + SecurePrint\u200b\u2122)'), self) + self.version135cButton = QRadioButton(self.tr('Version 1.35c (2 lines Unencrypted)'), self) + self.version135cSPButton = QRadioButton(self.tr(u'Version 1.35c (2 lines + SecurePrint\u200b\u2122)'), self) + self.backupTypeButtonGroup = QButtonGroup(self) + self.backupTypeButtonGroup.addButton(self.version135Button) + self.backupTypeButtonGroup.addButton(self.version135aButton) + self.backupTypeButtonGroup.addButton(self.version135aSPButton) + self.backupTypeButtonGroup.addButton(self.version135cButton) + self.backupTypeButtonGroup.addButton(self.version135cSPButton) + self.version135cButton.setChecked(True) + self.backupTypeButtonGroup.buttonClicked.connect(self.changeType) + + layoutRadio = QVBoxLayout() + layoutRadio.addWidget(self.version135Button) + layoutRadio.addWidget(self.version135aButton) + layoutRadio.addWidget(self.version135aSPButton) + layoutRadio.addWidget(self.version135cButton) + layoutRadio.addWidget(self.version135cSPButton) + layoutRadio.setSpacing(0) + + radioButtonFrame = QFrame() + radioButtonFrame.setLayout(layoutRadio) + + frmBackupType = makeVertFrame([lblType, radioButtonFrame]) + + self.lblSP = QRichLabel(self.tr(u'SecurePrint\u200b\u2122 Code:'), doWrap=False) + self.editSecurePrint = QLineEdit() + self.prfxList = [QLabel(self.tr('Root Key:')), QLabel(''), QLabel(self.tr('Chaincode:')), QLabel('')] + + inpMask = '
%s' % errorMsg \ + ), QMessageBox.Ok) + + self.reject() + return + + result, extra = self.processCallbackPayload(payload) + if result == False: + TheBDM.unregisterCustomPrompt(self.callbackId) + + reply = BridgeProto_pb2.RestoreReply() + reply.result = result + + if extra != None: + reply.extra = bytes(extra, 'utf-8') + + TheBridge.callbackFollowUp(reply, self.callbackId, callerId) + + ############################################################################# + def processCallbackPayload(self, payload): + msg = BridgeProto_pb2.RestorePrompt() + msg.ParseFromString(payload) + + if msg.promptType == BridgeProto_pb2.RestorePromptType.Value("Id") or \ + msg.promptType == BridgeProto_pb2.RestorePromptType.Value("ChecksumError"): + #check the id generated by this backup + + newWltID = msg.extra + if len(newWltID) > 0: + if self.thisIsATest: + # Stop here if this was just a test + verifyRecoveryTestID(self, newWltID, self.testWltID) + + #return false to caller to end the restore process + return False, None + + # return result of id comparison + dlgOwnWlt = None + if newWltID in self.main.walletMap: + dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) + + if (dlgOwnWlt.exec_()): + #TODO: deal with replacement code + if dlgOwnWlt.output == 0: + return False, None + else: + return False, None + else: + reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), \ + self.tr('The data you entered corresponds to a wallet with a wallet ID: \n\n' + '%s\n\nDoes this ID match the "Wallet Unique ID" ' + 'printed on your paper backup? If not, click "No" and reenter ' + 'key and chain-code data again.' % newWltID), \ + QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.Yes: + #return true to caller to proceed with restore operation + self.newWltID = newWltID + return True, None + + #reconstructed wallet id is invalid if we get this far + lineNumber = -1 + canBeSalvaged = True + if len(msg.checksums) != self.lineCount: + canBeSalvaged = False + + for i in range(0, len(msg.checksums)): + if msg.checksums[i] < 0 or msg.checksums[i] == UINT8_MAX: + lineNumber = i + 1 + break + + if lineNumber == -1 or canBeSalvaged == False: + QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( + 'Encountered an unkonwn error when restoring this backup. Aborting.'), \ + QMessageBox.Ok) + + self.reject() + return False, None + + reply = QMessageBox.critical(self, self.tr('Invalid Data'), self.tr( + 'There is an error in the data you entered that could not be ' + 'fixed automatically. Please double-check that you entered the ' + 'text exactly as it appears on the wallet-backup page.

' + 'The error occured on line #%d.' % lineNumber), \ + QMessageBox.Ok) + LOGERROR('Error in wallet restore field') + self.prfxList[i].setText(\ + '' + str(self.prfxList[i].text()) + '') + + return False, None + + if msg.promptType == BridgeProto_pb2.RestorePromptType.Value("Passphrase"): + #return new wallet's private keys password + passwd = [] + if self.chkEncrypt.isChecked(): + dlgPasswd = DlgChangePassphrase(self, self.main) + if dlgPasswd.exec_(): + passwd = str(dlgPasswd.edtPasswd1.text()) + return True, passwd + else: + QMessageBox.critical(self, self.tr('Cannot Encrypt'), \ + self.tr('You requested your restored wallet be encrypted, but no ' + 'valid passphrase was supplied. Aborting wallet recovery.'), \ + QMessageBox.Ok) + self.reject() + return False, None + + if msg.promptType == BridgeProto_pb2.RestorePromptType.Value("Control"): + #TODO: need UI to input control passphrase + return True, None + + if msg.promptType == BridgeProto_pb2.RestorePromptType.Value("Success"): + if self.newWltID == None or len(self.newWltID) == 0: + LOGERROR("wallet import did not yield an id") + raise Exception("wallet import did not yield an id") + + self.newWallet = PyBtcWallet() + self.newWallet.loadFromBridge(self.newWltID) + self.accept() + + return True, None + + if msg.promptType == BridgeProto_pb2.RestorePromptType.Value("FormatError") or \ + sg.promptType == BridgeProto_pb2.RestorePromptType.Value("Failure"): + + QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( + 'Encountered an unkonwn error when restoring this backup. Aborting.', \ + QMessageBox.Ok)) + + self.reject() + return False, None + + if msg.promptType == BridgeProto_pb2.RestorePromptType.Value("DecryptError"): + #TODO: notify of invalid SP pass + pass + + if msg.promptType == BridgeProto_pb2.RestorePromptType.Value("TypeError"): + #TODO: wallet type conveyed by backup is unknown + pass + + else: + #TODO: unknown error + return False, None + + + ############################################################################# + def verifyUserInput(self): + + root = [] + for i in range(2): + root.append(str(self.edtList[i].text())) + + chaincode = [] + if self.isLongForm: + for i in range(2): + chaincode.append(str(self.edtList[i+2].text())) + + self.lineCount = len(root) + len(chaincode) + + spPass = "" + if self.doMask: + #add secureprint passphrase if this backup is encrypted + spPass = str(self.editSecurePrint.text()).strip() + + ''' + verifyBackupString is a method that will trigger multiple callbacks + during the course of its execution. Unlike a password request callback + which only requires to generate a dedicated dialog to retrieve passwords + from users, verifyBackupString set of notifications is complex and comes + with branches. + + A dedicated callbackId is generated for this interaction and passed to + TheBDM callback map along with a py side method to handle the protobuf + packet from the C++ side. + + The C++ method is called with that id. + ''' + def callback(payload, callerId): + TheSignalExecution.executeMethod(self.processCallback, + [payload, callerId]) + + self.callbackId = TheBDM.registerCustomPrompt(callback) + TheBridge.restoreWallet(root, chaincode, spPass, self.callbackId) + + ''' + if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: + QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ + self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) + return + if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfBytes() == -1: + QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ + self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) + return + if nError > 0: + pluralStr = 'error' if nError == 1 else 'errors' + + msg = self.tr( + 'Detected errors in the data you entered. ' + 'Armory attempted to fix the errors but it is not ' + 'always right. Be sure to verify the "Wallet Unique ID" ' + 'closely on the next window.') + + QMessageBox.question(self, self.tr('Errors Corrected'), msg, \ + QMessageBox.Ok) + ''' + + +################################################################################ +class DlgRestoreFragged(ArmoryDialog): + def __init__(self, parent, main, thisIsATest=False, expectWltID=None): + super(DlgRestoreFragged, self).__init__(parent, main) + + self.thisIsATest = thisIsATest + self.testWltID = expectWltID + headerStr = '' + if thisIsATest: + headerStr = self.tr('Testing a ' + 'Fragmented Backup') + else: + headerStr = self.tr('Restore Wallet from Fragments') + + descr = self.trUtf8( + '%s

' + 'Use this form to enter all the fragments to be restored. Fragments ' + 'can be stored on a mix of paper printouts, and saved files. ' + u'If any of the fragments require a SecurePrint\u200b\u2122 code, ' + 'you will only have to enter it once, since that code is the same for ' + 'all fragments of any given wallet.' % headerStr) + + if self.thisIsATest: + descr += self.tr('

' + 'For testing purposes, you may enter more fragments than needed ' + 'and Armory will test all subsets of the entered fragments to verify ' + 'that each one still recovers the wallet successfully.') + + lblDescr = QRichLabel(descr) + + frmDescr = makeHorizFrame([lblDescr], STYLE_RAISED) + + # HLINE + + self.scrollFragInput = QScrollArea() + self.scrollFragInput.setWidgetResizable(True) + self.scrollFragInput.setMinimumHeight(150) + + lblFragList = QRichLabel(self.tr('Input Fragments Below:'), doWrap=False, bold=True) + self.btnAddFrag = QPushButton(self.tr('+Frag')) + self.btnRmFrag = QPushButton(self.tr('-Frag')) + self.btnRmFrag.setVisible(False) + self.connect(self.btnAddFrag, SIGNAL(CLICKED), self.addFragment) + self.connect(self.btnRmFrag, SIGNAL(CLICKED), self.removeFragment) + self.chkEncrypt = QCheckBox(self.tr('Encrypt Restored Wallet')) + self.chkEncrypt.setChecked(True) + frmAddRm = makeHorizFrame([self.chkEncrypt, STRETCH, self.btnRmFrag, self.btnAddFrag]) + + self.fragDataMap = {} + self.tableSize = 2 + self.wltType = UNKNOWN + self.fragIDPrefix = UNKNOWN + + doItText = self.tr('Test Backup') if thisIsATest else self.tr('Restore from Fragments') + + btnExit = QPushButton(self.tr('Cancel')) + self.btnRestore = QPushButton(doItText) + self.connect(btnExit, SIGNAL(CLICKED), self.reject) + self.connect(self.btnRestore, SIGNAL(CLICKED), self.processFrags) + frmBtns = makeHorizFrame([btnExit, STRETCH, self.btnRestore]) + + self.lblRightFrm = QRichLabel('', hAlign=Qt.AlignHCenter) + self.lblSecureStr = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code:'), \ + hAlign=Qt.AlignHCenter, + doWrap=False, + color='TextWarn') + self.displaySecureString = QLineEdit() + self.imgPie = QRichLabel('', hAlign=Qt.AlignHCenter) + self.imgPie.setMinimumWidth(96) + self.imgPie.setMinimumHeight(96) + self.lblReqd = QRichLabel('', hAlign=Qt.AlignHCenter) + self.lblWltID = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) + self.lblFragID = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) + self.lblSecureStr.setVisible(False) + self.displaySecureString.setVisible(False) + self.displaySecureString.setMaximumWidth(relaxedSizeNChar(self.displaySecureString, 16)[0]) + # The Secure String is now edited in DlgEnterOneFrag, It is only displayed here + self.displaySecureString.setEnabled(False) + frmSecPair = makeVertFrame([self.lblSecureStr, self.displaySecureString]) + frmSecCtr = makeHorizFrame([STRETCH, frmSecPair, STRETCH]) + + frmWltInfo = makeVertFrame([STRETCH, + self.lblRightFrm, + self.imgPie, + self.lblReqd, + self.lblWltID, + self.lblFragID, + HLINE(), + frmSecCtr, + 'Strut(200)', + STRETCH], STYLE_SUNKEN) + + + fragmentsLayout = QGridLayout() + fragmentsLayout.addWidget(frmDescr, 0, 0, 1, 2) + fragmentsLayout.addWidget(frmAddRm, 1, 0, 1, 1) + fragmentsLayout.addWidget(self.scrollFragInput, 2, 0, 1, 1) + fragmentsLayout.addWidget(frmWltInfo, 1, 1, 2, 1) + setLayoutStretchCols(fragmentsLayout, 1, 0) + + walletRestoreTabs = QTabWidget() + fragmentsFrame = QFrame() + fragmentsFrame.setLayout(fragmentsLayout) + walletRestoreTabs.addTab(fragmentsFrame, self.tr("Fragments")) + self.advancedOptionsTab = AdvancedOptionsFrame(parent, main) + walletRestoreTabs.addTab(self.advancedOptionsTab, self.tr("Advanced Options")) + + self.chkEncrypt.setChecked(not thisIsATest) + self.chkEncrypt.setVisible(not thisIsATest) + self.advancedOptionsTab.setEnabled(not thisIsATest) + if not thisIsATest: + self.connect(self.chkEncrypt, SIGNAL(CLICKED), self.onEncryptCheckboxChange) + + layout = QVBoxLayout() + layout.addWidget(walletRestoreTabs) + layout.addWidget(frmBtns) + self.setLayout(layout) + self.setMinimumWidth(650) + self.setMinimumHeight(500) + self.sizeHint = lambda: QSize(800, 650) + self.setWindowTitle(self.tr('Restore wallet from fragments')) + + self.makeFragInputTable() + self.checkRestoreParams() + + ############################################################################# + # Hide advanced options whenver the restored wallet is unencrypted + def onEncryptCheckboxChange(self): + self.advancedOptionsTab.setEnabled(self.chkEncrypt.isChecked()) + + def makeFragInputTable(self, addCount=0): + + self.tableSize += addCount + newLayout = QGridLayout() + newFrame = QFrame() + self.fragsDone = [] + newLayout.addWidget(HLINE(), 0, 0, 1, 5) + for i in range(self.tableSize): + btnEnter = QPushButton(self.tr('Type Data')) + btnLoad = QPushButton(self.tr('Load File')) + btnClear = QPushButton(self.tr('Clear')) + lblFragID = QRichLabel('', doWrap=False) + lblSecure = QLabel('') + if i in self.fragDataMap: + M, fnum, wltID, doMask, fid = ReadFragIDLineBin(self.fragDataMap[i][0]) + self.fragsDone.append(fnum) + lblFragID.setText('' + fid + '') + if doMask: + lblFragID.setText('' + fid + '', color='TextWarn') + + + self.connect(btnEnter, SIGNAL(CLICKED), \ + functools.partial(self.dataEnter, fnum=i)) + self.connect(btnLoad, SIGNAL(CLICKED), \ + functools.partial(self.dataLoad, fnum=i)) + self.connect(btnClear, SIGNAL(CLICKED), \ + functools.partial(self.dataClear, fnum=i)) + + + newLayout.addWidget(btnEnter, 2 * i + 1, 0) + newLayout.addWidget(btnLoad, 2 * i + 1, 1) + newLayout.addWidget(btnClear, 2 * i + 1, 2) + newLayout.addWidget(lblFragID, 2 * i + 1, 3) + newLayout.addWidget(lblSecure, 2 * i + 1, 4) + newLayout.addWidget(HLINE(), 2 * i + 2, 0, 1, 5) + + btnFrame = QFrame() + btnFrame.setLayout(newLayout) + + frmFinal = makeVertFrame([btnFrame, STRETCH], STYLE_SUNKEN) + self.scrollFragInput.setWidget(frmFinal) + + self.btnAddFrag.setVisible(self.tableSize < 12) + self.btnRmFrag.setVisible(self.tableSize > 2) + + + ############################################################################# + def addFragment(self): + self.makeFragInputTable(1) + + ############################################################################# + def removeFragment(self): + self.makeFragInputTable(-1) + toRemove = [] + for key, val in self.fragDataMap.iteritems(): + if key >= self.tableSize: + toRemove.append(key) + + # Have to do this in a separate loop, cause you can't remove items + # from a map while you are iterating over them + for key in toRemove: + self.dataClear(key) + + + ############################################################################# + def dataEnter(self, fnum): + dlg = DlgEnterOneFrag(self, self.main, self.fragsDone, self.wltType, self.displaySecureString.text()) + if dlg.exec_(): + LOGINFO('Good data from enter_one_frag exec! %d', fnum) + self.displaySecureString.setText(dlg.editSecurePrint.text()) + self.addFragToTable(fnum, dlg.fragData) + self.makeFragInputTable() + + + ############################################################################# + def dataLoad(self, fnum): + LOGINFO('Loading data for entry, %d', fnum) + toLoad = str(self.main.getFileLoad('Load Fragment File', \ + ['Wallet Fragments (*.frag)'])) + + if len(toLoad) == 0: + return + + if not os.path.exists(toLoad): + LOGERROR('File just chosen does not exist! %s', toLoad) + QMessageBox.critical(self, self.tr('File Does Not Exist'), self.tr( + 'The file you select somehow does not exist...? ' + '

%s

Try a different file' % toLoad), \ + QMessageBox.Ok) + + fragMap = {} + with open(toLoad, 'r') as fin: + allData = [line.strip() for line in fin.readlines()] + fragMap = {} + for line in allData: + if line[:2].lower() in ['id', 'x1', 'x2', 'x3', 'x4', \ + 'y1', 'y2', 'y3', 'y4', \ + 'f1', 'f2', 'f3', 'f4']: + fragMap[line[:2].lower()] = line[3:].strip().replace(' ', '') + + + cList, nList = [], [] + if len(fragMap) == 9: + cList, nList = ['x', 'y'], ['1', '2', '3', '4'] + elif len(fragMap) == 5: + cList, nList = ['f'], ['1', '2', '3', '4'] + elif len(fragMap) == 3: + cList, nList = ['f'], ['1', '2'] + else: + LOGERROR('Unexpected number of lines in the frag file, %d', len(fragMap)) + return + + fragData = [] + fragData.append(hex_to_binary(fragMap['id'])) + for c in cList: + for n in nList: + mapKey = c + n + rawBin, err = readSixteenEasyBytes(fragMap[c + n]) + if err == 'Error_2+': + QMessageBox.critical(self, self.tr('Fragment Error'), self.tr( + 'There was an unfixable error in the fragment file: ' + '

File: %s
Line: %s
' % (toLoad, mapKey)), \ + QMessageBox.Ok) + return + #fragData.append(SecureBinaryData(rawBin)) + rawBin = None + + self.addFragToTable(fnum, fragData) + self.makeFragInputTable() + + + ############################################################################# + def dataClear(self, fnum): + if not fnum in self.fragDataMap: + return + + for i in range(1, 3): + self.fragDataMap[fnum][i].destroy() + del self.fragDataMap[fnum] + self.makeFragInputTable() + self.checkRestoreParams() + + + ############################################################################# + def checkRestoreParams(self): + showRightFrm = False + self.btnRestore.setEnabled(False) + self.lblRightFrm.setText(self.tr( + 'Start entering fragments into the table to left...')) + for row, data in self.fragDataMap.iteritems(): + showRightFrm = True + M, fnum, setIDBin, doMask, idBase58 = ReadFragIDLineBin(data[0]) + self.lblRightFrm.setText(self.tr('Wallet Being Restored:')) + self.imgPie.setPixmap(QPixmap('./img/frag%df.png' % M).scaled(96,96)) + self.lblReqd.setText(self.tr('Frags Needed: %s' % M)) + self.lblFragID.setText(self.tr('Fragments: %s' % idBase58.split('-')[0])) + self.btnRestore.setEnabled(len(self.fragDataMap) >= M) + break + + anyMask = False + for row, data in self.fragDataMap.iteritems(): + M, fnum, wltIDBin, doMask, idBase58 = ReadFragIDLineBin(data[0]) + if doMask: + anyMask = True + break + # If all of the rows with a Mask have been removed clear the securePrintCode + if not anyMask: + self.displaySecureString.setText('') + self.lblSecureStr.setVisible(anyMask) + self.displaySecureString.setVisible(anyMask) + + if not showRightFrm: + self.fragIDPrefix = UNKNOWN + self.wltType = UNKNOWN + + self.imgPie.setVisible(showRightFrm) + self.lblReqd.setVisible(showRightFrm) + self.lblWltID.setVisible(showRightFrm) + self.lblFragID.setVisible(showRightFrm) + + + ############################################################################# + def addFragToTable(self, tableIndex, fragData): + + if len(fragData) == 9: + currType = '0' + elif len(fragData) == 5: + currType = BACKUP_TYPE_135A + elif len(fragData) == 3: + currType = BACKUP_TYPE_135C + else: + LOGERROR('How\'d we get fragData of size: %d', len(fragData)) + return + + if self.wltType == UNKNOWN: + self.wltType = currType + elif not self.wltType == currType: + QMessageBox.critical(self, self.tr('Mixed fragment types'), self.tr( + 'You entered a fragment for a different wallet type. Please check ' + 'that all fragments are for the same wallet, of the same version, ' + 'and require the same number of fragments.'), QMessageBox.Ok) + LOGERROR('Mixing frag types! How did that happen?') + return + + + M, fnum, wltIDBin, doMask, idBase58 = ReadFragIDLineBin(fragData[0]) + # If we don't know the Secure String Yet we have to get it + if doMask and len(str(self.displaySecureString.text()).strip()) == 0: + dlg = DlgEnterSecurePrintCode(self, self.main) + if dlg.exec_(): + self.displaySecureString.setText(dlg.editSecurePrint.text()) + else: + return + + if self.fragIDPrefix == UNKNOWN: + self.fragIDPrefix = idBase58.split('-')[0] + elif not self.fragIDPrefix == idBase58.split('-')[0]: + QMessageBox.critical(self, self.tr('Multiple Wallets'), self.tr( + 'The fragment you just entered is actually for a different wallet ' + 'than the previous fragments you entered. Please double-check that ' + 'all the fragments you are entering belong to the same wallet and ' + 'have the "number of needed fragments" (M-value, in M-of-N).'), \ + QMessageBox.Ok) + LOGERROR('Mixing fragments of different wallets! %s', idBase58) + return + + + if not self.verifyNonDuplicateFrag(fnum): + QMessageBox.critical(self, self.tr('Duplicate Fragment'), self.tr( + 'You just input fragment #%s, but that fragment has already been ' + 'entered!' % fnum), QMessageBox.Ok) + return + + #if currType == '0': + # X = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 5)])) + # Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(5, 9)])) + #elif currType == BACKUP_TYPE_135A: + # X = SecureBinaryData(int_to_binary(fnum + 1, widthBytes=64, endOut=BIGENDIAN)) + # Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 5)])) + #elif currType == BACKUP_TYPE_135C: + # X = SecureBinaryData(int_to_binary(fnum + 1, widthBytes=32, endOut=BIGENDIAN)) + # Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 3)])) + + self.fragDataMap[tableIndex] = [fragData[0][:], X.copy(), Y.copy()] + + X.destroy() + Y.destroy() + self.checkRestoreParams() + + ############################################################################# + def verifyNonDuplicateFrag(self, fnum): + for row, data in self.fragDataMap.iteritems(): + rowFrag = ReadFragIDLineBin(data[0])[1] + if fnum == rowFrag: + return False + + return True + + ############################################################################# + def processFrags(self): + if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: + QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ + self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) + return + if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfBytes() == -1: + QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ + self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) + return + SECPRINT = HardcodedKeyMaskParams() + pwd, ekey = '', '' + if self.displaySecureString.isVisible(): + pwd = str(self.displaySecureString.text()).strip() + maskKey = SECPRINT['FUNC_KDF'](pwd) + + fragMtrx, M = [], -1 + for row, trip in self.fragDataMap.iteritems(): + M, fnum, wltID, doMask, fid = ReadFragIDLineBin(trip[0]) + X, Y = trip[1], trip[2] + if doMask: + LOGINFO('Row %d needs unmasking' % row) + Y = SECPRINT['FUNC_UNMASK'](Y, ekey=maskKey) + else: + LOGINFO('Row %d is already unencrypted' % row) + fragMtrx.append([X.toBinStr(), Y.toBinStr()]) + + typeToBytes = {'0': 64, BACKUP_TYPE_135A: 64, BACKUP_TYPE_135C: 32} + nBytes = typeToBytes[self.wltType] + + + if self.thisIsATest and len(fragMtrx) > M: + self.testFragSubsets(fragMtrx, M) + return + + + SECRET = ReconstructSecret(fragMtrx, M, nBytes) + for i in range(len(fragMtrx)): + fragMtrx[i] = [] + + LOGINFO('Final length of frag mtrx: %d', len(fragMtrx)) + LOGINFO('Final length of secret: %d', len(SECRET)) + + priv, chain = '', '' + #if len(SECRET) == 64: + #priv = SecureBinaryData(SECRET[:32 ]) + #chain = SecureBinaryData(SECRET[ 32:]) + #elif len(SECRET) == 32: + #priv = SecureBinaryData(SECRET) + #chain = DeriveChaincodeFromRootKey(priv) + + + # If we got here, the data is valid, let's create the wallet and accept the dlg + # Now we should have a fully-plaintext rootkey and chaincode + root = PyBtcAddress().createFromPlainKeyData(priv) + root.chaincode = chain + + first = root.extendAddressChain() + newWltID = binary_to_base58((ADDRBYTE + first.getAddr160()[:5])[::-1]) + + # If this is a test, then bail + if self.thisIsATest: + verifyRecoveryTestID(self, newWltID, self.testWltID) + return + + dlgOwnWlt = None + if newWltID in self.main.walletMap: + dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) + + if (dlgOwnWlt.exec_()): + if dlgOwnWlt.output == 0: + return + else: + self.reject() + return + + reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), self.tr( + 'The data you entered corresponds to a wallet with the ' + 'ID:
{%s}
Does this ID ' + 'match the "Wallet Unique ID" printed on your paper backup? ' + 'If not, click "No" and reenter key and chain-code data ' + 'again.' % newWltID), QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.No: + return + + + passwd = [] + if self.chkEncrypt.isChecked(): + dlgPasswd = DlgChangePassphrase(self, self.main) + #if dlgPasswd.exec_(): + #passwd = SecureBinaryData(str(dlgPasswd.edtPasswd1.text())) + #else: + #QMessageBox.critical(self, self.tr('Cannot Encrypt'), self.tr( + #'You requested your restored wallet be encrypted, but no ' + #'valid passphrase was supplied. Aborting wallet ' + #'recovery.'), QMessageBox.Ok) + #return + + shortl = '' + longl = '' + nPool = 1000 + + if dlgOwnWlt is not None: + if dlgOwnWlt.Meta is not None: + shortl = ' - %s' % (dlgOwnWlt.Meta['shortLabel']) + longl = dlgOwnWlt.Meta['longLabel'] + nPool = max(nPool, dlgOwnWlt.Meta['naddress']) + + if passwd: + self.newWallet = PyBtcWallet().createNewWallet(\ + plainRootKey=priv, \ + chaincode=chain, \ + shortLabel='Restored - ' + newWltID + shortl, \ + longLabel=longl, \ + withEncrypt=True, \ + securePassphrase=passwd, \ + kdfTargSec=self.advancedOptionsTab.getKdfSec(), \ + kdfMaxMem=self.advancedOptionsTab.getKdfBytes(), + isActuallyNew=False, \ + doRegisterWithBDM=False) + else: + self.newWallet = PyBtcWallet().createNewWallet(\ + plainRootKey=priv, \ + chaincode=chain, \ + shortLabel='Restored - ' + newWltID +shortl, \ + longLabel=longl, \ + withEncrypt=False, \ + isActuallyNew=False, \ + doRegisterWithBDM=False) + + + # Will pop up a little "please wait..." window while filling addr pool + fillAddrPoolProgress = DlgProgress(self, self.parent, HBar=1, + Title=self.tr("Computing New Addresses")) + fillAddrPoolProgress.exec_(self.newWallet.fillAddressPool, nPool) + + if dlgOwnWlt is not None: + if dlgOwnWlt.Meta is not None: + from armoryengine.PyBtcWallet import WLT_UPDATE_ADD + for n_cmt in range(0, dlgOwnWlt.Meta['ncomments']): + entrylist = [] + entrylist = list(dlgOwnWlt.Meta[n_cmt]) + self.newWallet.walletFileSafeUpdate([[WLT_UPDATE_ADD, entrylist[2], entrylist[1], entrylist[0]]]) + + self.newWallet = PyBtcWallet().readWalletFile(self.newWallet.walletPath) + self.accept() + + ############################################################################# + def testFragSubsets(self, fragMtrx, M): + # If the user entered multiple fragments + fragMap = {} + for x, y in fragMtrx: + fragMap[binary_to_int(x, BIGENDIAN) - 1] = [x, y] + typeToBytes = {'0': 64, BACKUP_TYPE_135A: 64, BACKUP_TYPE_135C: 32} + + isRandom, results = testReconstructSecrets(fragMap, M, 100) + def privAndChainFromRow(secret): + priv, chain = None, None + #if len(secret) == 64: + #priv = SecureBinaryData(secret[:32 ]) + #chain = SecureBinaryData(secret[ 32:]) + #return (priv, chain) + #elif len(secret) == 32: + #priv = SecureBinaryData(secret) + #chain = DeriveChaincodeFromRootKey(priv) + #return (priv, chain) + #else: + #LOGERROR('Root secret is %s bytes ?!' % len(secret)) + #raise KeyDataError + + results = [(row[0], privAndChainFromRow(row[1])) for row in results] + subsAndIDs = [(row[0], calcWalletIDFromRoot(*row[1])) for row in results] + + DlgShowTestResults(self, isRandom, subsAndIDs, \ + M, len(fragMtrx), self.testWltID).exec_() + + +################################################################################ +class DlgEnterOneFrag(ArmoryDialog): + + def __init__(self, parent, main, fragList=[], wltType=UNKNOWN, securePrintCode=None): + super(DlgEnterOneFrag, self).__init__(parent, main) + self.fragData = [] + BLUE = htmlColor('TextBlue') + already = '' + if len(fragList) > 0: + strList = ['%d' % (BLUE, f) for f in fragList] + replStr = '[' + ','.join(strList[:]) + ']' + already = self.tr('You have entered fragments %s, so far.' % replStr) + + lblDescr = QRichLabel(self.tr( + 'Enter Another Fragment...

%s ' + 'The fragments can be entered in any order, as long as you provide ' + 'enough of them to restore the wallet. If any fragments use a ' + u'SecurePrint\u200b\u2122 code, please enter it once on the ' + 'previous window, and it will be applied to all fragments that ' + 'require it.' % already)) + + self.version0Button = QRadioButton(self.tr( BACKUP_TYPE_0_TEXT), self) + self.version135aButton = QRadioButton(self.tr( BACKUP_TYPE_135a_TEXT), self) + self.version135aSPButton = QRadioButton(self.tr( BACKUP_TYPE_135a_SP_TEXT), self) + self.version135cButton = QRadioButton(self.tr( BACKUP_TYPE_135c_TEXT), self) + self.version135cSPButton = QRadioButton(self.tr( BACKUP_TYPE_135c_SP_TEXT), self) + self.backupTypeButtonGroup = QButtonGroup(self) + self.backupTypeButtonGroup.addButton(self.version0Button) + self.backupTypeButtonGroup.addButton(self.version135aButton) + self.backupTypeButtonGroup.addButton(self.version135aSPButton) + self.backupTypeButtonGroup.addButton(self.version135cButton) + self.backupTypeButtonGroup.addButton(self.version135cSPButton) + self.version135cButton.setChecked(True) + self.connect(self.backupTypeButtonGroup, SIGNAL('buttonClicked(int)'), self.changeType) + + # This value will be locked after the first fragment is entered. + if wltType == UNKNOWN: + self.version135cButton.setChecked(True) + elif wltType == '0': + self.version0Button.setChecked(True) + self.version135aButton.setEnabled(False) + self.version135aSPButton.setEnabled(False) + self.version135cButton.setEnabled(False) + self.version135cSPButton.setEnabled(False) + elif wltType == BACKUP_TYPE_135A: + # Could be 1.35a with or without SecurePrintCode so remove the rest + self.version0Button.setEnabled(False) + self.version135cButton.setEnabled(False) + self.version135cSPButton.setEnabled(False) + if securePrintCode: + self.version135aSPButton.setChecked(True) + else: + self.version135aButton.setChecked(True) + elif wltType == BACKUP_TYPE_135C: + # Could be 1.35c with or without SecurePrintCode so remove the rest + self.version0Button.setEnabled(False) + self.version135aButton.setEnabled(False) + self.version135aSPButton.setEnabled(False) + if securePrintCode: + self.version135cSPButton.setChecked(True) + else: + self.version135cButton.setChecked(True) + + lblType = QRichLabel(self.tr('Backup Type:'), doWrap=False) + + layoutRadio = QVBoxLayout() + layoutRadio.addWidget(self.version0Button) + layoutRadio.addWidget(self.version135aButton) + layoutRadio.addWidget(self.version135aSPButton) + layoutRadio.addWidget(self.version135cButton) + layoutRadio.addWidget(self.version135cSPButton) + layoutRadio.setSpacing(0) + + radioButtonFrame = QFrame() + radioButtonFrame.setLayout(layoutRadio) + + frmBackupType = makeVertFrame([lblType, radioButtonFrame]) + + self.prfxList = ['x1:', 'x2:', 'x3:', 'x4:', \ + 'y1:', 'y2:', 'y3:', 'y4:', \ + 'F1:', 'F2:', 'F3:', 'F4:'] + self.prfxList = [QLabel(p) for p in self.prfxList] + inpMask = ' 127 + + ############################################################################# + def verifyUserInput(self): + self.fragData = [] + nError = 0 + rawBin = None + + sel = self.backupTypeButtonGroup.checkedId() + rng = [-1] + if sel == self.backupTypeButtonGroup.id(self.version0Button): + rng = range(8) + elif sel == self.backupTypeButtonGroup.id(self.version135aButton) or \ + sel == self.backupTypeButtonGroup.id(self.version135aSPButton): + rng = range(8, 12) + elif sel == self.backupTypeButtonGroup.id(self.version135cButton) or \ + sel == self.backupTypeButtonGroup.id(self.version135cSPButton): + rng = range(8, 10) + + + if sel == self.backupTypeButtonGroup.id(self.version135aSPButton) or \ + sel == self.backupTypeButtonGroup.id(self.version135cSPButton): + # Prepare the key mask parameters + SECPRINT = HardcodedKeyMaskParams() + securePrintCode = str(self.editSecurePrint.text()).strip() + if not checkSecurePrintCode(self, SECPRINT, securePrintCode): + return + elif self.isSecurePrintID(): + QMessageBox.critical(self, 'Bad Encryption Code', self.tr( + 'The ID field indicates that this is a SecurePrintâ„¢ ' + 'Backup Type. You have either entered the ID incorrectly or ' + 'have chosen an incorrect Backup Type.'), QMessageBox.Ok) + return + for i in rng: + hasError = False + try: + rawEntry = str(self.edtList[i].text()) + rawBin, err = readSixteenEasyBytes(rawEntry.replace(' ', '')) + if err == 'Error_2+': + hasError = True + elif err == 'Fixed_1': + nError += 1 + except KeyError: + hasError = True + + if hasError: + reply = QMessageBox.critical(self, self.tr('Verify Wallet ID'), self.tr( + 'There is an error in the data you entered that could not be ' + 'fixed automatically. Please double-check that you entered the ' + 'text exactly as it appears on the wallet-backup page.

' + 'The error occured on the "%s" line.' % str(self.prfxList[i].text())), QMessageBox.Ok) + LOGERROR('Error in wallet restore field') + self.prfxList[i].setText('' + str(self.prfxList[i].text()) + '') + self.destroyFragData() + return + + self.fragData.append(SecureBinaryData(rawBin)) + rawBin = None + + + idLine = str(self.edtID.text()).replace(' ', '') + self.fragData.insert(0, hex_to_binary(idLine)) + + M, fnum, wltID, doMask, fid = ReadFragIDLineBin(self.fragData[0]) + + reply = QMessageBox.question(self, self.tr('Verify Fragment ID'), self.tr( + 'The data you entered is for fragment: ' + '

%s

' + 'Does this ID match the "Fragment:" field displayed on your backup? ' + 'If not, click "No" and re-enter the fragment data.' % (htmlColor('TextBlue'), fid)), QMessageBox.Yes | QMessageBox.No) + + if reply == QMessageBox.Yes: + self.accept() + + +################################################################################ +class DlgRestoreWOData(ArmoryDialog): + ############################################################################# + def __init__(self, parent, main, thisIsATest=False, expectWltID=None): + super(DlgRestoreWOData, self).__init__(parent, main) + + self.thisIsATest = thisIsATest + self.testWltID = expectWltID + headerStr = '' + lblDescr = '' + + # Write the text at the top of the window. + if thisIsATest: + lblDescr = QRichLabel(self.tr( + 'Test a Watch-Only Wallet Restore ' + '

' + 'Use this window to test the restoration of a watch-only wallet using ' + 'the wallet\'s data. You can either type the data on a root data ' + 'printout or import the data from a file.')) + else: + lblDescr = QRichLabel(self.tr( + 'Restore a Watch-Only Wallet ' + '

' + 'Use this window to restore a watch-only wallet using the wallet\'s ' + 'data. You can either type the data on a root data printout or import ' + 'the data from a file.')) + + # Create the line that will contain the imported ID. + self.rootIDLabel = QRichLabel(self.tr('Watch-Only Root ID:'), doWrap=False) + inpMask = '
'), + QMessageBox.Ok) + LOGERROR('Error in root ID restore field') + LOGERROR('Error Type: %s', errType) + LOGERROR('Error Value: %s', errVal) + return + + # Save the version/key byte and the root ID. For now, ignore the version. + inRootVer = inRootChecked[0] # 1 byte + inRootID = inRootChecked[1:7] # 6 bytes + + # Read in the root data (public key & chain code) and handle any errors. + for i in range(nLine): + hasError = False + try: + rawEntry = str(self.pkccList[i].text()) + rawBin, err = readSixteenEasyBytes(rawEntry.replace(' ', '')) + if err == 'Error_2+': # 2+ bytes are wrong, so we need to stop. + hasError = True + elif err == 'Fixed_1': # 1 byte is wrong, so we may be okay. + nError += 1 + except: + hasError = True + + # If the root ID is busted, stop. + if hasError: + lineNumber = i+1 + reply = QMessageBox.critical(self, self.tr('Invalid Data'), self.tr( + 'There is an error in the root data you entered that could not be ' + 'fixed automatically. Please double-check that you entered the ' + 'text exactly as it appears on the wallet-backup page.

' + 'The error occured on line #%d.' % lineNumber), QMessageBox.Ok) + LOGERROR('Error in root data restore field') + return + + # If we've gotten this far, save the incoming line. + inputLines.append(rawBin) + + # Set up the root ID data. + pkVer = binary_to_int(inRootVer) & PYROOTPKCCVERMASK # Ignored for now. + pkSignByte = ((binary_to_int(inRootVer) & PYROOTPKCCSIGNMASK) >> 7) + 2 + rootPKComBin = int_to_binary(pkSignByte) + ''.join(inputLines[:2]) + rootPubKey = CryptoECDSA().UncompressPoint(SecureBinaryData(rootPKComBin)) + rootChainCode = SecureBinaryData(''.join(inputLines[2:])) + + # Now we should have a fully-plaintext root key and chain code, and can + # get some related data. + root = PyBtcAddress().createFromPublicKeyData(rootPubKey) + root.chaincode = rootChainCode + first = root.extendAddressChain() + newWltID = binary_to_base58(inRootID) + + # Stop here if this was just a test + if self.thisIsATest: + verifyRecoveryTestID(self, newWltID, self.testWltID) + return + + # If we already have the wallet, don't replace it, otherwise proceed. + dlgOwnWlt = None + if newWltID in self.main.walletMap: + QMessageBox.warning(self, self.tr('Wallet Already Exists'), self.tr( + 'The wallet already exists and will not be ' + 'replaced.'), QMessageBox.Ok) + self.reject() + return + else: + # Make sure the user is restoring the wallet they want to restore. + reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), \ + self.tr('The data you entered corresponds to a wallet with a wallet ' + 'ID: \n\n\t%s\n\nDoes this ' + 'ID match the "Wallet Unique ID" you intend to restore? ' + 'If not, click "No" and enter the key and chain-code data ' + 'again.' % binary_to_base58(inRootID)), QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.No: + return + + # Create the wallet. + self.newWallet = PyBtcWallet().createNewWalletFromPKCC(rootPubKey, \ + rootChainCode) + + # Create some more addresses and show a progress bar while restoring. + nPool = 1000 + fillAddrPoolProgress = DlgProgress(self, self.main, HBar=1, + Title=self.tr("Computing New Addresses")) + fillAddrPoolProgress.exec_(self.newWallet.fillAddressPool, nPool) + + self.accept() + + +################################################################################ +class DlgEnterSecurePrintCode(ArmoryDialog): + + def __init__(self, parent, main): + super(DlgEnterSecurePrintCode, self).__init__(parent, main) + + lblSecurePrintCodeDescr = QRichLabel(self.tr( + u'This fragment file requires a SecurePrint\u200b\u2122 code. ' + 'You will only have to enter this code once since it is the same ' + 'on all fragments.')) + lblSecurePrintCodeDescr.setMinimumWidth(440) + self.lblSP = QRichLabel(self.tr(u'SecurePrint\u200b\u2122 Code: '), doWrap=False) + self.editSecurePrint = QLineEdit() + spFrame = makeHorizFrame([self.lblSP, self.editSecurePrint, STRETCH]) + + self.btnAccept = QPushButton(self.tr("Done")) + self.btnCancel = QPushButton(self.tr("Cancel")) + self.connect(self.btnAccept, SIGNAL(CLICKED), self.verifySecurePrintCode) + self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) + buttonBox = QDialogButtonBox() + buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) + buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) + + layout = QVBoxLayout() + layout.addWidget(lblSecurePrintCodeDescr) + layout.addWidget(spFrame) + layout.addWidget(buttonBox) + self.setLayout(layout) + self.setWindowTitle(self.tr('Enter Secure Print Code')) + + def verifySecurePrintCode(self): + # Prepare the key mask parameters + SECPRINT = HardcodedKeyMaskParams() + securePrintCode = str(self.editSecurePrint.text()).strip() + + if not checkSecurePrintCode(self, SECPRINT, securePrintCode): + return + + self.accept() + + +################################################################################ +def OpenPaperBackupDialog(backupType, parent, main, wlt, unlockTitle=None): + result = True + verifyText = '' + if backupType == 'Single': + from qtdialogs.DlgBackupCenter import DlgPrintBackup + result = DlgPrintBackup(parent, main, wlt).exec_() + verifyText = parent.tr( + u'If the backup was printed with SecurePrint\u200b\u2122, please ' + u'make sure you wrote the SecurePrint\u200b\u2122 code on the ' + 'printed sheet of paper. Note that the code is ' + 'case-sensitive!') + elif backupType == 'Frag': + result = DlgFragBackup(parent, main, wlt).exec_() + verifyText = parent.tr( + u'If the backup was created with SecurePrint\u200b\u2122, please ' + u'make sure you wrote the SecurePrint\u200b\u2122 code on each ' + 'fragment (or stored with each file fragment). The code is the ' + 'same for all fragments.') + + doTest = MsgBoxCustom(MSGBOX.Warning, parent.tr('Verify Your Backup!'), parent.tr( + 'Verify your backup! ' + '

' + 'If you just made a backup, make sure that it is correct! ' + 'The following steps are recommended to verify its integrity: ' + '
' + '
    ' + '
  • Verify each line of the backup data contains 9 columns ' + 'of 4 letters each (excluding any "ID" lines).
  • ' + '
  • %s
  • ' + '
  • Use Armory\'s backup tester to test the backup before you ' + 'physiclly secure it.
  • ' + '
' + '
' + 'Armory has a backup tester that uses the exact same ' + 'process as restoring your wallet, but stops before it writes any ' + 'data to disk. Would you like to test your backup now? ' + % verifyText), yesStr="Test Backup", noStr="Cancel") + + if doTest: + if backupType == 'Single': + DlgRestoreSingle(parent, main, True, wlt.uniqueIDB58).exec_() + elif backupType == 'Frag': + DlgRestoreFragged(parent, main, True, wlt.uniqueIDB58).exec_() + + return result + + +################################################################################ +def verifyRecoveryTestID(parent, computedWltID, expectedWltID=None): + + if expectedWltID == None: + # Testing an arbitrary paper backup + yesno = QMessageBox.question(parent, parent.tr('Recovery Test'), parent.tr( + 'From the data you entered, Armory calculated the following ' + 'wallet ID: %s ' + '

' + 'Does this match the wallet ID on the backup you are ' + 'testing?' % computedWltID), QMessageBox.Yes | QMessageBox.No) + + if yesno == QMessageBox.No: + QMessageBox.critical(parent, parent.tr('Bad Backup!'), parent.tr( + 'If this is your only backup and you are sure that you entered ' + 'the data correctly, then it is highly recommended you stop using ' + 'this wallet! If this wallet currently holds any funds, ' + 'you should move the funds to a wallet that does ' + 'have a working backup. ' + '



' + 'Wallet ID of the data you entered: %s
' % computedWltID), \ + QMessageBox.Ok) + elif yesno == QMessageBox.Yes: + MsgBoxCustom(MSGBOX.Good, parent.tr('Backup is Good!'), parent.tr( + 'Your backup works! ' + '

' + 'The wallet ID is computed from a combination of the root ' + 'private key, the "chaincode" and the first address derived ' + 'from those two pieces of data. A matching wallet ID ' + 'guarantees it will produce the same chain of addresses as ' + 'the original.')) + else: # an expected wallet ID was supplied + if not computedWltID == expectedWltID: + QMessageBox.critical(parent, parent.tr('Bad Backup!'), parent.tr( + 'If you are sure that you entered the backup information ' + 'correctly, then it is highly recommended you stop using ' + 'this wallet! If this wallet currently holds any funds, ' + 'you should move the funds to a wallet that does ' + 'have a working backup.' + '

' + 'Computed wallet ID: %s
' + 'Expected wallet ID: %s

' + 'Is it possible that you loaded a different backup than the ' + 'one you just made?' % (computedWltID, expectedWltID)), \ + QMessageBox.Ok) + else: + MsgBoxCustom(MSGBOX.Good, parent.tr('Backup is Good!'), parent.tr( + 'Your backup works! ' + '

' + 'The wallet ID computed from the data you entered matches ' + 'the expected ID. This confirms that the backup produces ' + 'the same sequence of private keys as the original wallet! ' + '

' + 'Computed wallet ID: %s
' + 'Expected wallet ID: %s
' + '
' % (computedWltID, expectedWltID ))) diff --git a/qtdialogs/DlgRestoreFragged.py b/qtdialogs/DlgRestoreFragged.py deleted file mode 100644 index 140106ee1..000000000 --- a/qtdialogs/DlgRestoreFragged.py +++ /dev/null @@ -1,588 +0,0 @@ -############################################################################## -# # -# Copyright (C) 2011-2015, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -# Copyright (C) 2016-2022, goatpig # -# Distributed under the MIT license # -# See LICENSE-MIT or https://opensource.org/licenses/MIT # -# # -############################################################################## - -from PySide2.QtWidgets import QCheckBox, QFrame, QGridLayout, QLabel, \ - QLineEdit, QMessageBox, QPushButton, QScrollArea, QTabWidget, QVBoxLayout -from PySide2.QtGui import QPixmap -from PySide2.QtCore import QSize, Qt, SIGNAL - -from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, \ - STYLE_SUNKEN, makeHorizFrame, makeVertFrame, relaxedSizeNChar - -################################################################################ -class DlgRestoreFragged(ArmoryDialog): - def __init__(self, parent, main, thisIsATest=False, expectWltID=None): - super(DlgRestoreFragged, self).__init__(parent, main) - - self.thisIsATest = thisIsATest - self.testWltID = expectWltID - headerStr = '' - if thisIsATest: - headerStr = self.tr('Testing a ' - 'Fragmented Backup') - else: - headerStr = self.tr('Restore Wallet from Fragments') - - descr = self.trUtf8( - '%s

' - 'Use this form to enter all the fragments to be restored. Fragments ' - 'can be stored on a mix of paper printouts, and saved files. ' - u'If any of the fragments require a SecurePrint\u200b\u2122 code, ' - 'you will only have to enter it once, since that code is the same for ' - 'all fragments of any given wallet.' % headerStr) - - if self.thisIsATest: - descr += self.tr('

' - 'For testing purposes, you may enter more fragments than needed ' - 'and Armory will test all subsets of the entered fragments to verify ' - 'that each one still recovers the wallet successfully.') - - lblDescr = QRichLabel(descr) - - frmDescr = makeHorizFrame([lblDescr], STYLE_RAISED) - - # HLINE - - self.scrollFragInput = QScrollArea() - self.scrollFragInput.setWidgetResizable(True) - self.scrollFragInput.setMinimumHeight(150) - - lblFragList = QRichLabel(self.tr('Input Fragments Below:'), doWrap=False, bold=True) - self.btnAddFrag = QPushButton(self.tr('+Frag')) - self.btnRmFrag = QPushButton(self.tr('-Frag')) - self.btnRmFrag.setVisible(False) - self.connect(self.btnAddFrag, SIGNAL(CLICKED), self.addFragment) - self.connect(self.btnRmFrag, SIGNAL(CLICKED), self.removeFragment) - self.chkEncrypt = QCheckBox(self.tr('Encrypt Restored Wallet')) - self.chkEncrypt.setChecked(True) - frmAddRm = makeHorizFrame([self.chkEncrypt, STRETCH, self.btnRmFrag, self.btnAddFrag]) - - self.fragDataMap = {} - self.tableSize = 2 - self.wltType = UNKNOWN - self.fragIDPrefix = UNKNOWN - - doItText = self.tr('Test Backup') if thisIsATest else self.tr('Restore from Fragments') - - btnExit = QPushButton(self.tr('Cancel')) - self.btnRestore = QPushButton(doItText) - self.connect(btnExit, SIGNAL(CLICKED), self.reject) - self.connect(self.btnRestore, SIGNAL(CLICKED), self.processFrags) - frmBtns = makeHorizFrame([btnExit, STRETCH, self.btnRestore]) - - self.lblRightFrm = QRichLabel('', hAlign=Qt.AlignHCenter) - self.lblSecureStr = QRichLabel(self.trUtf8(u'SecurePrint\u200b\u2122 Code:'), \ - hAlign=Qt.AlignHCenter, - doWrap=False, - color='TextWarn') - self.displaySecureString = QLineEdit() - self.imgPie = QRichLabel('', hAlign=Qt.AlignHCenter) - self.imgPie.setMinimumWidth(96) - self.imgPie.setMinimumHeight(96) - self.lblReqd = QRichLabel('', hAlign=Qt.AlignHCenter) - self.lblWltID = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) - self.lblFragID = QRichLabel('', doWrap=False, hAlign=Qt.AlignHCenter) - self.lblSecureStr.setVisible(False) - self.displaySecureString.setVisible(False) - self.displaySecureString.setMaximumWidth(relaxedSizeNChar(self.displaySecureString, 16)[0]) - # The Secure String is now edited in DlgEnterOneFrag, It is only displayed here - self.displaySecureString.setEnabled(False) - frmSecPair = makeVertFrame([self.lblSecureStr, self.displaySecureString]) - frmSecCtr = makeHorizFrame([STRETCH, frmSecPair, STRETCH]) - - frmWltInfo = makeVertFrame([STRETCH, - self.lblRightFrm, - self.imgPie, - self.lblReqd, - self.lblWltID, - self.lblFragID, - HLINE(), - frmSecCtr, - 'Strut(200)', - STRETCH], STYLE_SUNKEN) - - - fragmentsLayout = QGridLayout() - fragmentsLayout.addWidget(frmDescr, 0, 0, 1, 2) - fragmentsLayout.addWidget(frmAddRm, 1, 0, 1, 1) - fragmentsLayout.addWidget(self.scrollFragInput, 2, 0, 1, 1) - fragmentsLayout.addWidget(frmWltInfo, 1, 1, 2, 1) - setLayoutStretchCols(fragmentsLayout, 1, 0) - - walletRestoreTabs = QTabWidget() - fragmentsFrame = QFrame() - fragmentsFrame.setLayout(fragmentsLayout) - walletRestoreTabs.addTab(fragmentsFrame, self.tr("Fragments")) - self.advancedOptionsTab = AdvancedOptionsFrame(parent, main) - walletRestoreTabs.addTab(self.advancedOptionsTab, self.tr("Advanced Options")) - - self.chkEncrypt.setChecked(not thisIsATest) - self.chkEncrypt.setVisible(not thisIsATest) - self.advancedOptionsTab.setEnabled(not thisIsATest) - if not thisIsATest: - self.connect(self.chkEncrypt, SIGNAL(CLICKED), self.onEncryptCheckboxChange) - - layout = QVBoxLayout() - layout.addWidget(walletRestoreTabs) - layout.addWidget(frmBtns) - self.setLayout(layout) - self.setMinimumWidth(650) - self.setMinimumHeight(500) - self.sizeHint = lambda: QSize(800, 650) - self.setWindowTitle(self.tr('Restore wallet from fragments')) - - self.makeFragInputTable() - self.checkRestoreParams() - - ############################################################################# - # Hide advanced options whenver the restored wallet is unencrypted - def onEncryptCheckboxChange(self): - self.advancedOptionsTab.setEnabled(self.chkEncrypt.isChecked()) - - def makeFragInputTable(self, addCount=0): - - self.tableSize += addCount - newLayout = QGridLayout() - newFrame = QFrame() - self.fragsDone = [] - newLayout.addWidget(HLINE(), 0, 0, 1, 5) - for i in range(self.tableSize): - btnEnter = QPushButton(self.tr('Type Data')) - btnLoad = QPushButton(self.tr('Load File')) - btnClear = QPushButton(self.tr('Clear')) - lblFragID = QRichLabel('', doWrap=False) - lblSecure = QLabel('') - if i in self.fragDataMap: - M, fnum, wltID, doMask, fid = ReadFragIDLineBin(self.fragDataMap[i][0]) - self.fragsDone.append(fnum) - lblFragID.setText('' + fid + '') - if doMask: - lblFragID.setText('' + fid + '', color='TextWarn') - - - self.connect(btnEnter, SIGNAL(CLICKED), \ - functools.partial(self.dataEnter, fnum=i)) - self.connect(btnLoad, SIGNAL(CLICKED), \ - functools.partial(self.dataLoad, fnum=i)) - self.connect(btnClear, SIGNAL(CLICKED), \ - functools.partial(self.dataClear, fnum=i)) - - - newLayout.addWidget(btnEnter, 2 * i + 1, 0) - newLayout.addWidget(btnLoad, 2 * i + 1, 1) - newLayout.addWidget(btnClear, 2 * i + 1, 2) - newLayout.addWidget(lblFragID, 2 * i + 1, 3) - newLayout.addWidget(lblSecure, 2 * i + 1, 4) - newLayout.addWidget(HLINE(), 2 * i + 2, 0, 1, 5) - - btnFrame = QFrame() - btnFrame.setLayout(newLayout) - - frmFinal = makeVertFrame([btnFrame, STRETCH], STYLE_SUNKEN) - self.scrollFragInput.setWidget(frmFinal) - - self.btnAddFrag.setVisible(self.tableSize < 12) - self.btnRmFrag.setVisible(self.tableSize > 2) - - - ############################################################################# - def addFragment(self): - self.makeFragInputTable(1) - - ############################################################################# - def removeFragment(self): - self.makeFragInputTable(-1) - toRemove = [] - for key, val in self.fragDataMap.iteritems(): - if key >= self.tableSize: - toRemove.append(key) - - # Have to do this in a separate loop, cause you can't remove items - # from a map while you are iterating over them - for key in toRemove: - self.dataClear(key) - - - ############################################################################# - def dataEnter(self, fnum): - dlg = DlgEnterOneFrag(self, self.main, self.fragsDone, self.wltType, self.displaySecureString.text()) - if dlg.exec_(): - LOGINFO('Good data from enter_one_frag exec! %d', fnum) - self.displaySecureString.setText(dlg.editSecurePrint.text()) - self.addFragToTable(fnum, dlg.fragData) - self.makeFragInputTable() - - - ############################################################################# - def dataLoad(self, fnum): - LOGINFO('Loading data for entry, %d', fnum) - toLoad = str(self.main.getFileLoad('Load Fragment File', \ - ['Wallet Fragments (*.frag)'])) - - if len(toLoad) == 0: - return - - if not os.path.exists(toLoad): - LOGERROR('File just chosen does not exist! %s', toLoad) - QMessageBox.critical(self, self.tr('File Does Not Exist'), self.tr( - 'The file you select somehow does not exist...? ' - '

%s

Try a different file' % toLoad), \ - QMessageBox.Ok) - - fragMap = {} - with open(toLoad, 'r') as fin: - allData = [line.strip() for line in fin.readlines()] - fragMap = {} - for line in allData: - if line[:2].lower() in ['id', 'x1', 'x2', 'x3', 'x4', \ - 'y1', 'y2', 'y3', 'y4', \ - 'f1', 'f2', 'f3', 'f4']: - fragMap[line[:2].lower()] = line[3:].strip().replace(' ', '') - - - cList, nList = [], [] - if len(fragMap) == 9: - cList, nList = ['x', 'y'], ['1', '2', '3', '4'] - elif len(fragMap) == 5: - cList, nList = ['f'], ['1', '2', '3', '4'] - elif len(fragMap) == 3: - cList, nList = ['f'], ['1', '2'] - else: - LOGERROR('Unexpected number of lines in the frag file, %d', len(fragMap)) - return - - fragData = [] - fragData.append(hex_to_binary(fragMap['id'])) - for c in cList: - for n in nList: - mapKey = c + n - rawBin, err = readSixteenEasyBytes(fragMap[c + n]) - if err == 'Error_2+': - QMessageBox.critical(self, self.tr('Fragment Error'), self.tr( - 'There was an unfixable error in the fragment file: ' - '

File: %s
Line: %s
' % (toLoad, mapKey)), \ - QMessageBox.Ok) - return -# fragData.append(SecureBinaryData(rawBin)) - rawBin = None - - self.addFragToTable(fnum, fragData) - self.makeFragInputTable() - - - ############################################################################# - def dataClear(self, fnum): - if not fnum in self.fragDataMap: - return - - for i in range(1, 3): - self.fragDataMap[fnum][i].destroy() - del self.fragDataMap[fnum] - self.makeFragInputTable() - self.checkRestoreParams() - - - ############################################################################# - def checkRestoreParams(self): - showRightFrm = False - self.btnRestore.setEnabled(False) - self.lblRightFrm.setText(self.tr( - 'Start entering fragments into the table to left...')) - for row, data in self.fragDataMap.iteritems(): - showRightFrm = True - M, fnum, setIDBin, doMask, idBase58 = ReadFragIDLineBin(data[0]) - self.lblRightFrm.setText(self.tr('Wallet Being Restored:')) - self.imgPie.setPixmap(QPixmap('./img/frag%df.png' % M).scaled(96,96)) - self.lblReqd.setText(self.tr('Frags Needed: %s' % M)) - self.lblFragID.setText(self.tr('Fragments: %s' % idBase58.split('-')[0])) - self.btnRestore.setEnabled(len(self.fragDataMap) >= M) - break - - anyMask = False - for row, data in self.fragDataMap.iteritems(): - M, fnum, wltIDBin, doMask, idBase58 = ReadFragIDLineBin(data[0]) - if doMask: - anyMask = True - break - # If all of the rows with a Mask have been removed clear the securePrintCode - if not anyMask: - self.displaySecureString.setText('') - self.lblSecureStr.setVisible(anyMask) - self.displaySecureString.setVisible(anyMask) - - if not showRightFrm: - self.fragIDPrefix = UNKNOWN - self.wltType = UNKNOWN - - self.imgPie.setVisible(showRightFrm) - self.lblReqd.setVisible(showRightFrm) - self.lblWltID.setVisible(showRightFrm) - self.lblFragID.setVisible(showRightFrm) - - - ############################################################################# - def addFragToTable(self, tableIndex, fragData): - - if len(fragData) == 9: - currType = '0' - elif len(fragData) == 5: - currType = BACKUP_TYPE_135A - elif len(fragData) == 3: - currType = BACKUP_TYPE_135C - else: - LOGERROR('How\'d we get fragData of size: %d', len(fragData)) - return - - if self.wltType == UNKNOWN: - self.wltType = currType - elif not self.wltType == currType: - QMessageBox.critical(self, self.tr('Mixed fragment types'), self.tr( - 'You entered a fragment for a different wallet type. Please check ' - 'that all fragments are for the same wallet, of the same version, ' - 'and require the same number of fragments.'), QMessageBox.Ok) - LOGERROR('Mixing frag types! How did that happen?') - return - - - M, fnum, wltIDBin, doMask, idBase58 = ReadFragIDLineBin(fragData[0]) - # If we don't know the Secure String Yet we have to get it - if doMask and len(str(self.displaySecureString.text()).strip()) == 0: - dlg = DlgEnterSecurePrintCode(self, self.main) - if dlg.exec_(): - self.displaySecureString.setText(dlg.editSecurePrint.text()) - else: - return - - if self.fragIDPrefix == UNKNOWN: - self.fragIDPrefix = idBase58.split('-')[0] - elif not self.fragIDPrefix == idBase58.split('-')[0]: - QMessageBox.critical(self, self.tr('Multiple Wallets'), self.tr( - 'The fragment you just entered is actually for a different wallet ' - 'than the previous fragments you entered. Please double-check that ' - 'all the fragments you are entering belong to the same wallet and ' - 'have the "number of needed fragments" (M-value, in M-of-N).'), \ - QMessageBox.Ok) - LOGERROR('Mixing fragments of different wallets! %s', idBase58) - return - - - if not self.verifyNonDuplicateFrag(fnum): - QMessageBox.critical(self, self.tr('Duplicate Fragment'), self.tr( - 'You just input fragment #%s, but that fragment has already been ' - 'entered!' % fnum), QMessageBox.Ok) - return - - - -# if currType == '0': -# X = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 5)])) -# Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(5, 9)])) -# elif currType == BACKUP_TYPE_135A: -# X = SecureBinaryData(int_to_binary(fnum + 1, widthBytes=64, endOut=BIGENDIAN)) -# Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 5)])) -# elif currType == BACKUP_TYPE_135C: -# X = SecureBinaryData(int_to_binary(fnum + 1, widthBytes=32, endOut=BIGENDIAN)) -# Y = SecureBinaryData(''.join([fragData[i].toBinStr() for i in range(1, 3)])) - - self.fragDataMap[tableIndex] = [fragData[0][:], X.copy(), Y.copy()] - - X.destroy() - Y.destroy() - self.checkRestoreParams() - - ############################################################################# - def verifyNonDuplicateFrag(self, fnum): - for row, data in self.fragDataMap.iteritems(): - rowFrag = ReadFragIDLineBin(data[0])[1] - if fnum == rowFrag: - return False - - return True - - - - ############################################################################# - def processFrags(self): - if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: - QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ - self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) - return - if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfBytes() == -1: - QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ - self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) - return - SECPRINT = HardcodedKeyMaskParams() - pwd, ekey = '', '' - if self.displaySecureString.isVisible(): - pwd = str(self.displaySecureString.text()).strip() - maskKey = SECPRINT['FUNC_KDF'](pwd) - - fragMtrx, M = [], -1 - for row, trip in self.fragDataMap.iteritems(): - M, fnum, wltID, doMask, fid = ReadFragIDLineBin(trip[0]) - X, Y = trip[1], trip[2] - if doMask: - LOGINFO('Row %d needs unmasking' % row) - Y = SECPRINT['FUNC_UNMASK'](Y, ekey=maskKey) - else: - LOGINFO('Row %d is already unencrypted' % row) - fragMtrx.append([X.toBinStr(), Y.toBinStr()]) - - typeToBytes = {'0': 64, BACKUP_TYPE_135A: 64, BACKUP_TYPE_135C: 32} - nBytes = typeToBytes[self.wltType] - - - if self.thisIsATest and len(fragMtrx) > M: - self.testFragSubsets(fragMtrx, M) - return - - - SECRET = ReconstructSecret(fragMtrx, M, nBytes) - for i in range(len(fragMtrx)): - fragMtrx[i] = [] - - LOGINFO('Final length of frag mtrx: %d', len(fragMtrx)) - LOGINFO('Final length of secret: %d', len(SECRET)) - - priv, chain = '', '' -# if len(SECRET) == 64: -# priv = SecureBinaryData(SECRET[:32 ]) -# chain = SecureBinaryData(SECRET[ 32:]) -# elif len(SECRET) == 32: -# priv = SecureBinaryData(SECRET) -# chain = DeriveChaincodeFromRootKey(priv) - - - # If we got here, the data is valid, let's create the wallet and accept the dlg - # Now we should have a fully-plaintext rootkey and chaincode - root = PyBtcAddress().createFromPlainKeyData(priv) - root.chaincode = chain - - first = root.extendAddressChain() - newWltID = binary_to_base58((ADDRBYTE + first.getAddr160()[:5])[::-1]) - - # If this is a test, then bail - if self.thisIsATest: - verifyRecoveryTestID(self, newWltID, self.testWltID) - return - - dlgOwnWlt = None - if newWltID in self.main.walletMap: - dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) - - if (dlgOwnWlt.exec_()): - if dlgOwnWlt.output == 0: - return - else: - self.reject() - return - - reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), self.tr( - 'The data you entered corresponds to a wallet with the ' - 'ID:
{%s}
Does this ID ' - 'match the "Wallet Unique ID" printed on your paper backup? ' - 'If not, click "No" and reenter key and chain-code data ' - 'again.' % newWltID), QMessageBox.Yes | QMessageBox.No) - if reply == QMessageBox.No: - return - - - passwd = [] - if self.chkEncrypt.isChecked(): - dlgPasswd = DlgChangePassphrase(self, self.main) -# if dlgPasswd.exec_(): -# passwd = SecureBinaryData(str(dlgPasswd.edtPasswd1.text())) -# else: -# QMessageBox.critical(self, self.tr('Cannot Encrypt'), self.tr( -# 'You requested your restored wallet be encrypted, but no ' -# 'valid passphrase was supplied. Aborting wallet ' -# 'recovery.'), QMessageBox.Ok) -# return - - shortl = '' - longl = '' - nPool = 1000 - - if dlgOwnWlt is not None: - if dlgOwnWlt.Meta is not None: - shortl = ' - %s' % (dlgOwnWlt.Meta['shortLabel']) - longl = dlgOwnWlt.Meta['longLabel'] - nPool = max(nPool, dlgOwnWlt.Meta['naddress']) - - if passwd: - self.newWallet = PyBtcWallet().createNewWallet(\ - plainRootKey=priv, \ - chaincode=chain, \ - shortLabel='Restored - ' + newWltID + shortl, \ - longLabel=longl, \ - withEncrypt=True, \ - securePassphrase=passwd, \ - kdfTargSec=self.advancedOptionsTab.getKdfSec(), \ - kdfMaxMem=self.advancedOptionsTab.getKdfBytes(), - isActuallyNew=False, \ - doRegisterWithBDM=False) - else: - self.newWallet = PyBtcWallet().createNewWallet(\ - plainRootKey=priv, \ - chaincode=chain, \ - shortLabel='Restored - ' + newWltID +shortl, \ - longLabel=longl, \ - withEncrypt=False, \ - isActuallyNew=False, \ - doRegisterWithBDM=False) - - - # Will pop up a little "please wait..." window while filling addr pool - fillAddrPoolProgress = DlgProgress(self, self.parent, HBar=1, - Title=self.tr("Computing New Addresses")) - fillAddrPoolProgress.exec_(self.newWallet.fillAddressPool, nPool) - - if dlgOwnWlt is not None: - if dlgOwnWlt.Meta is not None: - from armoryengine.PyBtcWallet import WLT_UPDATE_ADD - for n_cmt in range(0, dlgOwnWlt.Meta['ncomments']): - entrylist = [] - entrylist = list(dlgOwnWlt.Meta[n_cmt]) - self.newWallet.walletFileSafeUpdate([[WLT_UPDATE_ADD, entrylist[2], entrylist[1], entrylist[0]]]) - - self.newWallet = PyBtcWallet().readWalletFile(self.newWallet.walletPath) - self.accept() - - ############################################################################# - def testFragSubsets(self, fragMtrx, M): - # If the user entered multiple fragments - fragMap = {} - for x, y in fragMtrx: - fragMap[binary_to_int(x, BIGENDIAN) - 1] = [x, y] - typeToBytes = {'0': 64, BACKUP_TYPE_135A: 64, BACKUP_TYPE_135C: 32} - - isRandom, results = testReconstructSecrets(fragMap, M, 100) - def privAndChainFromRow(secret): - priv, chain = None, None -# if len(secret) == 64: -# priv = SecureBinaryData(secret[:32 ]) -# chain = SecureBinaryData(secret[ 32:]) -# return (priv, chain) -# elif len(secret) == 32: -# priv = SecureBinaryData(secret) -# chain = DeriveChaincodeFromRootKey(priv) -# return (priv, chain) -# else: -# LOGERROR('Root secret is %s bytes ?!' % len(secret)) -# raise KeyDataError - - results = [(row[0], privAndChainFromRow(row[1])) for row in results] - subsAndIDs = [(row[0], calcWalletIDFromRoot(*row[1])) for row in results] - - DlgShowTestResults(self, isRandom, subsAndIDs, \ - M, len(fragMtrx), self.testWltID).exec_() diff --git a/qtdialogs/DlgRestoreSingle.py b/qtdialogs/DlgRestoreSingle.py deleted file mode 100644 index 0baacc7a0..000000000 --- a/qtdialogs/DlgRestoreSingle.py +++ /dev/null @@ -1,407 +0,0 @@ -############################################################################## -# # -# Copyright (C) 2011-2015, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -# Copyright (C) 2016-2022, goatpig # -# Distributed under the MIT license # -# See LICENSE-MIT or https://opensource.org/licenses/MIT # -# # -############################################################################## - -from PySide2.QtWidgets import QButtonGroup, QCheckBox, QDialogButtonBox, \ - QFrame, QGridLayout, QLabel, QLayout, QLineEdit, QMessageBox, \ - QPushButton, QRadioButton, QTabWidget, QVBoxLayout - -from armoryengine import ClientProto_pb2 -from armoryengine.CppBridge import TheBridge -from armoryengine.ArmoryUtils import LOGERROR, UINT32_MAX, UINT8_MAX -from armoryengine.BDM import TheBDM -from armoryengine.PyBtcWallet import PyBtcWallet - -from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.DlgChangePassphrase import DlgChangePassphrase -from qtdialogs.DlgReplaceWallet import DlgReplaceWallet -from qtdialogs.qtdefines import HLINE, QRichLabel, STRETCH, STYLE_RAISED, \ - makeHorizFrame, makeVertFrame -from qtdialogs.qtdialogs import MaskedInputLineEdit, verifyRecoveryTestID - -from ui.WalletFrames import AdvancedOptionsFrame - -################################################################################ -class DlgRestoreSingle(ArmoryDialog): - ############################################################################# - def __init__(self, parent, main, thisIsATest=False, expectWltID=None): - super(DlgRestoreSingle, self).__init__(parent, main) - - self.newWltID = None - self.callbackId = None - self.thisIsATest = thisIsATest - self.testWltID = expectWltID - headerStr = '' - if thisIsATest: - lblDescr = QRichLabel(self.tr( - 'Test a Paper Backup ' - '

' - 'Use this window to test a single-sheet paper backup. If your ' - 'backup includes imported keys, those will not be covered by this test.')) - else: - lblDescr = QRichLabel(self.tr( - 'Restore a Wallet from Paper Backup ' - '

' - 'Use this window to restore a single-sheet paper backup. ' - 'If your backup includes extra pages with ' - 'imported keys, please restore the base wallet first, then ' - 'double-click the restored wallet and select "Import Private ' - 'Keys" from the right-hand menu.')) - - - lblType = QRichLabel(self.tr('Backup Type:'), doWrap=False) - - self.version135Button = QRadioButton(self.tr('Version 1.35 (4 lines)'), self) - self.version135aButton = QRadioButton(self.tr('Version 1.35a (4 lines Unencrypted)'), self) - self.version135aSPButton = QRadioButton(self.tr(u'Version 1.35a (4 lines + SecurePrint\u200b\u2122)'), self) - self.version135cButton = QRadioButton(self.tr('Version 1.35c (2 lines Unencrypted)'), self) - self.version135cSPButton = QRadioButton(self.tr(u'Version 1.35c (2 lines + SecurePrint\u200b\u2122)'), self) - self.backupTypeButtonGroup = QButtonGroup(self) - self.backupTypeButtonGroup.addButton(self.version135Button) - self.backupTypeButtonGroup.addButton(self.version135aButton) - self.backupTypeButtonGroup.addButton(self.version135aSPButton) - self.backupTypeButtonGroup.addButton(self.version135cButton) - self.backupTypeButtonGroup.addButton(self.version135cSPButton) - self.version135cButton.setChecked(True) - self.connect(self.backupTypeButtonGroup, SIGNAL('buttonClicked(int)'), self.changeType) - - layoutRadio = QVBoxLayout() - layoutRadio.addWidget(self.version135Button) - layoutRadio.addWidget(self.version135aButton) - layoutRadio.addWidget(self.version135aSPButton) - layoutRadio.addWidget(self.version135cButton) - layoutRadio.addWidget(self.version135cSPButton) - layoutRadio.setSpacing(0) - - radioButtonFrame = QFrame() - radioButtonFrame.setLayout(layoutRadio) - - frmBackupType = makeVertFrame([lblType, radioButtonFrame]) - - self.lblSP = QRichLabel(self.tr(u'SecurePrint\u200b\u2122 Code:'), doWrap=False) - self.editSecurePrint = QLineEdit() - self.prfxList = [QLabel(self.tr('Root Key:')), QLabel(''), QLabel(self.tr('Chaincode:')), QLabel('')] - - inpMask = '
%s' % errorMsg \ - ), QMessageBox.Ok) - - self.reject() - return - - result, extra = self.processCallbackPayload(payload) - if result == False: - TheBDM.unregisterCustomPrompt(self.callbackId) - - reply = ClientProto_pb2.RestoreReply() - reply.result = result - - if extra != None: - reply.extra = bytes(extra, 'utf-8') - - TheBridge.callbackFollowUp(reply, self.callbackId, callerId) - - ############################################################################# - def processCallbackPayload(self, payload): - msg = ClientProto_pb2.RestorePrompt() - msg.ParseFromString(payload) - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Id") or \ - msg.promptType == ClientProto_pb2.RestorePromptType.Value("ChecksumError"): - #check the id generated by this backup - - newWltID = msg.extra - if len(newWltID) > 0: - if self.thisIsATest: - # Stop here if this was just a test - verifyRecoveryTestID(self, newWltID, self.testWltID) - - #return false to caller to end the restore process - return False, None - - # return result of id comparison - dlgOwnWlt = None - if newWltID in self.main.walletMap: - dlgOwnWlt = DlgReplaceWallet(newWltID, self.parent, self.main) - - if (dlgOwnWlt.exec_()): - #TODO: deal with replacement code - if dlgOwnWlt.output == 0: - return False, None - else: - return False, None - else: - reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), \ - self.tr('The data you entered corresponds to a wallet with a wallet ID: \n\n' - '%s\n\nDoes this ID match the "Wallet Unique ID" ' - 'printed on your paper backup? If not, click "No" and reenter ' - 'key and chain-code data again.' % newWltID), \ - QMessageBox.Yes | QMessageBox.No) - if reply == QMessageBox.Yes: - #return true to caller to proceed with restore operation - self.newWltID = newWltID - return True, None - - #reconstructed wallet id is invalid if we get this far - lineNumber = -1 - canBeSalvaged = True - if len(msg.checksums) != self.lineCount: - canBeSalvaged = False - - for i in range(0, len(msg.checksums)): - if msg.checksums[i] < 0 or msg.checksums[i] == UINT8_MAX: - lineNumber = i + 1 - break - - if lineNumber == -1 or canBeSalvaged == False: - QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( - 'Encountered an unkonwn error when restoring this backup. Aborting.'), \ - QMessageBox.Ok) - - self.reject() - return False, None - - reply = QMessageBox.critical(self, self.tr('Invalid Data'), self.tr( - 'There is an error in the data you entered that could not be ' - 'fixed automatically. Please double-check that you entered the ' - 'text exactly as it appears on the wallet-backup page.

' - 'The error occured on line #%d.' % lineNumber), \ - QMessageBox.Ok) - LOGERROR('Error in wallet restore field') - self.prfxList[i].setText(\ - '' + str(self.prfxList[i].text()) + '') - - return False, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Passphrase"): - #return new wallet's private keys password - passwd = [] - if self.chkEncrypt.isChecked(): - dlgPasswd = DlgChangePassphrase(self, self.main) - if dlgPasswd.exec_(): - passwd = str(dlgPasswd.edtPasswd1.text()) - return True, passwd - else: - QMessageBox.critical(self, self.tr('Cannot Encrypt'), \ - self.tr('You requested your restored wallet be encrypted, but no ' - 'valid passphrase was supplied. Aborting wallet recovery.'), \ - QMessageBox.Ok) - self.reject() - return False, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Control"): - #TODO: need UI to input control passphrase - return True, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("Success"): - if self.newWltID == None or len(self.newWltID) == 0: - LOGERROR("wallet import did not yield an id") - raise Exception("wallet import did not yield an id") - - self.newWallet = PyBtcWallet() - self.newWallet.loadFromBridge(self.newWltID) - self.accept() - - return True, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("FormatError") or \ - sg.promptType == ClientProto_pb2.RestorePromptType.Value("Failure"): - - QMessageBox.critical(self, self.tr('Unknown Error'), self.tr( - 'Encountered an unkonwn error when restoring this backup. Aborting.', \ - QMessageBox.Ok)) - - self.reject() - return False, None - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("DecryptError"): - #TODO: notify of invalid SP pass - pass - - if msg.promptType == ClientProto_pb2.RestorePromptType.Value("TypeError"): - #TODO: wallet type conveyed by backup is unknown - pass - - else: - #TODO: unknown error - return False, None - - - ############################################################################# - def verifyUserInput(self): - - root = [] - for i in range(2): - root.append(str(self.edtList[i].text())) - - chaincode = [] - if self.isLongForm: - for i in range(2): - chaincode.append(str(self.edtList[i+2].text())) - - self.lineCount = len(root) + len(chaincode) - - spPass = "" - if self.doMask: - #add secureprint passphrase if this backup is encrypted - spPass = str(self.editSecurePrint.text()).strip() - - ''' - verifyBackupString is a method that will trigger multiple callbacks - during the course of its execution. Unlike a password request callback - which only requires to generate a dedicated dialog to retrieve passwords - from users, verifyBackupString set of notifications is complex and comes - with branches. - - A dedicated callbackId is generated for this interaction and passed to - TheBDM callback map along with a py side method to handle the protobuf - packet from the C++ side. - - The C++ method is called with that id. - ''' - def callback(payload, callerId): - self.main.signalExecution.executeMethod(\ - [self.processCallback, [payload, callerId]]) - - self.callbackId = TheBDM.registerCustomPrompt(callback) - TheBridge.restoreWallet(root, chaincode, spPass, self.callbackId) - - ''' - if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfSec() == -1: - QMessageBox.critical(self, self.tr('Invalid Target Compute Time'), \ - self.tr('You entered Target Compute Time incorrectly.\n\nEnter: (ms, s)'), QMessageBox.Ok) - return - if self.chkEncrypt.isChecked() and self.advancedOptionsTab.getKdfBytes() == -1: - QMessageBox.critical(self, self.tr('Invalid Max Memory Usage'), \ - self.tr('You entered Max Memory Usage incorrectly.\n\nEnter: (kB, MB)'), QMessageBox.Ok) - return - if nError > 0: - pluralStr = 'error' if nError == 1 else 'errors' - - msg = self.tr( - 'Detected errors in the data you entered. ' - 'Armory attempted to fix the errors but it is not ' - 'always right. Be sure to verify the "Wallet Unique ID" ' - 'closely on the next window.') - - QMessageBox.question(self, self.tr('Errors Corrected'), msg, \ - QMessageBox.Ok) - ''' \ No newline at end of file diff --git a/qtdialogs/DlgRestoreWOData.py b/qtdialogs/DlgRestoreWOData.py deleted file mode 100644 index 813c71ce9..000000000 --- a/qtdialogs/DlgRestoreWOData.py +++ /dev/null @@ -1,274 +0,0 @@ -############################################################################## -# # -# Copyright (C) 2011-2015, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -# Copyright (C) 2016-2022, goatpig # -# Distributed under the MIT license # -# See LICENSE-MIT or https://opensource.org/licenses/MIT # -# # -############################################################################## - -# Class that will create the watch-only wallet data (root public key & chain -# code) restoration window. -################################################################################ - -import os -import sys - -from PySide2.QtWidgets import QDialogButtonBox, QFrame, QGridLayout, \ - QLabel, QLayout, QMessageBox, QPushButton, QVBoxLayout -from PySide2.QtCore import SIGNAL - -from armoryengine.ArmoryUtils import LOGERROR, binary_to_base58, \ - binary_to_int, easyType16_to_binary, int_to_binary, \ - readSixteenEasyBytes, verifyChecksum -from armoryengine.PyBtcWallet import PYROOTPKCCSIGNMASK, \ - PYROOTPKCCVERMASK, PyBtcWallet -from armoryengine.PyBtcAddress import PyBtcAddress - -from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.DlgProgress import DlgProgress -from qtdialogs.qtdefines import GETFONT, HLINE, QRichLabel, STRETCH, \ - STYLE_RAISED, makeHorizFrame -from qtdialogs.qtdialogs import MaskedInputLineEdit, verifyRecoveryTestID - -class DlgRestoreWOData(ArmoryDialog): - ############################################################################# - def __init__(self, parent, main, thisIsATest=False, expectWltID=None): - super(DlgRestoreWOData, self).__init__(parent, main) - - self.thisIsATest = thisIsATest - self.testWltID = expectWltID - headerStr = '' - lblDescr = '' - - # Write the text at the top of the window. - if thisIsATest: - lblDescr = QRichLabel(self.tr( - 'Test a Watch-Only Wallet Restore ' - '

' - 'Use this window to test the restoration of a watch-only wallet using ' - 'the wallet\'s data. You can either type the data on a root data ' - 'printout or import the data from a file.')) - else: - lblDescr = QRichLabel(self.tr( - 'Restore a Watch-Only Wallet ' - '

' - 'Use this window to restore a watch-only wallet using the wallet\'s ' - 'data. You can either type the data on a root data printout or import ' - 'the data from a file.')) - - # Create the line that will contain the imported ID. - self.rootIDLabel = QRichLabel(self.tr('Watch-Only Root ID:'), doWrap=False) - inpMask = '
'), - QMessageBox.Ok) - LOGERROR('Error in root ID restore field') - LOGERROR('Error Type: %s', errType) - LOGERROR('Error Value: %s', errVal) - return - - # Save the version/key byte and the root ID. For now, ignore the version. - inRootVer = inRootChecked[0] # 1 byte - inRootID = inRootChecked[1:7] # 6 bytes - - # Read in the root data (public key & chain code) and handle any errors. - for i in range(nLine): - hasError = False - try: - rawEntry = str(self.pkccList[i].text()) - rawBin, err = readSixteenEasyBytes(rawEntry.replace(' ', '')) - if err == 'Error_2+': # 2+ bytes are wrong, so we need to stop. - hasError = True - elif err == 'Fixed_1': # 1 byte is wrong, so we may be okay. - nError += 1 - except: - hasError = True - - # If the root ID is busted, stop. - if hasError: - lineNumber = i+1 - reply = QMessageBox.critical(self, self.tr('Invalid Data'), self.tr( - 'There is an error in the root data you entered that could not be ' - 'fixed automatically. Please double-check that you entered the ' - 'text exactly as it appears on the wallet-backup page.

' - 'The error occured on line #%d.' % lineNumber), QMessageBox.Ok) - LOGERROR('Error in root data restore field') - return - - # If we've gotten this far, save the incoming line. - inputLines.append(rawBin) - - # Set up the root ID data. - pkVer = binary_to_int(inRootVer) & PYROOTPKCCVERMASK # Ignored for now. - pkSignByte = ((binary_to_int(inRootVer) & PYROOTPKCCSIGNMASK) >> 7) + 2 - rootPKComBin = int_to_binary(pkSignByte) + ''.join(inputLines[:2]) - rootPubKey = CryptoECDSA().UncompressPoint(SecureBinaryData(rootPKComBin)) - rootChainCode = SecureBinaryData(''.join(inputLines[2:])) - - # Now we should have a fully-plaintext root key and chain code, and can - # get some related data. - root = PyBtcAddress().createFromPublicKeyData(rootPubKey) - root.chaincode = rootChainCode - first = root.extendAddressChain() - newWltID = binary_to_base58(inRootID) - - # Stop here if this was just a test - if self.thisIsATest: - verifyRecoveryTestID(self, newWltID, self.testWltID) - return - - # If we already have the wallet, don't replace it, otherwise proceed. - dlgOwnWlt = None - if newWltID in self.main.walletMap: - QMessageBox.warning(self, self.tr('Wallet Already Exists'), self.tr( - 'The wallet already exists and will not be ' - 'replaced.'), QMessageBox.Ok) - self.reject() - return - else: - # Make sure the user is restoring the wallet they want to restore. - reply = QMessageBox.question(self, self.tr('Verify Wallet ID'), \ - self.tr('The data you entered corresponds to a wallet with a wallet ' - 'ID: \n\n\t%s\n\nDoes this ' - 'ID match the "Wallet Unique ID" you intend to restore? ' - 'If not, click "No" and enter the key and chain-code data ' - 'again.' % binary_to_base58(inRootID)), QMessageBox.Yes | QMessageBox.No) - if reply == QMessageBox.No: - return - - # Create the wallet. - self.newWallet = PyBtcWallet().createNewWalletFromPKCC(rootPubKey, \ - rootChainCode) - - # Create some more addresses and show a progress bar while restoring. - nPool = 1000 - fillAddrPoolProgress = DlgProgress(self, self.main, HBar=1, - Title=self.tr("Computing New Addresses")) - fillAddrPoolProgress.exec_(self.newWallet.fillAddressPool, nPool) - - self.accept() \ No newline at end of file diff --git a/qtdialogs/DlgSendBitcoins.py b/qtdialogs/DlgSendBitcoins.py index 8297e99ff..89d738bf1 100644 --- a/qtdialogs/DlgSendBitcoins.py +++ b/qtdialogs/DlgSendBitcoins.py @@ -13,6 +13,8 @@ from PySide2.QtCore import QSize from PySide2.QtWidgets import QVBoxLayout +from armoryengine.Settings import TheSettings + from qtdialogs.DlgOfflineTx import DlgOfflineTxCreated from qtdialogs.ArmoryDialog import ArmoryDialog @@ -60,7 +62,7 @@ def createTxAndBroadcast(self): ############################################################################# def saveGeometrySettings(self): geom = self.saveGeometry().data().hex() - self.main.writeSetting('SendBtcGeometry', geom) + TheSettings.set('SendBtcGeometry', geom) ############################################################################# def closeEvent(self, event): diff --git a/qtdialogs/DlgSettings.py b/qtdialogs/DlgSettings.py index d3ad301cd..3c333e610 100644 --- a/qtdialogs/DlgSettings.py +++ b/qtdialogs/DlgSettings.py @@ -22,36 +22,20 @@ QScrollArea, QFrame, QComboBox, QSlider from armoryengine.ArmoryUtils import BTC_HOME_DIR, DEFAULT_ADDR_TYPE, \ - OS_MACOSX, OS_WINDOWS, ARMORY_DB_DIR, LANGUAGES, OS_VARIANT, \ + OS_MACOSX, OS_WINDOWS, ARMORY_DB_DIR, OS_VARIANT, \ unixTimeToFormatStr, coin2str, str2coin, MIN_FEE_BYTE, \ MIN_TX_FEE, DEFAULT_FEE_TYPE, FORMAT_SYMBOLS, \ DEFAULT_DATE_FORMAT, DEFAULT_CHANGE_TYPE, DEFAULT_RECEIVE_TYPE - +from armoryengine.Settings import LANGUAGES from armoryengine.CoinSelection import NBLOCKS_TO_CONFIRM from qtdialogs.qtdefines import USERMODE, GETFONT, \ HLINE, tightSizeStr, tightSizeNChar, STYLE_RAISED, \ QRichLabel, createDirectorySelectButton, makeHorizFrame, \ - makeVertFrame - + makeVertFrame, createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog - from ui.AddressTypeSelectDialog import AddressLabelFrame -MIN_PASSWD_WIDTH = lambda obj: tightSizeStr(obj, '*' * 16)[0] -NO_CHANGE = 'NoChange' -STRETCH = 'Stretch' -BACKUP_TYPE_135A = '1.35a' -BACKUP_TYPE_135C = '1.35c' -BACKUP_TYPE_0_TEXT = 'Version 0 (from script, 9 lines)' -BACKUP_TYPE_135a_TEXT = 'Version 1.35a (5 lines Unencrypted)' -BACKUP_TYPE_135a_SP_TEXT = u'Version 1.35a (5 lines + SecurePrint\u200b\u2122)' -BACKUP_TYPE_135c_TEXT = 'Version 1.35c (3 lines Unencrypted)' -BACKUP_TYPE_135c_SP_TEXT = u'Version 1.35c (3 lines + SecurePrint\u200b\u2122)' -MAX_QR_SIZE = 198 -MAX_SATOSHIS = 2100000000000000 - - ############################################################################### class DlgSettings(ArmoryDialog): def __init__(self, parent=None, main=None): @@ -59,7 +43,7 @@ def __init__(self, parent=None, main=None): defaultWltID = self.main.walletIDList[0] self.wlt = self.main.walletMap[defaultWltID] - self.addrType = self.main.getSettingOrSetDefault('Default_ReceiveType', self.wlt.getDefaultAddressType()) + self.addrType = TheSettings.getSettingOrSetDefault('Default_ReceiveType', self.wlt.getDefaultAddressType()) ####################################################################### # bitcoind-management settings self.chkManageSatoshi = QCheckBox(self.tr( @@ -73,7 +57,7 @@ def __init__(self, parent=None, main=None): self.connect( self.chkManageSatoshi, SIGNAL('clicked()'), self.clickChkManage) - self.startChk = self.main.getSettingOrSetDefault( + self.startChk = TheSettings.getSettingOrSetDefault( 'ManageSatoshi', not OS_MACOSX) if self.startChk: self.chkManageSatoshi.setChecked(True) @@ -172,7 +156,7 @@ def __init__(self, parent=None, main=None): self.chkAskURIAtStartup = QCheckBox(self.tr( 'Check whether Armory is the default handler at startup')) - askuriDNAA = self.main.getSettingOrSetDefault('DNAA_DefaultApp', False) + askuriDNAA = TheSettings.getSettingOrSetDefault('DNAA_DefaultApp', False) self.chkAskURIAtStartup.setChecked(not askuriDNAA) def clickRegURI(): @@ -197,12 +181,12 @@ def clickRegURI(): 'minimize Armory instead of exiting the application. You can always use ' '"File" -> "Quit Armory" to actually close it.')) - moo = self.main.getSettingOrSetDefault('MinimizeOnOpen', False) + moo = TheSettings.getSettingOrSetDefault('MinimizeOnOpen', False) self.chkMinOnOpen = QCheckBox(self.tr('Minimize to system tray on open')) if moo: self.chkMinOnOpen.setChecked(True) - moc = self.main.getSettingOrSetDefault('MinimizeOrClose', 'DontKnow') + moc = TheSettings.getSettingOrSetDefault('MinimizeOrClose', 'DontKnow') self.chkMinOrClose = QCheckBox(self.tr('Minimize to system tray on close')) if moc == 'Minimize': @@ -235,10 +219,10 @@ def clickRegURI(): self.chkDiscon.setEnabled(False) self.chkReconn.setEnabled(False) else: - notifyBtcIn = self.main.getSettingOrSetDefault('NotifyBtcIn', True) - notifyBtcOut = self.main.getSettingOrSetDefault('NotifyBtcOut', True) - notifyDiscon = self.main.getSettingOrSetDefault('NotifyDiscon', True) - notifyReconn = self.main.getSettingOrSetDefault('NotifyReconn', True) + notifyBtcIn = TheSettings.getSettingOrSetDefault('NotifyBtcIn', True) + notifyBtcOut = TheSettings.getSettingOrSetDefault('NotifyBtcOut', True) + notifyDiscon = TheSettings.getSettingOrSetDefault('NotifyDiscon', True) + notifyReconn = TheSettings.getSettingOrSetDefault('NotifyReconn', True) self.chkBtcIn.setChecked(notifyBtcIn) self.chkBtcOut.setChecked(notifyBtcOut) self.chkDiscon.setChecked(notifyDiscon) @@ -270,7 +254,7 @@ def clickRegURI(): self.edtDateFormat = QLineEdit() self.edtDateFormat.setText(fmt) - self.ttipFormatDescr = self.main.createToolTipWidget(ttipStr) + self.ttipFormatDescr = createToolTipWidget(ttipStr) self.lblDateExample = QRichLabel('', doWrap=False) self.connect(self.edtDateFormat, SIGNAL('textEdited(QString)'), self.doExampleDate) @@ -453,11 +437,11 @@ def setupExtraTabs(self): ########## #fee - feeByte = self.main.getSettingOrSetDefault('Default_FeeByte', MIN_FEE_BYTE) - txFee = self.main.getSettingOrSetDefault('Default_Fee', MIN_TX_FEE) - adjustFee = self.main.getSettingOrSetDefault('AdjustFee', True) - feeOpt = self.main.getSettingOrSetDefault('FeeOption', DEFAULT_FEE_TYPE) - blocksToConfirm = self.main.getSettingOrSetDefault(\ + feeByte = TheSettings.getSettingOrSetDefault('Default_FeeByte', MIN_FEE_BYTE) + txFee = TheSettings.getSettingOrSetDefault('Default_Fee', MIN_TX_FEE) + adjustFee = TheSettings.getSettingOrSetDefault('AdjustFee', True) + feeOpt = TheSettings.getSettingOrSetDefault('FeeOption', DEFAULT_FEE_TYPE) + blocksToConfirm = TheSettings.getSettingOrSetDefault(\ "Default_FeeByte_BlocksToConfirm", NBLOCKS_TO_CONFIRM) def feeRadio(strArg): @@ -505,23 +489,23 @@ def setLblSliderText(): setLblSliderText() self.sliderAutoFee.valueChanged.connect(setLblSliderText) - toolTipAutoFee = self.main.createToolTipWidget(self.tr( + toolTipAutoFee = createToolTipWidget(self.tr( 'Fetch fee/byte from local Bitcoin node. ' 'Defaults to manual fee/byte on failure.')) self.radioFeeByte = QRadioButton(self.tr("Manual fee/byte")) self.connect(self.radioFeeByte, SIGNAL('clicked()'), getCallbck('FeeByte')) self.leFeeByte = QLineEdit(str(feeByte)) - toolTipFeeByte = self.main.createToolTipWidget(self.tr('Values in satoshis/byte')) + toolTipFeeByte = createToolTipWidget(self.tr('Values in satoshis/byte')) self.radioFlatFee = QRadioButton(self.tr("Flat fee")) self.connect(self.radioFlatFee, SIGNAL('clicked()'), getCallbck('FlatFee')) self.leFlatFee = QLineEdit(coin2str(txFee, maxZeros=0)) - toolTipFlatFee = self.main.createToolTipWidget(self.tr('Values in BTC')) + toolTipFlatFee = createToolTipWidget(self.tr('Values in BTC')) self.checkAdjust = QCheckBox(self.tr("Auto-adjust fee/byte for better privacy")) self.checkAdjust.setChecked(adjustFee) - feeToolTip = self.main.createToolTipWidget(self.tr( + feeToolTip = createToolTipWidget(self.tr( 'Auto-adjust fee may increase your total fee using the selected fee/byte rate ' 'as its basis in an attempt to align the amount of digits after the decimal ' 'point between your spend values and change value.' @@ -562,7 +546,7 @@ def setLblSliderText(): def setChangeType(changeType): self.changeType = changeType - self.changeType = self.main.getSettingOrSetDefault('Default_ChangeType', self.wlt.getDefaultAddressType()) + self.changeType = TheSettings.getSettingOrSetDefault('Default_ChangeType', self.wlt.getDefaultAddressType()) self.changeTypeFrame = AddressLabelFrame(self.main, setChangeType, self.wlt.getAddressTypes(), self.changeType) def changeRadio(strArg): @@ -593,7 +577,7 @@ def callbck(): self.radioAutoChange = QRadioButton(self.tr("Auto change")) self.connect(self.radioAutoChange, SIGNAL('clicked()'), changeCallbck('Auto')) - toolTipAutoChange = self.main.createToolTipWidget(self.tr( + toolTipAutoChange = createToolTipWidget(self.tr( "Change address type will match the address type of recipient " "addresses.
" @@ -632,7 +616,7 @@ def callbck(): def setAddrType(addrType): self.addrType = addrType - self.addrType = self.main.getSettingOrSetDefault('Default_ReceiveType', self.wlt.getDefaultAddressType()) + self.addrType = TheSettings.getSettingOrSetDefault('Default_ReceiveType', self.wlt.getDefaultAddressType()) self.addrTypeFrame = AddressLabelFrame(self.main, setAddrType, self.wlt.getAddressTypes(), self.addrType) self.addrTypeFrame.setType(self.addrType) @@ -664,7 +648,7 @@ def accept(self, *args): return if os.path.isfile(pathExe): pathExe = os.path.dirname(pathExe) - self.main.writeSetting('SatoshiExe', pathExe) + TheSettings.set('SatoshiExe', pathExe) else: self.main.settings.delete('SatoshiExe') @@ -679,7 +663,7 @@ def accept(self, *args): 'bitcoind. If you leave this field blank, the following ' 'path will be used:

%s' % BTC_HOME_DIR), QMessageBox.Ok) return - self.main.writeSetting('SatoshiDatadir', pathHome) + TheSettings.set('SatoshiDatadir', pathHome) else: self.main.settings.delete('SatoshiDatadir') @@ -694,16 +678,16 @@ def accept(self, *args): 'If you leave this field blank, the following ' 'path will be used:

%s' % ARMORY_DB_DIR), QMessageBox.Ok) return - self.main.writeSetting('ArmoryDbdir', pathDbdir) + TheSettings.set('ArmoryDbdir', pathDbdir) else: self.main.settings.delete('ArmoryDbdir') - self.main.writeSetting('ManageSatoshi', self.chkManageSatoshi.isChecked()) + TheSettings.set('ManageSatoshi', self.chkManageSatoshi.isChecked()) # Reset the DNAA flag as needed askuriDNAA = self.chkAskURIAtStartup.isChecked() - self.main.writeSetting('DNAA_DefaultApp', not askuriDNAA) + TheSettings.set('DNAA_DefaultApp', not askuriDNAA) if not self.main.setPreferredDateFormat(str(self.edtDateFormat.text())): return @@ -715,32 +699,32 @@ def accept(self, *args): self.main.setLang(LANGUAGES[self.cmbLang.currentIndex()]) if self.chkMinOrClose.isChecked(): - self.main.writeSetting('MinimizeOrClose', 'Minimize') + TheSettings.set('MinimizeOrClose', 'Minimize') else: - self.main.writeSetting('MinimizeOrClose', 'Close') + TheSettings.set('MinimizeOrClose', 'Close') - self.main.writeSetting('MinimizeOnOpen', self.chkMinOnOpen.isChecked()) + TheSettings.set('MinimizeOnOpen', self.chkMinOnOpen.isChecked()) - # self.main.writeSetting('LedgDisplayFee', self.chkInclFee.isChecked()) - self.main.writeSetting('NotifyBtcIn', self.chkBtcIn.isChecked()) - self.main.writeSetting('NotifyBtcOut', self.chkBtcOut.isChecked()) - self.main.writeSetting('NotifyDiscon', self.chkDiscon.isChecked()) - self.main.writeSetting('NotifyReconn', self.chkReconn.isChecked()) + # TheSettings.set('LedgDisplayFee', self.chkInclFee.isChecked()) + TheSettings.set('NotifyBtcIn', self.chkBtcIn.isChecked()) + TheSettings.set('NotifyBtcOut', self.chkBtcOut.isChecked()) + TheSettings.set('NotifyDiscon', self.chkDiscon.isChecked()) + TheSettings.set('NotifyReconn', self.chkReconn.isChecked()) #fee - self.main.writeSetting('FeeOption', self.feeOpt) - self.main.writeSetting('Default_FeeByte', str(self.leFeeByte.text())) - self.main.writeSetting('Default_Fee', str2coin(str(self.leFlatFee.text()))) - self.main.writeSetting('AdjustFee', self.checkAdjust.isChecked()) - self.main.writeSetting('Default_FeeByte_BlocksToConfirm', + TheSettings.set('FeeOption', self.feeOpt) + TheSettings.set('Default_FeeByte', str(self.leFeeByte.text())) + TheSettings.set('Default_Fee', str2coin(str(self.leFlatFee.text()))) + TheSettings.set('AdjustFee', self.checkAdjust.isChecked()) + TheSettings.set('Default_FeeByte_BlocksToConfirm', self.sliderAutoFee.value()) #change - self.main.writeSetting('Default_ChangeType', self.changeType) + TheSettings.set('Default_ChangeType', self.changeType) #addr type - self.main.writeSetting('Default_ReceiveType', self.addrType) + TheSettings.set('Default_ReceiveType', self.addrType) DEFAULT_ADDR_TYPE = self.addrType try: diff --git a/qtdialogs/DlgShowKeyList.py b/qtdialogs/DlgShowKeyList.py index d85d6f539..0975fae49 100644 --- a/qtdialogs/DlgShowKeyList.py +++ b/qtdialogs/DlgShowKeyList.py @@ -288,7 +288,7 @@ def fmtBin(s, nB=4, sw=False): def saveToFile(self): if self.havePriv: - if not self.main.getSettingOrSetDefault('DNAA_WarnPrintKeys', False): + if not TheSettings.getSettingOrSetDefault('DNAA_WarnPrintKeys', False): result = MsgBoxWithDNAA(self, self.main, MSGBOX.Warning, title=self.tr('Plaintext Private Keys'), \ msg=self.tr('REMEMBER: The data you ' 'are about to save contains private keys. Please make sure ' @@ -297,7 +297,7 @@ def saveToFile(self): dnaaMsg=None, wCancel=True) if not result[0]: return - self.main.writeSetting('DNAA_WarnPrintKeys', result[1]) + TheSettings.set('DNAA_WarnPrintKeys', result[1]) wltID = self.wlt.uniqueIDB58 fn = self.main.getFileSave(title=self.tr('Save Key List'), \ diff --git a/qtdialogs/DlgUniversalRestoreSelect.py b/qtdialogs/DlgUniversalRestoreSelect.py index 6759db3db..6e74b8427 100644 --- a/qtdialogs/DlgUniversalRestoreSelect.py +++ b/qtdialogs/DlgUniversalRestoreSelect.py @@ -15,9 +15,8 @@ from armoryengine.ArmoryUtils import LOGINFO from qtdialogs.qtdefines import HLINE, QRichLabel from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.DlgRestoreSingle import DlgRestoreSingle -from qtdialogs.DlgRestoreFragged import DlgRestoreFragged -from qtdialogs.DlgRestoreWOData import DlgRestoreWOData +from qtdialogs.DlgRestore import DlgRestoreSingle, DlgRestoreFragged, \ + DlgRestoreWOData ################################################################################ class DlgUniversalRestoreSelect(ArmoryDialog): diff --git a/qtdialogs/DlgUnlockWallet.py b/qtdialogs/DlgUnlockWallet.py index 40dfa3a7c..bd39558fd 100644 --- a/qtdialogs/DlgUnlockWallet.py +++ b/qtdialogs/DlgUnlockWallet.py @@ -4,33 +4,31 @@ # Distributed under the GNU Affero General Public License (AGPL v3) # # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # -# Copyright (C) 2016-2022, goatpig # +# Copyright (C) 2016-2023, goatpig # # Distributed under the MIT license # # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # ############################################################################## -from armoryengine.CppBridge import TheBridge - from PySide2.QtWidgets import QFrame, QVBoxLayout, QGridLayout, \ QPushButton, QLabel, QLineEdit, QDialogButtonBox, QButtonGroup, \ - QRadioButton, QSizePolicy, QLayout + QRadioButton, QSizePolicy, QLayout, QMessageBox + +from ui.QtExecuteSignal import TheSignalExecution +from armoryengine.CppBridge import ServerPush -from qtdialogs.qtdefines import makeHorizFrame from qtdialogs.ArmoryDialog import ArmoryDialog -from qtdialogs.qtdialogs import STRETCH, MIN_PASSWD_WIDTH, LetterButton +from armoryengine.Settings import TheSettings +from qtdialogs.qtdefines import makeHorizFrame, STRETCH, \ + MIN_PASSWD_WIDTH, LetterButton, createToolTipWidget + ################################################################################ class DlgUnlockWallet(ArmoryDialog): - def __init__(self, promptId, wltID, parent=None, main=None, \ - unlockMsg='Unlock Wallet', returnResult=False): + def __init__(self, wltID, parent=None, main=None, unlockMsg='Unlock'): super(DlgUnlockWallet, self).__init__(parent, main) - self.wltID = wltID - self.returnResult = returnResult - self.promptId = promptId - ##### Upper layout lblDescr = QLabel(self.tr("Enter your passphrase to unlock this wallet")) lblPasswd = QLabel(self.tr("Passphrase:")) @@ -57,7 +55,7 @@ def __init__(self, promptId, wltID, parent=None, main=None, \ ##### Lower layout # Add scrambled keyboard (EN-US only) - ttipScramble = self.main.createToolTipWidget(\ + ttipScramble = createToolTipWidget(\ self.tr('Using a visual keyboard to enter your passphrase ' 'protects you against simple keyloggers. Scrambling ' 'makes it difficult to use, but prevents even loggers ' @@ -72,7 +70,7 @@ def __init__(self, promptId, wltID, parent=None, main=None, \ btngrp.addButton(self.rdoScrambleLite) btngrp.addButton(self.rdoScrambleFull) btngrp.setExclusive(True) - defaultScramble = self.main.getSettingOrSetDefault('ScrambleDefault', 0) + defaultScramble = TheSettings.getSettingOrSetDefault('ScrambleDefault', 0) if defaultScramble == 0: self.rdoScrambleNone.setChecked(True) elif defaultScramble == 1: @@ -91,7 +89,7 @@ def __init__(self, promptId, wltID, parent=None, main=None, \ self.frmKeyboard = QFrame() self.frmKeyboard.setLayout(self.layoutKeyboard) - showOSD = self.main.getSettingOrSetDefault('KeybdOSD', False) + showOSD = TheSettings.getSettingOrSetDefault('KeybdOSD', False) self.layoutLower = QGridLayout() self.layoutLower.addWidget(btnRowFrm , 0, 0) self.layoutLower.addWidget(self.frmKeyboard , 1, 0) @@ -123,18 +121,18 @@ def __init__(self, promptId, wltID, parent=None, main=None, \ self.changeScramble() self.redrawKeys() + self.encryptionKeyIds = [] ############################################################################# def toggleOSD(self, *args): isChk = self.btnShowOSD.isChecked() - self.main.settings.set('KeybdOSD', isChk) + TheSettings.set('KeybdOSD', isChk) self.frmLower.setVisible(isChk) if isChk: self.btnShowOSD.setText(self.tr('Hide Keyboard <<<')) else: self.btnShowOSD.setText(self.tr('Show Keyboard >>>')) - ############################################################################# def createKeyboardKeyButton(self, keyLow, keyUp, defRow, special=None): theBtn = LetterButton(keyLow, keyUp, defRow, special, self.edtPasswd, self) @@ -142,7 +140,6 @@ def createKeyboardKeyButton(self, keyLow, keyUp, defRow, special=None): theBtn.setMaximumWidth(40) return theBtn - ############################################################################# def redrawKeys(self): for btn in self.btnList: @@ -269,7 +266,7 @@ def changeScramble(self): self.frmKeyboard.setLayout(self.layoutKeyboard) self.layoutLower.addWidget(self.frmKeyboard, 1, 0) - self.main.settings.set('ScrambleDefault', opt) + TheSettings.set('ScrambleDefault', opt) self.redrawKeys() ############################################################################# @@ -287,30 +284,64 @@ def completed(self): def acceptPassphrase(self): passphraseStr = str(self.edtPasswd.text()) - if self.returnResult: - self.edtPasswd.setText('') - self.accept() - return - - TheBridge.returnPassphrase(self.promptId, passphraseStr) + self.reply(passphraseStr) passphraseStr = '' ############################################################################# def rejectPassphrase(self): self.edtPasswd.setText('') - TheBridge.returnPassphrase(self.promptId, "") + self.reply("") self.reject() ############################################################################# def accept(self): self.edtPasswd.setText('') - if self.parent != None: - self.parent.cleanupPrompt(self.promptId) - super(ArmoryDialog, self).accept() + super().accept() ############################################################################# def reject(self): self.edtPasswd.setText('') - if self.parent != None: - self.parent.cleanupPrompt(self.promptId) - super(ArmoryDialog, self).reject() \ No newline at end of file + super().reject() + + ############################################################################# + def reply(self, passphrase): + raise Exception("override me") + + ############################################################################# + def setIds(self, ids): + print (f"set ids: {ids}, self.encryptionKeyIds: {self.encryptionKeyIds}") + if len(ids) == 0: + self.reject() + elif len(self.encryptionKeyIds) == 0: + self.encryptionKeyIds = ids + self.exec_() + elif self.encryptionKeyIds != ids: + raise Exception("encryption key ids mismtach") + else: + self.recycle() + self.show() + + +################################################################################ +class UnlockWalletHandler(ServerPush, DlgUnlockWallet): + def __init__(self, wltId, title, parent): + ServerPush.__init__(self) + DlgUnlockWallet.__init__(self, main=parent, parent=parent, + wltID=wltId, unlockMsg=title) + + ############################################################################# + def parseProtoPacket(self, protoPacket): + def processPacket(theDialog, protoPacket): + if protoPacket.HasField('cleanup'): + theDialog.reject() + return + elif protoPacket.HasField('unlock_request'): + theDialog.setIds(protoPacket.unlock_request.encryption_key_ids) + TheSignalExecution.executeMethod(processPacket, self, protoPacket) + + ############################################################################# + def reply(self, passphrase): + packet = self.getNewPacket() + packet.success = bool(len(passphrase) != 0) + packet.passphrase = passphrase + super().reply() diff --git a/qtdialogs/DlgWalletDetails.py b/qtdialogs/DlgWalletDetails.py index 46a8477f0..1e5e1c813 100644 --- a/qtdialogs/DlgWalletDetails.py +++ b/qtdialogs/DlgWalletDetails.py @@ -17,6 +17,7 @@ from armoryengine.ArmoryUtils import getVersionString, coin2str, isASCII from armoryengine.BDM import TheBDM, BDM_UNINITIALIZED, BDM_OFFLINE, \ BDM_SCANNING +from armoryengine.Settings import TheSettings from armorycolors import htmlColor from ui.TreeViewGUI import AddressTreeModel @@ -24,17 +25,19 @@ relaxedSizeNChar, relaxedSizeStr, QLabelButton, STYLE_SUNKEN, STYLE_NONE, \ QRichLabel, makeHorizFrame, restoreTableView, WLTTYPES, \ WLTFIELDS, tightSizeStr, saveTableView, tightSizeNChar, \ - UnicodeErrorBox + UnicodeErrorBox, STRETCH, createToolTipWidget, MSGBOX from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA -from qtdialogs.qtdialogs import STRETCH, LoadingDisp +from qtdialogs.qtdialogs import LoadingDisp from qtdialogs.DlgNewAddress import \ DlgNewAddressDisp, ShowRecvCoinsWarningIfNecessary from qtdialogs.DlgKeypoolSettings import DlgKeypoolSettings from qtdialogs.DlgSendBitcoins import DlgSendBitcoins from qtdialogs.DlgBackupCenter import DlgBackupCenter +from qtdialogs.DlgAddressInfo import DlgAddressInfo +from qtdialogs.DlgRestore import OpenPaperBackupDialog ################################################################################ @@ -195,12 +198,12 @@ def createVBoxSeparator(): self.lblBTC2 = QRichLabel('', doWrap=False) self.lblBTC3 = QRichLabel('', doWrap=False) - ttipTot = self.main.createToolTipWidget(\ + ttipTot = createToolTipWidget(\ self.tr('Total funds if all current transactions are confirmed. ' 'Value appears gray when it is the same as your spendable funds.')) - ttipSpd = self.main.createToolTipWidget(\ + ttipSpd = createToolTipWidget(\ self.tr('Funds that can be spent right now')) - ttipUcn = self.main.createToolTipWidget(\ + ttipUcn = createToolTipWidget(\ self.tr('Funds that have less than 6 confirmations')) self.setSummaryBalances() @@ -252,8 +255,8 @@ def createVBoxSeparator(): #self.doFilterAddr() - hexgeom = self.main.settings.get('WltPropGeometry') - tblgeom = self.main.settings.get('WltPropAddrCols') + hexgeom = TheSettings.get('WltPropGeometry') + tblgeom = TheSettings.get('WltPropAddrCols') if len(hexgeom) > 0: if type(hexgeom) == bytes: @@ -290,10 +293,10 @@ def remindBackup(): wltType = determineWalletType(wlt, main)[0] - chkLoad = (self.main.getSettingOrSetDefault('Load_Count', 1) % 5 == 0) + chkLoad = (TheSettings.getSettingOrSetDefault('Load_Count', 1) % 5 == 0) chkType = not wltType in (WLTTYPES.Offline, WLTTYPES.WatchOnly) chkDNAA = not self.main.getWltSetting(wlt.uniqueIDB58, 'DNAA_RemindBackup') - chkDont = not self.main.getSettingOrSetDefault('DNAA_AllBackupWarn', False) + chkDont = not TheSettings.getSettingOrSetDefault('DNAA_AllBackupWarn', False) if chkLoad and chkType and chkDNAA and chkDont: self.callLater(1, remindBackup) lbtnBackups.setText(self.tr('Backup This Wallet' % htmlColor('TextWarn'))) @@ -340,8 +343,8 @@ def setSummaryBalances(self): ############################################################################# def saveGeometrySettings(self): geom = self.saveGeometry().data().hex() - self.main.writeSetting('WltPropGeometry', geom) - self.main.writeSetting('WltPropAddrCols', saveTableView(self.wltAddrView)) + TheSettings.set('WltPropGeometry', geom) + TheSettings.set('WltPropAddrCols', saveTableView(self.wltAddrView)) ############################################################################# def closeEvent(self, event): @@ -472,7 +475,7 @@ def changeLabels(self): # but guarantees the file is updated, too newName = str(dlgLabels.edtName.text())[:32] newDescr = str(dlgLabels.edtDescr.toPlainText())[:256] - self.wlt.setWalletLabels(newName, newDescr) + self.wlt.setLabels(newName, newDescr) self.labelValues[WLTFIELDS.Name].setText(newName) self.labelValues[WLTFIELDS.Descr].setText(newDescr) @@ -582,7 +585,7 @@ def execPrintDlg(self): self.tr('This wallet does not contain any private keys. Nothing to backup!'), QMessageBox.Ok) return - OpenPaperBackupWindow('Single', self, self.main, self.wlt) + OpenPaperBackupDialog('Single', self, self.main, self.wlt) def execRemoveDlg(self): @@ -637,7 +640,7 @@ def execDeleteAddress(self): def execImportAddress(self): - if not self.main.getSettingOrSetDefault('DNAA_ImportWarning', False): + if not TheSettings.getSettingOrSetDefault('DNAA_ImportWarning', False): result = MsgBoxWithDNAA(self, self.main, MSGBOX.Warning, \ self.tr('Imported Address Warning'), self.tr( 'Armory supports importing of external private keys into your ' @@ -649,7 +652,7 @@ def execImportAddress(self): 'Individual private keys, including imported ones, can be ' 'backed up using the "Export Key Lists" option in the wallet ' 'backup window.'), None) - self.main.writeSetting('DNAA_ImportWarning', result[1]) + TheSettings.set('DNAA_ImportWarning', result[1]) # Now we are past the [potential] warning box. Actually open # the import dialog @@ -682,12 +685,12 @@ def execExpWOCopy(self): ############################################################################# def setWltDetailsFrame(self): - dispCrypto = self.wlt.useEncryption and (self.usermode == USERMODE.Advanced or \ - self.usermode == USERMODE.Expert) + dispCrypto = self.wlt.useEncryption and \ + self.usermode in [USERMODE.Advanced, USERMODE.Expert] self.wltID = self.wlt.uniqueIDB58 if dispCrypto: - mem = self.wlt.kdf.getMemoryReqtBytes() + mem = self.wlt.getKdfMemoryReqtBytes() kdfmemstr = str(mem / 1024) + ' kB' if mem >= 1024 * 1024: kdfmemstr = str(mem / (1024 * 1024)) + ' MB' @@ -695,29 +698,29 @@ def setWltDetailsFrame(self): tooltips = [[]] * 10 - tooltips[WLTFIELDS.Name] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Name] = createToolTipWidget(self.tr( 'This is the name stored with the wallet file. Click on the ' '"Change Labels" button on the right side of this ' 'window to change this field')) - tooltips[WLTFIELDS.Descr] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Descr] = createToolTipWidget(self.tr( 'This is the description of the wallet stored in the wallet file. ' 'Press the "Change Labels" button on the right side of this ' 'window to change this field')) - tooltips[WLTFIELDS.WltID] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.WltID] = createToolTipWidget(self.tr( 'This is a unique identifier for this wallet, based on the root key. ' 'No other wallet can have the same ID ' 'unless it is a copy of this one, regardless of whether ' 'the name and description match.')) - tooltips[WLTFIELDS.NumAddr] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.NumAddr] = createToolTipWidget(self.tr( 'This is the number of addresses *used* by this wallet so far. ' 'If you recently restored this wallet and you do not see all the ' 'funds you were expecting, click on this field to increase it.')) if self.typestr == 'Offline': - tooltips[WLTFIELDS.Secure] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Secure] = createToolTipWidget(self.tr( 'Offline: This is a "Watching-Only" wallet that you have identified ' 'belongs to you, but you cannot spend any of the wallet funds ' 'using this wallet. This kind of wallet ' @@ -725,41 +728,41 @@ def setWltDetailsFrame(self): 'incoming transactions, but the private keys needed ' 'to spend the money are stored on an offline computer.')) elif self.typestr == 'Watching-Only': - tooltips[WLTFIELDS.Secure] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Secure] = createToolTipWidget(self.tr( 'Watching-Only: You can only watch addresses in this wallet ' 'but cannot spend any of the funds.')) elif self.typestr == 'No Encryption': - tooltips[WLTFIELDS.Secure] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Secure] = createToolTipWidget(self.tr( 'No Encryption: This wallet contains private keys, and does not require ' 'a passphrase to spend funds available to this wallet. If someone ' 'else obtains a copy of this wallet, they can also spend your funds! ' '(You can click the "Change Encryption" button on the right side of this ' 'window to enabled encryption)')) elif self.typestr == 'Encrypted (AES256)': - tooltips[WLTFIELDS.Secure] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Secure] = createToolTipWidget(self.tr( 'This wallet contains the private keys needed to spend this wallet\'s ' 'funds, but they are encrypted on your harddrive. The wallet must be ' '"unlocked" with the correct passphrase before you can spend any of the ' 'funds. You can still generate new addresses and monitor incoming ' 'transactions, even with a locked wallet.')) - tooltips[WLTFIELDS.BelongsTo] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.BelongsTo] = createToolTipWidget(self.tr( 'Declare who owns this wallet. If you click on the field and select ' '"This wallet is mine", it\'s balance will be included in your total ' 'Armory Balance in the main window')) - tooltips[WLTFIELDS.Time] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Time] = createToolTipWidget(self.tr( 'This is exactly how long it takes your computer to unlock your ' 'wallet after you have entered your passphrase. If someone got ' 'ahold of your wallet, this is approximately how long it would take ' 'them to for each guess of your passphrase.')) - tooltips[WLTFIELDS.Mem] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Mem] = createToolTipWidget(self.tr( 'This is the amount of memory required to unlock your wallet. ' 'Memory values above 64 kB pretty much guarantee that GPU-acceleration ' 'will be useless for guessing your passphrase')) - tooltips[WLTFIELDS.Version] = self.main.createToolTipWidget(self.tr( + tooltips[WLTFIELDS.Version] = createToolTipWidget(self.tr( 'Wallets created with different versions of Armory, may have ' 'different wallet versions. Not all functionality may be ' 'available with all wallet versions. Creating a new wallet will ' @@ -852,7 +855,7 @@ def setWltDetailsFrame(self): # Not sure why this has to be connected downhere... it didn't work above it if dispCrypto: self.labelValues[WLTFIELDS.Time].setAlignment(Qt.AlignLeft | Qt.AlignVCenter) - self.connect(self.labelValues[WLTFIELDS.Time], SIGNAL(CLICKED), self.testKdfTime) + self.labelValues[WLTFIELDS.Time].linkActivated.connect(self.testKdfTime) labelNames[WLTFIELDS.Descr].setAlignment(Qt.AlignLeft | Qt.AlignTop) self.labelValues[WLTFIELDS.Descr].setWordWrap(True) @@ -985,7 +988,7 @@ def __init__(self, wltID, parent=None, main=None): layout.addWidget(lblDescr, 0, 0, 1, 2) layout.addWidget(self.chkIsMine, 1, 0) - ttip = self.main.createToolTipWidget(self.tr( + ttip = createToolTipWidget(self.tr( 'You might choose this option if you keep a full ' 'wallet on a non-internet-connected computer, and use this ' 'watching-only wallet on this computer to generate addresses ' diff --git a/qtdialogs/QRCodeWidget.py b/qtdialogs/QRCodeWidget.py index 6966124f6..0522381ea 100644 --- a/qtdialogs/QRCodeWidget.py +++ b/qtdialogs/QRCodeWidget.py @@ -10,14 +10,15 @@ # # ############################################################################## -from PySide2.QtCore import QSize +from PySide2.QtCore import Qt, QSize from PySide2.QtGui import QPainter, QColor -from PySide2.QtWidgets import QWidget +from PySide2.QtWidgets import QWidget, QApplication, QVBoxLayout from ui.QrCodeMatrix import CreateQRMatrix - from armoryengine.ArmoryUtils import LOGERROR -from qtdialogs.DlgInflatedQR import DlgInflatedQR + +from qtdialogs.qtdefines import makeHorizFrame, makeVertFrame, QRichLabel +from qtdialogs.ArmoryDialog import ArmoryDialog class QRCodeWidget(QWidget): @@ -100,3 +101,29 @@ def drawWidget(self, qp): def mouseDoubleClickEvent(self, *args): DlgInflatedQR(self.parent, self.theData).exec_() + +class DlgInflatedQR(ArmoryDialog): + def __init__(self, parent, dataToQR): + super(DlgInflatedQR, self).__init__(parent, parent.main) + + sz = QApplication.desktop().size() + w,h = sz.width(), sz.height() + qrSize = int(min(w,h)*0.8) + qrDisp = QRCodeWidget(dataToQR, prefSize=qrSize) + + def closeDlg(*args): + self.accept() + qrDisp.mouseDoubleClickEvent = closeDlg + self.mouseDoubleClickEvent = closeDlg + + lbl = QRichLabel(self.tr('Double-click or press ESC to close')) + lbl.setAlignment(Qt.AlignTop | Qt.AlignHCenter) + + frmQR = makeHorizFrame(['Stretch', qrDisp, 'Stretch']) + frmFull = makeVertFrame(['Stretch',frmQR, lbl, 'Stretch']) + + layout = QVBoxLayout() + layout.addWidget(frmFull) + + self.setLayout(layout) + self.showFullScreen() diff --git a/qtdialogs/qtdefines.py b/qtdialogs/qtdefines.py index 2f26ca43c..9fb45d300 100755 --- a/qtdialogs/qtdefines.py +++ b/qtdialogs/qtdefines.py @@ -14,6 +14,7 @@ import os import struct from tempfile import mkstemp +import urllib from PySide2.QtCore import Qt, QAbstractTableModel, QModelIndex, \ QSortFilterProxyModel, QEvent, Signal, SIGNAL, QSize @@ -22,9 +23,8 @@ from PySide2.QtWidgets import QWidget, QDialog, QFrame, QLabel, \ QStyledItemDelegate, QTableView, QHBoxLayout, QLayoutItem, \ QVBoxLayout, QCheckBox, QDialogButtonBox, QPushButton, \ - QSpacerItem, QSizePolicy, QGridLayout, QApplication, QRadioButton - -import urllib + QSpacerItem, QSizePolicy, QGridLayout, QApplication, QRadioButton, \ + QLineEdit from armoryengine.ArmoryUtils import enum, ARMORY_HOME_DIR, OS_MACOSX, \ USE_TESTNET, USE_REGTEST, OS_WINDOWS, coin2str, int_to_hex, toBytes, \ @@ -56,9 +56,10 @@ HORIZONTAL = 'horizontal' CHANGE_ADDR_DESCR_STRING = '[[ Change received ]]' -# Keep track of dialogs and wizard that are executing STRETCH = 'Stretch' +MIN_PASSWD_WIDTH = lambda obj: tightSizeStr(obj, '*' * 16)[0] +# Keep track of dialogs and wizard that are executing runningDialogsList = [] def AddToRunningDialogsList(func): @@ -712,4 +713,157 @@ def createDirectorySelectButton(parent, targetWidget, title="Select Directory"): fn = lambda: selectDirectoryForQLineEdit(parent, targetWidget, title) parent.connect(btn, SIGNAL('clicked()'), fn) - return btn \ No newline at end of file + return btn + +############################################################################# +class LetterButton(QPushButton): + def __init__(self, Low, Up, Row, Spec, edtTarget, parent): + super(LetterButton, self).__init__('') + self.lower = Low + self.upper = Up + self.defRow = Row + self.special = Spec + self.target = edtTarget + self.parent = parent + + if self.special: + super(LetterButton, self).setFont(GETFONT('Var', 8)) + else: + super(LetterButton, self).setFont(GETFONT('Fixed', 10)) + if self.special == 'space': + self.setText(self.tr('SPACE')) + self.lower = ' ' + self.upper = ' ' + self.special = 5 + elif self.special == 'shift': + self.setText(self.tr('SHIFT')) + self.special = 5 + self.insertLetter = self.pressShift + elif self.special == 'delete': + self.setText(self.tr('DEL')) + self.special = 5 + self.insertLetter = self.pressBackspace + + def insertLetter(self): + currPwd = str(self.parent.edtPasswd.text()) + insChar = self.upper if self.parent.btnShift.isChecked() else self.lower + if len(insChar) == 2 and insChar.startswith('#'): + insChar = insChar[1] + + self.parent.edtPasswd.setText(currPwd + insChar) + self.parent.reshuffleKeys() + + def pressShift(self): + self.parent.redrawKeys() + + def pressBackspace(self): + currPwd = str(self.parent.edtPasswd.text()) + if len(currPwd) > 0: + self.parent.edtPasswd.setText(currPwd[:-1]) + self.parent.redrawKeys() + +############################################################################# +def createToolTipWidget(tiptext, iconSz=2): + """ + The is to signal to Qt that it should be interpretted as HTML/Rich + text even if no HTML tags are used. This appears to be necessary for Qt + to wrap the tooltip text + """ + fgColor = htmlColor('ToolTipQ') + lbl = QLabel('(?)' % (iconSz, fgColor)) + lbl.setMaximumWidth(int(relaxedSizeStr(lbl, '(?)')[0])) + + def setAllText(wself, txt): + def pressEv(ev): + QWhatsThis.showText(ev.globalPos(), txt, self) + wself.mousePressEvent = pressEv + wself.setToolTip('' + txt) + + # Calling setText on this widget will update both the tooltip and QWT + from types import MethodType + lbl.setText = MethodType(setAllText, lbl) + lbl.setText(tiptext) + return lbl + +############################################################################# +class AdvancedOptionsFrame(ArmoryFrame): + def __init__(self, parent, main, initLabel=''): + super(AdvancedOptionsFrame, self).__init__(parent, main) + lblComputeDescription = QRichLabel( \ + self.tr('Armory will test your system\'s speed to determine the most ' + 'challenging encryption settings that can be applied ' + 'in a given amount of time. High settings make it much harder ' + 'for someone to guess your passphrase. This is used for all ' + 'encrypted wallets, but the default parameters can be changed below.\n')) + lblComputeDescription.setWordWrap(True) + timeDescriptionTip = createToolTipWidget( \ + self.tr('This is the amount of time it will take for your computer ' + 'to unlock your wallet after you enter your passphrase. ' + '(the actual time used will be less than the specified ' + 'time, but more than one half of it). ')) + + # Set maximum compute time + self.editComputeTime = QLineEdit() + self.editComputeTime.setText('250 ms') + self.editComputeTime.setMaxLength(12) + lblComputeTime = QLabel(self.tr('Target compute &time (s, ms):')) + memDescriptionTip = createToolTipWidget( \ + self.tr('This is the maximum memory that will be ' + 'used as part of the encryption process. The actual value used ' + 'may be lower, depending on your system\'s speed. If a ' + 'low value is chosen, Armory will compensate by chaining ' + 'together more calculations to meet the target time. High ' + 'memory target will make GPU-acceleration useless for ' + 'guessing your passphrase.')) + lblComputeTime.setBuddy(self.editComputeTime) + + # Set maximum memory usage + self.editComputeMem = QLineEdit() + self.editComputeMem.setText('32.0 MB') + self.editComputeMem.setMaxLength(12) + lblComputeMem = QLabel(self.tr('Max &memory usage (kB, MB):')) + lblComputeMem.setBuddy(self.editComputeMem) + + self.editComputeTime.setMaximumWidth( tightSizeNChar(self, 20)[0] ) + self.editComputeMem.setMaximumWidth( tightSizeNChar(self, 20)[0] ) + + entryFrame = QFrame() + entryLayout = QGridLayout() + entryLayout.addWidget(timeDescriptionTip, 0, 0, 1, 1) + entryLayout.addWidget(lblComputeTime, 0, 1, 1, 1) + entryLayout.addWidget(self.editComputeTime, 0, 2, 1, 1) + entryLayout.addWidget(memDescriptionTip, 1, 0, 1, 1) + entryLayout.addWidget(lblComputeMem, 1, 1, 1, 1) + entryLayout.addWidget(self.editComputeMem, 1, 2, 1, 1) + entryFrame.setLayout(entryLayout) + layout = QVBoxLayout() + layout.addWidget(lblComputeDescription) + layout.addWidget(entryFrame) + layout.addStretch() + self.setLayout(layout) + + def getKdfSec(self): + # return -1 if the input is invalid + kdfSec = -1 + try: + kdfT, kdfUnit = str(self.editComputeTime.text()).strip().split(' ') + if kdfUnit.lower() == 'ms': + kdfSec = float(kdfT) / 1000. + elif kdfUnit.lower() in ('s', 'sec', 'seconds'): + kdfSec = float(kdfT) + except: + pass + return kdfSec + + def getKdfBytes(self): + # return -1 if the input is invalid + kdfBytes = -1 + try: + kdfM, kdfUnit = str(self.editComputeMem.text()).split(' ') + if kdfUnit.lower() == 'mb': + kdfBytes = round(float(kdfM) * (1024.0 ** 2)) + elif kdfUnit.lower() == 'kb': + kdfBytes = round(float(kdfM) * (1024.0)) + except: + pass + return kdfBytes diff --git a/qtdialogs/qtdialogs.py b/qtdialogs/qtdialogs.py index e0531e2b2..8d8377dda 100755 --- a/qtdialogs/qtdialogs.py +++ b/qtdialogs/qtdialogs.py @@ -38,7 +38,6 @@ from ui.TreeViewGUI import AddressTreeModel from ui.QrCodeMatrix import CreateQRMatrix from armoryengine.Block import PyBlockHeader -from armoryengine import ClientProto_pb2 from qtdialogs.qtdefines import USERMODE, GETFONT, tightSizeStr, \ determineWalletType, WLTTYPES, MSGBOX, QRichLabel @@ -47,8 +46,6 @@ from qtdialogs.MsgBoxCustom import MsgBoxCustom NO_CHANGE = 'NoChange' -MIN_PASSWD_WIDTH = lambda obj: tightSizeStr(obj, '*' * 16)[0] -STRETCH = 'Stretch' BACKUP_TYPE_135A = '1.35a' BACKUP_TYPE_135C = '1.35c' BACKUP_TYPE_0_TEXT = 'Version 0 (from script, 9 lines)' @@ -59,53 +56,6 @@ MAX_QR_SIZE = 198 MAX_SATOSHIS = 2100000000000000 -############################################################################# -class LetterButton(QPushButton): - def __init__(self, Low, Up, Row, Spec, edtTarget, parent): - super(LetterButton, self).__init__('') - self.lower = Low - self.upper = Up - self.defRow = Row - self.special = Spec - self.target = edtTarget - self.parent = parent - - if self.special: - super(LetterButton, self).setFont(GETFONT('Var', 8)) - else: - super(LetterButton, self).setFont(GETFONT('Fixed', 10)) - if self.special == 'space': - self.setText(self.tr('SPACE')) - self.lower = ' ' - self.upper = ' ' - self.special = 5 - elif self.special == 'shift': - self.setText(self.tr('SHIFT')) - self.special = 5 - self.insertLetter = self.pressShift - elif self.special == 'delete': - self.setText(self.tr('DEL')) - self.special = 5 - self.insertLetter = self.pressBackspace - - def insertLetter(self): - currPwd = str(self.parent.edtPasswd.text()) - insChar = self.upper if self.parent.btnShift.isChecked() else self.lower - if len(insChar) == 2 and insChar.startswith('#'): - insChar = insChar[1] - - self.parent.edtPasswd.setText(currPwd + insChar) - self.parent.reshuffleKeys() - - def pressShift(self): - self.parent.redrawKeys() - - def pressBackspace(self): - currPwd = str(self.parent.edtPasswd.text()) - if len(currPwd) > 0: - self.parent.edtPasswd.setText(currPwd[:-1]) - self.parent.redrawKeys() - ################################################################################ class DlgGenericGetPassword(ArmoryDialog): def __init__(self, descriptionStr, parent=None, main=None): @@ -1266,401 +1216,6 @@ def __init__(self, addrList, wlt, parent=None, main=None): self.setWindowTitle(self.tr('Duplicate Addresses')) - - - -############################################################################# -class DlgAddressInfo(ArmoryDialog): - def __init__(self, wlt, addrObj, parent=None, main=None, mode=None): - super(DlgAddressInfo, self).__init__(parent, main) - - self.wlt = wlt - self.addrObj = addrObj - addr160 = addrObj.getPrefixedAddr() - - self.ledgerTable = [] - - self.mode = mode - if mode == None: - if main == None: - self.mode = USERMODE.Standard - else: - self.mode = self.main.usermode - - - dlgLayout = QGridLayout() - addrStr = addrObj.getAddressString() - - frmInfo = QFrame() - frmInfo.setFrameStyle(STYLE_RAISED) - frmInfoLayout = QGridLayout() - - lbls = [] - - # Hash160 - if mode in (USERMODE.Advanced, USERMODE.Expert): - bin25 = base58_to_binary(addrStr) - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(\ - self.tr('This is the computer-readable form of the address'))) - lbls[-1].append(QRichLabel(self.tr('Public Key Hash'))) - h160Str = binary_to_hex(bin25[1:-4]) - if mode == USERMODE.Expert: - network = binary_to_hex(bin25[:1 ]) - hash160 = binary_to_hex(bin25[ 1:-4 ]) - addrChk = binary_to_hex(bin25[ -4:]) - h160Str += self.tr('%s (Network: %s / Checksum: %s)' % (hash160, network, addrChk)) - lbls[-1].append(QLabel(h160Str)) - - - - lbls.append([]) - lbls[-1].append(QLabel('')) - lbls[-1].append(QRichLabel(self.tr('Wallet:'))) - lbls[-1].append(QLabel(self.wlt.labelName)) - - lbls.append([]) - lbls[-1].append(QLabel('')) - lbls[-1].append(QRichLabel(self.tr('Address:'))) - lbls[-1].append(QLabel(addrStr)) - - - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(self.tr( - 'Address type is either Imported or Permanent. ' - 'Permanent ' - 'addresses are part of the base wallet, and are protected by printed ' - 'paper backups, regardless of when the backup was performed. ' - 'Imported addresses are only protected by digital backups, or manually ' - 'printing the individual keys list, and only if the wallet was backed up ' - 'after the keys were imported.'))) - - lbls[-1].append(QRichLabel(self.tr('Address Type:'))) - if self.addrObj.chainIndex == -2: - lbls[-1].append(QLabel(self.tr('Imported'))) - else: - lbls[-1].append(QLabel(self.tr('Permanent'))) - - # TODO: fix for BIP-32 - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( - self.tr('The index of this address within the wallet.'))) - lbls[-1].append(QRichLabel(self.tr('Index:'))) - if self.addrObj.chainIndex > -1: - lbls[-1].append(QLabel(str(self.addrObj.chainIndex+1))) - else: - lbls[-1].append(QLabel(self.tr("Imported"))) - - - # Current Balance of address - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(self.tr( - 'This is the current spendable balance of this address, ' - 'not including zero-confirmation transactions from others.'))) - lbls[-1].append(QRichLabel(self.tr('Current Balance'))) - try: - balCoin = addrObj.getSpendableBalance() - balStr = coin2str(balCoin, maxZeros=1) - if balCoin > 0: - goodColor = htmlColor('MoneyPos') - lbls[-1].append(QRichLabel(\ - '' + balStr.strip() + ' BTC')) - else: - lbls[-1].append(QRichLabel(balStr.strip() + ' BTC')) - except: - lbls[-1].append(QRichLabel("N/A")) - - - lbls.append([]) - lbls[-1].append(QLabel('')) - lbls[-1].append(QRichLabel(self.tr('Comment:'))) - if self.addrObj.chainIndex > -1: - lbls[-1].append(QLabel(str(wlt.commentsMap[addr160]) if addr160 in wlt.commentsMap else '')) - else: - lbls[-1].append(QLabel('')) - - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget( - self.tr('The total number of transactions in which this address was involved'))) - lbls[-1].append(QRichLabel(self.tr('Transaction Count:'))) - #lbls[-1].append(QLabel(str(len(txHashes)))) - try: - txnCount = self.addrObj.getTxioCount() - lbls[-1].append(QLabel(str(txnCount))) - except: - lbls[-1].append(QLabel("N/A")) - - - for i in range(len(lbls)): - for j in range(1, 3): - lbls[i][j].setTextInteractionFlags(Qt.TextSelectableByMouse | \ - Qt.TextSelectableByKeyboard) - for j in range(3): - if (i, j) == (0, 2): - frmInfoLayout.addWidget(lbls[i][j], i, j, 1, 2) - else: - frmInfoLayout.addWidget(lbls[i][j], i, j, 1, 1) - - qrcode = QRCodeWidget(addrStr, 80, parent=self) - qrlbl = QRichLabel(self.tr('Double-click to expand')) - frmqr = makeVertFrame([qrcode, qrlbl]) - - frmInfoLayout.addWidget(frmqr, 0, 4, len(lbls), 1) - frmInfo.setLayout(frmInfoLayout) - dlgLayout.addWidget(frmInfo, 0, 0, 1, 1) - - - # ## Set up the address ledger - self.ledgerModel = LedgerDispModelSimple(self.ledgerTable, self, self.main) - delegateId = TheBridge.getLedgerDelegateIdForScrAddr(\ - self.wlt.uniqueIDB58, addr160) - self.ledgerModel.setLedgerDelegateId(delegateId) - - def ledgerToTableScrAddr(ledger): - return self.main.convertLedgerToTable(ledger, \ - wltIDIn=self.wlt.uniqueIDB58) - self.ledgerModel.setConvertLedgerMethod(ledgerToTableScrAddr) - - self.frmLedgUpDown = QFrame() - #self.ledgerView = ArmoryTableView(self, self.main, self.frmLedgUpDown) - self.ledgerView = QTableView(self.main) - self.ledgerView.setModel(self.ledgerModel) - self.ledgerView.setItemDelegate(LedgerDispDelegate(self)) - - self.ledgerView.hideColumn(LEDGERCOLS.isOther) - self.ledgerView.hideColumn(LEDGERCOLS.UnixTime) - self.ledgerView.hideColumn(LEDGERCOLS.WltID) - self.ledgerView.hideColumn(LEDGERCOLS.WltName) - self.ledgerView.hideColumn(LEDGERCOLS.TxHash) - self.ledgerView.hideColumn(LEDGERCOLS.isCoinbase) - self.ledgerView.hideColumn(LEDGERCOLS.toSelf) - self.ledgerView.hideColumn(LEDGERCOLS.optInRBF) - - self.ledgerView.setSelectionBehavior(QTableView.SelectRows) - self.ledgerView.setSelectionMode(QTableView.SingleSelection) - self.ledgerView.horizontalHeader().setStretchLastSection(True) - self.ledgerView.verticalHeader().setDefaultSectionSize(20) - self.ledgerView.verticalHeader().hide() - self.ledgerView.setMinimumWidth(650) - - dateWidth = tightSizeStr(self.ledgerView, '_9999-Dec-99 99:99pm__')[0] - initialColResize(self.ledgerView, [20, 0, dateWidth, 72, 0, 0.45, 0.3]) - - ttipLedger = self.main.createToolTipWidget(self.tr( - 'Unlike the wallet-level ledger, this table shows every ' - 'transaction input and output as a separate entry. ' - 'Therefore, there may be multiple entries for a single transaction, ' - 'which will happen if money was sent-to-self (explicitly, or as ' - 'the change-back-to-self address).')) - lblLedger = QLabel(self.tr('All Address Activity:')) - - lblstrip = makeLayoutFrame(HORIZONTAL, [lblLedger, ttipLedger, STRETCH]) - bottomRow = makeHorizFrame([STRETCH, self.frmLedgUpDown, STRETCH], condenseMargins=True) - frmLedger = makeLayoutFrame(VERTICAL, [lblstrip, self.ledgerView, bottomRow]) - dlgLayout.addWidget(frmLedger, 1, 0, 1, 1) - - - # Now add the right-hand-side option buttons - lbtnCopyAddr = QLabelButton(self.tr('Copy Address to Clipboard')) - lbtnViewKeys = QLabelButton(self.tr('View Address Keys')) - # lbtnSweepA = QLabelButton('Sweep Address') - lbtnDelete = QLabelButton(self.tr('Delete Address')) - - lbtnCopyAddr.linkActivated.connect(self.copyAddr) - lbtnViewKeys.linkActivated.connect(self.viewKeys) - lbtnDelete.linkActivated.connect(self.deleteAddr) - - optFrame = QFrame() - optFrame.setFrameStyle(STYLE_SUNKEN) - - hasPriv = self.addrObj.hasPrivKey - adv = (self.main.usermode in (USERMODE.Advanced, USERMODE.Expert)) - watch = self.wlt.watchingOnly - - - self.lblCopied = QRichLabel('') - self.lblCopied.setMinimumHeight(tightSizeNChar(self.lblCopied, 1)[1]) - - self.lblLedgerWarning = QRichLabel(self.tr( - 'NOTE: The ledger shows each transaction input and ' - 'output for this address. There are typically many ' - 'inputs and outputs for each transaction, therefore the entries ' - 'represent only partial transactions. Do not worry if these entries ' - 'do not look familiar.')) - - - optLayout = QVBoxLayout() - if True: optLayout.addWidget(lbtnCopyAddr) - if adv: optLayout.addWidget(lbtnViewKeys) - - if True: optLayout.addStretch() - if True: optLayout.addWidget(self.lblCopied) - - optLayout.addWidget(self.lblLedgerWarning) - - optLayout.addStretch() - optFrame.setLayout(optLayout) - - rightFrm = makeLayoutFrame(VERTICAL, [QLabel(self.tr('Available Actions:')), optFrame]) - dlgLayout.addWidget(rightFrm, 0, 1, 2, 1) - - btnGoBack = QPushButton(self.tr('<<< Go Back')) - btnGoBack.clicked.connect(self.reject) - - self.setLayout(dlgLayout) - self.setWindowTitle(self.tr('Address Information')) - - self.ledgerModel.reset() - - def copyAddr(self): - clipb = QApplication.clipboard() - clipb.clear() - clipb.setText(self.addrObj.getAddr160()) - self.lblCopied.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) - self.lblCopied.setText(self.tr('Copied!')) - - def makePaper(self): - pass - - def viewKeys(self): - if self.wlt.useEncryption and self.wlt.isLocked: - unlockdlg = DlgUnlockWallet(self.wlt, self, self.main, 'View Private Keys') - if not unlockdlg.exec_(): - QMessageBox.critical(self, self.tr('Wallet is Locked'), \ - self.tr('Key information will not include the private key data.'), \ - QMessageBox.Ok) - - addr = self.addr.copy() - dlg = DlgShowKeys(addr, self.wlt, self, self.main) - dlg.exec_() - - def deleteAddr(self): - pass - -############################################################################# -class DlgShowKeys(ArmoryDialog): - - def __init__(self, addr, wlt, parent=None, main=None): - super(DlgShowKeys, self).__init__(parent, main) - - self.addr = addr - self.wlt = wlt - - self.scrAddr = \ - self.wlt.cppWallet.getAddrObjByIndex(self.addr.chainIndex).getScrAddr() - - - lblWarn = QRichLabel('') - plainPriv = False - if addr.binPrivKey32_Plain.getSize() > 0: - plainPriv = True - lblWarn = QRichLabel(self.tr( - 'Warning: the unencrypted private keys ' - 'for this address are shown below. They are "private" because ' - 'anyone who obtains them can spend the money held ' - 'by this address. Please protect this information the ' - 'same as you protect your wallet.' % htmlColor('TextWarn'))) - warnFrm = makeLayoutFrame(HORIZONTAL, [lblWarn]) - - endianness = self.main.getSettingOrSetDefault('PrefEndian', BIGENDIAN) - estr = 'BE' if endianness == BIGENDIAN else 'LE' - def formatBinData(binStr, endian=LITTLEENDIAN): - binHex = binary_to_hex(binStr) - if endian != LITTLEENDIAN: - binHex = hex_switchEndian(binHex) - binHexPieces = [binHex[i:i + 8] for i in range(0, len(binHex), 8)] - return ' '.join(binHexPieces) - - - lblDescr = QRichLabel(self.tr('Key Data for address: %s' % self.scrAddr)) - - lbls = [] - - lbls.append([]) - binKey = self.addr.binPrivKey32_Plain.toBinStr() - lbls[-1].append(self.main.createToolTipWidget(self.tr( - 'The raw form of the private key for this address. It is ' - '32-bytes of randomly generated data'))) - lbls[-1].append(QRichLabel(self.tr('Private Key (hex,%s):' % estr))) - if not addr.hasPrivKey(): - lbls[-1].append(QRichLabel(self.tr('[[ No Private Key in Watching-Only Wallet ]]'))) - elif plainPriv: - lbls[-1].append(QLabel(formatBinData(binKey))) - else: - lbls[-1].append(QRichLabel(self.tr('[[ ENCRYPTED ]]'))) - - if plainPriv: - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(self.tr( - 'This is a more compact form of the private key, and includes ' - 'a checksum for error detection.'))) - lbls[-1].append(QRichLabel(self.tr('Private Key (Base58):'))) - b58Key = encodePrivKeyBase58(binKey) - lbls[-1].append(QLabel(' '.join([b58Key[i:i + 6] for i in range(0, len(b58Key), 6)]))) - - - - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(self.tr( - 'The raw public key data. This is the X-coordinate of ' - 'the Elliptic-curve public key point.'))) - lbls[-1].append(QRichLabel(self.tr('Public Key X (%s):' % estr))) - lbls[-1].append(QRichLabel(formatBinData(self.addr.binPublicKey65.toBinStr()[1:1 + 32]))) - - - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(self.tr( - 'The raw public key data. This is the Y-coordinate of ' - 'the Elliptic-curve public key point.'))) - lbls[-1].append(QRichLabel(self.tr('Public Key Y (%s):' % estr))) - lbls[-1].append(QRichLabel(formatBinData(self.addr.binPublicKey65.toBinStr()[1 + 32:1 + 32 + 32]))) - - - bin25 = base58_to_binary(self.scrAddr) - network = binary_to_hex(bin25[:1 ]) - hash160 = binary_to_hex(bin25[ 1:-4 ]) - addrChk = binary_to_hex(bin25[ -4:]) - h160Str = self.tr('%s (Network: %s / Checksum: %s)' % (hash160, network, addrChk)) - - lbls.append([]) - lbls[-1].append(self.main.createToolTipWidget(\ - self.tr('This is the hexadecimal version if the address string'))) - lbls[-1].append(QRichLabel(self.tr('Public Key Hash:'))) - lbls[-1].append(QLabel(h160Str)) - - frmKeyData = QFrame() - frmKeyData.setFrameStyle(STYLE_RAISED) - frmKeyDataLayout = QGridLayout() - - - # Now set the label properties and jam them into an information frame - for row, lbl3 in enumerate(lbls): - lbl3[1].setFont(GETFONT('Var')) - lbl3[2].setFont(GETFONT('Fixed')) - lbl3[2].setTextInteractionFlags(Qt.TextSelectableByMouse | \ - Qt.TextSelectableByKeyboard) - lbl3[2].setWordWrap(False) - - for j in range(3): - frmKeyDataLayout.addWidget(lbl3[j], row, j) - - - frmKeyData.setLayout(frmKeyDataLayout) - - bbox = QDialogButtonBox(QDialogButtonBox.Ok) - self.connect(bbox, SIGNAL('accepted()'), self.accept) - - - dlgLayout = QVBoxLayout() - dlgLayout.addWidget(lblWarn) - dlgLayout.addWidget(lblDescr) - dlgLayout.addWidget(frmKeyData) - dlgLayout.addWidget(bbox) - - - self.setLayout(dlgLayout) - self.setWindowTitle(self.tr('Address Key Information')) - ############################################################################# class DlgImportPaperWallet(ArmoryDialog): @@ -1853,65 +1408,6 @@ def fillAddrPoolAndAccept(): # Will pop up a little "please wait..." window while filling addr pool DlgExecLongProcess(fillAddrPoolAndAccept, self.tr("Recovering wallet..."), self, self.main).exec_() -################################################################################ -def OpenPaperBackupWindow(backupType, parent, main, wlt, unlockTitle=None): - - if wlt.useEncryption and wlt.isLocked: - if unlockTitle == None: - unlockTitle = parent.tr("Unlock Paper Backup") - dlg = DlgUnlockWallet(wlt, parent, main, unlockTitle) - if not dlg.exec_(): - QMessageBox.warning(parent, parent.tr('Unlock Failed'), parent.tr( - 'The wallet could not be unlocked. Please try again with ' - 'the correct unlock passphrase.'), QMessageBox.Ok) - return False - - result = True - verifyText = '' - if backupType == 'Single': - from qtdialogs.DlgBackupCenter import DlgPrintBackup - result = DlgPrintBackup(parent, main, wlt).exec_() - verifyText = parent.tr( - u'If the backup was printed with SecurePrint\u200b\u2122, please ' - u'make sure you wrote the SecurePrint\u200b\u2122 code on the ' - 'printed sheet of paper. Note that the code is ' - 'case-sensitive!') - elif backupType == 'Frag': - result = DlgFragBackup(parent, main, wlt).exec_() - verifyText = parent.tr( - u'If the backup was created with SecurePrint\u200b\u2122, please ' - u'make sure you wrote the SecurePrint\u200b\u2122 code on each ' - 'fragment (or stored with each file fragment). The code is the ' - 'same for all fragments.') - - doTest = MsgBoxCustom(MSGBOX.Warning, parent.tr('Verify Your Backup!'), parent.tr( - 'Verify your backup! ' - '

' - 'If you just made a backup, make sure that it is correct! ' - 'The following steps are recommended to verify its integrity: ' - '
' - '
    ' - '
  • Verify each line of the backup data contains 9 columns ' - 'of 4 letters each (excluding any "ID" lines).
  • ' - '
  • %s
  • ' - '
  • Use Armory\'s backup tester to test the backup before you ' - 'physiclly secure it.
  • ' - '
' - '
' - 'Armory has a backup tester that uses the exact same ' - 'process as restoring your wallet, but stops before it writes any ' - 'data to disk. Would you like to test your backup now? ' - % verifyText), yesStr="Test Backup", noStr="Cancel") - - if doTest: - if backupType == 'Single': - DlgRestoreSingle(parent, main, True, wlt.uniqueIDB58).exec_() - elif backupType == 'Frag': - DlgRestoreFragged(parent, main, True, wlt.uniqueIDB58).exec_() - - return result - - ################################################################################ class DlgRemoveWallet(ArmoryDialog): def __init__(self, wlt, parent=None, main=None): @@ -2114,7 +1610,7 @@ def removeWallet(self, wlt): # Open the print dialog. If they hit cancel at any time, then # we go back to the primary wallet-remove dialog without any other action if self.chkPrintBackup.isChecked(): - if not OpenPaperBackupWindow('Single', self, self.main, wlt, \ + if not OpenPaperBackupDialog('Single', self, self.main, wlt, \ self.tr('Unlock Paper Backup')): QMessageBox.warning(self, self.tr('Operation Aborted'), self.tr( 'You requested a paper backup before deleting the wallet, but ' @@ -2170,7 +1666,7 @@ def removeWallet(self, wlt): elif self.radioDelete.isChecked(): LOGINFO('***Completely deleting wallet') - if TheBridge.deleteWallet(wltID) != 1: + if not wlt.delete(): LOGERROR("failed to delete wallet") raise Exception("failed to delete wallet") @@ -3302,24 +2798,6 @@ def createPrintScene(self): self.view.update() -################################################################################ -# Create a special QLineEdit with a masked input -# Forces the cursor to start at position 0 whenever there is no input -class MaskedInputLineEdit(QLineEdit): - - def __init__(self, inputMask): - super(MaskedInputLineEdit, self).__init__() - self.setInputMask(inputMask) - fixFont = GETFONT('Fix', 9) - self.setFont(fixFont) - self.setMinimumWidth(tightSizeStr(fixFont, inputMask)[0] + 10) - self.cursorPositionChanged.connect(self.controlCursor) - - def controlCursor(self, oldpos, newpos): - if newpos != 0 and len(str(self.text()).strip()) == 0: - self.setCursorPosition(0) - - def checkSecurePrintCode(context, SECPRINT, securePrintCode): result = True try: @@ -3428,343 +2906,6 @@ def __init__(self, parent, isRandom, subsAndIDs, M, nFrag, expectID): self.setWindowTitle(self.tr('Fragment Test Results')) self.setMinimumWidth(500) -################################################################################ -class DlgEnterSecurePrintCode(ArmoryDialog): - - def __init__(self, parent, main): - super(DlgEnterSecurePrintCode, self).__init__(parent, main) - - lblSecurePrintCodeDescr = QRichLabel(self.tr( - u'This fragment file requires a SecurePrint\u200b\u2122 code. ' - 'You will only have to enter this code once since it is the same ' - 'on all fragments.')) - lblSecurePrintCodeDescr.setMinimumWidth(440) - self.lblSP = QRichLabel(self.tr(u'SecurePrint\u200b\u2122 Code: '), doWrap=False) - self.editSecurePrint = QLineEdit() - spFrame = makeHorizFrame([self.lblSP, self.editSecurePrint, STRETCH]) - - self.btnAccept = QPushButton(self.tr("Done")) - self.btnCancel = QPushButton(self.tr("Cancel")) - self.connect(self.btnAccept, SIGNAL(CLICKED), self.verifySecurePrintCode) - self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) - buttonBox = QDialogButtonBox() - buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) - buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) - - layout = QVBoxLayout() - layout.addWidget(lblSecurePrintCodeDescr) - layout.addWidget(spFrame) - layout.addWidget(buttonBox) - self.setLayout(layout) - self.setWindowTitle(self.tr('Enter Secure Print Code')) - - def verifySecurePrintCode(self): - # Prepare the key mask parameters - SECPRINT = HardcodedKeyMaskParams() - securePrintCode = str(self.editSecurePrint.text()).strip() - - if not checkSecurePrintCode(self, SECPRINT, securePrintCode): - return - - self.accept() - -################################################################################ -class DlgEnterOneFrag(ArmoryDialog): - - def __init__(self, parent, main, fragList=[], wltType=UNKNOWN, securePrintCode=None): - super(DlgEnterOneFrag, self).__init__(parent, main) - self.fragData = [] - BLUE = htmlColor('TextBlue') - already = '' - if len(fragList) > 0: - strList = ['%d' % (BLUE, f) for f in fragList] - replStr = '[' + ','.join(strList[:]) + ']' - already = self.tr('You have entered fragments %s, so far.' % replStr) - - lblDescr = QRichLabel(self.tr( - 'Enter Another Fragment...

%s ' - 'The fragments can be entered in any order, as long as you provide ' - 'enough of them to restore the wallet. If any fragments use a ' - u'SecurePrint\u200b\u2122 code, please enter it once on the ' - 'previous window, and it will be applied to all fragments that ' - 'require it.' % already)) - - self.version0Button = QRadioButton(self.tr( BACKUP_TYPE_0_TEXT), self) - self.version135aButton = QRadioButton(self.tr( BACKUP_TYPE_135a_TEXT), self) - self.version135aSPButton = QRadioButton(self.tr( BACKUP_TYPE_135a_SP_TEXT), self) - self.version135cButton = QRadioButton(self.tr( BACKUP_TYPE_135c_TEXT), self) - self.version135cSPButton = QRadioButton(self.tr( BACKUP_TYPE_135c_SP_TEXT), self) - self.backupTypeButtonGroup = QButtonGroup(self) - self.backupTypeButtonGroup.addButton(self.version0Button) - self.backupTypeButtonGroup.addButton(self.version135aButton) - self.backupTypeButtonGroup.addButton(self.version135aSPButton) - self.backupTypeButtonGroup.addButton(self.version135cButton) - self.backupTypeButtonGroup.addButton(self.version135cSPButton) - self.version135cButton.setChecked(True) - self.connect(self.backupTypeButtonGroup, SIGNAL('buttonClicked(int)'), self.changeType) - - # This value will be locked after the first fragment is entered. - if wltType == UNKNOWN: - self.version135cButton.setChecked(True) - elif wltType == '0': - self.version0Button.setChecked(True) - self.version135aButton.setEnabled(False) - self.version135aSPButton.setEnabled(False) - self.version135cButton.setEnabled(False) - self.version135cSPButton.setEnabled(False) - elif wltType == BACKUP_TYPE_135A: - # Could be 1.35a with or without SecurePrintCode so remove the rest - self.version0Button.setEnabled(False) - self.version135cButton.setEnabled(False) - self.version135cSPButton.setEnabled(False) - if securePrintCode: - self.version135aSPButton.setChecked(True) - else: - self.version135aButton.setChecked(True) - elif wltType == BACKUP_TYPE_135C: - # Could be 1.35c with or without SecurePrintCode so remove the rest - self.version0Button.setEnabled(False) - self.version135aButton.setEnabled(False) - self.version135aSPButton.setEnabled(False) - if securePrintCode: - self.version135cSPButton.setChecked(True) - else: - self.version135cButton.setChecked(True) - - lblType = QRichLabel(self.tr('Backup Type:'), doWrap=False) - - layoutRadio = QVBoxLayout() - layoutRadio.addWidget(self.version0Button) - layoutRadio.addWidget(self.version135aButton) - layoutRadio.addWidget(self.version135aSPButton) - layoutRadio.addWidget(self.version135cButton) - layoutRadio.addWidget(self.version135cSPButton) - layoutRadio.setSpacing(0) - - radioButtonFrame = QFrame() - radioButtonFrame.setLayout(layoutRadio) - - frmBackupType = makeVertFrame([lblType, radioButtonFrame]) - - self.prfxList = ['x1:', 'x2:', 'x3:', 'x4:', \ - 'y1:', 'y2:', 'y3:', 'y4:', \ - 'F1:', 'F2:', 'F3:', 'F4:'] - self.prfxList = [QLabel(p) for p in self.prfxList] - inpMask = ' 127 - - ############################################################################# - def verifyUserInput(self): - self.fragData = [] - nError = 0 - rawBin = None - - sel = self.backupTypeButtonGroup.checkedId() - rng = [-1] - if sel == self.backupTypeButtonGroup.id(self.version0Button): - rng = range(8) - elif sel == self.backupTypeButtonGroup.id(self.version135aButton) or \ - sel == self.backupTypeButtonGroup.id(self.version135aSPButton): - rng = range(8, 12) - elif sel == self.backupTypeButtonGroup.id(self.version135cButton) or \ - sel == self.backupTypeButtonGroup.id(self.version135cSPButton): - rng = range(8, 10) - - - if sel == self.backupTypeButtonGroup.id(self.version135aSPButton) or \ - sel == self.backupTypeButtonGroup.id(self.version135cSPButton): - # Prepare the key mask parameters - SECPRINT = HardcodedKeyMaskParams() - securePrintCode = str(self.editSecurePrint.text()).strip() - if not checkSecurePrintCode(self, SECPRINT, securePrintCode): - return - elif self.isSecurePrintID(): - QMessageBox.critical(self, 'Bad Encryption Code', self.tr( - 'The ID field indicates that this is a SecurePrintâ„¢ ' - 'Backup Type. You have either entered the ID incorrectly or ' - 'have chosen an incorrect Backup Type.'), QMessageBox.Ok) - return - for i in rng: - hasError = False - try: - rawEntry = str(self.edtList[i].text()) - rawBin, err = readSixteenEasyBytes(rawEntry.replace(' ', '')) - if err == 'Error_2+': - hasError = True - elif err == 'Fixed_1': - nError += 1 - except KeyError: - hasError = True - - if hasError: - reply = QMessageBox.critical(self, self.tr('Verify Wallet ID'), self.tr( - 'There is an error in the data you entered that could not be ' - 'fixed automatically. Please double-check that you entered the ' - 'text exactly as it appears on the wallet-backup page.

' - 'The error occured on the "%s" line.' % str(self.prfxList[i].text())), QMessageBox.Ok) - LOGERROR('Error in wallet restore field') - self.prfxList[i].setText('' + str(self.prfxList[i].text()) + '') - self.destroyFragData() - return - - self.fragData.append(SecureBinaryData(rawBin)) - rawBin = None - - - idLine = str(self.edtID.text()).replace(' ', '') - self.fragData.insert(0, hex_to_binary(idLine)) - - M, fnum, wltID, doMask, fid = ReadFragIDLineBin(self.fragData[0]) - - reply = QMessageBox.question(self, self.tr('Verify Fragment ID'), self.tr( - 'The data you entered is for fragment: ' - '

%s

' - 'Does this ID match the "Fragment:" field displayed on your backup? ' - 'If not, click "No" and re-enter the fragment data.' % (htmlColor('TextBlue'), fid)), QMessageBox.Yes | QMessageBox.No) - - if reply == QMessageBox.Yes: - self.accept() - - -################################################################################ -def verifyRecoveryTestID(parent, computedWltID, expectedWltID=None): - - if expectedWltID == None: - # Testing an arbitrary paper backup - yesno = QMessageBox.question(parent, parent.tr('Recovery Test'), parent.tr( - 'From the data you entered, Armory calculated the following ' - 'wallet ID: %s ' - '

' - 'Does this match the wallet ID on the backup you are ' - 'testing?' % computedWltID), QMessageBox.Yes | QMessageBox.No) - - if yesno == QMessageBox.No: - QMessageBox.critical(parent, parent.tr('Bad Backup!'), parent.tr( - 'If this is your only backup and you are sure that you entered ' - 'the data correctly, then it is highly recommended you stop using ' - 'this wallet! If this wallet currently holds any funds, ' - 'you should move the funds to a wallet that does ' - 'have a working backup. ' - '



' - 'Wallet ID of the data you entered: %s
' % computedWltID), \ - QMessageBox.Ok) - elif yesno == QMessageBox.Yes: - MsgBoxCustom(MSGBOX.Good, parent.tr('Backup is Good!'), parent.tr( - 'Your backup works! ' - '

' - 'The wallet ID is computed from a combination of the root ' - 'private key, the "chaincode" and the first address derived ' - 'from those two pieces of data. A matching wallet ID ' - 'guarantees it will produce the same chain of addresses as ' - 'the original.')) - else: # an expected wallet ID was supplied - if not computedWltID == expectedWltID: - QMessageBox.critical(parent, parent.tr('Bad Backup!'), parent.tr( - 'If you are sure that you entered the backup information ' - 'correctly, then it is highly recommended you stop using ' - 'this wallet! If this wallet currently holds any funds, ' - 'you should move the funds to a wallet that does ' - 'have a working backup.' - '

' - 'Computed wallet ID: %s
' - 'Expected wallet ID: %s

' - 'Is it possible that you loaded a different backup than the ' - 'one you just made?' % (computedWltID, expectedWltID)), \ - QMessageBox.Ok) - else: - MsgBoxCustom(MSGBOX.Good, parent.tr('Backup is Good!'), parent.tr( - 'Your backup works! ' - '

' - 'The wallet ID computed from the data you entered matches ' - 'the expected ID. This confirms that the backup produces ' - 'the same sequence of private keys as the original wallet! ' - '

' - 'Computed wallet ID: %s
' - 'Expected wallet ID: %s
' - '
' % (computedWltID, expectedWltID ))) - ################################################################################# class DlgFactoryReset(ArmoryDialog): def __init__(self, main=None, parent=None): diff --git a/samplemodules/DustBGonePlugin.py b/samplemodules/DustBGonePlugin.py index e80f419af..7760bbe70 100644 --- a/samplemodules/DustBGonePlugin.py +++ b/samplemodules/DustBGonePlugin.py @@ -173,7 +173,7 @@ def getTabToDisplay(self): def injectShutdownFunc(self): try: - self.main.writeSetting('DustLedgerCols', saveTableView(self.dustTableView)) + TheSettings.set('DustLedgerCols', saveTableView(self.dustTableView)) except: LOGEXCEPT('Strange error during shutdown') diff --git a/ui/AddressTypeSelectDialog.py b/ui/AddressTypeSelectDialog.py index 72dbf0da8..6a1120edc 100755 --- a/ui/AddressTypeSelectDialog.py +++ b/ui/AddressTypeSelectDialog.py @@ -145,7 +145,7 @@ def updateAddressTypes(self, addrTypes, currType): def setType(self, _type): self.addrType = _type - addrTypeStr = TheBridge.getNameForAddrType(_type) + addrTypeStr = TheBridge.utils.getNameForAddrType(_type) self.typeLabel.setText(\ self.main.tr("%s" \ % addrTypeStr)) diff --git a/ui/CoinControlUI.py b/ui/CoinControlUI.py index ae36f1607..57d337bee 100755 --- a/ui/CoinControlUI.py +++ b/ui/CoinControlUI.py @@ -9,7 +9,7 @@ ############################################################################## from qtdialogs.qtdefines import QRichLabel, makeHorizFrame, \ - saveTableView, restoreTableView + saveTableView, restoreTableView, createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog from ui.TreeViewGUI import CoinControlTreeModel, RBFTreeModel @@ -34,7 +34,7 @@ def __init__(self, parent, main, wlt): 'all other addresses.')) self.useAllCheckBox = QCheckBox(self.tr("Use all selected UTXOs")) - useAllToolTip = self.main.createToolTipWidget(self.tr( + useAllToolTip = createToolTipWidget(self.tr( 'By default, Armory will pick a subset of the UTXOs you chose ' 'explicitly through the coin control feature to best suit the ' 'total spend value of the transaction you are constructing. ' @@ -132,8 +132,8 @@ def reject(self, *args): ############################################################################# def saveGeometrySettings(self): if self.isVisible() == True: - self.main.writeSetting('ccDlgGeometry', self.saveGeometry().toHex()) - self.main.writeSetting('ccDlgAddrCols', saveTableView(self.ccView)) + TheSettings.set('ccDlgGeometry', self.saveGeometry().toHex()) + TheSettings.set('ccDlgAddrCols', saveTableView(self.ccView)) ############################################################################# def exec_(self): @@ -186,8 +186,8 @@ def reject(self, *args): ############################################################################# def saveGeometrySettings(self): - self.main.writeSetting('rbfDlgGeometry', self.saveGeometry().toHex()) - self.main.writeSetting('rbfDlgAddrCols', saveTableView(self.rbfView)) + TheSettings.set('rbfDlgGeometry', self.saveGeometry().toHex()) + TheSettings.set('rbfDlgAddrCols', saveTableView(self.rbfView)) ############################################################################# def exec_(self): diff --git a/ui/FeeSelectUI.py b/ui/FeeSelectUI.py index b76ce47f0..617af731d 100755 --- a/ui/FeeSelectUI.py +++ b/ui/FeeSelectUI.py @@ -13,14 +13,16 @@ QLabel, QPushButton, QCheckBox, QSlider, QComboBox from qtdialogs.qtdefines import STYLE_RAISED, GETFONT, \ - tightSizeNChar, QLabelButton, makeHorizFrame, STYLE_NONE + tightSizeNChar, QLabelButton, makeHorizFrame, STYLE_NONE, \ + createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog from armoryengine.CppBridge import TheBridge from armoryengine.ArmoryUtils import str2coin, coin2str, MIN_TX_FEE, \ - MIN_FEE_BYTE, DEFAULT_FEE_TYPE + MIN_FEE_BYTE, DEFAULT_FEE_TYPE, LOGWARN from armoryengine.CoinSelection import NBLOCKS_TO_CONFIRM, \ FEEBYTE_CONSERVATIVE, FEEBYTE_ECONOMICAL +from armoryengine.Settings import TheSettings class FeeSelectionDialog(ArmoryDialog): @@ -32,12 +34,12 @@ def __init__(self, parent, main, cs_callback, get_csstate): self.lblButtonFee = QLabelButton("") #get default values - flatFee = self.main.getSettingOrSetDefault("Default_Fee", MIN_TX_FEE) + flatFee = TheSettings.getSettingOrSetDefault("Default_Fee", MIN_TX_FEE) flatFee = coin2str(flatFee, maxZeros=1).strip() - fee_byte = str(self.main.getSettingOrSetDefault("Default_FeeByte", MIN_FEE_BYTE)) - blocksToConfirm = self.main.getSettingOrSetDefault(\ + fee_byte = str(TheSettings.getSettingOrSetDefault("Default_FeeByte", MIN_FEE_BYTE)) + blocksToConfirm = TheSettings.getSettingOrSetDefault(\ "Default_FeeByte_BlocksToConfirm", NBLOCKS_TO_CONFIRM) - feeStrategy = str(self.main.getSettingOrSetDefault(\ + feeStrategy = str(TheSettings.getSettingOrSetDefault(\ "Default_FeeByte_Strategy", FEEBYTE_CONSERVATIVE)) self.coinSelectionCallback = cs_callback @@ -45,11 +47,11 @@ def __init__(self, parent, main, cs_callback, get_csstate): self.validAutoFee = True isSmartFee = True - feeEstimate, isSmartFee, errorMsg = self.getFeeByteFromNode( + feeEstimate, isSmartFee = self.getFeeByteFromNode( blocksToConfirm, feeStrategy) defaultCheckState = \ - self.main.getSettingOrSetDefault("FeeOption", DEFAULT_FEE_TYPE) + TheSettings.getSettingOrSetDefault("FeeOption", DEFAULT_FEE_TYPE) #flat free def setFlatFee(): @@ -112,7 +114,7 @@ def callbck(): self.checkBoxAdjust = QCheckBox(self.tr('Adjust fee/byte for privacy')) self.checkBoxAdjust.setChecked(\ - self.main.getSettingOrSetDefault('AdjustFee', True)) + TheSettings.getSettingOrSetDefault('AdjustFee', True)) self.connect(self.checkBoxAdjust, SIGNAL('clicked()'), updateLbl) @@ -176,11 +178,8 @@ def getSliderLabelTxt(): def updateAutoFeeByte(): blocksToConfirm = self.sliderAutoFeeByte.value() - try: - feeEstimate, version, err = \ - self.getFeeByteFromNode(blocksToConfirm, FEEBYTE_CONSERVATIVE) - except: - feeEstimate = "N/A" + feeEstimate, version = self.getFeeByteFromNode( + blocksToConfirm, FEEBYTE_CONSERVATIVE) self.lblSlider.setText(getSliderLabelTxt()) self.lblAutoFeeByte.setText(feeByteToStr(feeEstimate)) @@ -253,7 +252,7 @@ def feeByteToStr(feeByte): self.lblSlider = QLabel() self.lblStrat = QLabel(self.tr("Profile:")) - self.ttStart = self.main.createToolTipWidget(self.tr( + self.ttStart = createToolTipWidget(self.tr( ''' Fee Estimation Profiles:

CONSERVATIVE: Short term estimate. More reactive to current @@ -283,11 +282,8 @@ def getSliderLabelTxt(): def updateAutoFeeByte(): blocksToConfirm = self.sliderAutoFeeByte.value() strategy = getStrategyString() - try: - feeEstimate, version, err = \ - self.getFeeByteFromNode(blocksToConfirm, strategy) - except: - feeEstimate = "N/A" + feeEstimate, version = self.getFeeByteFromNode( + blocksToConfirm, strategy) self.lblSlider.setText(getSliderLabelTxt()) self.lblAutoFeeByte.setText(feeByteToStr(feeEstimate)) @@ -324,14 +320,14 @@ def stratComboChange(): ############################################################################# def getFeeByteFromNode(self, blocksToConfirm, strategy): - feeEstimateResult = TheBridge.estimateFee(blocksToConfirm, strategy) - if feeEstimateResult.error == None or len(feeEstimateResult.error) == 0: - return feeEstimateResult.feeByte * 100000, \ - feeEstimateResult.smartFee, \ - feeEstimateResult.error - else: + try: + feeEstimateResult = TheBridge.service.estimateFee( + blocksToConfirm, strategy) + return feeEstimateResult.fee_byte * 100000, feeEstimateResult.smart_fee + except Exception as e: + LOGWARN(f"[getFeeByteFromNode] failed with error: {str(e)}") self.validAutoFee = False - return "N/A", False, str(feeEstimateResult.error) + return "N/A", False ############################################################################# def selectType(self, strType): diff --git a/ui/MultiSigDialogs.py b/ui/MultiSigDialogs.py index 1d426a382..306662409 100644 --- a/ui/MultiSigDialogs.py +++ b/ui/MultiSigDialogs.py @@ -34,7 +34,7 @@ from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA from ui.MultiSigModels import LOCKBOXCOLS, LockboxDisplayModel, LockboxDisplayProxy from ui.WalletFrames import SelectWalletFrame - +from ui.QtExecuteSignal import TheSignalExecution from armoryengine.MultiSigUtils import * ############################################################################# @@ -1165,7 +1165,7 @@ def funcCopyClip(): clipb = QApplication.clipboard() clipb.clear() clipb.setText(scrAddr_to_addrStr(lbox.getAddr())) - self.main.signalExecution.callLater(1, lambda: self.btnCopyClip.setText('Copy Address')) + TheSignalExecution.callLater(1, lambda: self.btnCopyClip.setText('Copy Address')) def funcReqPayment(): lbox = self.getSelectedLockbox() @@ -1479,7 +1479,7 @@ def getModelStr(col): DlgQRCodeDisplay(self, self.main, p2shAddr, p2shAddr, createLockboxEntryStr(lboxId)).exec_() return elif action == actionReqPayment: - if not self.main.getSettingOrSetDefault('DNAA_P2SHCompatWarn', False): + if not TheSettings.getSettingOrSetDefault('DNAA_P2SHCompatWarn', False): oldStartChar = "'m' or 'n'" if USE_TESTNET or USE_REGTEST else "'1'" newStartChar = "'2'" if USE_TESTNET or USE_REGTEST else "'3'" reply = MsgBoxWithDNAA(self, self.main, MSGBOX.Warning, self.tr('Compatibility Warning'), @@ -1496,7 +1496,7 @@ def getModelStr(col): dnaaMsg=self.tr('Do not show this message again')) if reply[1]==True: - self.main.writeSetting('DNAA_P2SHCompatWarn', True) + TheSettings.set('DNAA_P2SHCompatWarn', True) DlgRequestPayment(self, self.main, p2shAddr).exec_() return @@ -1840,9 +1840,9 @@ def doSpend(self): ############################################################################# def saveGeometrySettings(self): - self.main.writeSetting('LockboxGeometry', self.saveGeometry().toHex()) - self.main.writeSetting('LockboxAddrCols', saveTableView(self.lboxView)) - self.main.writeSetting('LockboxLedgerCols', saveTableView(self.ledgerView)) + TheSettings.set('LockboxGeometry', self.saveGeometry().toHex()) + TheSettings.set('LockboxAddrCols', saveTableView(self.lboxView)) + TheSettings.set('LockboxLedgerCols', saveTableView(self.ledgerView)) ############################################################################# def closeEvent(self, event): @@ -2402,7 +2402,7 @@ def mailLB(self): QDesktopServices.openUrl(finalUrl) - if not self.main.getSettingOrSetDefault('DNAA_MailtoWarn', False): + if not TheSettings.getSettingOrSetDefault('DNAA_MailtoWarn', False): reply = MsgBoxWithDNAA(self, self.main, MSGBOX.Warning, self.tr('Email Triggered'), self.tr( 'Armory attempted to execute a "mailto:" link which should trigger ' 'your email application or web browser to open a compose-email window. ' @@ -2411,7 +2411,7 @@ def mailLB(self): ), dnaaMsg=self.tr('Do not show this message again'), dnaaStartChk=True) if reply[1]: - self.main.writeSetting('DNAA_MailtoWarn', True) + TheSettings.set('DNAA_MailtoWarn', True) self.lblCopyMail.setText('Email produced!') diff --git a/ui/QrCodeMatrix.py b/ui/QrCodeMatrix.py index 07d96f8db..5944f60e5 100644 --- a/ui/QrCodeMatrix.py +++ b/ui/QrCodeMatrix.py @@ -24,7 +24,7 @@ def CreateQRMatrix(dataToEncode, errLevel=QRErrorCorrectLevel.L): while sz<20: try: errCorrectEnum = getattr(QRErrorCorrectLevel, errLevel.upper()) - qr = QRCode(sz, errCorrectEnum) + qr = QRCode(int(sz), errCorrectEnum) qr.addData(dataToEncode) qr.make() success=True diff --git a/ui/QtExecuteSignal.py b/ui/QtExecuteSignal.py index cc5dae362..fe4893fd3 100755 --- a/ui/QtExecuteSignal.py +++ b/ui/QtExecuteSignal.py @@ -20,43 +20,32 @@ class QtExecuteSignalError(Exception): ############################################################################## class QtExecuteSignal(QObject): - executeSignal = Signal(list) ########################################################################### - def __init__(self, mainWnd): - super(QtExecuteSignal, self).__init__(mainWnd) - self.mainWnd = mainWnd + def __init__(self): + super(QtExecuteSignal, self).__init__() self.executeSignal.connect(self.methodSlot) self.waiting = {} ########################################################################### - def executeMethod(self, callableList): - if len(callableList) == 0: - raise Exception("-- invalid arg list --") - self.executeSignal.emit(callableList) - - ########################################################################### - def methodSlot(self, callableList): - if type(callableList) != list or len(callableList) == 0: - LOGERROR('[ArmoryQt::methodSlot] invalid callabale list:') - LOGERROR(str(callableList)) - raise QtExecuteSignalError("invalid callable list") - - if not callable(callableList[0]): - raise QtExecuteSignalError("first list entry isn't callable") + def executeMethod(self, _callable, *args): + if not callable(_callable): + print (f"** executeMethod: {str(_callable)} **") + print (f"** args type: {str(type(args))}, args: {str(args)} **") + raise QtExecuteSignalError("invalid arguments") - args = [] - if len(callableList) == 2: - if type(callableList[1]) != list: - raise QtExecuteSignalError("second list entry isn't list") - args = callableList[1] + self.executeSignal.emit([{ + 'callable' : _callable, + 'args' : args + }]) - callableList[0](*args) + ########################################################################### + def methodSlot(self, execList): + execList[0]['callable'](*(execList[0]['args'])) ########################################################################### def callLater(self, delay, _callable, *_args): - #if a given method is already waiting on delayed execution, update the #args and return if _callable in self.waiting: @@ -71,4 +60,6 @@ def callLater(self, delay, _callable, *_args): def callLaterThread(self, delay, _callable, *args): sleep(delay) args = self.waiting.pop(_callable, []) - self.executeMethod([_callable, args]) + self.executeMethod(_callable, *args) + +TheSignalExecution = QtExecuteSignal() \ No newline at end of file diff --git a/ui/TxFrames.py b/ui/TxFrames.py index 5fa5dbf3a..63590cb5b 100755 --- a/ui/TxFrames.py +++ b/ui/TxFrames.py @@ -1,21 +1,26 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -################################################################################ -# # -# Copyright (C) 2011-2021, Armory Technologies, Inc. # -# Distributed under the GNU Affero General Public License (AGPL v3) # -# See LICENSE or http://www.gnu.org/licenses/agpl.html # -# # -################################################################################ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2023, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## import random from qtdialogs.qtdefines import ArmoryFrame, tightSizeNChar, \ GETFONT, QRichLabel, VLINE, QLabelButton, USERMODE, \ VERTICAL, makeHorizFrame, STYLE_RAISED, makeVertFrame, \ - relaxedSizeNChar, STYLE_SUNKEN, CHANGE_ADDR_DESCR_STRING + relaxedSizeNChar, STYLE_SUNKEN, CHANGE_ADDR_DESCR_STRING, \ + STRETCH, createToolTipWidget -from qtdialogs.qtdialogs import NO_CHANGE, STRETCH +from qtdialogs.qtdialogs import NO_CHANGE from qtdialogs.DlgDispTxInfo import DlgDispTxInfo from qtdialogs.DlgConfirmSend import DlgConfirmSend @@ -29,8 +34,10 @@ str2coin, CPP_TXOUT_STDSINGLESIG, CPP_TXOUT_P2SH, \ coin2str, MIN_FEE_BYTE, getNameForAddrType, addrTypeInSet, \ getAddressTypeForOutputType, binary_to_hex +from armoryengine.Settings import TheSettings from ui.FeeSelectUI import FeeSelectionDialog +from ui.QtExecuteSignal import TheSignalExecution from PySide2.QtCore import Qt, QByteArray from PySide2.QtGui import QPalette @@ -53,96 +60,86 @@ ################################################################################ class CoinSelectionInstance(object): - def __init__(self, wltID, topHeight): - self.id = None - self.wltId = wltID + def __init__(self, wallet, topHeight): + self.wallet = wallet self.height = topHeight + self.csInstance = None ############################################################################# def __del__(self): - TheBridge.destroyCoinSelectionInstance(self.id) + if self.csInstance: + self.csInstance.destroyCoinSelectionInstance() ############################################################################# def setup(self): - self.id = TheBridge.initCoinSelectionInstance(self.wltId, self.height) + self.csInstance = self.wallet.initCoinSelectionInstance(self.height) ############################################################################# def setRecipient(self, addr, value, id): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - TheBridge.setCoinSelectionRecipient(self.id, addr, value, id) + self.csInstance.setCoinSelectionRecipient(addr, value, id) ############################################################################# def updateRecipient(self, addr, value, id): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - TheBridge.setCoinSelectionRecipient(self.id, addr, value, id) + self.csInstance.setCoinSelectionRecipient(addr, value, id) ############################################################################# def resetRecipients(self): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - TheBridge.resetCoinSelection() + self.csInstance.reset() ############################################################################# def selectUTXOs(self, fee, feePerByte, processFlags): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - TheBridge.cs_SelectUTXOs(self.id, fee, feePerByte, processFlags) + self.csInstance.selectUTXOs(fee, feePerByte, processFlags) ############################################################################# def processCustomUtxoList(self, utxoList, fee, feePerByte, processFlags): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - TheBridge.cs_ProcessCustomUtxoList(\ - self.id, utxoList, fee, feePerByte, processFlags) + self.csInstance.processCustomUtxoList( + utxoList, fee, feePerByte, processFlags) ############################################################################# def getUtxoSelection(self): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - return TheBridge.cs_getUtxoSelection(self.id) + return self.csInstance.getUtxoSelection() ############################################################################# def getFlatFee(self): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - return TheBridge.cs_getFlatFee(self.id) + return self.csInstance.getFlatFee() ############################################################################# def getFeeByte(self): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - return TheBridge.cs_getFeeByte(self.id) + return self.csInstance.getFeeByte() ############################################################################# def getSizeEstimate(self): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - return TheBridge.cs_getSizeEstimate(self.id) + return self.csInstance.getSizeEstimate() ############################################################################# def getFeeForMaxVal(self, feePerByte): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - return TheBridge.cs_getFeeForMaxVal(self.id, feePerByte) + return self.csInstance.getFeeForMaxVal(feePerByte) ############################################################################# def getFeeForMaxValUtxoVector(self, utxoList, feePerByte): - if self.id == None: + if not self.csInstance: raise Exception("uninitialized coin selection instance") - - return TheBridge.cs_getFeeForMaxValUtxoVector(self.id, utxoList, feePerByte) + return self.csInstance.getFeeForMaxValUtxoVector(utxoList, feePerByte) ################################################################################ @@ -187,7 +184,7 @@ def getWalletIdList(onlyOfflineWallets): if wltIDList == None: self.wltIDList = getWalletIdList(onlyOfflineWallets) - feetip = self.main.createToolTipWidget(\ + feetip = createToolTipWidget(\ self.tr('Transaction fees go to users who contribute computing power to ' 'keep the Bitcoin network secure, and in return they get your transaction ' 'included in the blockchain faster.')) @@ -218,30 +215,30 @@ def feeDlg(): self.chkRememberChng = QCheckBox(self.tr('Remember for future transactions')) self.vertLine = VLINE() - self.ttipSendChange = self.main.createToolTipWidget(\ + self.ttipSendChange = createToolTipWidget(\ self.tr('Most transactions end up with oversized inputs and Armory will send ' 'the change to the next address in this wallet. You may change this ' 'behavior by checking this box.')) - self.ttipFeedback = self.main.createToolTipWidget(\ + self.ttipFeedback = createToolTipWidget(\ self.tr('Guarantees that no new addresses will be created to receive ' 'change. This reduces anonymity, but is useful if you ' 'created this wallet solely for managing imported addresses, ' 'and want to keep all funds within existing addresses.')) - self.ttipSpecify = self.main.createToolTipWidget(\ + self.ttipSpecify = createToolTipWidget(\ self.tr('You can specify any valid Bitcoin address for the change. ' 'NOTE: If the address you specify is not in this wallet, ' 'Armory will not be able to distinguish the outputs when it shows ' 'up in your ledger. The change will look like a second recipient, ' 'and the total debit to your wallet will be equal to the amount ' 'you sent to the recipient plus the change.')) - self.ttipUnsigned = self.main.createToolTipWidget(\ + self.ttipUnsigned = createToolTipWidget(\ self.tr('Check this box to create an unsigned transaction to be signed' ' and/or broadcast later.')) self.unsignedCheckbox = QCheckBox(self.tr('Create Unsigned')) self.RBFcheckbox = QCheckBox(self.tr('enable RBF')) self.RBFcheckbox.setChecked(True) - self.ttipRBF = self.main.createToolTipWidget(\ + self.ttipRBF = createToolTipWidget(\ self.tr('RBF flagged inputs allow to respend the underlying outpoint for a ' 'higher fee as long as the original spending transaction remains ' 'unconfirmed.

' @@ -297,7 +294,7 @@ def feeDlg(): metaButtonList, STYLE_RAISED, condenseMargins=True) buttonFrame = makeHorizFrame(buttonList, condenseMargins=True) btnEnterURI = QPushButton(self.tr('Manually Enter "bitcoin:" Link')) - ttipEnterURI = self.main.createToolTipWidget( self.tr( + ttipEnterURI = createToolTipWidget(self.tr( 'Armory does not always succeed at registering itself to handle ' 'URL links from webpages and email. ' 'Click this button to copy a "bitcoin:" link directly into Armory.')) @@ -387,7 +384,7 @@ def feeDlg(): self.toggleSpecify(False) self.toggleChngAddr(False) - hexgeom = self.main.settings.get('SendBtcGeometry') + hexgeom = TheSettings.get('SendBtcGeometry') if len(hexgeom) > 0: geom = QByteArray(bytes.fromhex(hexgeom)) self.restoreGeometry(geom) @@ -513,8 +510,8 @@ def setupCoinSelectionInstance(self): self.coinSelection = None return - self.coinSelection = CoinSelectionInstance(\ - self.wltID, TheBDM.getTopBlockHeight()) + self.coinSelection = CoinSelectionInstance( + self.wlt, TheBDM.getTopBlockHeight()) self.coinSelection.setup() try: @@ -524,12 +521,12 @@ def setupCoinSelectionInstance(self): ############################################################################# def setupCoinSelectionForLockbox(self, lbox): - try: + try: lbCppWlt = self.main.cppLockboxWltMap[lbox.uniqueIDB58] self.coinSelection = Cpp.CoinSelectionInstance(\ lbCppWlt, lbox.M, lbox.N, \ TheBDM.getTopBlockHeight(), lbCppWlt.getSpendableBalance()) - + except: self.coinSelection = None @@ -604,7 +601,7 @@ def serializeUtxoList(self, utxoList): return serializedUtxoList ############################################################################# - def resolveCoinSelection(self): + def resolveCoinSelection(self): maxRecipientID = self.getMaxRecipientID() if maxRecipientID != None: self.setMaximum(maxRecipientID) @@ -629,6 +626,7 @@ def resolveCoinSelection(self): self.feeDialog.updateLabelButton() except RuntimeError as e: + print (f"[resolveCoinSelection] failed with error: {str(e)}") self.resetCoinSelectionText() raise e @@ -881,7 +879,6 @@ def validateInputsGetUSTX(self, peek=False): if not reply==QMessageBox.Yes: return False - if len(utxoSelect) == 0: QMessageBox.critical(self, self.tr('Coin Selection Error'), self.tr( 'There was an error constructing your transaction, due to a ' @@ -993,7 +990,6 @@ def validateInputsGetUSTX(self, peek=False): if not dlg.exec_(): return False - else: self.main.warnNewUSTXFormat() @@ -1022,21 +1018,26 @@ def createTxAndBroadcast(self): if len(self.comments[i][0].strip()) > 0: commentStr += '%s (%s); ' % (self.comments[i][0], coin2str_approx(amt).strip()) - def finalizeSignTx(): + def finalizeSignTx(success): #this needs to run in the GUI thread - def signTxLastStep(): - finalTx = ustx.getSignedPyTx() - - if len(commentStr) > 0: - self.wlt.setComment(finalTx.getHash(), commentStr) - self.main.broadcastTransaction(finalTx) + def signTxLastStep(success): + print (f"signtx success: {success}") + if success: + finalTx = ustx.getSignedPyTx() - if self.sendCallback: - self.sendCallback() + if len(commentStr) > 0: + self.wlt.setComment(finalTx.getHash(), commentStr) + self.main.broadcastTransaction(finalTx) - self.main.signalExecution.executeMethod([signTxLastStep]) + if self.sendCallback: + self.sendCallback() + else: + QMessageBox.warning(self, self.tr('Error'), + self.tr('Failed to sign transaction!'), + QMessageBox.Ok) + TheSignalExecution.executeMethod(signTxLastStep, success) - ustx.signTx(self.wlt.uniqueIDB58, finalizeSignTx) + ustx.signTx(self.wlt.uniqueIDB58, finalizeSignTx, self) except: LOGEXCEPT('Problem sending transaction!') @@ -1063,8 +1064,8 @@ def getUsableTxOutList(self): self.resolveCoinSelection() utxoVec = self.coinSelection.getUtxoSelection() utxoSelect = [] - for i in range(len(utxoVec)): - pyUtxo = PyUnspentTxOut().createFromBridgeUtxo(utxoVec[i]) + for i in range(len(utxoVec.utxo)): + pyUtxo = PyUnspentTxOut().createFromBridgeUtxo(utxoVec.utxo[i]) utxoSelect.append(pyUtxo) return utxoSelect @@ -1118,7 +1119,7 @@ def getAddr(typeStr): outputAddressTypes = set() for i in range(0, len(scriptValPairs)): svPair = scriptValPairs[i] - scriptType = TheBridge.getTxOutScriptType(svPair[0]) + scriptType = TheBridge.scriptUtils.getTxOutScriptType(svPair[0]) try: addrType = getAddressTypeForOutputType(scriptType) outputAddressTypes.add(addrType) @@ -1166,7 +1167,7 @@ def determineChangeScript(self, utxoList, scriptValPairs, peek=False): if self.lbox is None: changeAddrObj = self.getDefaultChangeAddress(scriptValPairs, peek) changeAddr160 = changeAddrObj.getAddr160() - changeScript = TheBridge.getTxOutScriptForScrAddr(\ + changeScript = TheBridge.scriptUtils.getTxOutScriptForScrAddr(\ changeAddrObj.getPrefixedAddr()) self.wlt.setComment(changeAddr160, CHANGE_ADDR_DESCR_STRING) else: @@ -1373,7 +1374,7 @@ def createOpReturnWidget(widget_obj): r = len(self.widgetTable) self.widgetTable.append({}) - self.widgetTable[r]['UID'] = TheBridge.generateRandomHex(8) + self.widgetTable[r]['UID'] = TheBridge.utils.generateRandomHex(8) if not is_opreturn: createAddrWidget(self.widgetTable[r], r) diff --git a/ui/TxFramesOffline.py b/ui/TxFramesOffline.py index e9e1b8c89..49f8c8198 100644 --- a/ui/TxFramesOffline.py +++ b/ui/TxFramesOffline.py @@ -24,9 +24,8 @@ GETFONT, QRichLabel, HLINE, QLabelButton, USERMODE, \ VERTICAL, HORIZONTAL, STYLE_RAISED, relaxedSizeNChar, STYLE_SUNKEN, \ relaxedSizeStr, makeLayoutFrame, tightSizeStr, NETWORKMODE, \ - MSGBOX + MSGBOX, STRETCH, createToolTipWidget -from qtdialogs.qtdialogs import STRETCH from qtdialogs.DlgDispTxInfo import DlgDispTxInfo, extractTxInfo from qtdialogs.DlgConfirmSend import DlgConfirmSend from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA @@ -37,6 +36,8 @@ CPP_TXOUT_STDSINGLESIG, CPP_TXOUT_P2SH, coin2str, enum, \ script_to_scrAddr, binary_to_hex, coin2strNZS, BadAddressError, \ NetworkIDError, UnserializeError +from armoryengine.Settings import TheSettings +from ui.QtExecuteSignal import TheSignalExecution ################################################################################ class SignBroadcastOfflineTxFrame(ArmoryFrame): @@ -102,20 +103,20 @@ def __init__(self, parent=None, main=None, initLabel=''): # ## self.infoLbls.append([]) - self.infoLbls[-1].append(self.main.createToolTipWidget(\ + self.infoLbls[-1].append(createToolTipWidget(\ self.tr('This is wallet from which the offline transaction spends bitcoins'))) self.infoLbls[-1].append(QRichLabel('Wallet:')) self.infoLbls[-1].append(QRichLabel('')) # ## self.infoLbls.append([]) - self.infoLbls[-1].append(self.main.createToolTipWidget(self.tr('The name of the wallet'))) + self.infoLbls[-1].append(createToolTipWidget(self.tr('The name of the wallet'))) self.infoLbls[-1].append(QRichLabel(self.tr('Wallet Label:'))) self.infoLbls[-1].append(QRichLabel('')) # ## self.infoLbls.append([]) - self.infoLbls[-1].append(self.main.createToolTipWidget(self.tr( + self.infoLbls[-1].append(createToolTipWidget(self.tr( 'A unique string that identifies an unsigned transaction. ' 'This is different than the ID that the transaction will have when ' 'it is finally broadcast, because the broadcast ID cannot be ' @@ -125,7 +126,7 @@ def __init__(self, parent=None, main=None, initLabel=''): # ## self.infoLbls.append([]) - self.infoLbls[-1].append(self.main.createToolTipWidget(\ + self.infoLbls[-1].append(createToolTipWidget(\ self.tr('Net effect on this wallet\'s balance'))) self.infoLbls[-1].append(QRichLabel(self.tr('Transaction Amount:'))) self.infoLbls[-1].append(QRichLabel('')) @@ -251,13 +252,13 @@ def processUSTX(self): self.makeReviewFrame() return elif not self.enoughSigs: - if not self.main.getSettingOrSetDefault('DNAA_ReviewOfflineTx', False): + if not TheSettings.getSettingOrSetDefault('DNAA_ReviewOfflineTx', False): result = MsgBoxWithDNAA(self, self.main, MSGBOX.Warning, title=self.tr('Offline Warning'), \ msg=self.tr('Please review your transaction carefully before ' 'signing and broadcasting it! The extra security of ' 'using offline wallets is lost if you do ' 'not confirm the transaction is correct!'), dnaaMsg=None) - self.main.writeSetting('DNAA_ReviewOfflineTx', result[1]) + TheSettings.set('DNAA_ReviewOfflineTx', result[1]) self.lblStatus.setText(self.tr('Unsigned')) self.btnSign.setEnabled(True) self.btnBroadcast.setEnabled(False) @@ -440,17 +441,21 @@ def signTx(self): if not dlg.exec_(): return - def completeSignProcess(): - def signTxLastStep(): - serTx = self.ustxObj.serialize(self.signerTypeSelect.fromType()) - self.txtUSTX.setText(serTx) + def completeSignProcess(success): + def signTxLastStep(success): + if success: + serTx = self.ustxObj.serialize(self.signerTypeSelect.fromType()) + self.txtUSTX.setText(serTx) - if not self.fileLoaded == None: - self.saveTxAuto() - - self.main.signalExecution.executeMethod([signTxLastStep]) + if not self.fileLoaded == None: + self.saveTxAuto() + else: + QMessageBox.warning(self, self.tr('Error'), + self.tr('Failed to sign transaction!'), + QMessageBox.Ok) + TheSignalExecution.executeMethod(signTxLastStep, success) - self.ustxObj.signTx(self.wlt.uniqueIDB58, completeSignProcess) + self.ustxObj.signTx(self.wlt.uniqueIDB58, completeSignProcess, self) def broadTx(self): if self.main.netMode == NETWORKMODE.Disconnected: diff --git a/ui/WalletFrames.py b/ui/WalletFrames.py index 3b8038563..1a8c227ff 100755 --- a/ui/WalletFrames.py +++ b/ui/WalletFrames.py @@ -20,7 +20,7 @@ from qtdialogs.qtdefines import ArmoryFrame, VERTICAL, HORIZONTAL, \ tightSizeNChar, makeHorizFrame, makeVertFrame, QRichLabel, QGridLayout, \ QPixmapButton, GETFONT, STYLE_SUNKEN, HLINE, determineWalletType, \ - QMoneyLabel, makeLayoutFrame + QMoneyLabel, makeLayoutFrame, createToolTipWidget from armorycolors import htmlColor from armoryengine.ArmoryUtils import enum, isASCII, coin2str @@ -34,11 +34,9 @@ from qtdialogs.DlgUnlockWallet import DlgUnlockWallet from qtdialogs.DlgShowKeyList import DlgShowKeyList - -# Need to put circular imports at the end of the script to avoid an import deadlock -from qtdialogs.qtdialogs import STRETCH, MIN_PASSWD_WIDTH, \ - OpenPaperBackupWindow -from qtdialogs.qtdefines import QRadioButtonBackupCtr +from qtdialogs.DlgRestore import OpenPaperBackupDialog +from qtdialogs.qtdefines import QRadioButtonBackupCtr, STRETCH, \ + MIN_PASSWD_WIDTH from ui.CoinControlUI import CoinControlDlg, RBFDlg @@ -495,89 +493,6 @@ def getName(self): def getDescription(self): return str(self.editDescription.toPlainText()) -class AdvancedOptionsFrame(ArmoryFrame): - def __init__(self, parent, main, initLabel=''): - super(AdvancedOptionsFrame, self).__init__(parent, main) - lblComputeDescription = QRichLabel( \ - self.tr('Armory will test your system\'s speed to determine the most ' - 'challenging encryption settings that can be applied ' - 'in a given amount of time. High settings make it much harder ' - 'for someone to guess your passphrase. This is used for all ' - 'encrypted wallets, but the default parameters can be changed below.\n')) - lblComputeDescription.setWordWrap(True) - timeDescriptionTip = main.createToolTipWidget( \ - self.tr('This is the amount of time it will take for your computer ' - 'to unlock your wallet after you enter your passphrase. ' - '(the actual time used will be less than the specified ' - 'time, but more than one half of it). ')) - - # Set maximum compute time - self.editComputeTime = QLineEdit() - self.editComputeTime.setText('250 ms') - self.editComputeTime.setMaxLength(12) - lblComputeTime = QLabel(self.tr('Target compute &time (s, ms):')) - memDescriptionTip = main.createToolTipWidget( \ - self.tr('This is the maximum memory that will be ' - 'used as part of the encryption process. The actual value used ' - 'may be lower, depending on your system\'s speed. If a ' - 'low value is chosen, Armory will compensate by chaining ' - 'together more calculations to meet the target time. High ' - 'memory target will make GPU-acceleration useless for ' - 'guessing your passphrase.')) - lblComputeTime.setBuddy(self.editComputeTime) - - # Set maximum memory usage - self.editComputeMem = QLineEdit() - self.editComputeMem.setText('32.0 MB') - self.editComputeMem.setMaxLength(12) - lblComputeMem = QLabel(self.tr('Max &memory usage (kB, MB):')) - lblComputeMem.setBuddy(self.editComputeMem) - - self.editComputeTime.setMaximumWidth( tightSizeNChar(self, 20)[0] ) - self.editComputeMem.setMaximumWidth( tightSizeNChar(self, 20)[0] ) - - entryFrame = QFrame() - entryLayout = QGridLayout() - entryLayout.addWidget(timeDescriptionTip, 0, 0, 1, 1) - entryLayout.addWidget(lblComputeTime, 0, 1, 1, 1) - entryLayout.addWidget(self.editComputeTime, 0, 2, 1, 1) - entryLayout.addWidget(memDescriptionTip, 1, 0, 1, 1) - entryLayout.addWidget(lblComputeMem, 1, 1, 1, 1) - entryLayout.addWidget(self.editComputeMem, 1, 2, 1, 1) - entryFrame.setLayout(entryLayout) - layout = QVBoxLayout() - layout.addWidget(lblComputeDescription) - layout.addWidget(entryFrame) - layout.addStretch() - self.setLayout(layout) - - def getKdfSec(self): - # return -1 if the input is invalid - kdfSec = -1 - try: - kdfT, kdfUnit = str(self.editComputeTime.text()).strip().split(' ') - if kdfUnit.lower() == 'ms': - kdfSec = float(kdfT) / 1000. - elif kdfUnit.lower() in ('s', 'sec', 'seconds'): - kdfSec = float(kdfT) - except: - pass - return kdfSec - - def getKdfBytes(self): - # return -1 if the input is invalid - kdfBytes = -1 - try: - kdfM, kdfUnit = str(self.editComputeMem.text()).split(' ') - if kdfUnit.lower() == 'mb': - kdfBytes = round(float(kdfM) * (1024.0 ** 2)) - elif kdfUnit.lower() == 'kb': - kdfBytes = round(float(kdfM) * (1024.0)) - except: - pass - return kdfBytes - - class CardDeckFrame(ArmoryFrame): def __init__(self, parent, main, initLabel=''): super(CardDeckFrame, self).__init__(parent, main) @@ -850,40 +765,40 @@ def __init__(self, parent, main, initLabel=''): F = self.FEATURES - self.featuresTips[F.ProtGen] = self.main.createToolTipWidget(self.tr( + self.featuresTips[F.ProtGen] = createToolTipWidget(self.tr( 'Every time you click "Receive Bitcoins," a new address is generated. ' 'All of these addresses are generated from a single seed value, which ' 'is included in all backups. Therefore, all addresses that you have ' 'generated so far and will ever be generated with this wallet, ' 'are protected by this backup! ')) - self.featuresTips[F.ProtImport] = self.main.createToolTipWidget(self.tr( + self.featuresTips[F.ProtImport] = createToolTipWidget(self.tr( 'This wallet does not currently have any imported ' 'addresses, so you can safely ignore this feature! ' 'When imported addresses are present, backups only protects those ' 'imported before the backup was made. You must replace that ' 'backup if you import more addresses! ')) - self.featuresTips[F.LostPass] = self.main.createToolTipWidget(self.tr( + self.featuresTips[F.LostPass] = createToolTipWidget(self.tr( 'Lost/forgotten passphrases are, by far, the most common ' 'reason for users losing bitcoins. It is critical you have ' 'at least one backup that works if you forget your wallet ' 'passphrase. ')) - self.featuresTips[F.Durable] = self.main.createToolTipWidget(self.tr( + self.featuresTips[F.Durable] = createToolTipWidget(self.tr( 'USB drives and CD/DVD disks are not intended for long-term storage. ' 'They will probably last many years, but not guaranteed ' 'even for 3-5 years. On the other hand, printed text on paper will ' 'last many decades, and useful even when thoroughly faded. ')) - self.featuresTips[F.Visual] = self.main.createToolTipWidget(self.tr( + self.featuresTips[F.Visual] = createToolTipWidget(self.tr( 'The ability to look at a backup and determine if ' 'it is still usable. If a digital backup is stored in a safe ' 'deposit box, you have no way to verify its integrity unless ' 'you take a secure computer/device with you. A simple glance at ' 'a paper backup is enough to verify that it is still intact. ')) - self.featuresTips[F.Physical] = self.main.createToolTipWidget(self.tr( + self.featuresTips[F.Physical] = createToolTipWidget(self.tr( 'If multiple pieces/fragments are required to restore this wallet. ' 'For instance, encrypted backups require the backup ' 'and the passphrase. This feature is only needed for those ' @@ -1134,9 +1049,9 @@ def clickedDoIt(self): Progress=unlockProgress.UpdateHBar) if self.optPaperBackupOne.isChecked(): - isBackupCreated = OpenPaperBackupWindow('Single', self.parent(), self.main, self.wlt) + isBackupCreated = OpenPaperBackupDialog('Single', self.parent(), self.main, self.wlt) elif self.optPaperBackupFrag.isChecked(): - isBackupCreated = OpenPaperBackupWindow('Frag', self.parent(), self.main, self.wlt) + isBackupCreated = OpenPaperBackupDialog('Frag', self.parent(), self.main, self.wlt) elif self.optDigitalBackupPlain.isChecked(): if self.main.digitalBackupWarning(): isBackupCreated = self.main.makeWalletCopy(self, self.wlt, 'Decrypt', 'decrypt') From dc18b65c2d7bad6fb1ee4db129031c3fd0f05a8e Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 5 May 2023 15:23:23 +0200 Subject: [PATCH 39/47] [ARMORY-8] fix spending, isolate python address code in armoryengine.AddressUtils --- ArmoryQt.py | 7 +- armoryengine/AddressUtils.py | 391 ++++++++++++++++++++++++++ armoryengine/ArmoryUtils.py | 462 +------------------------------ armoryengine/CoinSelection.py | 321 +-------------------- armoryengine/Decorators.py | 4 +- armoryengine/PyBtcAddress.py | 12 +- armoryengine/PyBtcWallet.py | 17 +- armoryengine/Transaction.py | 143 ++-------- armoryengine/UserAddressUtils.py | 2 + armorymodels.py | 3 +- qtdialogs/DlgAddressBook.py | 4 +- qtdialogs/DlgAddressInfo.py | 1 + qtdialogs/DlgBackupCenter.py | 2 + qtdialogs/DlgConfirmSend.py | 8 +- qtdialogs/DlgDispTxInfo.py | 9 +- qtdialogs/DlgRestore.py | 1 + qtdialogs/DlgShowKeyList.py | 1 + qtdialogs/DlgUnlockWallet.py | 1 - qtdialogs/DlgWalletDetails.py | 1 + ui/AddressTypeSelectDialog.py | 2 +- ui/CoinControlUI.py | 9 +- ui/MultiSigDialogs.py | 14 +- ui/TreeViewGUI.py | 43 ++- ui/TxFrames.py | 11 +- ui/TxFramesOffline.py | 19 +- ui/WalletFrames.py | 5 +- ui/toolsDialogs.py | 4 +- 27 files changed, 531 insertions(+), 966 deletions(-) create mode 100644 armoryengine/AddressUtils.py diff --git a/ArmoryQt.py b/ArmoryQt.py index 8d8b29560..6bd8a3cb4 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -54,10 +54,13 @@ enum, GetExecDir, RightNow, CLI_ARGS, ARMORY_HOME_DIR, DEFAULT, \ ARMORY_DB_DIR, coin2str, DEFAULT_DATE_FORMAT, \ unixTimeToFormatStr, binary_to_hex, BTC_HOME_DIR, secondsToHumanTime, \ - LEVELDB_BLKDATA, LOGRAWDATA, LOGPPRINT, hex_to_binary, \ + LEVELDB_BLKDATA, LOGPPRINT, hex_to_binary, \ getRandomHexits_NotSecure, coin2strNZS, bytesToHumanSize, hash256, \ DEFAULT_ADDR_TYPE, hex_switchEndian, BLOCKEXPLORE_NAME, getBridgeArgList from armoryengine.Settings import TheSettings +from armoryengine.AddressUtils import base58_to_binary, Hash160ToScrAddr, \ + hash160_to_addrStr, addrStr_to_hash160, scrAddr_to_script, \ + scrAddr_to_addrStr, LOGRAWDATA from armoryengine.Block import PyBlock from armoryengine.Decorators import RemoveRepeatingExtensions @@ -5467,7 +5470,7 @@ def bumpFee(self, walletId, txHash): wlt = self.walletMap[walletId] #grab ZC from DB - zctx = TheBDM.bdv().getTxByHash(txHash) + zctx = TheBDM.service.getTxByHash(txHash) pytx = PyTx().unserialize(zctx.serialize()) #create tx batch diff --git a/armoryengine/AddressUtils.py b/armoryengine/AddressUtils.py new file mode 100644 index 000000000..cd8466720 --- /dev/null +++ b/armoryengine/AddressUtils.py @@ -0,0 +1,391 @@ +############################################################################## +# # +# Copyright (C) 2011-2015, Armory Technologies, Inc. # +# Distributed under the GNU Affero General Public License (AGPL v3) # +# See LICENSE or http://www.gnu.org/licenses/agpl.html # +# # +# Copyright (C) 2016-2023, goatpig # +# Distributed under the MIT license # +# See LICENSE-MIT or https://opensource.org/licenses/MIT # +# # +############################################################################## +import logging +import traceback +import binascii + +from armoryengine.BinaryPacker import BinaryPacker, UINT8, BINARY_CHUNK +from armoryengine.ArmoryUtils import DATATYPE, ADDRBYTE, P2SHBYTE, \ + binary_to_hex, prettyHex, hash256, hash160, SCRADDR_BYTE_LIST, \ + ADDRBYTE, P2SHBYTE, LOGERROR + + +################################################################################ +BASE58CHARS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +DEFAULT_RAWDATA_LOGLEVEL = logging.DEBUG +HASH160PREFIX = '\x00' + +#address types, copied from cppForSwig/Wallets/Addresses.h +AddressEntryType_Default = 0 +AddressEntryType_P2PKH = 1 +AddressEntryType_P2PK = 2 +AddressEntryType_P2WPKH = 3 +AddressEntryType_Multisig = 4 +AddressEntryType_Uncompressed = 0x10000000 +AddressEntryType_P2SH = 0x40000000 +AddressEntryType_P2WSH = 0x80000000 + +#### +class BadAddressError(Exception): pass +class NonBase58CharacterError(Exception): pass + +################################################################################ +def CheckHash160(scrAddr): + if not len(scrAddr)==21: + raise BadAddressError("Supplied scrAddr is not a Hash160 value!") + if scrAddr[0] != int.from_bytes(ADDRBYTE, "little") and \ + scrAddr[0] != int.from_bytes(P2SHBYTE, "little"): + raise BadAddressError("Supplied scrAddr is not a Hash160 value!") + return scrAddr[1:] + +#### +def Hash160ToScrAddr(a160): + if not len(a160)==20: + LOGERROR('Invalid hash160 value!') + return HASH160PREFIX + a160 + +################################################################################ +# BINARY/BASE58 CONVERSIONS +def binary_to_base58(binstr): + """ + This method applies the Bitcoin-specific conversion from binary to Base58 + which may includes some extra "zero" bytes, such as is the case with the + main-network addresses. + + This method is labeled as outputting an "addrStr", but it's really this + special kind of Base58 converter, which makes it usable for encoding other + data, such as ECDSA keys or scripts. + """ + padding = 0 + for b in binstr: + if b==b'\x00': + padding+=1 + else: + break + + n = 0 + for ch in binstr: + n *= 256 + n += ch + + b58 = '' + while n > 0: + n, r = divmod (n, 58) + b58 = BASE58CHARS[r] + b58 + return '1'*padding + b58 + +#### +def base58_to_binary(s): + """ + This method applies the Bitcoin-specific conversion from Base58 to binary + which may includes some extra "zero" bytes, such as is the case with the + main-network addresses. + + This method is labeled as inputting an "addrStr", but it's really this + special kind of Base58 converter, which makes it usable for encoding other + data, such as ECDSA keys or scripts. + """ + + if not s: + return b'' + + # Convert the string to an integer + n = 0 + for c in s: + n *= 58 + if c not in BASE58CHARS: + raise NonBase58CharacterError('Character %r is not a valid base58 character' % c) + digit = BASE58CHARS.index(c) + n += digit + + # Convert the integer to bytes + h = '%x' % n + if len(h) % 2: + h = '0' + h + res = bytes(binascii.unhexlify(h.encode('utf8'))) + + # Add padding back. + pad = 0 + for c in s[:-1]: + if c == BASE58CHARS[0]: pad += 1 + else: break + return b'\x00' * pad + res + +#### +def encodePrivKeyBase58(privKeyBin): + bin33 = PRIVKEYBYTE + privKeyBin + chk = computeChecksum(bin33) + return binary_to_base58(bin33 + chk) + +################################################################################ +def hash160_to_addrStr(binStr, netbyte=ADDRBYTE): + """ + Converts the 20-byte pubKeyHash to 25-byte binary Bitcoin address + which includes the network byte (prefix) and 4-byte checksum (suffix) + """ + + if not len(binStr) == 20: + raise InvalidHashError('Input string is %d bytes' % len(binStr)) + + packer = BinaryPacker() + packer.put(BINARY_CHUNK, netbyte) + packer.put(BINARY_CHUNK, binStr) + hash21 = hash256(packer.getBinaryString()) + packer.put(BINARY_CHUNK, hash21[:4]) + return binary_to_base58(packer.getBinaryString()) + +################################################################################ +def hash160_to_p2shAddrStr(binStr): + if not len(binStr) == 20: + raise InvalidHashError('Input string is %d bytes' % len(binStr)) + + packer = BinaryPacker() + packer.put(BINARY_CHUNK, netbyte) + packer.put(BINARY_CHUNK, binStr) + hash21 = hash256(packer.getBinaryString()) + packer.put(BINARY_CHUNK, hash21[:4]) + return binary_to_base58(packer.getBinaryString()) + +################################################################################ +def binScript_to_p2shAddrStr(binScript): + return hash160_to_p2shAddrStr(hash160(binScript)) + +################################################################################ +def addrStr_is_p2sh(b58Str): + if len(b58Str)==0: + return False + + if sum([(0 if c in BASE58CHARS else 1) for c in b58Str]) > 0: + return False + + binStr = base58_to_binary(b58Str) + if not len(binStr)==25: + return False + + if not hash256(binStr[:21])[:4] == binStr[-4:]: + return False + + return (binStr[0] == P2SHBYTE) + +################################################################################ +# As of version 0.90.1, this returns the prefix byte with the hash160. This is +# because we need to handle/distinguish regular addresses from P2SH. All code +# using this method must be updated to expect 2 outputs and check the prefix. +def addrStr_to_hash160(b58Str, p2shAllowed=True, \ + addrByte = ADDRBYTE, p2shByte = P2SHBYTE): + binStr = base58_to_binary(b58Str) + + addrByteInt = int.from_bytes(addrByte, "little") + p2shByteInt = int.from_bytes(p2shByte, "little") + + if not p2shAllowed and binStr[0]==addrByteInt: + raise P2SHNotSupportedError + if not len(binStr) == 25: + raise BadAddressError('Address string is %d bytes' % len(binStr)) + + if not hash256(binStr[:21])[:4] == binStr[-4:]: + raise ChecksumError('Address string has invalid checksum') + + if not binStr[0] in (addrByteInt, p2shByteInt): + raise BadAddressError('Unknown addr prefix: %s' % binary_to_hex(binStr[0])) + + return (binStr[0], binStr[1:-4]) + +################################################################################ +def isLikelyDataType(theStr, dtype=None): + """ + This really shouldn't be used on short strings. Hence + why it's called "likely" datatype... + """ + ret = None + try: + hexCount = sum([1 if c in BASE16CHARS else 0 for c in theStr]) + except: + return DATATYPE.Binary + b58Count = sum([1 if c in BASE58CHARS else 0 for c in theStr]) + canBeHex = hexCount==len(theStr) + canBeB58 = b58Count==len(theStr) + if canBeHex: + ret = DATATYPE.Hex + elif canBeB58 and not canBeHex: + ret = DATATYPE.Base58 + else: + ret = DATATYPE.Binary + + if dtype==None: + return ret + else: + return dtype==ret + +################################################################################ +def scrAddr_to_script(scraddr): + """ + Convert a scrAddr string (used by BDM) to the correct TxOut script + Note this only works for P2PKH and P2SH scraddrs. Multi-sig and + all non-standard scripts cannot be derived from scrAddrs. In a way, + a scrAddr is intended to be an intelligent "hash" of the script, + and it's a perk that most of the time we can reverse it to get the script. + """ + if len(scraddr)==0: + raise BadAddressError('Empty scraddr') + + prefix = scraddr[0] + if not prefix in SCRADDR_BYTE_LIST or not len(scraddr)==21: + LOGERROR('Bad scraddr: "%s"' % binary_to_hex(scraddr)) + raise BadAddressError('Invalid ScrAddress') + + if prefix==ADDRBYTE: + return hash160_to_p2pkhash_script(scraddr[1:]) + elif prefix==P2SHBYTE: + return hash160_to_p2sh_script(scraddr[1:]) + else: + LOGERROR('Unsupported scraddr type: "%s"' % binary_to_hex(scraddr)) + raise BadAddressError('Can only convert P2PKH and P2SH scripts') + +################################################################################ +def script_to_scrAddr(binScript): + """ Convert a binary script to scrAddr string (used by BDM) """ + from armoryengine.CppBridge import TheBridge + return TheBridge.scriptUtils.getScrAddrForScript(binScript) + +################################################################################ +def script_to_addrStr(binScript): + """ Convert a binary script to scrAddr string (used by BDM) """ + return scrAddr_to_addrStr(script_to_scrAddr(binScript)) + +################################################################################ +def scrAddr_to_addrStr(scrAddr): + from armoryengine.CppBridge import TheBridge + return TheBridge.scriptUtils.getAddrStrForScrAddr(scrAddr) + +################################################################################ +# We beat around the bush here, to make sure it goes through addrStr which +# triggers errors if this isn't a regular addr or P2SH addr +def scrAddr_to_hash160(scrAddr): + addr = scrAddr_to_addrStr(scrAddr) + atype, a160 = addrStr_to_hash160(addr) + return (atype, a160) + +################################################################################ +def addrStr_to_scrAddr(addrStr, p2pkhByte = ADDRBYTE, p2shByte = P2SHBYTE): + if addrStr == '': + return '' + + if not checkAddrStrValid(addrStr, [p2pkhByte, p2shByte]): + BadAddressError('Invalid address: "%s"' % addrStr) + + atype, a160 = addrStr_to_hash160(addrStr, True, p2pkhByte, p2shByte) + if atype==int.from_bytes(p2pkhByte, "little"): + return p2pkhByte + a160 + elif atype==int.from_bytes(p2shByte, "little"): + return p2shByte + a160 + else: + BadAddressError('Invalid address: "%s"' % addrStr) + +################################################################################ +# output script type to address type resolution +def getAddressTypeForOutputType(scriptType): + if scriptType == CPP_TXOUT_STDHASH160: + return AddressEntryType_P2PKH + + elif scriptType == CPP_TXOUT_STDPUBKEY33: + return AddressEntryType_P2PK + + elif scriptType == CPP_TXOUT_STDPUBKEY65: + return AddressEntryType_P2PK + AddressEntryType_Uncompressed + + elif scriptType == CPP_TXOUT_MULTISIG: + return AddressEntryType_Multisig + + elif scriptType == CPP_TXOUT_P2SH: + return AddressEntryType_P2SH + + elif scriptType == CPP_TXOUT_P2WPKH: + return AddressEntryType_P2WPKH + + elif scriptType == CPP_TXOUT_P2WSH: + return AddressEntryType_P2WSH + + raise Exception("unknown address type") + +################################################################################ +def addrTypeInSet(addrType, addrTypeSet): + if addrType in addrTypeSet: + return True + + def nestedSearch(nestedType): + if not (addrType & nestedType): + return False + + for aType in addrTypeSet: + if aType & nestedType: + return True + return False + + #couldn't find an exact address type match, try to filter by nested types + if nestedSearch(AddressEntryType_P2SH): + return True + + if nestedSearch(AddressEntryType_P2WSH): + return True + + return False + +################################################################################ +def ComputeFragIDBase58(M, wltIDBin): + mBin4 = int_to_binary(M, widthBytes=4, endOut=BIGENDIAN) + fragBin = hash256(wltIDBin + mBin4)[:4] + fragB58 = str(M) + binary_to_base58(fragBin) + return fragB58 + +################################################################################ +def ComputeFragIDLineHex(M, index, wltIDBin, isSecure=False, addSpaces=False): + fragID = int_to_hex((128+M) if isSecure else M) + fragID += int_to_hex(index+1) + fragID += binary_to_hex(wltIDBin) + + if addSpaces: + fragID = ' '.join([fragID[i*4:(i+1)*4] for i in range(4)]) + + return fragID + +################################################################################ +def ReadFragIDLineBin(binLine): + doMask = binary_to_int(binLine[0]) > 127 + M = binary_to_int(binLine[0]) & 0x7f + fnum = binary_to_int(binLine[1]) + fragID = binLine[2:] + + idBase58 = ComputeFragIDBase58(M, fragID) + '-#' + str(fnum) + return (M, fnum, fragID, doMask, idBase58) + +################################################################################ +def ReadFragIDLineHex(hexLine): + return ReadFragIDLineBin( hex_to_binary(hexLine.strip().replace(' ',''))) + +################################################################################ +# For super-debug mode, we'll write out raw data +def LOGRAWDATA(rawStr, loglevel=DEFAULT_RAWDATA_LOGLEVEL): + dtype = isLikelyDataType(rawStr) + stkOneUp = traceback.extract_stack()[-2] + filename,method = stkOneUp[0], stkOneUp[1] + methodStr = '(PPRINT from %s:%d)\n' % (filename,method) + pstr = rawStr[:] + if dtype==DATATYPE.Binary: + pstr = binary_to_hex(rawStr) + pstr = prettyHex(pstr, indent=' ', withAddr=False) + elif dtype==DATATYPE.Hex: + pstr = prettyHex(pstr, indent=' ', withAddr=False) + else: + pstr = ' ' + '\n '.join(pstr.split('\n')) + + logging.log(loglevel, methodStr + pstr) diff --git a/armoryengine/ArmoryUtils.py b/armoryengine/ArmoryUtils.py index da28ddf6f..de512b3e2 100755 --- a/armoryengine/ArmoryUtils.py +++ b/armoryengine/ArmoryUtils.py @@ -147,7 +147,6 @@ parser.add_option("--coverage_include", dest="coverageInclude", default=None, type="str", help="Unit Test Argument - Do not consume") # Some useful constants to be used throughout everything -BASE58CHARS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' BASE16CHARS = '0123456789abcdefABCDEF' BASE16CHARS_NOCAPS = '0123456789abcdef' LITTLEENDIAN = '<' @@ -213,7 +212,6 @@ class UnserializeError(Exception): pass -class BadAddressError(Exception): pass class VerifyScriptError(Exception): pass class FileExistsError(Exception): pass class ECDSA_Error(Exception): pass @@ -245,7 +243,6 @@ class ShouldNotGetHereError(Exception): pass class BadInputError(Exception): pass class UstxError(Exception): pass class P2SHNotSupportedError(Exception): pass -class NonBase58CharacterError(Exception): pass class isMSWallet(Exception): pass class SignerException(Exception): pass @@ -310,7 +307,7 @@ class SignerException(Exception): pass # Figure out the default directories for Satoshi client, and BicoinArmory OS_NAME = '' OS_VARIANT = '' -USER_HOME_DIR = '' +USER_HOME_DIR = '' BTC_HOME_DIR = '' ARMORY_HOME_DIR = '' ARMORY_DB_DIR = '' @@ -335,7 +332,7 @@ class SignerException(Exception): pass BTC_HOME_DIR = os.path.join(USER_HOME_DIR, 'Bitcoin') if SUBDIR != '': BTC_HOME_DIR = os.path.join(BTC_HOME_DIR, SUBDIR) - + ARMORY_HOME_DIR = os.path.join(USER_HOME_DIR, 'Armory', SUBDIR) BLKFILE_DIR = os.path.join(BTC_HOME_DIR, 'blocks') BLKFILE_1stFILE = os.path.join(BLKFILE_DIR, 'blk00000.dat') @@ -344,12 +341,12 @@ class SignerException(Exception): pass OS_NAME = 'Linux' OS_VARIANT = distro.linux_distribution() USER_HOME_DIR = os.getenv('HOME') - + if BTC_HOME_DIR == '': BTC_HOME_DIR = os.path.join(USER_HOME_DIR, '.bitcoin') if SUBDIR != '': BTC_HOME_DIR = os.path.join(BTC_HOME_DIR, SUBDIR) - + ARMORY_HOME_DIR = os.path.join(USER_HOME_DIR, '.armory', SUBDIR) BLKFILE_DIR = os.path.join(BTC_HOME_DIR, 'blocks') BLKFILE_1stFILE = os.path.join(BLKFILE_DIR, 'blk00000.dat') @@ -358,12 +355,12 @@ class SignerException(Exception): pass OS_NAME = 'MacOSX' OS_VARIANT = platform.mac_ver() USER_HOME_DIR = os.path.expanduser('~/Library/Application Support') - + if BTC_HOME_DIR == '': BTC_HOME_DIR = os.path.join(USER_HOME_DIR, 'Bitcoin') if SUBDIR != '': - BTC_HOME_DIR = os.path.join(BTC_HOME_DIR, SUBDIR) - + BTC_HOME_DIR = os.path.join(BTC_HOME_DIR, SUBDIR) + ARMORY_HOME_DIR = os.path.join(USER_HOME_DIR, 'Armory', SUBDIR) BLKFILE_DIR = os.path.join(BTC_HOME_DIR, 'blocks') BLKFILE_1stFILE = os.path.join(BLKFILE_DIR, 'blk00000.dat') @@ -621,16 +618,6 @@ def readVersionInt(verInt): CPP_TXIN_SCRIPT_NAMES[CPP_TXIN_SPENDP2SH] = 'Spend P2SH' CPP_TXIN_SCRIPT_NAMES[CPP_TXIN_NONSTANDARD] = 'Non-Standard' -#address types, copied from cppForSwig/Wallets/Addresses.h -AddressEntryType_Default = 0 -AddressEntryType_P2PKH = 1 -AddressEntryType_P2PK = 2 -AddressEntryType_P2WPKH = 3 -AddressEntryType_Multisig = 4 -AddressEntryType_Uncompressed = 0x10000000 -AddressEntryType_P2SH = 0x40000000 -AddressEntryType_P2WSH = 0x80000000 - ################################################################################ if not CLI_OPTIONS.satoshiPort == DEFAULT: try: @@ -831,7 +818,6 @@ def chopLogFile(filename, size): DEFAULT_FILE_LOGTHRESH = logging.INFO DEFAULT_PPRINT_LOGLEVEL = logging.DEBUG -DEFAULT_RAWDATA_LOGLEVEL = logging.DEBUG rootLogger = logging.getLogger('') if CLI_OPTIONS.doDebug or CLI_OPTIONS.netlog or CLI_OPTIONS.mtdebug: @@ -884,24 +870,6 @@ def LOGPPRINT(theObj, loglevel=DEFAULT_PPRINT_LOGLEVEL): methodStr = '(PPRINT from %s:%d)\n' % (filename,method) logging.log(loglevel, methodStr + printedStr) -# For super-debug mode, we'll write out raw data -def LOGRAWDATA(rawStr, loglevel=DEFAULT_RAWDATA_LOGLEVEL): - dtype = isLikelyDataType(rawStr) - stkOneUp = traceback.extract_stack()[-2] - filename,method = stkOneUp[0], stkOneUp[1] - methodStr = '(PPRINT from %s:%d)\n' % (filename,method) - pstr = rawStr[:] - if dtype==DATATYPE.Binary: - pstr = binary_to_hex(rawStr) - pstr = prettyHex(pstr, indent=' ', withAddr=False) - elif dtype==DATATYPE.Hex: - pstr = prettyHex(pstr, indent=' ', withAddr=False) - else: - pstr = ' ' + '\n '.join(pstr.split('\n')) - - logging.log(loglevel, methodStr + pstr) - - cpplogfile = None if CLI_OPTIONS.logDisable: print('Logging is disabled') @@ -1462,128 +1430,6 @@ def pubkeylist_to_multisig_script(pkList, M, withSort=True): return outScript -################################################################################ -def scrAddr_to_script(scraddr): - """ - Convert a scrAddr string (used by BDM) to the correct TxOut script - Note this only works for P2PKH and P2SH scraddrs. Multi-sig and - all non-standard scripts cannot be derived from scrAddrs. In a way, - a scrAddr is intended to be an intelligent "hash" of the script, - and it's a perk that most of the time we can reverse it to get the script. - """ - if len(scraddr)==0: - raise BadAddressError('Empty scraddr') - - prefix = scraddr[0] - if not prefix in SCRADDR_BYTE_LIST or not len(scraddr)==21: - LOGERROR('Bad scraddr: "%s"' % binary_to_hex(scraddr)) - raise BadAddressError('Invalid ScrAddress') - - if prefix==ADDRBYTE: - return hash160_to_p2pkhash_script(scraddr[1:]) - elif prefix==P2SHBYTE: - return hash160_to_p2sh_script(scraddr[1:]) - else: - LOGERROR('Unsupported scraddr type: "%s"' % binary_to_hex(scraddr)) - raise BadAddressError('Can only convert P2PKH and P2SH scripts') - - -################################################################################ -def script_to_scrAddr(binScript): - """ Convert a binary script to scrAddr string (used by BDM) """ - from armoryengine.CppBridge import TheBridge - return TheBridge.scriptUtils.getScrAddrForScript(binScript) - -################################################################################ -def script_to_addrStr(binScript): - """ Convert a binary script to scrAddr string (used by BDM) """ - return scrAddr_to_addrStr(script_to_scrAddr(binScript)) - -################################################################################ -def scrAddr_to_addrStr(scrAddr): - from armoryengine.CppBridge import TheBridge - return TheBridge.scriptUtils.getAddrStrForScrAddr(scrAddr) - -################################################################################ -# We beat around the bush here, to make sure it goes through addrStr which -# triggers errors if this isn't a regular addr or P2SH addr -def scrAddr_to_hash160(scrAddr): - addr = scrAddr_to_addrStr(scrAddr) - atype, a160 = addrStr_to_hash160(addr) - return (atype, a160) - - -################################################################################ -def addrStr_to_scrAddr(addrStr, p2pkhByte = ADDRBYTE, p2shByte = P2SHBYTE): - if addrStr == '': - return '' - - if not checkAddrStrValid(addrStr, [p2pkhByte, p2shByte]): - BadAddressError('Invalid address: "%s"' % addrStr) - - atype, a160 = addrStr_to_hash160(addrStr, True, p2pkhByte, p2shByte) - if atype==int.from_bytes(p2pkhByte, "little"): - return p2pkhByte + a160 - elif atype==int.from_bytes(p2shByte, "little"): - return p2shByte + a160 - else: - BadAddressError('Invalid address: "%s"' % addrStr) - - -################################################################################ -def addrStr_to_script(addrStr): - """ Convert an addr string to a binary script """ - return scrAddr_to_script(addrStr_to_scrAddr(addrStr)) - -################################################################################ -# output script type to address type resolution -def getAddressTypeForOutputType(scriptType): - if scriptType == CPP_TXOUT_STDHASH160: - return AddressEntryType_P2PKH - - elif scriptType == CPP_TXOUT_STDPUBKEY33: - return AddressEntryType_P2PK - - elif scriptType == CPP_TXOUT_STDPUBKEY65: - return AddressEntryType_P2PK + AddressEntryType_Uncompressed - - elif scriptType == CPP_TXOUT_MULTISIG: - return AddressEntryType_Multisig - - elif scriptType == CPP_TXOUT_P2SH: - return AddressEntryType_P2SH - - elif scriptType == CPP_TXOUT_P2WPKH: - return AddressEntryType_P2WPKH - - elif scriptType == CPP_TXOUT_P2WSH: - return AddressEntryType_P2WSH - - raise Exception("unknown address type") - -################################################################################ -def addrTypeInSet(addrType, addrTypeSet): - if addrType in addrTypeSet: - return True - - def nestedSearch(nestedType): - if not (addrType & nestedType): - return False - - for aType in addrTypeSet: - if aType & nestedType: - return True - return False - - #couldn't find an exact address type match, try to filter by nested types - if nestedSearch(AddressEntryType_P2SH): - return True - - if nestedSearch(AddressEntryType_P2WSH): - return True - - return False - ################################################################################ # We need to have some methods for casting ASCII<->Unicode<->Preferred DEFAULT_ENCODING = 'utf-8' @@ -1651,73 +1497,12 @@ def enum(*sequential, **named): DATATYPE = enum("Binary", 'Base58', 'Hex') INTERNET_STATUS = enum('Available', 'Unavailable', 'DidNotCheck') - -def isLikelyDataType(theStr, dtype=None): - """ - This really shouldn't be used on short strings. Hence - why it's called "likely" datatype... - """ - ret = None - try: - hexCount = sum([1 if c in BASE16CHARS else 0 for c in theStr]) - except: - return DATATYPE.Binary - b58Count = sum([1 if c in BASE58CHARS else 0 for c in theStr]) - canBeHex = hexCount==len(theStr) - canBeB58 = b58Count==len(theStr) - if canBeHex: - ret = DATATYPE.Hex - elif canBeB58 and not canBeHex: - ret = DATATYPE.Base58 - else: - ret = DATATYPE.Binary - - if dtype==None: - return ret - else: - return dtype==ret - cpplogfile = None if CLI_OPTIONS.logDisable: print('Logging is disabled') rootLogger.disabled = True - - - -# The database uses prefixes to identify type of address. Until the new -# wallet format is created that supports more than just hash160 addresses -# we have to explicitly add the prefix to any hash160 values that are being -# sent to any of the C++ utilities. For instance, the BlockDataManager (BDM) -# (C++ stuff) tracks regular hash160 addresses, P2SH, multisig, and all -# non-standard scripts. Any such "scrAddrs" (script-addresses) will eventually -# be valid entities for tracking in a wallet. Until then, all of our python -# utilities all use just hash160 values, and we manually add the prefix -# before talking to the BDM. -HASH160PREFIX = '\x00' -HASH160_TESTNET = '\x6f' -P2SHPREFIX = '\x05' -P2SH_TESTNET = '\xc4' -MSIGPREFIX = '\xfe' -NONSTDPREFIX = '\xff' -def CheckHash160(scrAddr): - if not len(scrAddr)==21: - raise BadAddressError("Supplied scrAddr is not a Hash160 value!") - if scrAddr[0] != int.from_bytes(ADDRBYTE, "little") and scrAddr[0] != int.from_bytes(P2SHBYTE, "little"): - raise BadAddressError("Supplied scrAddr is not a Hash160 value!") - return scrAddr[1:] - -def Hash160ToScrAddr(a160): - if not len(a160)==20: - LOGERROR('Invalid hash160 value!') - return HASH160PREFIX + a160 - -def HexHash160ToScrAddr(a160): - if not len(a160)==40: - LOGERROR('Invalid hash160 value!') - return HASH160PREFIX + hex_to_binary(a160) - # Some time methods (RightNow() return local unix timestamp) RightNow = time.time def RightNowUTC(): @@ -1917,174 +1702,9 @@ def bitset_to_int(bitset): n += (0 if bit=='0' else 1) * 2**i return n - - EmptyHash = hex_to_binary('00'*32) -################################################################################ -# BINARY/BASE58 CONVERSIONS -def binary_to_base58(binstr): - """ - This method applies the Bitcoin-specific conversion from binary to Base58 - which may includes some extra "zero" bytes, such as is the case with the - main-network addresses. - - This method is labeled as outputting an "addrStr", but it's really this - special kind of Base58 converter, which makes it usable for encoding other - data, such as ECDSA keys or scripts. - """ - padding = 0 - for b in binstr: - if b==b'\x00': - padding+=1 - else: - break - - n = 0 - for ch in binstr: - n *= 256 - n += ch - - b58 = '' - while n > 0: - n, r = divmod (n, 58) - b58 = BASE58CHARS[r] + b58 - return '1'*padding + b58 - - -################################################################################ -def base58_to_binary(s): - """ - This method applies the Bitcoin-specific conversion from Base58 to binary - which may includes some extra "zero" bytes, such as is the case with the - main-network addresses. - - This method is labeled as inputting an "addrStr", but it's really this - special kind of Base58 converter, which makes it usable for encoding other - data, such as ECDSA keys or scripts. - """ - - if not s: - return b'' - - # Convert the string to an integer - n = 0 - for c in s: - n *= 58 - if c not in BASE58CHARS: - raise NonBase58CharacterError('Character %r is not a valid base58 character' % c) - digit = BASE58CHARS.index(c) - n += digit - - # Convert the integer to bytes - h = '%x' % n - if len(h) % 2: - h = '0' + h - res = bytes(binascii.unhexlify(h.encode('utf8'))) - - # Add padding back. - pad = 0 - for c in s[:-1]: - if c == BASE58CHARS[0]: pad += 1 - else: break - return b'\x00' * pad + res - -################################################################################ -def privKey_to_base58(binKey): - '''Convert a 32-byte private key to the Satoshi client Base58 format.''' - - retBase58 = '' - # For now, we don't support compressed private keys. (When we do, add - # 0x01 after the private key when returning a private key.) - try: - compByte = '' - if 0: - compByte = '\x01' - privHashAddr = SecureBinaryData(PRIVKEYBYTE + binKey + compByte) - privHash256 = \ - SecureBinaryData(hash256(privHashAddr.toBinStr())[0:4]) - privHashFinal = \ - SecureBinaryData(binary_to_base58(privHashAddr.toBinStr() + \ - privHash256.toBinStr())) - retBase58 = privHashFinal.toBinStr() - finally: - privHashAddr.destroy() - privHash256.destroy() - privHashFinal.destroy() - - return retBase58 - - -################################################################################ -def hash160_to_addrStr(binStr, netbyte=ADDRBYTE): - """ - Converts the 20-byte pubKeyHash to 25-byte binary Bitcoin address - which includes the network byte (prefix) and 4-byte checksum (suffix) - """ - - if not len(binStr) == 20: - raise InvalidHashError('Input string is %d bytes' % len(binStr)) - - addr21 = netbyte + binStr - addr25 = addr21 + hash256(addr21)[:4] - return binary_to_base58(addr25) - -################################################################################ -def hash160_to_p2shAddrStr(binStr): - if not len(binStr) == 20: - raise InvalidHashError('Input string is %d bytes' % len(binStr)) - - addr21 = P2SHBYTE + binStr - addr25 = addr21 + hash256(addr21)[:4] - return binary_to_base58(addr25) - -################################################################################ -def binScript_to_p2shAddrStr(binScript): - return hash160_to_p2shAddrStr(hash160(binScript)) - -################################################################################ -def addrStr_is_p2sh(b58Str): - if len(b58Str)==0: - return False - - if sum([(0 if c in BASE58CHARS else 1) for c in b58Str]) > 0: - return False - - binStr = base58_to_binary(b58Str) - if not len(binStr)==25: - return False - - if not hash256(binStr[:21])[:4] == binStr[-4:]: - return False - - return (binStr[0] == P2SHBYTE) - -################################################################################ -# As of version 0.90.1, this returns the prefix byte with the hash160. This is -# because we need to handle/distinguish regular addresses from P2SH. All code -# using this method must be updated to expect 2 outputs and check the prefix. -def addrStr_to_hash160(b58Str, p2shAllowed=True, \ - addrByte = ADDRBYTE, p2shByte = P2SHBYTE): - binStr = base58_to_binary(b58Str) - - addrByteInt = int.from_bytes(addrByte, "little") - p2shByteInt = int.from_bytes(p2shByte, "little") - - if not p2shAllowed and binStr[0]==addrByteInt: - raise P2SHNotSupportedError - if not len(binStr) == 25: - raise BadAddressError('Address string is %d bytes' % len(binStr)) - - if not hash256(binStr[:21])[:4] == binStr[-4:]: - raise ChecksumError('Address string has invalid checksum') - - if not binStr[0] in (addrByteInt, p2shByteInt): - raise BadAddressError('Unknown addr prefix: %s' % binary_to_hex(binStr[0])) - - return (binStr[0], binStr[1:-4]) - - ###### Typing-friendly Base16 ##### # Implements "hexadecimal" encoding but using only easy-to-type # characters in the alphabet. Hex usually includes the digits 0-9 @@ -2596,41 +2216,6 @@ def testReconstructSecrets(fragMap, M, maxTestCount=20): return isRandom, testResults - -################################################################################ -def ComputeFragIDBase58(M, wltIDBin): - mBin4 = int_to_binary(M, widthBytes=4, endOut=BIGENDIAN) - fragBin = hash256(wltIDBin + mBin4)[:4] - fragB58 = str(M) + binary_to_base58(fragBin) - return fragB58 - -################################################################################ -def ComputeFragIDLineHex(M, index, wltIDBin, isSecure=False, addSpaces=False): - fragID = int_to_hex((128+M) if isSecure else M) - fragID += int_to_hex(index+1) - fragID += binary_to_hex(wltIDBin) - - if addSpaces: - fragID = ' '.join([fragID[i*4:(i+1)*4] for i in range(4)]) - - return fragID - - -################################################################################ -def ReadFragIDLineBin(binLine): - doMask = binary_to_int(binLine[0]) > 127 - M = binary_to_int(binLine[0]) & 0x7f - fnum = binary_to_int(binLine[1]) - fragID = binLine[2:] - - idBase58 = ComputeFragIDBase58(M, fragID) + '-#' + str(fnum) - return (M, fnum, fragID, doMask, idBase58) - - -################################################################################ -def ReadFragIDLineHex(hexLine): - return ReadFragIDLineBin( hex_to_binary(hexLine.strip().replace(' ',''))) - # END FINITE FIELD OPERATIONS ################################################################################ ################################################################################ @@ -2773,15 +2358,6 @@ def parsePrivateKeyData(theStr): return binEntry, keyType - -################################################################################ -def encodePrivKeyBase58(privKeyBin): - bin33 = PRIVKEYBYTE + privKeyBin - chk = computeChecksum(bin33) - return binary_to_base58(bin33 + chk) - - - URI_VERSION_STR = '1.0' @@ -3256,30 +2832,6 @@ def touchFile(fname): os.fsync(f.fileno()) f.close() -################################################################################ -def calcLockboxID(script=None, scraddr=None): - # ScrAddr is "Script/Address" and for multisig it is 0xfe followed by - # M and N, then the SORTED hash160 values of the public keys - # Part of the reason for using "ScrAddrs" is to bundle together - # different scripts that have the same effective signing authority. - # Different sortings of the same public key list have same signing - # authority and therefore should have the same ScrAddr - - if script is not None: - scrType = getTxOutScriptType(script) - if not scrType==CPP_TXOUT_MULTISIG: - LOGERROR('Not a multisig script!') - return None - scraddr = script_to_scrAddr(script) - - if not scraddr.startswith(SCRADDR_MULTISIG_BYTE): - LOGERROR('ScrAddr is not a multisig script!') - return None - - hashedData = hash160(MAGIC_BYTES + scraddr) - return binary_to_base58(hashedData)[1:9] - - ################################################################################ def getNameForAddrType(addrType): from armoryengine.CppBridge import TheBridge diff --git a/armoryengine/CoinSelection.py b/armoryengine/CoinSelection.py index e229e68e8..9c914557a 100644 --- a/armoryengine/CoinSelection.py +++ b/armoryengine/CoinSelection.py @@ -62,8 +62,10 @@ import math import random -from armoryengine.ArmoryUtils import CheckHash160, binary_to_hex, coin2str, \ - hash160_to_addrStr, ONE_BTC, CENT, int_to_binary, MIN_RELAY_TX_FEE, MIN_TX_FEE +from armoryengine.ArmoryUtils import binary_to_hex, coin2str, \ + ONE_BTC, CENT, int_to_binary, MIN_RELAY_TX_FEE, MIN_TX_FEE +from armoryengine.AddressUtils import CheckHash160, hash160_to_addrStr, \ + scrAddr_to_script from armoryengine.Timer import TimeThisFunction from armoryengine.Transaction import * from armoryengine.BDM import TheBDM @@ -79,11 +81,11 @@ # (correctly) throw errors if you don't. We can upgrade this in # the future. class PyUnspentTxOut(object): - def __init__(self, scrAddr=None, txHash=None, txoIdx=None, val=None, - numConf=None, fullScript=None): + def __init__(self, scrAddr=None, txHash=None, txoIdx=None, val=None, + numConf=None, fullScript=None): - self.initialize(scrAddr, txHash, None, None, None, - txoIdx, val, numConf, fullScript) + self.initialize(scrAddr, txHash, None, None, None, + txoIdx, val, numConf, fullScript) ############################################################################# @@ -179,16 +181,16 @@ def isChecked(self): ############################################################################# def toBridgeUtxo(self): - from armoryengine import ClientProto_pb2 - bridgeUtxo = ClientProto_pb2.BridgeUtxo() + from armoryengine import BridgeProto_pb2 + bridgeUtxo = BridgeProto_pb2.Utxo() - bridgeUtxo.scrAddr = self.scrAddr + bridgeUtxo.scraddr = self.scrAddr bridgeUtxo.value = self.val - bridgeUtxo.txHash = self.txHash - bridgeUtxo.txOutIndex = self.txOutIndex + bridgeUtxo.tx_hash = self.txHash + bridgeUtxo.txout_index = self.txOutIndex bridgeUtxo.script = self.binScript - bridgeUtxo.txHeight = self.txHeight - bridgeUtxo.txIndex = self.txIndex + bridgeUtxo.tx_height = self.txHeight + bridgeUtxo.tx_index = self.txIndex return bridgeUtxo @@ -212,101 +214,6 @@ def pprintUnspentTxOutList(utxoList, headerLine='Coin Selection: '): print(' ',str(utxo.getNumConfirm()).rjust(8), end=' ') print(' ', ('%0.2f' % (utxo.getValue()*utxo.getNumConfirm()/(ONE_BTC*144.))).rjust(16)) - -################################################################################ -# Sorting currently implemented in C++, but we implement a different kind, here -def PySortCoins(unspentTxOutInfo, sortMethod=1): - """ - Here we define a few different ways to sort a list of unspent TxOut objects. - Most of them are simple, some are more complex. In particular, the last - method (4) tries to be intelligent, by grouping together inputs from the - same address. - - The goal is not to do the heavy lifting for SelectCoins... we simply need - a few different ways to sort coins so that the SelectCoins algorithms has - a variety of different inputs to play with. Each sorting method is useful - for some types of unspent-TxOut lists, so as long as we have one good - sort, the PyEvalCoinSelect method will pick it out. - - As a precaution we send all the zero-confirmation UTXO's to the back - of the list, so that they will only be used if absolutely necessary. - """ - zeroConfirm = [] - - if sortMethod==0: - priorityFn = lambda a: a.getValue() * a.getNumConfirm() - return sorted(unspentTxOutInfo, key=priorityFn, reverse=True) - if sortMethod==1: - priorityFn = lambda a: (a.getValue() * a.getNumConfirm())**(1/3.) - return sorted(unspentTxOutInfo, key=priorityFn, reverse=True) - if sortMethod==2: - priorityFn = lambda a: (math.log(a.getValue()*a.getNumConfirm()+1)+4)**4 - return sorted(unspentTxOutInfo, key=priorityFn, reverse=True) - if sortMethod==3: - priorityFn = lambda a: a.getValue() if a.getNumConfirm()>0 else 0 - return sorted(unspentTxOutInfo, key=priorityFn, reverse=True) - if sortMethod==4: - addrMap = {} - zeroConfirm = [] - for utxo in unspentTxOutInfo: - if utxo.getNumConfirm() == 0: - zeroConfirm.append(utxo) - else: - scrType = getTxOutScriptType(utxo.getScript()) - if scrType in CPP_TXOUT_HAS_ADDRSTR: - addr = script_to_addrStr(utxo.getScript()) - else: - addr = script_to_scrAddr(utxo.getScript()) - - if addr not in addrMap: - addrMap[addr] = [utxo] - else: - addrMap[addr].append(utxo) - - priorityUTXO = (lambda a: (a.getNumConfirm()*a.getValue()**0.333)) - for addr,txoutList in addrMap.iteritems(): - txoutList.sort(key=priorityUTXO, reverse=True) - - priorityGrp = lambda a: max([priorityUTXO(utxo) for utxo in a]) - finalSortedList = [] - for utxo in sorted(addrMap.values(), key=priorityGrp, reverse=True): - finalSortedList.extend(utxo) - - finalSortedList.extend(zeroConfirm) - return finalSortedList - if sortMethod in (5, 6, 7): - utxoSorted = PySortCoins(unspentTxOutInfo, 1) - if len(utxoSorted) == 0: - return utxoSorted - # Rotate the top 1,2 or 3 elements to the bottom of the list - for i in range(sortMethod-4): - utxoSorted.append(utxoSorted[0]) - del utxoSorted[0] - return utxoSorted - - # TODO: Add a semi-random sort method: it will favor putting high-priority - # outputs at the front of the list, but will not be deterministic - # This should give us some high-fitness variation compared to sorting - # uniformly - if sortMethod==8: - utxosNoZC = filter(lambda a: a.getNumConfirm()!=0, unspentTxOutInfo) - random.shuffle(utxosNoZC) - utxosNoZC.extend(filter(lambda a: a.getNumConfirm()==0, unspentTxOutInfo)) - return utxosNoZC - if sortMethod==9: - utxoSorted = PySortCoins(unspentTxOutInfo, 1) - sz = len(filter(lambda a: a.getNumConfirm()!=0, utxoSorted)) - # swap 1/3 of the values at random - topsz = int(min(max(round(sz/3), 5), sz)) - for i in range(topsz): - pick1 = int(random.uniform(0,topsz)) - pick2 = int(random.uniform(0,sz-topsz)) - utxoSorted[pick1], utxoSorted[pick2] = utxoSorted[pick2], utxoSorted[pick1] - return utxoSorted - - - - ################################################################################ # Now we try half a dozen different selection algorithms ################################################################################ @@ -438,172 +345,6 @@ def PySelectCoins_MultiInput_DoubleValue( \ -################################################################################ -def getSelectCoinsScores(utxoSelectList, targetOutVal, minFee): - """ - Define a metric for scoring the output of SelectCoints. The output of - this method is a tuple of scores which identify a few different factors - of a txOut selection that users might care about in a selectCoins algorithm. - - This method only returns an absolute score, usually between 0 and 1 for - each factor. It is up to the person calling this method to decide how - much "weight" they want to give each one. You could even use the scores - as multiplicative factors if you wanted, though they were designed with - the following equation in mind: finalScore = sum(WEIGHT[i] * SCORE[i]) - - TODO: I need to recalibrate some of these factors, and modify them to - represent more directly what the user would be concerned about -- - such as PayFeeFactor, AnonymityFactor, etc. The information is - indirectly available with the current set of factors here - """ - - # Need to calculate how much the change will be returned to sender on this tx - totalIn = sum([utxo.getValue() for utxo in utxoSelectList]) - totalChange = totalIn - (targetOutVal+minFee) - - # Abort if this is an empty list (negative score) or not enough coins - if len(utxoSelectList)==0 or totalIn 0) - # - # On the other hand, if we have 1.832 and 10.00, and the 10.000 is the - # change, we don't really care that they're not close, it's still - # damned good/deceptive output anonymity (so: only execute - # the following block if outAnonFactor <= 1) - if 0 < outAnonFactor <= 1 and not totalChange==0: - outValDiff = abs(totalChange - targetOutVal) - diffPct = (outValDiff / max(totalChange, targetOutVal)) - if diffPct < 0.20: - outAnonFactor *= 1 - elif diffPct < 0.50: - outAnonFactor *= 0.7 - elif diffPct < 1.0: - outAnonFactor *= 0.3 - else: - outAnonFactor = 0 - - - ################## - # Tx size: we don't have signatures yet, but we assume that each txin is - # about 180 Bytes, TxOuts are 35, and 10 other bytes in the Tx - numBytes = 10 - numBytes += 180 * len(utxoSelectList) - numBytes += 35 * (1 if totalChange==0 else 2) - txSizeFactor = 0 - numKb = int(numBytes / 1000) - # Will compute size factor after we see this tx priority and AllowFree - # results. If the tx qualifies for free, we don't need to penalize - # a 3 kB transaction vs one that is 0.5 kB - - - ################## - # Priority: If our priority is above the 1-btc-after-1-day threshold - # then we might be allowed a free tx. But, if its priority - # isn't much above this thresh, it might take a couple blocks - # to be included - dPriority = 0 - anyZeroConfirm = False - for utxo in utxoSelectList: - if utxo.getNumConfirm() == 0: - anyZeroConfirm = True - else: - dPriority += utxo.getValue() * utxo.getNumConfirm() - - dPriority = dPriority / numBytes - priorityThresh = ONE_BTC * 144 / 250 - if dPriority < priorityThresh: - priorityFactor = 0 - elif dPriority < 10.0*priorityThresh: - priorityFactor = 0.7 - elif dPriority < 100.0*priorityThresh: - priorityFactor = 0.9 - else: - priorityFactor = 1.0 - - - ################## - # AllowFree: If three conditions are met, then the tx can be sent safely - # without a tx fee. Granted, it may not be included in the - # current block if the free space is full, but definitely in - # the next one - isFreeAllowed = 0 - haveDustOutputs = (0= priorityThresh and \ - numBytes <= 10000): - isFreeAllowed = 1 - - - ################## - # Finish size-factor calculation -- if free is allowed, kB is irrelevant - txSizeFactor = 0 - if isFreeAllowed or numKb<1: - txSizeFactor = 1 - else: - if numKb < 2: - txSizeFactor=0.2 - elif numKb<3: - txSizeFactor=0.1 - elif numKb<4: - txSizeFactor=0 - else: - txSizeFactor=-1 #if this is huge, actually subtract score - - return (isFreeAllowed, noZeroConf, priorityFactor, numAddrFactor, txSizeFactor, outAnonFactor) - - ################################################################################ # We define default preferences for weightings. Weightings are used to # determine the "priorities" for ranking various SelectCoins results @@ -848,38 +589,6 @@ def calcMinSuggestedFees(selectCoinsResult, targetOutVal, preSelectedFee, return suggestedFee -################################################################################ -def approxTxInSizeForTxOut(utxoScript, lboxList=None): - """ - Since this is always used for fee estimation, we overestimate the size to - be conservative. However, if this is P2SH, we won't have a clue what the - hashed script is, so unless we find it in our lockbox map, we assume the - max-max which is 1,650 bytes. - - Otherwise the TxIn is always: - PrevTxHash(32), PrevTxOutIndex(4), Script(_), Sequence(4) - """ - - scrType = getTxOutScriptType(utxoScript) - if scrType == CPP_TXOUT_STDHASH160: - return 180 - elif scrType in [CPP_TXOUT_STDPUBKEY33, CPP_TXOUT_STDPUBKEY65]: - return 110 - elif scrType == CPP_TXOUT_MULTISIG: - M,N,a160s,pubs = getMultisigScriptInfo(utxoScript) - return M*70 + 40 - elif scrType == CPP_TXOUT_P2SH and not lboxList is None: - scrAddr = script_to_scrAddr(utxoScript) - for lbox in lboxList: - if scrAddr == lbox.getAddr(): - M,N,a160s,pubs = getMultisigScriptInfo(lbox.binScript) - return M*70 + 40 - - # If we got here, we didn't identify it at all. Assume max for TxIn - return 1650 - - - ################################################################################ # I needed a new function that was going to be as accurate as possible for # arbitrary coin selections (and recipient lists). However, this doesn't diff --git a/armoryengine/Decorators.py b/armoryengine/Decorators.py index 42dd39db5..7bf4b3050 100644 --- a/armoryengine/Decorators.py +++ b/armoryengine/Decorators.py @@ -15,8 +15,8 @@ # ################################################################################ -from armoryengine.ArmoryUtils import send_email, LOGERROR, LOGRAWDATA,\ - CLI_OPTIONS +from armoryengine.ArmoryUtils import send_email, LOGERROR, CLI_OPTIONS +from armoryengine.AddressUtils import LOGRAWDATA import functools import sys from threading import Lock diff --git a/armoryengine/PyBtcAddress.py b/armoryengine/PyBtcAddress.py index 3d92d049d..9c098a1e7 100644 --- a/armoryengine/PyBtcAddress.py +++ b/armoryengine/PyBtcAddress.py @@ -7,17 +7,13 @@ # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # ################################################################################ -from armoryengine.ArmoryUtils import ADDRBYTE, hash256, binary_to_base58, \ +from armoryengine.ArmoryUtils import ADDRBYTE, hash256, \ KeyDataError, RightNow, LOGERROR, ChecksumError, convertKeyDataToAddress, \ verifyChecksum, WalletLockError, createDERSigFromRS, binary_to_int, \ computeChecksum, getVersionInt, PYBTCWALLET_VERSION, bitset_to_int, \ - LOGDEBUG, Hash160ToScrAddr, int_to_bitset, UnserializeError, \ - hash160_to_addrStr, int_to_binary, BIGENDIAN, \ - BadAddressError, checkAddrStrValid, binary_to_hex, ENABLE_DETSIGN, \ - AddressEntryType_Default, AddressEntryType_P2PKH, AddressEntryType_P2PK, \ - AddressEntryType_P2WPKH, AddressEntryType_Multisig, \ - AddressEntryType_Uncompressed, AddressEntryType_P2SH, \ - AddressEntryType_P2WSH + LOGDEBUG, int_to_bitset, UnserializeError, int_to_binary, BIGENDIAN, \ + checkAddrStrValid, binary_to_hex, ENABLE_DETSIGN +from armoryengine.AddressUtils import AddressEntryType_Default from armoryengine.BinaryPacker import BinaryPacker, UINT8, UINT16, UINT32, \ UINT64, INT8, INT16, INT32, INT64, VAR_INT, VAR_STR, FLOAT, BINARY_CHUNK from armoryengine.BinaryUnpacker import BinaryUnpacker diff --git a/armoryengine/PyBtcWallet.py b/armoryengine/PyBtcWallet.py index 49b091227..8cb5814d8 100644 --- a/armoryengine/PyBtcWallet.py +++ b/armoryengine/PyBtcWallet.py @@ -14,8 +14,8 @@ import os.path import shutil -from armoryengine.ArmoryUtils import AddressEntryType_Default, UINT32_MAX, \ - emptyFunc, PYBTCWALLET_VERSION, USE_TESTNET, USE_REGTEST, CLI_OPTIONS, \ +from armoryengine.ArmoryUtils import UINT32_MAX, emptyFunc, \ + PYBTCWALLET_VERSION, USE_TESTNET, USE_REGTEST, CLI_OPTIONS, \ LOGINFO, LOGEXCEPT, LOGWARN, LOGERROR from armoryengine.BinaryPacker import * from armoryengine.BinaryUnpacker import * @@ -23,6 +23,8 @@ from armoryengine.Decorators import singleEntrantMethod from armoryengine.CppBridge import TheBridge, BridgeWalletWrapper from armoryengine.PyBtcAddress import PyBtcAddress +from armoryengine.AddressUtils import addrStr_to_hash160, \ + scrAddr_to_addrStr, AddressEntryType_Default BLOCKCHAIN_READONLY = 0 BLOCKCHAIN_READWRITE = 1 @@ -333,17 +335,6 @@ def getCommentForTxList(self, a160, txhashList): return '' - ############################################################################# - @CheckWalletRegistration - def printAddressBook(self): - addrbook = self.cppWallet.createAddressBook() - for abe in addrbook: - print(hash160_to_addrStr(abe.getAddr160()), end=' ') - txlist = abe.getTxList() - print(len(txlist)) - for rtx in txlist: - print('\t', binary_to_hex(rtx.getTxHash(), BIGENDIAN)) - ############################################################################# def hasAnyImported(self): for a160,addr in self.addrMap.items(): diff --git a/armoryengine/Transaction.py b/armoryengine/Transaction.py index a519ed724..bfbac8489 100644 --- a/armoryengine/Transaction.py +++ b/armoryengine/Transaction.py @@ -9,11 +9,23 @@ ################################################################################ import logging import os - -from armoryengine.ArmoryUtils import * -from armoryengine.BinaryPacker import * -from armoryengine.BinaryUnpacker import * - +import binascii + +from armoryengine.ArmoryUtils import BlockComponent, BIGENDIAN, \ + LITTLEENDIAN, enum, UINT32_MAX, UNINITIALIZED, WITNESS_MARKER, hash256, \ + CPP_TXOUT_NONSTANDARD, CPP_TXOUT_P2SH, CPP_TXOUT_MULTISIG, \ + CPP_TXOUT_P2WPKH, CPP_TXOUT_P2WSH, CPP_TXOUT_NONSTANDARD, \ + CPP_TXOUT_STDPUBKEY33, CPP_TXOUT_STDPUBKEY65, CPP_TXOUT_STDHASH160, \ + CPP_TXIN_STDUNCOMPR, CPP_TXIN_STDCOMPR, CPP_TXIN_SPENDP2SH, \ + CPP_TXIN_P2WPKH_P2SH, CPP_TXIN_P2WSH_P2SH, MIN_RELAY_TX_FEE, \ + int_to_binary, SignatureError, indent, binary_to_hex, \ + hash160, sha256, ONE_BTC +from armoryengine.AddressUtils import hash160_to_addrStr, binary_to_base58, \ + CheckHash160, binScript_to_p2shAddrStr, script_to_addrStr, \ + script_to_scrAddr, scrAddr_to_addrStr, BadAddressError +from armoryengine.BinaryPacker import BinaryPacker, UINT8, UINT32, UINT64, \ + VAR_INT, BINARY_CHUNK +from armoryengine.BinaryUnpacker import BinaryUnpacker from armoryengine.AsciiSerialize import AsciiSerializable from armoryengine.CppBridge import TheBridge, BridgeSigner from armoryengine.CoinSelection import sumTxOutList @@ -268,7 +280,7 @@ def TxInExtractAddrStrIfAvail(txinObj): elif scrType == CPP_TXIN_P2WPKH_P2SH: return binScript_to_p2shAddrStr(hash160(rawScript)) elif scrType == CPP_TXIN_P2WSH_P2SH: - return binScript_to_p2shAddrStr(hash160(rawScript[1:])) + return binScript_to_p2shAddrStr(sha256(rawScript[1:])) else: return '' @@ -324,6 +336,9 @@ def serialize(self): binOut.put(UINT32, self.txOutIndex) return binOut.getBinaryString() + def getTxHashStr(self): + return binascii.hexlify(self.txHash) + def pprint(self, nIndent=0, endian=BIGENDIAN): indstr = indent*nIndent print(indstr + 'OutPoint:') @@ -976,102 +991,6 @@ def __init__(self, rawSupportTx='', if self.scriptType==CPP_TXOUT_P2SH: nested = True - ''' - # If we're here, we should've passed in a P2SH script - if len(self.p2shMap) == 0: - self.isInitialized = False - raise UstxError('No P2SH script supplied for P2SH input') - - # Sanity check that the supplied P2SH script actually matches - self.p2shScrAddr = script_to_scrAddr(baseScript) - scriptHash = hash160(self.p2shMap[BASE_SCRIPT]) - if not P2SHBYTE + scriptHash == self.p2shScrAddr: - self.isInitialized = False - raise InvalidScriptError('No P2SH script info avail for TxDP') - - # Replace script type with that of the sub-script - # We can use the presence of p2shScript to identify it's p2sh - # Do the rest of the processing with the baseScript though we - # will leave self.txoScript alone since that needs to be the - # original script - baseScript = self.p2shMap[BASE_SCRIPT] - self.scriptType = getTxOutScriptType(baseScript) - ''' - - ##### - ''' - # Fill some of the other fields with info needed to spend the script - if self.scriptType==CPP_TXOUT_MULTISIG: - #nested or raw MS scripts for lockboxes - M, N, a160s, pubs = getMultisigScriptInfo(baseScript) - self.sigsNeeded = M - self.keysListed = N - self.scrAddrs = [SCRADDR_P2PKH_BYTE+a for a in a160s] - self.pubKeys = pubs[:] - self.signatures = ['']*N - self.wltLocators = ['']*N - - elif nested == False: - #legacy single sig types - if self.scriptType==CPP_TXOUT_P2SH: - # If this is a P2SH script, we've already overwritten the script - # type with the type of sub script. If we're here, this means - # that the subscript is also P2SH, which is not allowed - raise InvalidScriptError('Cannot have recursive P2SH scripts!') - elif self.scriptType in CPP_TXOUT_STDSINGLESIG: - scrAddr = script_to_scrAddr(baseScript) - if pubKeyMap is None or pubKeyMap.get(scrAddr) is None: - raise KeyDataError('Must give pubkey map for singlesig USTXI!') - self.sigsNeeded = 1 - self.keysListed = 1 - self.scrAddrs = [scrAddr] - self.pubKeys = [pubKeyMap[scrAddr]] - self.signatures = [''] - self.wltLocators = [''] - else: - LOGWARN("Non-standard script for TxIn %d" % i) - pass - - else: - #new nested single sig types - scrType = self.scriptType - if scrType in CPP_TXOUT_NESTED_SINGLESIG and \ - BASE_SCRIPT in self.p2shMap: - scrAddr = P2SHBYTE + hash160(self.p2shMap[BASE_SCRIPT]) - self.sigsNeeded = 1 - self.keysListed = 1 - self.scrAddrs = [scrAddr] - self.pubKeys = [pubKeyMap[scrAddr]] - self.signatures = [''] - self.wltLocators = [''] - self.isLegacyScript = False - - if scrType == CPP_TXOUT_P2WPKH: - self.isSegWit = True - - elif scrType == CPP_TXOUT_P2WSH: - try: - baseScript = self.p2shMap[BASE_SCRIPT] - msScript = self.p2shMap[baseScript] - except: - LOGERROR("missing p2wsh pre image" % i) - - M, N, a160s, pubs = getMultisigScriptInfo(msScript) - self.sigsNeeded = M - self.keysListed = N - self.scrAddrs = [SCRADDR_P2PKH_BYTE+a for a in a160s] - self.pubKeys = pubs[:] - self.signatures = ['']*N - self.wltLocators = ['']*N - self.isLegacyScript = False - self.isSegWit = True - - else: - LOGWARN("Unexpected nested type for TxIn %d" % i) - pass - ''' - - # "insert*s" can either be a single values, or a list # of pairs [multisgIndex, pubKey] sigInsertMethod = self.setSignature @@ -2021,24 +1940,6 @@ def createFromPyTx(self, pytx, pubKeyMap=None, txMap=None, p2shMap=None): else: raise InvalidScriptError('No previous-tx data available for TxDP') - txoScript = pyPrevTx.outputs[txoIdx].binScript - txoScrAddr = script_to_scrAddr(txoScript) - txoType = getTxOutScriptType(txoScript) - - ''' - p2shMap_copy = {} - if txoType==CPP_TXOUT_P2SH: - p2sh = p2shMap.get(binary_to_hex(txoScrAddr)) - if not p2sh: - raise InvalidHashError('P2SH script not supplied') - p2shMap_copy[BASE_SCRIPT] = p2sh - script_key = p2sh - while script_key in p2shMap: - val = p2shMap[script_key] - p2shMap_copy[script_key] = val - script_key = val - ''' - ustxiList.append(UnsignedTxInput(pyPrevTx.serializeWithoutWitness(), txoIdx, {}, @@ -2508,7 +2409,7 @@ def determineSentToSelfAmt(le, wlt): creative with this tx, this may not actually work. """ amt = 0 - if le.sent_to_senf: + if le.sent_to_self: txProto = TheBridge.service.getTxByHash(le.hash) if txProto == None: return (0, 0) diff --git a/armoryengine/UserAddressUtils.py b/armoryengine/UserAddressUtils.py index 897c42b62..86d593f69 100755 --- a/armoryengine/UserAddressUtils.py +++ b/armoryengine/UserAddressUtils.py @@ -12,6 +12,8 @@ isBareLockbox, isP2SHLockbox from armoryengine.Transaction import getTxOutScriptType, getMultisigScriptInfo from armoryengine.CppBridge import TheBridge, BridgeError +from armoryengine.AddressUtils import script_to_scrAddr, script_to_addrStr, \ + scrAddr_to_addrStr ############################################################################# def getScriptForUserStringImpl(userStr, wltMap, lboxList): diff --git a/armorymodels.py b/armorymodels.py index 0dc3744ea..b9e1d718a 100755 --- a/armorymodels.py +++ b/armorymodels.py @@ -21,7 +21,6 @@ QLineEdit, QFrame, QGridLayout, QPushButton, QAbstractItemView, \ QStyledItemDelegate, QTableView, QLabel - from armoryengine.ArmoryUtils import enum, coin2str, binary_to_hex, \ int_to_hex, CPP_TXOUT_SCRIPT_NAMES, CPP_TXOUT_MULTISIG, \ CPP_TXIN_SCRIPT_NAMES @@ -32,6 +31,8 @@ from armoryengine.Timer import TimeThisFunction from armoryengine.BDM import TheBDM, BDM_BLOCKCHAIN_READY from armoryengine.CppBridge import TheBridge +from armoryengine.AddressUtils import Hash160ToScrAddr, addrStr_to_hash160, \ + scrAddr_to_addrStr from armorycolors import Colors from qtdialogs.qtdefines import determineWalletType, WLTTYPES, \ diff --git a/qtdialogs/DlgAddressBook.py b/qtdialogs/DlgAddressBook.py index 8a7aad078..2cccc57f2 100644 --- a/qtdialogs/DlgAddressBook.py +++ b/qtdialogs/DlgAddressBook.py @@ -18,8 +18,8 @@ from armorymodels import AllWalletsDispModel, WLTVIEWCOLS, \ SentToAddrBookModel, SentAddrSortProxy, ADDRBOOKCOLS, \ WalletAddrDispModel, WalletAddrSortProxy, ADDRESSCOLS -from armoryengine.ArmoryUtils import DEFAULT_RECEIVE_TYPE, \ - addrStr_to_hash160, P2SHBYTE +from armoryengine.ArmoryUtils import DEFAULT_RECEIVE_TYPE, P2SHBYTE +from armoryengine.AddressUtils import addrStr_to_hash160 from armoryengine.MultiSigUtils import isBareLockbox, isP2SHLockbox from armoryengine.Settings import TheSettings diff --git a/qtdialogs/DlgAddressInfo.py b/qtdialogs/DlgAddressInfo.py index 00bc277db..13aff0f56 100644 --- a/qtdialogs/DlgAddressInfo.py +++ b/qtdialogs/DlgAddressInfo.py @@ -15,6 +15,7 @@ from armoryengine.ArmoryUtils import BIGENDIAN, LITTLEENDIAN, binary_to_hex from armoryengine.Settings import TheSettings +from armoryengine.AddressUtils import encodePrivKeyBase58 from armorymodels import LedgerDispModelSimple, LedgerDispDelegate, \ LEDGERCOLS, GETFONT diff --git a/qtdialogs/DlgBackupCenter.py b/qtdialogs/DlgBackupCenter.py index b1a7edccb..0b73ca9b8 100644 --- a/qtdialogs/DlgBackupCenter.py +++ b/qtdialogs/DlgBackupCenter.py @@ -19,6 +19,8 @@ QGraphicsLineItem, QGraphicsItem from armoryengine.ArmoryUtils import toUnicode, USE_TESTNET, USE_REGTEST +from armoryengine.AddressUtils import binary_to_base58, encodePrivKeyBase58, \ + hash160_to_addrStr from armorycolors import htmlColor from ui.WalletFrames import WalletBackupFrame diff --git a/qtdialogs/DlgConfirmSend.py b/qtdialogs/DlgConfirmSend.py index 9de75c0a7..0324a6e05 100644 --- a/qtdialogs/DlgConfirmSend.py +++ b/qtdialogs/DlgConfirmSend.py @@ -12,13 +12,13 @@ from PySide2.QtCore import Qt from PySide2.QtGui import QPixmap, QFont -from PySide2.QtWidgets import QLabel, QGridLayout, QSpacerItem, QPushButton, \ - QDialogButtonBox, QFrame +from PySide2.QtWidgets import QLabel, QGridLayout, QSpacerItem, \ + QPushButton, QDialogButtonBox, QFrame from armoryengine.ArmoryUtils import CPP_TXOUT_HAS_ADDRSTR, \ - CPP_TXOUT_P2WPKH, CPP_TXOUT_P2WSH, script_to_scrAddr, \ - scrAddr_to_hash160, coin2strNZS, coin2str + CPP_TXOUT_P2WPKH, CPP_TXOUT_P2WSH, coin2strNZS, coin2str from armoryengine.Transaction import getTxOutScriptType +from armoryengine.AddressUtils import script_to_scrAddr, scrAddr_to_hash160 from armorycolors import htmlColor from qtdialogs.qtdefines import USERMODE, QRichLabel, \ diff --git a/qtdialogs/DlgDispTxInfo.py b/qtdialogs/DlgDispTxInfo.py index 0470f773a..80e8f7291 100644 --- a/qtdialogs/DlgDispTxInfo.py +++ b/qtdialogs/DlgDispTxInfo.py @@ -17,12 +17,13 @@ QVBoxLayout, QDialogButtonBox, QApplication from armoryengine.ArmoryUtils import enum, CPP_TXOUT_MULTISIG, \ - CPP_TXOUT_P2SH, CPP_TXOUT_HAS_ADDRSTR, script_to_addrStr, \ - addrStr_to_hash160, script_to_scrAddr, BIGENDIAN, binary_to_hex, \ + CPP_TXOUT_P2SH, CPP_TXOUT_HAS_ADDRSTR, BIGENDIAN, binary_to_hex, \ hex_to_binary, coin2str, coin2strNZS, LOGEXCEPT, LOGERROR, \ CPP_TXIN_SCRIPT_NAMES, CPP_TXOUT_SCRIPT_NAMES, int_to_hex, \ - script_to_scrAddr, scrAddr_to_addrStr, unixTimeToFormatStr, \ - UINT32_MAX, hash256 + unixTimeToFormatStr, UINT32_MAX, hash256 +from armoryengine.AddressUtils import addrStr_to_hash160, \ + script_to_scrAddr, script_to_addrStr, scrAddr_to_addrStr, \ + addrStr_to_scrAddr, script_to_scrAddr from armoryengine.BDM import TheBDM, BDM_BLOCKCHAIN_READY from armoryengine.Transaction import UnsignedTransaction, \ diff --git a/qtdialogs/DlgRestore.py b/qtdialogs/DlgRestore.py index 55120cc62..a63090efc 100644 --- a/qtdialogs/DlgRestore.py +++ b/qtdialogs/DlgRestore.py @@ -20,6 +20,7 @@ UNKNOWN from armoryengine.BDM import TheBDM from armoryengine.PyBtcWallet import PyBtcWallet +from armoryengine.AddressUtils import binary_to_base58 from ui.QtExecuteSignal import TheSignalExecution from qtdialogs.ArmoryDialog import ArmoryDialog diff --git a/qtdialogs/DlgShowKeyList.py b/qtdialogs/DlgShowKeyList.py index 0975fae49..09b18acce 100644 --- a/qtdialogs/DlgShowKeyList.py +++ b/qtdialogs/DlgShowKeyList.py @@ -11,6 +11,7 @@ ############################################################################## from qtdialogs.ArmoryDialog import ArmoryDialog +from armoryengine.AddressUtils import encodePrivKeyBase58 ################################################################################ class DlgShowKeyList(ArmoryDialog): diff --git a/qtdialogs/DlgUnlockWallet.py b/qtdialogs/DlgUnlockWallet.py index bd39558fd..2e24fed48 100644 --- a/qtdialogs/DlgUnlockWallet.py +++ b/qtdialogs/DlgUnlockWallet.py @@ -309,7 +309,6 @@ def reply(self, passphrase): ############################################################################# def setIds(self, ids): - print (f"set ids: {ids}, self.encryptionKeyIds: {self.encryptionKeyIds}") if len(ids) == 0: self.reject() elif len(self.encryptionKeyIds) == 0: diff --git a/qtdialogs/DlgWalletDetails.py b/qtdialogs/DlgWalletDetails.py index 1e5e1c813..27e0b41ac 100644 --- a/qtdialogs/DlgWalletDetails.py +++ b/qtdialogs/DlgWalletDetails.py @@ -15,6 +15,7 @@ QTreeView, QLabel, QCheckBox, QLineEdit, QDialogButtonBox, QTextEdit from armoryengine.ArmoryUtils import getVersionString, coin2str, isASCII +from armoryengine.AddressUtils import addrStr_to_hash160 from armoryengine.BDM import TheBDM, BDM_UNINITIALIZED, BDM_OFFLINE, \ BDM_SCANNING from armoryengine.Settings import TheSettings diff --git a/ui/AddressTypeSelectDialog.py b/ui/AddressTypeSelectDialog.py index 6a1120edc..7ada8bf8d 100755 --- a/ui/AddressTypeSelectDialog.py +++ b/ui/AddressTypeSelectDialog.py @@ -15,7 +15,7 @@ from qtdialogs.qtdefines import STYLE_RAISED, QLabelButton from armoryengine.BDM import TheBDM from armoryengine.CppBridge import TheBridge -from armoryengine.PyBtcAddress import AddressEntryType_P2PKH, \ +from armoryengine.AddressUtils import AddressEntryType_P2PKH, \ AddressEntryType_P2PK, AddressEntryType_P2WPKH, \ AddressEntryType_Multisig, AddressEntryType_Uncompressed, \ AddressEntryType_P2SH, AddressEntryType_P2WSH diff --git a/ui/CoinControlUI.py b/ui/CoinControlUI.py index 57d337bee..e0a158940 100755 --- a/ui/CoinControlUI.py +++ b/ui/CoinControlUI.py @@ -8,6 +8,7 @@ # # ############################################################################## +from armoryengine.Settings import TheSettings from qtdialogs.qtdefines import QRichLabel, makeHorizFrame, \ saveTableView, restoreTableView, createToolTipWidget from qtdialogs.ArmoryDialog import ArmoryDialog @@ -60,8 +61,8 @@ def __init__(self, parent, main, wlt): buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) - hexgeom = self.main.settings.get('ccDlgGeometry') - tblgeom = self.main.settings.get('ccDlgAddrCols') + hexgeom = TheSettings.get('ccDlgGeometry') + tblgeom = TheSettings.get('ccDlgAddrCols') if len(hexgeom) > 0: if type(hexgeom) == str: @@ -159,8 +160,8 @@ def __init__(self, parent, main, wlt): buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) - hexgeom = self.main.settings.get('rbfDlgGeometry') - tblgeom = self.main.settings.get('rbfDlgAddrCols') + hexgeom = TheSettings.get('rbfDlgGeometry') + tblgeom = TheSettings.get('rbfDlgAddrCols') if len(hexgeom) > 0: if type(hexgeom) == str: diff --git a/ui/MultiSigDialogs.py b/ui/MultiSigDialogs.py index 306662409..36412ad57 100644 --- a/ui/MultiSigDialogs.py +++ b/ui/MultiSigDialogs.py @@ -17,7 +17,19 @@ from armorycolors import htmlColor from qtdialogs.ArmoryDialog import ArmoryDialog -from armoryengine.ArmoryUtils import BLOCKEXPLORE_NAME, BLOCKEXPLORE_URL_ADDR, BadAddressError, CPP_TXOUT_MULTISIG, CheckHash160, DATATYPE, DEFAULT_DATE_FORMAT, LB_MAXM, LB_MAXN, LOGDEBUG, LOGERROR, LOGEXCEPT, LOGINFO, NegativeValueError, RightNow, SignerException, TooMuchPrecisionError, USE_REGTEST, USE_TESTNET, addrStr_is_p2sh, addrStr_to_hash160, binScript_to_p2shAddrStr, binary_to_hex, checkAddrStrValid, coin2strNZS, getBlockID, hash160_to_addrStr, hash160_to_p2pkhash_script, hex_switchEndian, hex_to_binary, isLikelyDataType, pubkeylist_to_multisig_script, scrAddr_to_addrStr, script_to_addrStr, script_to_p2sh_script, script_to_scrAddr, str2coin, unixTimeToFormatStr +from armoryengine.ArmoryUtils import BLOCKEXPLORE_NAME, \ + BLOCKEXPLORE_URL_ADDR, CPP_TXOUT_MULTISIG, \ + DATATYPE, DEFAULT_DATE_FORMAT, LB_MAXM, LB_MAXN, \ + LOGDEBUG, LOGERROR, LOGEXCEPT, LOGINFO, NegativeValueError, \ + RightNow, SignerException, TooMuchPrecisionError, USE_REGTEST, \ + USE_TESTNET, binary_to_hex, checkAddrStrValid, coin2strNZS, \ + getBlockID, hash160_to_p2pkhash_script, hex_switchEndian, \ + hex_to_binary, pubkeylist_to_multisig_script, \ + script_to_p2sh_script, str2coin, unixTimeToFormatStr +from armoryengine.AddressUtils import scrAddr_to_addrStr, BadAddressError, \ + CheckHash160, addrStr_is_p2sh, hash160_to_addrStr, script_to_addrStr, \ + script_to_scrAddr, addrStr_to_hash160, binScript_to_p2shAddrStr, \ + isLikelyDataType from armoryengine.BDM import BDM_BLOCKCHAIN_READY, TheBDM from armoryengine.CoinSelection import PySelectCoins, sumTxOutList from armoryengine.Transaction import BASE_SCRIPT, DecoratedTxOut, PyTx, TXIN_SIGSTAT, UnsignedTransaction, UnsignedTxInput, convertScriptToOpStrings, getTxOutScriptType diff --git a/ui/TreeViewGUI.py b/ui/TreeViewGUI.py index fa1e5cb4f..7115baf8e 100644 --- a/ui/TreeViewGUI.py +++ b/ui/TreeViewGUI.py @@ -7,11 +7,11 @@ # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # ############################################################################## - +import binascii from PySide2.QtCore import Qt, QAbstractItemModel, QModelIndex, QObject -from armoryengine.ArmoryUtils import coin2str, hash160_to_addrStr, \ - addrStr_to_hash160, getNameForAddrType +from armoryengine.ArmoryUtils import coin2str, getNameForAddrType +from armoryengine.AddressUtils import addrStr_to_hash160 from armoryengine.BDM import TheBDM from armoryengine.Transaction import PyTx, getFeeForTx from armoryengine.CppBridge import TheBridge @@ -130,7 +130,7 @@ def __init__(self, parent, utxo): (str(utxo.getTxIndex()), \ str(utxo.getTxOutIndex()))) else: - self.name = QObject().tr("Block: #%s | Tx: #s2 | TxOut: #%s" % \ + self.name = QObject().tr("Block: #%s | Tx: #%s | TxOut: #%s" % \ (str(utxo.getTxHeight()), \ str(utxo.getTxIndex()), \ str(utxo.getTxOutIndex()))) @@ -138,12 +138,10 @@ def __init__(self, parent, utxo): self.state = Qt.Checked if utxo.isChecked() == False: self.state = Qt.Unchecked - - h160 = utxo.getRecipientHash160() - binAddr = utxo.getRecipientScrAddr() - self.scrAddr = hash160_to_addrStr(h160, binAddr[0]) - self.value = coin2str(utxo.getValue(), maxZeros=2) + self.addrStr = TheBridge.scriptUtils.getAddrStrForScrAddr( + utxo.getRecipientScrAddr()) + self.value = coin2str(utxo.getValue(), maxZeros=2) def rowCount(self): return 0 @@ -158,7 +156,7 @@ def getValue(self): return self.value def getAddress(self): - return self.scrAddr + return self.addrStr def checked(self): return self.state @@ -631,11 +629,11 @@ def setup(self): addrDict = self.treeData['CPFP'] for cpfp in cpfpList: binAddr = cpfp.getRecipientScrAddr() - addrStr = TheBridge.getAddrStrForScrAddr(binAddr) + addrStr = TheBridge.scriptUtils.getAddrStrForScrAddr(binAddr) - if not scrAddr in addrDict: - addrDict[scrAddr] = [] - addrDict[scrAddr].append(cpfp) + if not addrStr in addrDict: + addrDict[addrStr] = [] + addrDict[addrStr].append(cpfp) #create root node self.root = CoinControlTreeNode(self, "root", True, None) @@ -706,24 +704,25 @@ def setup(self): utxoList = self.rbfDict[parentHash] utxoList.append(utxo) - for txhash in self.rbfDict: + for txHashStr in self.rbfDict: #get outpoints for spender tx - entryList = self.rbfDict[txhash] - cppTx = TheBDM.bdv().getTxByHash(txhash) + entryList = self.rbfDict[txHashStr] + txHash = binascii.unhexlify(txHashStr) + cppTx = TheBridge.service.getTxByHash(txHash) - if cppTx.isInitialized(): - pytx = PyTx().unserialize(cppTx.serialize()) + if cppTx is not None: + pytx = PyTx().unserialize(cppTx.raw) else: continue for _input in pytx.inputs: - spentHash = _input.outpoint.txHash + spentHash = _input.outpoint.getTxHashStr() #if this tx redeems an output in our list of RBF tx, - #link it to the spendee + #link it to the spendee if spentHash in self.rbfDict: spendeeList = self.rbfDict[spentHash] - spendeeList.append([txhash, entryList]) + spendeeList.append([txHashStr, entryList]) def getRBFDict(): return self.rbfDict diff --git a/ui/TxFrames.py b/ui/TxFrames.py index 63590cb5b..ebfe2b6f2 100755 --- a/ui/TxFrames.py +++ b/ui/TxFrames.py @@ -31,10 +31,12 @@ from armoryengine.MultiSigUtils import createLockboxEntryStr from armoryengine.ArmoryUtils import MAX_COMMENT_LENGTH, getAddrByte, \ LOGEXCEPT, LOGERROR, LOGINFO, NegativeValueError, TooMuchPrecisionError, \ - str2coin, CPP_TXOUT_STDSINGLESIG, CPP_TXOUT_P2SH, \ - coin2str, MIN_FEE_BYTE, getNameForAddrType, addrTypeInSet, \ - getAddressTypeForOutputType, binary_to_hex + str2coin, CPP_TXOUT_STDSINGLESIG, CPP_TXOUT_P2SH, coin2str, \ + MIN_FEE_BYTE, getNameForAddrType, binary_to_hex from armoryengine.Settings import TheSettings +from armoryengine.AddressUtils import hash160_to_addrStr, addrTypeInSet, \ + addrStr_to_hash160, script_to_addrStr, getAddressTypeForOutputType, \ + BadAddressError from ui.FeeSelectUI import FeeSelectionDialog from ui.QtExecuteSignal import TheSignalExecution @@ -53,7 +55,7 @@ CS_ADJUST_FEE = 2 CS_SHUFFLE_ENTRIES = 4 -from armoryengine.PyBtcAddress import AddressEntryType_Default, \ +from armoryengine.AddressUtils import AddressEntryType_Default, \ AddressEntryType_P2PKH, AddressEntryType_P2PK, AddressEntryType_P2WPKH, \ AddressEntryType_Multisig, AddressEntryType_Uncompressed, \ AddressEntryType_P2SH, AddressEntryType_P2WSH @@ -1021,7 +1023,6 @@ def createTxAndBroadcast(self): def finalizeSignTx(success): #this needs to run in the GUI thread def signTxLastStep(success): - print (f"signtx success: {success}") if success: finalTx = ustx.getSignedPyTx() diff --git a/ui/TxFramesOffline.py b/ui/TxFramesOffline.py index 49f8c8198..e635c4f16 100644 --- a/ui/TxFramesOffline.py +++ b/ui/TxFramesOffline.py @@ -6,7 +6,7 @@ # Distributed under the GNU Affero General Public License (AGPL v3) # # See LICENSE or http://www.gnu.org/licenses/agpl.html # # # -# Copyright (C) 2016-2021, goatpig # +# Copyright (C) 2016-2023, goatpig # # Distributed under the MIT license # # See LICENSE-MIT or https://opensource.org/licenses/MIT # # # @@ -20,6 +20,15 @@ QVBoxLayout, QLabel, QMessageBox, QTextEdit, QSizePolicy, \ QApplication, QRadioButton +from ui.QtExecuteSignal import TheSignalExecution +from armoryengine.Transaction import UnsignedTransaction, \ + USTX_TYPE_MODERN, USTX_TYPE_LEGACY, USTX_TYPE_PSBT, USTX_TYPE_UNKNOWN +from armoryengine.ArmoryUtils import LOGEXCEPT, LOGERROR, LOGINFO, \ + CPP_TXOUT_STDSINGLESIG, CPP_TXOUT_P2SH, coin2str, enum, binary_to_hex, \ + coin2strNZS, NetworkIDError, UnserializeError, OS_WINDOWS +from armoryengine.Settings import TheSettings +from armoryengine.AddressUtils import script_to_scrAddr, BadAddressError + from qtdialogs.qtdefines import ArmoryFrame, tightSizeNChar, \ GETFONT, QRichLabel, HLINE, QLabelButton, USERMODE, \ VERTICAL, HORIZONTAL, STYLE_RAISED, relaxedSizeNChar, STYLE_SUNKEN, \ @@ -30,14 +39,6 @@ from qtdialogs.DlgConfirmSend import DlgConfirmSend from qtdialogs.MsgBoxWithDNAA import MsgBoxWithDNAA -from armoryengine.Transaction import UnsignedTransaction, \ - USTX_TYPE_MODERN, USTX_TYPE_LEGACY, USTX_TYPE_PSBT, USTX_TYPE_UNKNOWN -from armoryengine.ArmoryUtils import LOGEXCEPT, LOGERROR, LOGINFO, \ - CPP_TXOUT_STDSINGLESIG, CPP_TXOUT_P2SH, coin2str, enum, \ - script_to_scrAddr, binary_to_hex, coin2strNZS, BadAddressError, \ - NetworkIDError, UnserializeError -from armoryengine.Settings import TheSettings -from ui.QtExecuteSignal import TheSignalExecution ################################################################################ class SignBroadcastOfflineTxFrame(ArmoryFrame): diff --git a/ui/WalletFrames.py b/ui/WalletFrames.py index 1a8c227ff..7096d362f 100755 --- a/ui/WalletFrames.py +++ b/ui/WalletFrames.py @@ -296,7 +296,7 @@ def doCoinCtrl(self): elif nUtxo == 1: utxo = self.customUtxoList[0] binAddr = utxo.getRecipientScrAddr() - aStr = TheBridge.getAddrStrForScrAddr(binAddr) + aStr = TheBridge.scriptUtils.getAddrStrForScrAddr(binAddr) self.lblCoinCtrl.setText(self.tr('Source: %s...' % aStr[:12])) elif nUtxo > 1: self.lblCoinCtrl.setText(self.tr('Source: %d Outputs' % nUtxo)) @@ -331,8 +331,7 @@ def updateRBFLabel(self): nUtxo = len(self.customUtxoList) if nUtxo == 1: utxo = self.customUtxoList[0] - binAddr = utxo.getRecipientScrAddr() - aStr = hash160_to_addrStr(utxo.getRecipientHash160(), binAddr[0]) + aStr = TheBridge.scriptUtils.getAddrStrForScrAddr(utxo.getRecipientScrAddr()) self.lblRBF.setText(self.tr('Source: %s...' % aStr[:12])) else: self.lblRBF.setText(self.tr("Source: %s Outputs" % str(nUtxo))) diff --git a/ui/toolsDialogs.py b/ui/toolsDialogs.py index be35f04f1..ebaff4de9 100755 --- a/ui/toolsDialogs.py +++ b/ui/toolsDialogs.py @@ -14,8 +14,8 @@ from PySide2.QtGui import QIcon from PySide2.QtCore import SIGNAL -from armoryengine.ArmoryUtils import ADDRBYTE, LOGWARN, P2SHBYTE, \ - addrStr_to_hash160, isASCII +from armoryengine.ArmoryUtils import ADDRBYTE, LOGWARN, P2SHBYTE, isASCII +from armoryengine.AddressUtils import addrStr_to_hash160 from jasvet import ASv0, ASv1B64, ASv1CS, readSigBlock, verifySignature from qtdialogs.ArmoryDialog import ArmoryDialog from qtdialogs.DlgUnlockWallet import DlgUnlockWallet From b2919758c2b496c3495e943ba743dda00aeca050 Mon Sep 17 00:00:00 2001 From: goatpig Date: Tue, 16 May 2023 13:43:44 +0200 Subject: [PATCH 40/47] [ARMORY-8] fix tests --- cppForSwig/Signer/Signer.cpp | 21 ++++--- cppForSwig/Signer/Signer.h | 2 +- cppForSwig/Wallets/Accounts/AccountTypes.cpp | 2 +- .../Wallets/Accounts/AddressAccounts.cpp | 56 +++++++++++++++++-- cppForSwig/Wallets/Accounts/AssetAccounts.cpp | 5 +- cppForSwig/Wallets/Accounts/AssetAccounts.h | 14 +++++ cppForSwig/Wallets/DerivationScheme.cpp | 22 +++++--- cppForSwig/Wallets/DerivationScheme.h | 2 + cppForSwig/Wallets/Wallets.cpp | 8 +-- cppForSwig/gtest/SignerTests.cpp | 6 +- 10 files changed, 109 insertions(+), 29 deletions(-) diff --git a/cppForSwig/Signer/Signer.cpp b/cppForSwig/Signer/Signer.cpp index de53ce9f7..7227caa2c 100644 --- a/cppForSwig/Signer/Signer.cpp +++ b/cppForSwig/Signer/Signer.cpp @@ -447,9 +447,9 @@ BinaryData ScriptSpender::getAvailableInputScript() const } //////////////////////////////////////////////////////////////////////////////// -BinaryData ScriptSpender::getSerializedInput(bool withSig) const +BinaryData ScriptSpender::getSerializedInput(bool withSig, bool loose) const { - if (legacyStatus_ == SpenderStatus::Unknown) + if (legacyStatus_ == SpenderStatus::Unknown && !loose) { throw SpenderException("unresolved spender"); } @@ -1520,8 +1520,13 @@ void ScriptSpender::sign(shared_ptr proxy) } }; - signStack(legacyStack_, false); - signStack(witnessStack_, true); + try + { + signStack(legacyStack_, false); + signStack(witnessStack_, true); + } + catch (const exception&) + {} processStacks(); } @@ -2726,7 +2731,7 @@ BinaryDataRef Signer::serializeSignedTx(void) const //txins for (auto& spender : spenders_) - bw.put_BinaryData(spender->getSerializedInput(true)); + bw.put_BinaryData(spender->getSerializedInput(true, false)); //txout count auto recVector = getRecipientVector(); @@ -2793,7 +2798,7 @@ BinaryDataRef Signer::serializeUnsignedTx(bool loose) //txins for (auto& spender : spenders_) - bw.put_BinaryData(spender->getSerializedInput(false)); + bw.put_BinaryData(spender->getSerializedInput(false, loose)); //txout count auto recVector = getRecipientVector(); @@ -2856,7 +2861,7 @@ BinaryData Signer::serializeAvailableResolvedData(void) const { try { - bw.put_BinaryData(spender->getSerializedInput(false)); + bw.put_BinaryData(spender->getSerializedInput(false, false)); } catch (const exception&) { @@ -4050,7 +4055,7 @@ BinaryData Signer::getTxId_const() const if (!spender->isSegWit() && !spender->isSigned()) throw runtime_error("cannot get hash for unsigned legacy input"); - bw.put_BinaryData(spender->getSerializedInput(false)); + bw.put_BinaryData(spender->getSerializedInput(false, false)); } //outputs diff --git a/cppForSwig/Signer/Signer.h b/cppForSwig/Signer/Signer.h index 17f197fab..68069af43 100644 --- a/cppForSwig/Signer/Signer.h +++ b/cppForSwig/Signer/Signer.h @@ -199,7 +199,7 @@ namespace Armory BinaryDataRef getOutputScript(void) const; BinaryDataRef getOutputHash(void) const; unsigned getOutputIndex(void) const; - BinaryData getSerializedInput(bool) const; + BinaryData getSerializedInput(bool, bool) const; BinaryData getEmptySerializedInput(void) const; BinaryDataRef getFinalizedWitnessData(void) const; BinaryData serializeAvailableWitnessData(void) const; diff --git a/cppForSwig/Wallets/Accounts/AccountTypes.cpp b/cppForSwig/Wallets/Accounts/AccountTypes.cpp index ad127c942..a893765e9 100644 --- a/cppForSwig/Wallets/Accounts/AccountTypes.cpp +++ b/cppForSwig/Wallets/Accounts/AccountTypes.cpp @@ -320,7 +320,7 @@ AddressAccountId AccountType_ECDH::getAccountID() const } else { - auto&& root_pub = CryptoECDSA().ComputePublicKey(privateKey_); + auto&& root_pub = CryptoECDSA().ComputePublicKey(privateKey_, true); root_pub.getPtr()[0] ^= (uint8_t)type(); auto&& pub_hash160 = BtcUtils::getHash160(root_pub); diff --git a/cppForSwig/Wallets/Accounts/AddressAccounts.cpp b/cppForSwig/Wallets/Accounts/AddressAccounts.cpp index 65b3ae9b7..034c4c037 100644 --- a/cppForSwig/Wallets/Accounts/AddressAccounts.cpp +++ b/cppForSwig/Wallets/Accounts/AddressAccounts.cpp @@ -41,7 +41,7 @@ unique_ptr AddressAccount::make_new( unique_ptr addressAccountPtr; const auto& addressAccountId = accType->getAccountID(); - addressAccountPtr.reset(new AddressAccount(dbName, accType->getAccountID())); + addressAccountPtr.reset(new AddressAccount(dbName, addressAccountId)); //create root asset auto createRootAsset = @@ -286,8 +286,8 @@ unique_ptr AddressAccount::make_new( //create assets auto cipherData = make_unique( encrypted_root, move(cipher_copy)); - auto priv_asset = - make_shared(assetId, move(cipherData)); + auto priv_asset = make_shared( + assetId, move(cipherData)); rootAsset = make_shared( assetId, pubkey, priv_asset); } @@ -918,13 +918,30 @@ AddressAccountPublicData AddressAccount::exportPublicData() const rootData = woRoot->serialize(); SecureBinaryData derData; + std::shared_ptr extended = nullptr; if (assetData->derScheme_ != nullptr) + { derData = assetData->derScheme_->serialize(); + //check for salts + auto derEcdh = dynamic_pointer_cast( + assetData->derScheme_); + if (derEcdh != nullptr) + { + auto saltMap = derEcdh->getSaltMap(); + auto salts = std::make_shared(); + + for (const auto& saltPair : saltMap) + salts->salts_.emplace(saltPair.second, saltPair.first); + extended = salts; + } + } + AssetAccountPublicData assaPD { assetData->id_, rootData, derData, accPtr->getHighestUsedIndex(), accPtr->getLastComputedIndex() }; + assaPD.extendedData = extended; aapd.accountDataMap_.emplace(assetData->id_, move(assaPD)); } @@ -946,6 +963,37 @@ void AddressAccount::importPublicData(const AddressAccountPublicData& aapd) if (accPtr == nullptr) throw AccountException("[importPublicData] missing asset account"); + switch (accPtr->type()) + { + case AssetAccountTypeEnum::AssetAccountTypeEnum_ECDH: + { + //ecdh account, inject the existing salts + auto accEcdh = dynamic_cast(accPtr.get()); + if (accEcdh == nullptr) + throw AccountException("[importPublicData] account isnt ECDH"); + + auto saltMap = dynamic_pointer_cast( + assapd.second.extendedData); + if (saltMap == nullptr) + { + throw AccountException("[importPublicData]" + " imported data missing salt map"); + } + + for (const auto& saltPair : saltMap->salts_) + { + if (accEcdh->addSalt(nullptr, saltPair.second) != saltPair.first) + { + throw AccountException("[importPublicData]" + " injected salt order mismtach"); + } + } + } + + default: + break; + } + //do not allow rollbacks if (assapd.second.lastComputedIndex_ > accPtr->getLastComputedIndex()) { @@ -960,7 +1008,7 @@ void AddressAccount::importPublicData(const AddressAccountPublicData& aapd) //sync address set instantiatedAddressTypes_ = aapd.instantiatedAddressTypes_; - //check the assets for addresses do exist + //TODO: check the assets for addresses do exist } diff --git a/cppForSwig/Wallets/Accounts/AssetAccounts.cpp b/cppForSwig/Wallets/Accounts/AssetAccounts.cpp index 57113cb6d..a14551d1b 100644 --- a/cppForSwig/Wallets/Accounts/AssetAccounts.cpp +++ b/cppForSwig/Wallets/Accounts/AssetAccounts.cpp @@ -37,6 +37,9 @@ shared_ptr AssetAccountData::copy( return accDataCopy; } +AssetAccountExtendedData::~AssetAccountExtendedData() {} +AssetAccountSaltMap::~AssetAccountSaltMap() {} + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //// AssetAccount @@ -801,7 +804,7 @@ AssetKeyType AssetAccount_ECDH::addSalt( shared_ptr tx, const SecureBinaryData& salt) { - auto derScheme = + auto derScheme = dynamic_pointer_cast(data_->derScheme_); if (derScheme == nullptr) diff --git a/cppForSwig/Wallets/Accounts/AssetAccounts.h b/cppForSwig/Wallets/Accounts/AssetAccounts.h index d2a6f6fd5..cf12b3c18 100644 --- a/cppForSwig/Wallets/Accounts/AssetAccounts.h +++ b/cppForSwig/Wallets/Accounts/AssetAccounts.h @@ -83,6 +83,18 @@ namespace Armory }; ////////////////////////////////////////////////////////////////////////// + struct AssetAccountExtendedData + { + virtual ~AssetAccountExtendedData(void) = 0; + }; + + struct AssetAccountSaltMap : public AssetAccountExtendedData + { + std::map salts_; + ~AssetAccountSaltMap(void) override; + }; + + //// struct AssetAccountPublicData { const Wallets::AssetAccountId id_; @@ -92,6 +104,8 @@ namespace Armory const Wallets::AssetKeyType lastUsedIndex_; const Wallets::AssetKeyType lastComputedIndex_; + + std::shared_ptr extendedData; }; ////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/Wallets/DerivationScheme.cpp b/cppForSwig/Wallets/DerivationScheme.cpp index a960f9332..34ae67235 100644 --- a/cppForSwig/Wallets/DerivationScheme.cpp +++ b/cppForSwig/Wallets/DerivationScheme.cpp @@ -577,9 +577,6 @@ BinaryData DerivationScheme_ECDH::serialize() const AssetKeyType DerivationScheme_ECDH::addSalt(const SecureBinaryData& salt, shared_ptr txPtr) { - if (txPtr == nullptr) - throw DerivationSchemeException("addSalt: null tx"); - if (salt.getSize() != 32) throw DerivationSchemeException("salt is too small"); @@ -595,8 +592,9 @@ AssetKeyType DerivationScheme_ECDH::addSalt(const SecureBinaryData& salt, if (!insertIter.second) throw DerivationSchemeException("failed to insert salt"); - //update on disk - putSalt(id, salt, txPtr); + //update on disk if we have a db tx + if (txPtr != nullptr) + putSalt(id, salt, txPtr); //return insert index return id; @@ -615,7 +613,11 @@ void DerivationScheme_ECDH::putSalt(AssetKeyType id, auto dataRef = txPtr->getDataRef(bwKey.getData()); if (!dataRef.empty()) { - if (dataRef != salt) + //read the salt + BinaryRefReader brr(dataRef); + auto size = brr.get_var_int(); + auto saltRef = brr.get_BinaryDataRef(size); + if (saltRef != salt) { throw DerivationSchemeException( "trying to write a salt different from the one on disk"); @@ -849,4 +851,10 @@ AssetKeyType DerivationScheme_ECDH::getIdForSalt( throw DerivationSchemeException("missing salt"); return iter->second; -} \ No newline at end of file +} + +//////////////////////////////////////////////////////////////////////////////// +const map& DerivationScheme_ECDH::getSaltMap() const +{ + return saltMap_; +} diff --git a/cppForSwig/Wallets/DerivationScheme.h b/cppForSwig/Wallets/DerivationScheme.h index 2ea1a8558..922da6a20 100644 --- a/cppForSwig/Wallets/DerivationScheme.h +++ b/cppForSwig/Wallets/DerivationScheme.h @@ -275,6 +275,8 @@ namespace Armory void putAllSalts(std::shared_ptr); void getAllSalts(std::shared_ptr); Wallets::AssetKeyType getIdForSalt(const SecureBinaryData&); + const std::map& + getSaltMap(void) const; }; }; //namespace Assets }; //namespace Armory diff --git a/cppForSwig/Wallets/Wallets.cpp b/cppForSwig/Wallets/Wallets.cpp index a448c337a..2f5444d60 100644 --- a/cppForSwig/Wallets/Wallets.cpp +++ b/cppForSwig/Wallets/Wallets.cpp @@ -904,7 +904,7 @@ string AssetWallet::forkWatchingOnly( if (DBUtils::fileExists(newname, 0)) throw WalletException("WO wallet filename already exists"); - //open original wallet db & new + //open original wallet db & new auto originIface = getIfaceFromFile(filename, true, passLbd); auto masterID = getMasterID(originIface); @@ -1854,8 +1854,8 @@ const SecureBinaryData& AssetWallet_Single::getArmory135Chaincode() const } //////////////////////////////////////////////////////////////////////////////// -void AssetWallet_Single::importPublicData( - const WalletPublicData& wpd, std::shared_ptr iface) +void AssetWallet_Single::importPublicData(const WalletPublicData& wpd, + std::shared_ptr iface) { //TODO: merging from exported data @@ -2041,7 +2041,7 @@ void AssetWallet_Single::importPublicData( "Failed to resolve address account type"); } - //address account main flag + //flag main account if (accData.ID_ == wpd.mainAccountID_) accTypePtr->setMain(true); diff --git a/cppForSwig/gtest/SignerTests.cpp b/cppForSwig/gtest/SignerTests.cpp index d47ecd21b..8f9db4b89 100644 --- a/cppForSwig/gtest/SignerTests.cpp +++ b/cppForSwig/gtest/SignerTests.cpp @@ -3628,7 +3628,7 @@ TEST_F(SignerTest, GetUnsignedTxId) //locktime deser test { signer2.setLockTime(++locktime); - auto unsignedTx = signer2.serializeUnsignedTx(); + auto unsignedTx = signer2.serializeUnsignedTx(true); Tx tx(unsignedTx); EXPECT_EQ(tx.getLockTime(), locktime); @@ -3661,7 +3661,7 @@ TEST_F(SignerTest, GetUnsignedTxId) //locktime deser test { - auto unsignedTx = signer3.serializeUnsignedTx(); + auto unsignedTx = signer3.serializeUnsignedTx(true); Tx tx(unsignedTx); EXPECT_EQ(tx.getLockTime(), locktime); @@ -3679,7 +3679,7 @@ TEST_F(SignerTest, GetUnsignedTxId) signer4.setFeed(assetFeed2); { - auto unsignedTx = signer4.serializeUnsignedTx(); + auto unsignedTx = signer4.serializeUnsignedTx(true); Tx tx(unsignedTx); EXPECT_EQ(tx.getLockTime(), locktime); From 0076e1cfc588130d092c6a21b10b8beb126da867 Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 19 May 2023 11:29:46 +0200 Subject: [PATCH 41/47] [ARMORY-29] BIP39 support --- cppForSwig/BridgeAPI/CppBridge.cpp | 4 +- cppForSwig/BridgeAPI/WalletManager.cpp | 6 +- cppForSwig/BridgeAPI/WalletManager.h | 4 +- cppForSwig/Makefile.am | 5 +- cppForSwig/Signer/ResolverFeed_Wallets.cpp | 4 +- cppForSwig/Wallets/Assets.cpp | 95 ------------- cppForSwig/Wallets/Assets.h | 24 ---- .../Seeds/Backups.cpp} | 15 +- .../Seeds/Backups.h} | 4 +- cppForSwig/Wallets/Seeds/Seeds.cpp | 111 +++++++++++++++ cppForSwig/Wallets/Seeds/Seeds.h | 40 ++++++ cppForSwig/Wallets/WalletFileInterface.cpp | 4 +- cppForSwig/Wallets/WalletFileInterface.h | 4 +- cppForSwig/Wallets/Wallets.cpp | 29 ++-- cppForSwig/Wallets/Wallets.h | 14 +- cppForSwig/gtest/WalletTests.cpp | 134 +++++++++--------- 16 files changed, 273 insertions(+), 224 deletions(-) rename cppForSwig/{ArmoryBackups.cpp => Wallets/Seeds/Backups.cpp} (99%) rename cppForSwig/{ArmoryBackups.h => Wallets/Seeds/Backups.h} (98%) create mode 100644 cppForSwig/Wallets/Seeds/Seeds.cpp create mode 100644 cppForSwig/Wallets/Seeds/Seeds.h diff --git a/cppForSwig/BridgeAPI/CppBridge.cpp b/cppForSwig/BridgeAPI/CppBridge.cpp index cddf22c65..5987b738f 100755 --- a/cppForSwig/BridgeAPI/CppBridge.cpp +++ b/cppForSwig/BridgeAPI/CppBridge.cpp @@ -10,7 +10,7 @@ #include "BridgeSocket.h" #include "TerminalPassphrasePrompt.h" #include "PassphrasePrompt.h" -#include "../ArmoryBackups.h" +#include "../Wallets/Seeds/Backups.h" #include "ProtobufConversions.h" #include "ProtobufCommandParser.h" #include "../Signer/ResolverFeed_Wallets.h" @@ -271,7 +271,7 @@ void CppBridge::createBackupStringForWallet(const string& waaId, }); auto lbd = passPromptObj->getLambda(); - Armory::Backups::WalletBackup backupData; + Armory::Seeds::WalletBackup backupData; try { //grab wallet diff --git a/cppForSwig/BridgeAPI/WalletManager.cpp b/cppForSwig/BridgeAPI/WalletManager.cpp index 080aaebb0..fbe582186 100644 --- a/cppForSwig/BridgeAPI/WalletManager.cpp +++ b/cppForSwig/BridgeAPI/WalletManager.cpp @@ -7,7 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "WalletManager.h" -#include "ArmoryBackups.h" +#include "Wallets/Seeds/Backups.h" #include "PassphrasePrompt.h" #ifdef _WIN32 @@ -635,7 +635,7 @@ map> WalletContainer::getUpdatedAddressMap( } //////////////////////////////////////////////////////////////////////////////// -Armory::Backups::WalletBackup WalletContainer::getBackupStrings( +Armory::Seeds::WalletBackup WalletContainer::getBackupStrings( const PassphraseLambda& passLbd) const { auto wltSingle = dynamic_pointer_cast(wallet_); @@ -647,7 +647,7 @@ Armory::Backups::WalletBackup WalletContainer::getBackupStrings( } wltSingle->setPassphrasePromptLambda(passLbd); - auto backupStrings = Armory::Backups::Helpers::getWalletBackup(wltSingle); + auto backupStrings = Armory::Seeds::Helpers::getWalletBackup(wltSingle); wltSingle->resetPassphrasePromptLambda(); return backupStrings; diff --git a/cppForSwig/BridgeAPI/WalletManager.h b/cppForSwig/BridgeAPI/WalletManager.h index 02402ac15..263588bac 100644 --- a/cppForSwig/BridgeAPI/WalletManager.h +++ b/cppForSwig/BridgeAPI/WalletManager.h @@ -27,7 +27,7 @@ namespace Armory { - namespace Backups + namespace Seeds { class WalletBackup; }; @@ -215,7 +215,7 @@ class WalletContainer Armory::Wallets::AssetKeyType getHighestUsedIndex(void) const; std::map> getUpdatedAddressMap(); - Armory::Backups::WalletBackup getBackupStrings(const PassphraseLambda&) const; + Armory::Seeds::WalletBackup getBackupStrings(const PassphraseLambda&) const; void setComment(const std::string&, const std::string&); void setLabels(const std::string&, const std::string&); diff --git a/cppForSwig/Makefile.am b/cppForSwig/Makefile.am index 331a3098a..04ad99b48 100644 --- a/cppForSwig/Makefile.am +++ b/cppForSwig/Makefile.am @@ -93,7 +93,9 @@ ARMORYWALLETS_SOURCE_FILES = Wallets/BIP32_Node.cpp \ Wallets/WalletFileInterface.cpp \ Wallets/AssetEncryption.cpp \ Wallets/Wallets.cpp \ - Wallets/AuthorizedPeers.cpp + Wallets/AuthorizedPeers.cpp \ + Wallets/Seeds/Backups.cpp \ + Wallets/Seeds/Seeds.cpp ARMORYSIGNER_SOURCE_FILES = Signer/Script.cpp \ Signer/ScriptRecipient.cpp \ @@ -156,7 +158,6 @@ ARMORYCOMMON_SOURCE_FILES = AsyncClient.cpp \ BitcoinSettings.cpp \ ReentrantLock.cpp \ SecureBinaryData.cpp \ - ArmoryBackups.cpp \ SocketObject.cpp \ TxClasses.cpp \ TxOutScrRef.cpp \ diff --git a/cppForSwig/Signer/ResolverFeed_Wallets.cpp b/cppForSwig/Signer/ResolverFeed_Wallets.cpp index 9aff629ac..82f8859d2 100644 --- a/cppForSwig/Signer/ResolverFeed_Wallets.cpp +++ b/cppForSwig/Signer/ResolverFeed_Wallets.cpp @@ -151,7 +151,7 @@ BinaryData Armory::Signer::ResolverFeed_AssetWalletSingle::getByVal(const Binary } //////////////////////////////////////////////////////////////////////////////// -const SecureBinaryData& Armory::Signer::ResolverFeed_AssetWalletSingle::getPrivKeyForPubkey( +const SecureBinaryData& ResolverFeed_AssetWalletSingle::getPrivKeyForPubkey( const BinaryData& pubkey) { //check cache first @@ -176,7 +176,7 @@ const SecureBinaryData& Armory::Signer::ResolverFeed_AssetWalletSingle::getPrivK wltPtr_->derivePrivKeyFromPath(pathIter->second.first); } - return wltPtr_->getDecrypedPrivateKeyForId(pathIter->second.second); + return wltPtr_->getDecryptedPrivateKeyForId(pathIter->second.second); } } diff --git a/cppForSwig/Wallets/Assets.cpp b/cppForSwig/Wallets/Assets.cpp index 7d3b55153..b9ea85663 100644 --- a/cppForSwig/Wallets/Assets.cpp +++ b/cppForSwig/Wallets/Assets.cpp @@ -17,8 +17,6 @@ #define ASSETENTRY_BIP32ROOT_VERSION 0x00000002 #define ASSETENTRY_LEGACYROOT_VERSION 0x00000001 -#define ENCRYPTED_SEED_VERSION 0x00000001 - #define PRIVKEY_VERSION 0x00000002 #define PUBKEY_COMPRESSED_VERSION 0x00000001 #define PUBKEY_UNCOMPRESSED_VERSION 0x00000001 @@ -831,99 +829,6 @@ unique_ptr Asset_PrivateKey::deserialize( return assetPtr; } -//////////////////////////////////////////////////////////////////////////////// -// -//// EncryptedSeed -// -//////////////////////////////////////////////////////////////////////////////// -const AssetId EncryptedSeed::seedAssetId_(0x5EED, 0xDEE5, 0x5EED); - -//////////////////////////////////////////////////////////////////////////////// -BinaryData EncryptedSeed::serialize() const -{ - BinaryWriter bw; - bw.put_uint32_t(ENCRYPTED_SEED_VERSION); - bw.put_uint8_t(WALLET_SEED_BYTE); - - auto&& cipherData = getCipherDataPtr()->serialize(); - bw.put_var_int(cipherData.getSize()); - bw.put_BinaryData(cipherData); - - BinaryWriter finalBw; - finalBw.put_var_int(bw.getSize()); - finalBw.put_BinaryDataRef(bw.getDataRef()); - return finalBw.getData(); -} - -//////////////////////////////////////////////////////////////////////////////// -bool EncryptedSeed::isSame(Encryption::EncryptedAssetData* const seed) const -{ - auto asset_ed = dynamic_cast(seed); - if (asset_ed == nullptr) - return false; - - return Encryption::EncryptedAssetData::isSame(seed); -} - -//////////////////////////////////////////////////////////////////////////////// -const AssetId& EncryptedSeed::getAssetId() const -{ - return seedAssetId_; -} - -//////////////////////////////////////////////////////////////////////////////// -unique_ptr EncryptedSeed::deserialize( - const BinaryDataRef& data) -{ - BinaryRefReader brr(data); - - //return ptr - unique_ptr assetPtr = nullptr; - - //version - auto version = brr.get_uint32_t(); - - //prefix - auto prefix = brr.get_uint8_t(); - - switch (prefix) - { - case WALLET_SEED_BYTE: - { - switch (version) - { - case 0x00000001: - { - auto len = brr.get_var_int(); - if (len > brr.getSizeRemaining()) - throw runtime_error("invalid serialized encrypted data len"); - - auto cipherBdr = brr.get_BinaryDataRef(len); - BinaryRefReader cipherBrr(cipherBdr); - auto cipherData = Encryption::CipherData::deserialize(cipherBrr); - - //ptr - assetPtr = make_unique(move(cipherData)); - break; - } - - default: - throw runtime_error("unsupported seed version"); - } - - break; - } - - default: - throw runtime_error("unexpected encrypted data prefix"); - } - - if (assetPtr == nullptr) - throw runtime_error("failed to deserialize encrypted asset"); - - return assetPtr; -} - //////////////////////////////////////////////////////////////////////////////// // //// MetaData diff --git a/cppForSwig/Wallets/Assets.h b/cppForSwig/Wallets/Assets.h index 9ebc481e1..952af7d1b 100644 --- a/cppForSwig/Wallets/Assets.h +++ b/cppForSwig/Wallets/Assets.h @@ -188,30 +188,6 @@ namespace Armory const Wallets::AssetId&, const BinaryDataRef&); }; - ////////////////////////////////////////////////////////////////////////// - class EncryptedSeed : public Wallets::Encryption::EncryptedAssetData - { - public: - static const Wallets::AssetId seedAssetId_; - - public: - //tors - EncryptedSeed( - std::unique_ptr cipher) : - Wallets::Encryption::EncryptedAssetData(move(cipher)) - {} - - //virtual - bool isSame( - Wallets::Encryption::EncryptedAssetData* const) const override; - BinaryData serialize(void) const override; - const Wallets::AssetId& getAssetId(void) const override; - - //static - static std::unique_ptr deserialize( - const BinaryDataRef&); - }; - ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// class AssetEntry diff --git a/cppForSwig/ArmoryBackups.cpp b/cppForSwig/Wallets/Seeds/Backups.cpp similarity index 99% rename from cppForSwig/ArmoryBackups.cpp rename to cppForSwig/Wallets/Seeds/Backups.cpp index ad0e55390..709a8d036 100644 --- a/cppForSwig/ArmoryBackups.cpp +++ b/cppForSwig/Wallets/Seeds/Backups.cpp @@ -1,15 +1,16 @@ //////////////////////////////////////////////////////////////////////////////// // // -// Copyright (C) 2020, goatpig // +// Copyright (C) 2020 - 2023, goatpig // // Distributed under the MIT license // // See LICENSE-MIT or https://opensource.org/licenses/MIT // // // //////////////////////////////////////////////////////////////////////////////// -#include "ArmoryBackups.h" +#include "Backups.h" #include "EncryptionUtils.h" #include "BtcUtils.h" -#include "Wallets/WalletIdTypes.h" +#include "../WalletIdTypes.h" +#include "Seeds.h" #define EASY16_CHECKSUM_LEN 2 #define EASY16_INDEX_MAX 15 @@ -18,12 +19,12 @@ #define WALLET_RESTORE_LOOKUP 1000 using namespace std; -using namespace Armory::Backups; +using namespace Armory::Seeds; using namespace Armory::Assets; using namespace Armory::Wallets; //////////////////////////////////////////////////////////////////////////////// -const vector BackupEasy16::e16chars_ = +const vector BackupEasy16::e16chars_ = { 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', @@ -49,7 +50,7 @@ encryption IV and salt. Using the first 256 digits of Pi for the the IV, and first 256 digits of e for the salt (hashed) */ -const string SecurePrint::digits_pi_ = +const string SecurePrint::digits_pi_ = { "ARMORY_ENCRYPTION_INITIALIZATION_VECTOR_" "1415926535897932384626433832795028841971693993751058209749445923" @@ -58,7 +59,7 @@ const string SecurePrint::digits_pi_ = "9303819644288109756659334461284756482337867831652712019091456485" }; -const string SecurePrint::digits_e_ = +const string SecurePrint::digits_e_ = { "ARMORY_KEY_DERIVATION_FUNCTION_SALT_" "7182818284590452353602874713526624977572470936999595749669676277" diff --git a/cppForSwig/ArmoryBackups.h b/cppForSwig/Wallets/Seeds/Backups.h similarity index 98% rename from cppForSwig/ArmoryBackups.h rename to cppForSwig/Wallets/Seeds/Backups.h index 9140dce32..43a952138 100644 --- a/cppForSwig/ArmoryBackups.h +++ b/cppForSwig/Wallets/Seeds/Backups.h @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // // -// Copyright (C) 2020, goatpig // +// Copyright (C) 2020 - 2023, goatpig // // Distributed under the MIT license // // See LICENSE-MIT or https://opensource.org/licenses/MIT // // // @@ -20,7 +20,7 @@ namespace Armory { - namespace Backups + namespace Seeds { //// class RestoreUserException : public std::runtime_error diff --git a/cppForSwig/Wallets/Seeds/Seeds.cpp b/cppForSwig/Wallets/Seeds/Seeds.cpp new file mode 100644 index 000000000..b1186a591 --- /dev/null +++ b/cppForSwig/Wallets/Seeds/Seeds.cpp @@ -0,0 +1,111 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (C) 2023, goatpig // +// Distributed under the MIT license // +// See LICENSE-MIT or https://opensource.org/licenses/MIT // +// // +//////////////////////////////////////////////////////////////////////////////// + +#include "Seeds.h" +#include "../AssetEncryption.h" +#include "../WalletIdTypes.h" + +using namespace std; +using namespace Armory; +using namespace Armory::Seed; +using namespace Armory::Wallets; + +#define ENCRYPTED_SEED_VERSION 0x00000001 + +//////////////////////////////////////////////////////////////////////////////// +// +//// EncryptedSeed +// +//////////////////////////////////////////////////////////////////////////////// +const Wallets::AssetId EncryptedSeed::seedAssetId_(0x5EED, 0xDEE5, 0x5EED); + +//////////////////////////////////////////////////////////////////////////////// +BinaryData EncryptedSeed::serialize() const +{ + BinaryWriter bw; + bw.put_uint32_t(ENCRYPTED_SEED_VERSION); + bw.put_uint8_t(WALLET_SEED_BYTE); + + auto&& cipherData = getCipherDataPtr()->serialize(); + bw.put_var_int(cipherData.getSize()); + bw.put_BinaryData(cipherData); + + BinaryWriter finalBw; + finalBw.put_var_int(bw.getSize()); + finalBw.put_BinaryDataRef(bw.getDataRef()); + return finalBw.getData(); +} + +//////////////////////////////////////////////////////////////////////////////// +bool EncryptedSeed::isSame(Encryption::EncryptedAssetData* const seed) const +{ + auto asset_ed = dynamic_cast(seed); + if (asset_ed == nullptr) + return false; + + return Encryption::EncryptedAssetData::isSame(seed); +} + +//////////////////////////////////////////////////////////////////////////////// +const AssetId& EncryptedSeed::getAssetId() const +{ + return seedAssetId_; +} + +//////////////////////////////////////////////////////////////////////////////// +unique_ptr EncryptedSeed::deserialize( + const BinaryDataRef& data) +{ + BinaryRefReader brr(data); + + //return ptr + unique_ptr assetPtr = nullptr; + + //version + auto version = brr.get_uint32_t(); + + //prefix + auto prefix = brr.get_uint8_t(); + + switch (prefix) + { + case WALLET_SEED_BYTE: + { + switch (version) + { + case 0x00000001: + { + auto len = brr.get_var_int(); + if (len > brr.getSizeRemaining()) + throw runtime_error("invalid serialized encrypted data len"); + + auto cipherBdr = brr.get_BinaryDataRef(len); + BinaryRefReader cipherBrr(cipherBdr); + auto cipherData = Encryption::CipherData::deserialize(cipherBrr); + + //ptr + assetPtr = make_unique(move(cipherData)); + break; + } + + default: + throw runtime_error("unsupported seed version"); + } + + break; + } + + default: + throw runtime_error("unexpected encrypted data prefix"); + } + + if (assetPtr == nullptr) + throw runtime_error("failed to deserialize encrypted asset"); + + return assetPtr; +} diff --git a/cppForSwig/Wallets/Seeds/Seeds.h b/cppForSwig/Wallets/Seeds/Seeds.h new file mode 100644 index 000000000..ade6fa9c6 --- /dev/null +++ b/cppForSwig/Wallets/Seeds/Seeds.h @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (C) 2023, goatpig // +// Distributed under the MIT license // +// See LICENSE-MIT or https://opensource.org/licenses/MIT // +// // +//////////////////////////////////////////////////////////////////////////////// + +#pragma once +#include "../AssetEncryption.h" + +namespace Armory +{ + namespace Seed + { + ////////////////////////////////////////////////////////////////////////// + class EncryptedSeed : public Wallets::Encryption::EncryptedAssetData + { + public: + static const Wallets::AssetId seedAssetId_; + + public: + //tors + EncryptedSeed( + std::unique_ptr cipher) : + Wallets::Encryption::EncryptedAssetData(move(cipher)) + {} + + //overrides + bool isSame(Wallets::Encryption::EncryptedAssetData* const) + const override; + BinaryData serialize(void) const override; + const Wallets::AssetId& getAssetId(void) const override; + + //static + static std::unique_ptr deserialize( + const BinaryDataRef&); + }; + } +} //namespace Armory \ No newline at end of file diff --git a/cppForSwig/Wallets/WalletFileInterface.cpp b/cppForSwig/Wallets/WalletFileInterface.cpp index 6a76ca36d..19d79f150 100644 --- a/cppForSwig/Wallets/WalletFileInterface.cpp +++ b/cppForSwig/Wallets/WalletFileInterface.cpp @@ -11,10 +11,10 @@ #include "DBUtils.h" #include "WalletHeader.h" #include "DecryptedDataContainer.h" -#include "Assets.h" +#include "Seeds/Seeds.h" using namespace std; -using namespace Armory::Assets; +using namespace Armory::Seed; using namespace Armory::Wallets::IO; using namespace Armory::Wallets::Encryption; diff --git a/cppForSwig/Wallets/WalletFileInterface.h b/cppForSwig/Wallets/WalletFileInterface.h index df7443152..41ce89fff 100644 --- a/cppForSwig/Wallets/WalletFileInterface.h +++ b/cppForSwig/Wallets/WalletFileInterface.h @@ -31,7 +31,7 @@ class PRNG_Fortuna; namespace Armory { - namespace Assets + namespace Seed { class EncryptedSeed; }; @@ -160,7 +160,7 @@ namespace Armory std::unique_ptr decryptedData_; std::unique_ptr controlLock_; - std::unique_ptr controlSeed_; + std::unique_ptr controlSeed_; unsigned encryptionVersion_ = UINT32_MAX; std::unique_ptr fortuna_; diff --git a/cppForSwig/Wallets/Wallets.cpp b/cppForSwig/Wallets/Wallets.cpp index 2f5444d60..cbe5d5687 100644 --- a/cppForSwig/Wallets/Wallets.cpp +++ b/cppForSwig/Wallets/Wallets.cpp @@ -9,12 +9,15 @@ #include "ArmoryConfig.h" #include "Wallets.h" #include "WalletFileInterface.h" +#include "Seeds/Seeds.h" +#include "Seeds/Backups.h" using namespace std; using namespace Armory::Signer; using namespace Armory::Assets; using namespace Armory::Accounts; using namespace Armory::Wallets; +using namespace Armory::Seed; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -1723,7 +1726,7 @@ shared_ptr AssetWallet_Single::initWalletDbWithPubRoot( return walletPtr; } -//////////////////////////////////////////////////////////////////////////////// +//////////////// -- decrypt private key methods -- ///////////////////////////// const SecureBinaryData& AssetWallet_Single::getDecryptedValue( shared_ptr assetPtr) { @@ -1731,7 +1734,7 @@ const SecureBinaryData& AssetWallet_Single::getDecryptedValue( return decryptedData_->getClearTextAssetData(assetPtr); } -//////////////////////////////////////////////////////////////////////////////// +//////// const SecureBinaryData& AssetWallet_Single::getDecryptedPrivateKeyForAsset( std::shared_ptr assetPtr) { @@ -1743,10 +1746,23 @@ const SecureBinaryData& AssetWallet_Single::getDecryptedPrivateKeyForAsset( assetPrivKey = account->fillPrivateKey(iface_, decryptedData_, assetPtr->getID()); } - + return getDecryptedValue(assetPrivKey); } +//////// +const SecureBinaryData& AssetWallet_Single::getDecryptedPrivateKeyForId( + const AssetId& id) const +{ + return decryptedData_->getClearTextAssetData(id); +} + +//////// +std::shared_ptr AssetWallet_Single::getEncryptedSeed() const +{ + return seed_; +} + //////////////////////////////////////////////////////////////////////////////// const AssetId& AssetWallet_Single::derivePrivKeyFromPath( const BIP32_AssetPath& path) @@ -1783,13 +1799,6 @@ const AssetId& AssetWallet_Single::derivePrivKeyFromPath( hdNode.private_key, BTC_ECKEY_PKEY_LENGTH); } -//////////////////////////////////////////////////////////////////////////////// -const SecureBinaryData& AssetWallet_Single::getDecrypedPrivateKeyForId( - const AssetId& id) const -{ - return decryptedData_->getClearTextAssetData(id); -} - //////////////////////////////////////////////////////////////////////////////// void AssetWallet_Single::changePrivateKeyPassphrase( const std::function& newPassLbd) diff --git a/cppForSwig/Wallets/Wallets.h b/cppForSwig/Wallets/Wallets.h index 8c47f3733..e5c69ace0 100644 --- a/cppForSwig/Wallets/Wallets.h +++ b/cppForSwig/Wallets/Wallets.h @@ -38,7 +38,12 @@ namespace Armory namespace Signer { class BIP32_AssetPath; - }; + } + + namespace Seed + { + class EncryptedSeed; + } namespace Wallets { @@ -251,7 +256,7 @@ namespace Armory protected: std::shared_ptr root_ = nullptr; - std::shared_ptr seed_ = nullptr; + std::shared_ptr seed_ = nullptr; protected: //virtual @@ -308,11 +313,10 @@ namespace Armory std::shared_ptr); const AssetId& derivePrivKeyFromPath( const Signer::BIP32_AssetPath&); - const SecureBinaryData& getDecrypedPrivateKeyForId( + const SecureBinaryData& getDecryptedPrivateKeyForId( const AssetId&) const; - std::shared_ptr getEncryptedSeed(void) const - { return seed_; } + std::shared_ptr getEncryptedSeed(void) const; Signer::BIP32_AssetPath getBip32PathForAsset( std::shared_ptr) const; diff --git a/cppForSwig/gtest/WalletTests.cpp b/cppForSwig/gtest/WalletTests.cpp index 2f487bc38..d169d82ea 100644 --- a/cppForSwig/gtest/WalletTests.cpp +++ b/cppForSwig/gtest/WalletTests.cpp @@ -9,7 +9,8 @@ #include "TestUtils.h" #include "../Wallets/PassphraseLambda.h" -#include "../ArmoryBackups.h" +#include "../Wallets/Seeds/Backups.h" +#include "../Wallets/Seeds/Seeds.h" #include "../Wallets/WalletFileInterface.h" using namespace std; @@ -19,6 +20,7 @@ using namespace Armory::Assets; using namespace Armory::Accounts; using namespace Armory::Wallets; using namespace Armory::Wallets::Encryption; +using namespace Armory::Seed; //////////////////////////////////////////////////////////////////////////////// #define METHOD_ASSERT_EQ(a, b) \ @@ -8492,16 +8494,16 @@ class BackupTests : public ::testing::Test //////////////////////////////////////////////////////////////////////////////// TEST_F(BackupTests, Easy16) { - for (const auto& index : Armory::Backups::BackupEasy16::eligibleIndexes_) + for (const auto& index : Armory::Seeds::BackupEasy16::eligibleIndexes_) { auto root = CryptoPRNG::generateRandom(32); //encode the root - auto encoded = Armory::Backups::BackupEasy16::encode(root.getRef(), index); + auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), index); ASSERT_EQ(encoded.size(), 2ULL); - auto decoded = Armory::Backups::BackupEasy16::decode(encoded); + auto decoded = Armory::Seeds::BackupEasy16::decode(encoded); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); EXPECT_EQ(decoded.checksumIndexes_[0], index); EXPECT_EQ(decoded.checksumIndexes_[1], index); @@ -8528,7 +8530,7 @@ TEST_F(BackupTests, Easy16_Repair) char newChar; while (true) { - newChar = Armory::Backups::BackupEasy16::e16chars_[newVal % 16]; + newChar = Armory::Seeds::BackupEasy16::e16chars_[newVal % 16]; if (newChar != val) break; @@ -8547,7 +8549,7 @@ TEST_F(BackupTests, Easy16_Repair) auto root = prng.generateRandom(32); //encode the root - auto encoded = Armory::Backups::BackupEasy16::encode(root.getRef(), 0); + auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), 0); ASSERT_EQ(encoded.size(), 2ULL); //corrupt one character in one line @@ -8563,7 +8565,7 @@ TEST_F(BackupTests, Easy16_Repair) ASSERT_NE(encoded[lineSelect], corrupted[lineSelect]); //decode the corrupted data, should yield an incorrect value - auto decoded = Armory::Backups::BackupEasy16::decode(corrupted); + auto decoded = Armory::Seeds::BackupEasy16::decode(corrupted); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); if (lineSelect == 0) { @@ -8581,7 +8583,7 @@ TEST_F(BackupTests, Easy16_Repair) //attempt to repair, may fail because of collisions (no unique solution) try { - auto result = Armory::Backups::BackupEasy16::repair(decoded); + auto result = Armory::Seeds::BackupEasy16::repair(decoded); if (result) { ASSERT_EQ(decoded.repairedIndexes_.size(), 2ULL); @@ -8592,7 +8594,7 @@ TEST_F(BackupTests, Easy16_Repair) ++succesfulRepairs; } } - catch (const Armory::Backups::Easy16RepairError&) + catch (const Armory::Seeds::Easy16RepairError&) {} } @@ -8604,7 +8606,7 @@ TEST_F(BackupTests, Easy16_Repair) auto root = prng.generateRandom(32); //encode the root - auto encoded = Armory::Backups::BackupEasy16::encode(root.getRef(), 0); + auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), 0); ASSERT_EQ(encoded.size(), 2ULL); //corrupt 2 characters in one line @@ -8627,7 +8629,7 @@ TEST_F(BackupTests, Easy16_Repair) ASSERT_NE(encoded[lineSelect], corrupted[lineSelect]); //decode, should yield an incorrect value - auto decoded = Armory::Backups::BackupEasy16::decode(corrupted); + auto decoded = Armory::Seeds::BackupEasy16::decode(corrupted); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); if (lineSelect == 0) { @@ -8643,7 +8645,7 @@ TEST_F(BackupTests, Easy16_Repair) EXPECT_NE(root, decoded.data_); //attempt to repair, should fail - auto result = Armory::Backups::BackupEasy16::repair(decoded); + auto result = Armory::Seeds::BackupEasy16::repair(decoded); if (result) { EXPECT_NE(decoded.data_, root); @@ -8657,7 +8659,7 @@ TEST_F(BackupTests, Easy16_Repair) auto root = prng.generateRandom(32); //encode the root - auto encoded = Armory::Backups::BackupEasy16::encode(root.getRef(), 0); + auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), 0); ASSERT_EQ(encoded.size(), 2ULL); //corrupt 1 character per line @@ -8676,7 +8678,7 @@ TEST_F(BackupTests, Easy16_Repair) corruptLine(corrupted, 1, wordSelect2, charSelect2, newVal2); //decode, should yield an incorrect value - auto decoded = Armory::Backups::BackupEasy16::decode(corrupted); + auto decoded = Armory::Seeds::BackupEasy16::decode(corrupted); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); EXPECT_NE(decoded.checksumIndexes_[0], 0); EXPECT_NE(decoded.checksumIndexes_[1], 0); @@ -8684,7 +8686,7 @@ TEST_F(BackupTests, Easy16_Repair) //attempt to repair, may fail because of collisions (no evident solution) try { - auto result = Armory::Backups::BackupEasy16::repair(decoded); + auto result = Armory::Seeds::BackupEasy16::repair(decoded); if (result) { ASSERT_EQ(decoded.repairedIndexes_.size(), 2ULL); @@ -8698,7 +8700,7 @@ TEST_F(BackupTests, Easy16_Repair) ++succesfulRepairs; } } - catch (const Armory::Backups::Easy16RepairError&) + catch (const Armory::Seeds::Easy16RepairError&) {} } @@ -8711,14 +8713,14 @@ TEST_F(BackupTests, SecurePrint) auto root = CryptoPRNG::generateRandom(32); //encrypt the root - Armory::Backups::SecurePrint spEncr; + Armory::Seeds::SecurePrint spEncr; auto encryptedData = spEncr.encrypt(root, {}); ASSERT_FALSE(spEncr.getPassphrase().empty()); ASSERT_EQ(encryptedData.first.getSize(), 32ULL); ASSERT_EQ(encryptedData.second.getSize(), 0ULL); EXPECT_NE(encryptedData.first, root); - Armory::Backups::SecurePrint spDecr; + Armory::Seeds::SecurePrint spDecr; auto decryptedData = spDecr.decrypt(encryptedData.first, spEncr.getPassphrase()); @@ -8728,7 +8730,7 @@ TEST_F(BackupTests, SecurePrint) //with chaincode auto chaincode = CryptoPRNG::generateRandom(32); - Armory::Backups::SecurePrint spWithCC; + Armory::Seeds::SecurePrint spWithCC; auto dataWithCC = spWithCC.encrypt(root, chaincode); ASSERT_FALSE(spWithCC.getPassphrase().empty()); @@ -8739,7 +8741,7 @@ TEST_F(BackupTests, SecurePrint) EXPECT_NE(spEncr.getPassphrase(), spWithCC.getPassphrase()); EXPECT_NE(encryptedData.first, dataWithCC.first); - Armory::Backups::SecurePrint spDecrWithCC; + Armory::Seeds::SecurePrint spDecrWithCC; auto decrRoot = spDecrWithCC.decrypt( dataWithCC.first, spWithCC.getPassphrase()); @@ -8759,7 +8761,7 @@ TEST_F(BackupTests, SecurePrint) ASSERT_GE(mangledPass.getSize(), 4ULL); mangledPass.getPtr()[3] ^= 0xFF; - Armory::Backups::SecurePrint spDecrWithCC; + Armory::Seeds::SecurePrint spDecrWithCC; auto decrypted = spDecrWithCC.decrypt( dataWithCC.first, mangledPass); @@ -8780,7 +8782,7 @@ TEST_F(BackupTests, SecurePrint) auto passB58 = BinaryData::fromString( BtcUtils::base58_encode(passphrase)); - Armory::Backups::SecurePrint spDecrWithCC; + Armory::Seeds::SecurePrint spDecrWithCC; auto decrypted = spDecrWithCC.decrypt( dataWithCC.first, passB58); @@ -8793,7 +8795,7 @@ TEST_F(BackupTests, SecurePrint) //mismatched pass { - Armory::Backups::SecurePrint spDecrWithCC; + Armory::Seeds::SecurePrint spDecrWithCC; auto decrypted = spDecrWithCC.decrypt( dataWithCC.first, spEncr.getPassphrase()); EXPECT_NE(decrypted, dataWithCC.first); @@ -8819,29 +8821,29 @@ TEST_F(BackupTests, BackupStrings_Legacy) }; assetWlt->setPassphrasePromptLambda(passLbd); - auto backupData = Armory::Backups::Helpers::getWalletBackup(assetWlt); + auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Backups::RestorePromptType promptType, + const Armory::Seeds::RestorePromptType promptType, const vector checksums, SecureBinaryData& extra)->bool { switch (promptType) { - case Armory::Backups::RestorePromptType::Passphrase: + case Armory::Seeds::RestorePromptType::Passphrase: { extra = newPass; return true; } - case Armory::Backups::RestorePromptType::Control: + case Armory::Seeds::RestorePromptType::Control: { extra = newCtrl; return true; } - case Armory::Backups::RestorePromptType::Id: + case Armory::Seeds::RestorePromptType::Id: { EXPECT_EQ(extra, SecureBinaryData::fromString(backupData.wltId_)); @@ -8864,7 +8866,7 @@ TEST_F(BackupTests, BackupStrings_Legacy) string filename; { //restore wallet - auto newWltPtr = Armory::Backups::Helpers::restoreFromBackup( + auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( backupData.rootClear_, {}, newHomeDir, callback); EXPECT_NE(newWltPtr, nullptr); @@ -8895,29 +8897,29 @@ TEST_F(BackupTests, BackupStrings_Legacy_SecurePrint) }; assetWlt->setPassphrasePromptLambda(passLbd); - auto backupData = Armory::Backups::Helpers::getWalletBackup(assetWlt); + auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Backups::RestorePromptType promptType, + const Armory::Seeds::RestorePromptType promptType, const vector checksums, SecureBinaryData& extra)->bool { switch (promptType) { - case Armory::Backups::RestorePromptType::Passphrase: + case Armory::Seeds::RestorePromptType::Passphrase: { extra = newPass; return true; } - case Armory::Backups::RestorePromptType::Control: + case Armory::Seeds::RestorePromptType::Control: { extra = newCtrl; return true; } - case Armory::Backups::RestorePromptType::Id: + case Armory::Seeds::RestorePromptType::Id: { if (extra != SecureBinaryData::fromString(backupData.wltId_)) return false; @@ -8943,17 +8945,17 @@ TEST_F(BackupTests, BackupStrings_Legacy_SecurePrint) //try without sp pass try { - Armory::Backups::Helpers::restoreFromBackup( + Armory::Seeds::Helpers::restoreFromBackup( backupData.rootEncr_, {}, newHomeDir, callback); ASSERT_TRUE(false); } - catch (const Armory::Backups::RestoreUserException& e) + catch (const Armory::Seeds::RestoreUserException& e) { EXPECT_EQ(e.what(), string("user rejected id")); } //try with secure print now - auto newWltPtr = Armory::Backups::Helpers::restoreFromBackup( + auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( backupData.rootEncr_, backupData.spPass_, newHomeDir, callback); EXPECT_NE(newWltPtr, nullptr); @@ -8968,7 +8970,7 @@ TEST_F(BackupTests, BackupStrings_Legacy_SecurePrint) TEST_F(BackupTests, Easy16_AutoRepair) { /*NOTE: this test will lead to a lot of hashing*/ - auto corruptLine = [](vector& lines, + auto corruptLine = [](vector& lines, uint8_t lineSelect, uint8_t wordSelect, uint8_t charSelect, uint8_t newVal) { auto& line = lines[lineSelect]; @@ -8982,7 +8984,7 @@ TEST_F(BackupTests, Easy16_AutoRepair) char newChar; while (true) { - newChar = Armory::Backups::BackupEasy16::e16chars_[newVal % 16]; + newChar = Armory::Seeds::BackupEasy16::e16chars_[newVal % 16]; if (newChar != val) break; @@ -9014,7 +9016,7 @@ TEST_F(BackupTests, Easy16_AutoRepair) auto wltID = computeWalletID(root); //encode the root - auto encoded = Armory::Backups::BackupEasy16::encode(root.getRef(), 0); + auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), 0); ASSERT_EQ(encoded.size(), 2ULL); //corrupt one character in one line @@ -9030,7 +9032,7 @@ TEST_F(BackupTests, Easy16_AutoRepair) ASSERT_NE(encoded[lineSelect], corrupted[lineSelect]); //decode the corrupted data, should yield an incorrect value - auto decoded = Armory::Backups::BackupEasy16::decode(corrupted); + auto decoded = Armory::Seeds::BackupEasy16::decode(corrupted); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); if (lineSelect == 0) { @@ -9049,19 +9051,19 @@ TEST_F(BackupTests, Easy16_AutoRepair) try { auto userPrompt = [&wltID, &decoded, &succesfulRepairs]( - Armory::Backups::RestorePromptType promptType, + Armory::Seeds::RestorePromptType promptType, const vector& chksumIndexes, SecureBinaryData& extra)->bool { switch (promptType) { - case Armory::Backups::RestorePromptType::ChecksumError: + case Armory::Seeds::RestorePromptType::ChecksumError: { EXPECT_EQ(chksumIndexes, decoded.checksumIndexes_); return false; } - case Armory::Backups::RestorePromptType::Id: + case Armory::Seeds::RestorePromptType::Id: { EXPECT_EQ(chksumIndexes, decoded.checksumIndexes_); string extraStr(extra.toCharPtr(), extra.getSize()); @@ -9076,7 +9078,7 @@ TEST_F(BackupTests, Easy16_AutoRepair) } }; - Armory::Backups::Helpers::restoreFromBackup( + Armory::Seeds::Helpers::restoreFromBackup( corrupted, BinaryDataRef(), string(), userPrompt); } catch (const exception&) @@ -9106,29 +9108,29 @@ TEST_F(BackupTests, BackupStrings_LegacyWithChaincode_SecurePrint) }; assetWlt->setPassphrasePromptLambda(passLbd); - auto backupData = Armory::Backups::Helpers::getWalletBackup(assetWlt); + auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Backups::RestorePromptType promptType, + const Armory::Seeds::RestorePromptType promptType, const vector checksums, SecureBinaryData& extra)->bool { switch (promptType) { - case Armory::Backups::RestorePromptType::Passphrase: + case Armory::Seeds::RestorePromptType::Passphrase: { extra = newPass; return true; } - case Armory::Backups::RestorePromptType::Control: + case Armory::Seeds::RestorePromptType::Control: { extra = newCtrl; return true; } - case Armory::Backups::RestorePromptType::Id: + case Armory::Seeds::RestorePromptType::Id: { if (extra != SecureBinaryData::fromString(backupData.wltId_)) return false; @@ -9164,17 +9166,17 @@ TEST_F(BackupTests, BackupStrings_LegacyWithChaincode_SecurePrint) //try without sp pass try { - Armory::Backups::Helpers::restoreFromBackup( + Armory::Seeds::Helpers::restoreFromBackup( backupData.rootEncr_, {}, newHomeDir, callback); ASSERT_TRUE(false); } - catch (const Armory::Backups::RestoreUserException& e) + catch (const Armory::Seeds::RestoreUserException& e) { EXPECT_EQ(e.what(), string("user rejected id")); } //try with secure print now - auto newWltPtr = Armory::Backups::Helpers::restoreFromBackup( + auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( rootData, backupData.spPass_, newHomeDir, callback); EXPECT_NE(newWltPtr, nullptr); @@ -9203,29 +9205,29 @@ TEST_F(BackupTests, BackupStrings_BIP32) }; assetWlt->setPassphrasePromptLambda(passLbd); - auto backupData = Armory::Backups::Helpers::getWalletBackup(assetWlt); + auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Backups::RestorePromptType promptType, + const Armory::Seeds::RestorePromptType promptType, const vector checksums, SecureBinaryData& extra)->bool { switch (promptType) { - case Armory::Backups::RestorePromptType::Passphrase: + case Armory::Seeds::RestorePromptType::Passphrase: { extra = newPass; return true; } - case Armory::Backups::RestorePromptType::Control: + case Armory::Seeds::RestorePromptType::Control: { extra = newCtrl; return true; } - case Armory::Backups::RestorePromptType::Id: + case Armory::Seeds::RestorePromptType::Id: { EXPECT_EQ(extra, SecureBinaryData::fromString(backupData.wltId_)); @@ -9248,7 +9250,7 @@ TEST_F(BackupTests, BackupStrings_BIP32) string filename; { //restore wallet - auto newWltPtr = Armory::Backups::Helpers::restoreFromBackup( + auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( backupData.rootClear_, {}, newHomeDir, callback); EXPECT_NE(newWltPtr, nullptr); @@ -9277,30 +9279,30 @@ TEST_F(BackupTests, BackupStrings_BIP32_Custom) }; assetWlt->setPassphrasePromptLambda(passLbd); - auto backupData = Armory::Backups::Helpers::getWalletBackup( - assetWlt, Armory::Backups::BackupType::BIP32_Seed_Virgin); + auto backupData = Armory::Seeds::Helpers::getWalletBackup( + assetWlt, Armory::Seeds::BackupType::BIP32_Seed_Virgin); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Backups::RestorePromptType promptType, + const Armory::Seeds::RestorePromptType promptType, const vector checksums, SecureBinaryData& extra)->bool { switch (promptType) { - case Armory::Backups::RestorePromptType::Passphrase: + case Armory::Seeds::RestorePromptType::Passphrase: { extra = newPass; return true; } - case Armory::Backups::RestorePromptType::Control: + case Armory::Seeds::RestorePromptType::Control: { extra = newCtrl; return true; } - case Armory::Backups::RestorePromptType::Id: + case Armory::Seeds::RestorePromptType::Id: { EXPECT_EQ(extra, SecureBinaryData::fromString(backupData.wltId_)); @@ -9321,7 +9323,7 @@ TEST_F(BackupTests, BackupStrings_BIP32_Custom) mkdir(newHomeDir); //restore wallet - auto newWltPtr = Armory::Backups::Helpers::restoreFromBackup( + auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( backupData.rootClear_, {}, newHomeDir, callback); EXPECT_NE(newWltPtr, nullptr); From 1e16f0701c9d983d980a40fd8ea5cef07ee39919 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 28 May 2023 19:33:39 +0200 Subject: [PATCH 42/47] [ARMORY-29] - create wallets from ClearTextSeed - create EncryptedSeed at from ClearTextSeed at wallet creation - convert EncryptedSeed to ClearTextSeed - create WalletBackup from ClearTextSeed and vice versa --- build-aux/m4/ax_cxx_compile_stdcxx.m4 | 536 +++++++- build-aux/m4/ax_cxx_compile_stdcxx_17.m4 | 35 + configure.ac | 4 +- cppForSwig/BinaryData.h | 6 +- cppForSwig/BridgeAPI/CppBridge.cpp | 61 +- cppForSwig/BridgeAPI/WalletManager.cpp | 21 +- cppForSwig/BridgeAPI/WalletManager.h | 3 +- cppForSwig/SecureBinaryData.h | 12 +- cppForSwig/Signer/ResolverFeed.cpp | 4 +- cppForSwig/Wallets/AssetEncryption.h | 1 - cppForSwig/Wallets/AuthorizedPeers.cpp | 10 +- cppForSwig/Wallets/BIP32_Node.cpp | 2 +- cppForSwig/Wallets/BIP32_Node.h | 2 +- cppForSwig/Wallets/DecryptedDataContainer.cpp | 53 +- cppForSwig/Wallets/Seeds/Backups.cpp | 1023 +++++++++------ cppForSwig/Wallets/Seeds/Backups.h | 210 ++- cppForSwig/Wallets/Seeds/Seeds.cpp | 616 ++++++++- cppForSwig/Wallets/Seeds/Seeds.h | 241 +++- cppForSwig/Wallets/WalletFileInterface.cpp | 5 +- cppForSwig/Wallets/WalletFileInterface.h | 4 +- cppForSwig/Wallets/WalletIdTypes.cpp | 65 +- cppForSwig/Wallets/WalletIdTypes.h | 26 +- cppForSwig/Wallets/Wallets.cpp | 392 +++--- cppForSwig/Wallets/Wallets.h | 65 +- cppForSwig/gtest/SignerTests.cpp | 452 +++---- cppForSwig/gtest/SupernodeTests.cpp | 37 +- cppForSwig/gtest/WalletTests.cpp | 1168 +++++++++++------ cppForSwig/gtest/ZeroConfTests.cpp | 75 +- cppForSwig/protobuf/BridgeProto.proto | 53 +- qtdialogs/DlgRestore.py | 37 +- qtdialogs/DlgUniversalRestoreSelect.py | 2 +- 31 files changed, 3650 insertions(+), 1571 deletions(-) create mode 100644 build-aux/m4/ax_cxx_compile_stdcxx_17.m4 diff --git a/build-aux/m4/ax_cxx_compile_stdcxx.m4 b/build-aux/m4/ax_cxx_compile_stdcxx.m4 index 5a787b485..7c8e6123a 100644 --- a/build-aux/m4/ax_cxx_compile_stdcxx.m4 +++ b/build-aux/m4/ax_cxx_compile_stdcxx.m4 @@ -1,5 +1,5 @@ # =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS @@ -10,13 +10,13 @@ # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and -# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) -# or '14' (for the C++14 standard). +# CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for +# the respective C++ standard version. # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with -# preference for an extended mode. +# preference for no added switch, and then for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is @@ -33,21 +33,26 @@ # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# Copyright (c) 2021 Jörn Heusipp # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 4 +#serial 18 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl - m4_if([$1], [11], [], - [$1], [14], [], - [$1], [17], [m4_fatal([support for C++17 not yet implemented in AX_CXX_COMPILE_STDCXX])], + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [$1], [20], [ax_cxx_compile_alternatives="20"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], @@ -57,26 +62,23 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], [$3], [optional], [ax_cxx_compile_cxx$1_required=false], [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) - m4_if([$4], [], [ax_cxx_compile_cxx$1_try_default=true], - [$4], [default], [ax_cxx_compile_cxx$1_try_default=true], - [$4], [nodefault], [ax_cxx_compile_cxx$1_try_default=false], - [m4_fatal([invalid fourth argument `$4' to AX_CXX_COMPILE_STDCXX])]) AC_LANG_PUSH([C++])dnl ac_success=no - m4_if([$4], [nodefault], [], [dnl - AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, - ax_cv_cxx_compile_cxx$1, - [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], - [ax_cv_cxx_compile_cxx$1=yes], - [ax_cv_cxx_compile_cxx$1=no])]) - if test x$ax_cv_cxx_compile_cxx$1 = xyes; then - ac_success=yes - fi]) + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then - for switch in -std=gnu++$1 -std=gnu++0x; do + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, @@ -102,22 +104,36 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" - for switch in -std=c++$1 -std=c++0x +std=c++$1 "-h std=c++$1"; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) - AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, - $cachevar, - [ac_save_CXX="$CXX" - CXX="$CXX $switch" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], - [eval $cachevar=yes], - [eval $cachevar=no]) - CXX="$ac_save_CXX"]) - if eval test x\$$cachevar = xyes; then - CXX="$CXX $switch" - if test -n "$CXXCPP" ; then - CXXCPP="$CXXCPP $switch" + dnl MSVC needs -std:c++NN for C++17 and later (default is C++14) + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}" MSVC; do + if test x"$switch" = xMSVC; then + dnl AS_TR_SH maps both `:` and `=` to `_` so -std:c++17 would collide + dnl with -std=c++17. We suffix the cache variable name with _MSVC to + dnl avoid this. + switch=-std:c++${alternative} + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_${switch}_MSVC]) + else + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) fi - ac_success=yes + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then break fi done @@ -146,7 +162,6 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 ) - dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], @@ -154,6 +169,23 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 ) +dnl Test body for checking C++17 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Test body for checking C++20 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 +) + dnl Tests for new features in C++11 @@ -166,7 +198,11 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201103L +// MSVC always sets __cplusplus to 199711L in older versions; newer versions +// only set it correctly if /Zc:__cplusplus is specified as well as a +// /std:c++NN switch: +// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ +#elif __cplusplus < 201103L && !defined _MSC_VER #error "This is not a C++11 compiler" @@ -191,11 +227,13 @@ namespace cxx11 struct Base { + virtual ~Base() {} virtual void f() {} }; struct Derived : public Base { + virtual ~Derived() override {} virtual void f() override {} }; @@ -396,13 +434,13 @@ namespace cxx11 template struct sum { - static const int value = N0 + sum::value; + static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { - static const int value = 0; + static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); @@ -455,7 +493,7 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ #error "This is not a C++ compiler" -#elif __cplusplus < 201402L +#elif __cplusplus < 201402L && !defined _MSC_VER #error "This is not a C++14 compiler" @@ -524,7 +562,7 @@ namespace cxx14 } - namespace test_digit_seperators + namespace test_digit_separators { constexpr auto ten_million = 100'000'000; @@ -566,3 +604,415 @@ namespace cxx14 #endif // __cplusplus >= 201402L ]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L && !defined _MSC_VER + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L && !defined _MSC_VER + +]]) + + +dnl Tests for new features in C++20 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 202002L && !defined _MSC_VER + +#error "This is not a C++20 compiler" + +#else + +#include + +namespace cxx20 +{ + +// As C++20 supports feature test macros in the standard, there is no +// immediate need to actually test for feature availability on the +// Autoconf side. + +} // namespace cxx20 + +#endif // __cplusplus < 202002L && !defined _MSC_VER + +]]) \ No newline at end of file diff --git a/build-aux/m4/ax_cxx_compile_stdcxx_17.m4 b/build-aux/m4/ax_cxx_compile_stdcxx_17.m4 new file mode 100644 index 000000000..685247a5a --- /dev/null +++ b/build-aux/m4/ax_cxx_compile_stdcxx_17.m4 @@ -0,0 +1,35 @@ +# ============================================================================= +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_17.html +# ============================================================================= +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_17([ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++17 +# standard; if necessary, add switches to CXX and CXXCPP to enable +# support. +# +# This macro is a convenience alias for calling the AX_CXX_COMPILE_STDCXX +# macro with the version set to C++17. The two optional arguments are +# forwarded literally as the second and third argument respectively. +# Please see the documentation for the AX_CXX_COMPILE_STDCXX macro for +# more information. If you want to use this macro, you also need to +# download the ax_cxx_compile_stdcxx.m4 file. +# +# LICENSE +# +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016 Krzesimir Nowak +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 2 + +AX_REQUIRE_DEFINED([AX_CXX_COMPILE_STDCXX]) +AC_DEFUN([AX_CXX_COMPILE_STDCXX_17], [AX_CXX_COMPILE_STDCXX([17], [$1], [$2])]) \ No newline at end of file diff --git a/configure.ac b/configure.ac index 34eea4f78..b6bcd7415 100755 --- a/configure.ac +++ b/configure.ac @@ -80,8 +80,8 @@ AC_CONFIG_MACRO_DIR([build-aux/m4]) m4_include([build-aux/m4/ax_check_compile_flag.m4]) m4_include([build-aux/m4/ax_cxx_compile_stdcxx.m4]) -#check for c++14 -AX_CXX_COMPILE_STDCXX([14], [noext], [mandatory], [nodefault]) +#check for c++17 +AX_CXX_COMPILE_STDCXX_17([noext], [mandatory]) # Make the compilation flags quiet unless V=1 is used. m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) diff --git a/cppForSwig/BinaryData.h b/cppForSwig/BinaryData.h index 610bccaf3..d1bb1913f 100644 --- a/cppForSwig/BinaryData.h +++ b/cppForSwig/BinaryData.h @@ -108,7 +108,7 @@ class BinaryData ///////////////////////////////////////////////////////////////////////////// BinaryData(void) : data_(0) { } explicit BinaryData(size_t sz) { alloc(sz); } - BinaryData(uint8_t const * inData, size_t sz) + BinaryData(uint8_t const * inData, size_t sz) { copyFrom(inData, sz); } BinaryData(char const * inData, size_t sz) { copyFrom(inData, sz); } BinaryData(uint8_t const * dstart, uint8_t const * dend ) @@ -561,16 +561,14 @@ class BinaryData std::vector data_; private: - void alloc(size_t sz) + void alloc(size_t sz) { if(sz != getSize()) { data_.clear(); data_.resize(sz); } - } - }; diff --git a/cppForSwig/BridgeAPI/CppBridge.cpp b/cppForSwig/BridgeAPI/CppBridge.cpp index 5987b738f..7ecc22a07 100755 --- a/cppForSwig/BridgeAPI/CppBridge.cpp +++ b/cppForSwig/BridgeAPI/CppBridge.cpp @@ -271,7 +271,7 @@ void CppBridge::createBackupStringForWallet(const string& waaId, }); auto lbd = passPromptObj->getLambda(); - Armory::Seeds::WalletBackup backupData; + unique_ptr backupData = nullptr; try { //grab wallet @@ -290,7 +290,7 @@ void CppBridge::createBackupStringForWallet(const string& waaId, auto reply = payload->mutable_reply(); reply->set_reference_id(msgId); - if (backupData.rootClear_.empty()) + if (backupData == nullptr) { //return on error reply->set_success(false); @@ -298,25 +298,58 @@ void CppBridge::createBackupStringForWallet(const string& waaId, return; } - auto backupStringProto = reply->mutable_wallet()->mutable_backup_string(); - for (auto& line : backupData.rootClear_) - backupStringProto->add_root_clear(line); + auto backupE16 = dynamic_cast( + backupData.get()); + if (backupE16 == nullptr) + { + throw runtime_error("[createBackupStringForWallet]" + " invalid backup type"); + } - for (auto& line : backupData.rootEncr_) - backupStringProto->add_root_encr(line); + auto backupStringProto = reply->mutable_wallet()->mutable_backup_string(); - if (!backupData.chaincodeClear_.empty()) + //cleartext root { - for (auto& line : backupData.chaincodeClear_) - backupStringProto->add_chain_clear(line); + auto line1 = backupE16->getRoot( + Armory::Seeds::Backup_Easy16::LineIndex::One, false); + backupStringProto->add_root_clear(line1.data(), line1.size()); + auto line2 = backupE16->getRoot( + Armory::Seeds::Backup_Easy16::LineIndex::Two, false); + backupStringProto->add_root_clear(line2.data(), line2.size()); + + //encrypted root + auto line3 = backupE16->getRoot( + Armory::Seeds::Backup_Easy16::LineIndex::One, true); + backupStringProto->add_root_encr(line3.data(), line3.size()); + auto line4 = backupE16->getRoot( + Armory::Seeds::Backup_Easy16::LineIndex::Two, true); + backupStringProto->add_root_encr(line3.data(), line3.size()); + } - for (auto& line : backupData.chaincodeEncr_) - backupStringProto->add_chain_encr(line); + if (backupE16->hasChaincode()) + { + //cleartext chaincode + auto line1 = backupE16->getChaincode( + Armory::Seeds::Backup_Easy16::LineIndex::One, false); + backupStringProto->add_chain_clear(line1.data(), line1.size()); + + auto line2 = backupE16->getChaincode( + Armory::Seeds::Backup_Easy16::LineIndex::Two, false); + backupStringProto->add_chain_clear(line2.data(), line2.size()); + + //encrypted chaincode + auto line3 = backupE16->getChaincode( + Armory::Seeds::Backup_Easy16::LineIndex::One, true); + backupStringProto->add_chain_encr(line3.data(), line3.size()); + + auto line4 = backupE16->getChaincode( + Armory::Seeds::Backup_Easy16::LineIndex::Two, true); + backupStringProto->add_chain_encr(line4.data(), line4.size()); } //secure print passphrase - backupStringProto->set_sp_pass( - backupData.spPass_.toCharPtr(), backupData.spPass_.getSize()); + auto spPass = backupE16->getSpPass(); + backupStringProto->set_sp_pass(spPass.data(), spPass.size()); reply->set_success(true); writeToClient(move(payload)); diff --git a/cppForSwig/BridgeAPI/WalletManager.cpp b/cppForSwig/BridgeAPI/WalletManager.cpp index fbe582186..a9f29b1a2 100644 --- a/cppForSwig/BridgeAPI/WalletManager.cpp +++ b/cppForSwig/BridgeAPI/WalletManager.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // // -// Copyright (C) 2016-20, goatpig // +// Copyright (C) 2016-2023, goatpig // // Distributed under the MIT license // // See LICENSE-MIT or https://opensource.org/licenses/MIT // // // @@ -9,6 +9,7 @@ #include "WalletManager.h" #include "Wallets/Seeds/Backups.h" #include "PassphrasePrompt.h" +#include "../Wallets/Seeds/Seeds.h" #ifdef _WIN32 #include "leveldb_windows_port\win32_posix\dirent_win32.h" @@ -20,6 +21,7 @@ using namespace std; using namespace Armory::Signer; using namespace Armory::Accounts; using namespace Armory::Wallets; +using namespace Armory::Seeds; #define WALLET_135_HEADER "\xbaWALLET\x00" #define PYBTC_ADDRESS_SIZE 237 @@ -151,8 +153,11 @@ shared_ptr WalletManager::createNewWallet( if (extraEntropy.getSize() >= 32) root.XOR(extraEntropy); - auto wallet = AssetWallet_Single::createFromPrivateRoot_Armory135( - path_, root, {}, pass, controlPass, lookup); + unique_ptr seed(new ClearTextSeed_Armory135(root, + ClearTextSeed_Armory135::LegacyType::Armory200)); + auto wallet = AssetWallet_Single::createFromSeed(move(seed), + pass, controlPass, + path_, lookup); return addWallet(wallet, wallet->getMainAccountID()); } @@ -635,7 +640,7 @@ map> WalletContainer::getUpdatedAddressMap( } //////////////////////////////////////////////////////////////////////////////// -Armory::Seeds::WalletBackup WalletContainer::getBackupStrings( +unique_ptr WalletContainer::getBackupStrings( const PassphraseLambda& passLbd) const { auto wltSingle = dynamic_pointer_cast(wallet_); @@ -932,9 +937,11 @@ shared_ptr Armory135Header::migrate( } else { - wallet = AssetWallet_Single::createFromPrivateRoot_Armory135( - folder, decryptedRoot, chaincodeCopy, - privKeyPass, controlPass, highestIndex); + unique_ptr seed(new ClearTextSeed_Armory135( + decryptedRoot, chaincodeCopy)); + wallet = AssetWallet_Single::createFromSeed(move(seed), + privKeyPass, controlPass, + folder, highestIndex); } //main account id, check it matches armory wallet id diff --git a/cppForSwig/BridgeAPI/WalletManager.h b/cppForSwig/BridgeAPI/WalletManager.h index 263588bac..6e2cffd25 100644 --- a/cppForSwig/BridgeAPI/WalletManager.h +++ b/cppForSwig/BridgeAPI/WalletManager.h @@ -215,7 +215,8 @@ class WalletContainer Armory::Wallets::AssetKeyType getHighestUsedIndex(void) const; std::map> getUpdatedAddressMap(); - Armory::Seeds::WalletBackup getBackupStrings(const PassphraseLambda&) const; + std::unique_ptr getBackupStrings( + const PassphraseLambda&) const; void setComment(const std::string&, const std::string&); void setLabels(const std::string&, const std::string&); diff --git a/cppForSwig/SecureBinaryData.h b/cppForSwig/SecureBinaryData.h index 87fb06a0e..e793d0018 100644 --- a/cppForSwig/SecureBinaryData.h +++ b/cppForSwig/SecureBinaryData.h @@ -148,7 +148,17 @@ class SecureBinaryData : public BinaryData return {}; SecureBinaryData sbd(str.size()); - memcpy(sbd.getPtr(), str.c_str(), str.size()); + memcpy(sbd.getPtr(), str.data(), str.size()); + return sbd; + } + + static SecureBinaryData fromStringView(const std::string_view& strv) + { + if (strv.empty()) + return {}; + + SecureBinaryData sbd(strv.size()); + memcpy(sbd.getPtr(), strv.data(), strv.size()); return sbd; } }; diff --git a/cppForSwig/Signer/ResolverFeed.cpp b/cppForSwig/Signer/ResolverFeed.cpp index 0fd4e7e34..6e4eec490 100644 --- a/cppForSwig/Signer/ResolverFeed.cpp +++ b/cppForSwig/Signer/ResolverFeed.cpp @@ -42,7 +42,9 @@ uint32_t BIP32_PublicDerivedRoot::getThisFingerprint() const if (thisFingerprint_ == UINT32_MAX) { BIP32_Node node; - node.initFromBase58(SecureBinaryData::fromString(xpub_)); + BinaryDataRef xpubRef; + xpubRef.setRef(xpub_); + node.initFromBase58(xpubRef); thisFingerprint_ = node.getThisFingerprint(); } diff --git a/cppForSwig/Wallets/AssetEncryption.h b/cppForSwig/Wallets/AssetEncryption.h index 6929fd9d4..85c151309 100644 --- a/cppForSwig/Wallets/AssetEncryption.h +++ b/cppForSwig/Wallets/AssetEncryption.h @@ -20,7 +20,6 @@ #define PRIVKEY_BYTE 0x82 #define ENCRYPTIONKEY_BYTE 0x83 -#define WALLET_SEED_BYTE 0x84 #define CIPHER_DATA_VERSION 0x00000001 #define ENCRYPTION_KEY_VERSION 0x00000001 diff --git a/cppForSwig/Wallets/AuthorizedPeers.cpp b/cppForSwig/Wallets/AuthorizedPeers.cpp index f3fcc61ce..df0227393 100644 --- a/cppForSwig/Wallets/AuthorizedPeers.cpp +++ b/cppForSwig/Wallets/AuthorizedPeers.cpp @@ -14,6 +14,7 @@ #include "AuthorizedPeers.h" #include "btc/ecc.h" #include "WalletFileInterface.h" +#include "Seeds/Seeds.h" #include "TerminalPassphrasePrompt.h" @@ -21,6 +22,7 @@ using namespace std; using namespace Armory::Assets; using namespace Armory::Accounts; using namespace Armory::Wallets; +using namespace Armory::Seeds; //////////////////////////////////////////////////////////////////////////////// AuthorizedPeers::AuthorizedPeers( @@ -170,10 +172,10 @@ void AuthorizedPeers::createWallet( derPath.push_back(0xF0000000); //generate bip32 node from random seed - auto&& seed = CryptoPRNG::generateRandom(32); - - wallet_ = AssetWallet_Single::createFromSeed_BIP32_Blank( - baseDir, seed, password, controlPassphrase); + wallet_ = AssetWallet_Single::createFromSeed( + make_unique( + CryptoPRNG::generateRandom(32), SeedType::BIP32_Virgin), + password, controlPassphrase, baseDir); auto wltSingle = dynamic_pointer_cast(wallet_); auto rootBip32 = dynamic_pointer_cast( diff --git a/cppForSwig/Wallets/BIP32_Node.cpp b/cppForSwig/Wallets/BIP32_Node.cpp index 7cee9ecf1..e85abc3a0 100644 --- a/cppForSwig/Wallets/BIP32_Node.cpp +++ b/cppForSwig/Wallets/BIP32_Node.cpp @@ -120,7 +120,7 @@ void BIP32_Node::initFromSeed(const SecureBinaryData& seed) } //////////////////////////////////////////////////////////////////////////////// -void BIP32_Node::initFromBase58(const SecureBinaryData& b58) +void BIP32_Node::initFromBase58(BinaryDataRef b58) { //sbd doesnt 0 terminate strings as it is not specialized for char strings, //have to set it manually since libbtc b58 code derives string length from diff --git a/cppForSwig/Wallets/BIP32_Node.h b/cppForSwig/Wallets/BIP32_Node.h index 3af9c10da..192034a17 100644 --- a/cppForSwig/Wallets/BIP32_Node.h +++ b/cppForSwig/Wallets/BIP32_Node.h @@ -38,7 +38,7 @@ class BIP32_Node //init void initFromSeed(const SecureBinaryData&); - void initFromBase58(const SecureBinaryData&); + void initFromBase58(BinaryDataRef); void initFromPrivateKey(uint8_t depth, unsigned leaf_id, unsigned fingerprint, const SecureBinaryData& privKey, const SecureBinaryData& chaincode); void initFromPublicKey(uint8_t depth, unsigned leaf_id, unsigned fingerprint, diff --git a/cppForSwig/Wallets/DecryptedDataContainer.cpp b/cppForSwig/Wallets/DecryptedDataContainer.cpp index 16cd45e82..15fed862b 100644 --- a/cppForSwig/Wallets/DecryptedDataContainer.cpp +++ b/cppForSwig/Wallets/DecryptedDataContainer.cpp @@ -63,11 +63,14 @@ void DecryptedDataContainer::lockOther( shared_ptr other) { if (!ownsLock()) - throw DecryptedDataContainerException("unlocked/does not own lock"); + { + throw DecryptedDataContainerException( + "[DecryptedDataContainer::lockOther] unlocked/does not own lock"); + } if (lockedDecryptedData_ == nullptr) throw DecryptedDataContainerException( - "nullptr lock! how did we get this far?"); + "nullptr lock! how did we get this far?"); otherLocks_.push_back(OtherLockedContainer(other)); } @@ -103,11 +106,17 @@ unique_ptr DecryptedDataContainer::deriveEncryptionKey( { //sanity check if (!ownsLock()) - throw DecryptedDataContainerException("unlocked/does not own lock"); + { + throw DecryptedDataContainerException( + "[DecryptedDataContainer::deriveEncryptionKey]" + " unlocked/does not own lock"); + } if (lockedDecryptedData_ == nullptr) + { throw DecryptedDataContainerException( - "nullptr lock! how did we get this far?"); + "nullptr lock! how did we get this far?"); + } //does the decryption key have this derivation? auto derivationIter = decrKey->derivedKeys_.find(kdfid); @@ -146,7 +155,11 @@ const SecureBinaryData& DecryptedDataContainer::getClearTextAssetData( //sanity check if (!ownsLock()) - throw DecryptedDataContainerException("unlocked/does not own lock"); + { + throw DecryptedDataContainerException( + "[DecryptedDataContainer::getClearTextAssetData]" + " unlocked/does not own lock"); + } if (lockedDecryptedData_ == nullptr) throw DecryptedDataContainerException( @@ -289,7 +302,11 @@ EncryptionKeyId DecryptedDataContainer::populateEncryptionKey( //sanity check if (!ownsLock()) - throw DecryptedDataContainerException("unlocked/does not own lock"); + { + throw DecryptedDataContainerException( + "[DecryptedDataContainer::populateEncryptionKey]" + " unlocked/does not own lock"); + } if (lockedDecryptedData_ == nullptr) throw DecryptedDataContainerException( @@ -437,7 +454,11 @@ SecureBinaryData DecryptedDataContainer::encryptData( throw DecryptedDataContainerException("null cipher"); if (!ownsLock()) - throw DecryptedDataContainerException("unlocked/does not own lock"); + { + throw DecryptedDataContainerException( + "[DecryptedDataContainer::encryptData]" + " unlocked/does not own lock"); + } if (lockedDecryptedData_ == nullptr) throw DecryptedDataContainerException( @@ -573,7 +594,11 @@ void DecryptedDataContainer::deleteFromDisk( { //sanity checks if (!ownsLock()) - throw DecryptedDataContainerException("unlocked/does not own lock"); + { + throw DecryptedDataContainerException( + "[DecryptedDataContainer::deleteFromDisk]" + " unlocked/does not own lock"); + } //erase key, db interface will wipe it from file tx->erase(key); @@ -656,7 +681,11 @@ void DecryptedDataContainer::encryptEncryptionKey( //we have to own the lock on this container before proceeding if (!ownsLock()) - throw DecryptedDataContainerException("unlocked/does not own lock"); + { + throw DecryptedDataContainerException( + "[DecryptedDataContainer::encryptEncryptionKey]" + " unlocked/does not own lock"); + } if (lockedDecryptedData_ == nullptr) { @@ -790,7 +819,11 @@ void DecryptedDataContainer::eraseEncryptionKey( //we have to own the lock on this container before proceeding if (!ownsLock()) - throw DecryptedDataContainerException("unlocked/does not own lock"); + { + throw DecryptedDataContainerException( + "[DecryptedDataContainer::eraseEncryptionKey]" + " unlocked/does not own lock"); + } if (lockedDecryptedData_ == nullptr) { diff --git a/cppForSwig/Wallets/Seeds/Backups.cpp b/cppForSwig/Wallets/Seeds/Backups.cpp index 709a8d036..c8caf140f 100644 --- a/cppForSwig/Wallets/Seeds/Backups.cpp +++ b/cppForSwig/Wallets/Seeds/Backups.cpp @@ -11,6 +11,7 @@ #include "BtcUtils.h" #include "../WalletIdTypes.h" #include "Seeds.h" +#include "protobuf/BridgeProto.pb.h" #define EASY16_CHECKSUM_LEN 2 #define EASY16_INDEX_MAX 15 @@ -24,7 +25,7 @@ using namespace Armory::Assets; using namespace Armory::Wallets; //////////////////////////////////////////////////////////////////////////////// -const vector BackupEasy16::e16chars_ = +const vector Easy16Codec::e16chars_ = { 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', @@ -33,18 +34,18 @@ const vector BackupEasy16::e16chars_ = }; //////////////////////////////////////////////////////////////////////////////// -const set BackupEasy16::eligibleIndexes_ = +const set Easy16Codec::eligibleIndexes_ = { - (uint8_t)BackupType::Armory135, - (uint8_t)BackupType::BIP32_Seed_Structured, - (uint8_t)BackupType::BIP32_Root, - (uint8_t)BackupType::BIP32_Seed_Virgin, + BackupType::Armory135, + BackupType::Armory200a, + BackupType::Armory200b, + BackupType::Armory200c, + BackupType::Armory200d }; - //////////////////////////////////////////////////////////////////////////////// -/* +/* - comment from etotheipi: - Nothing up my sleeve! Need some hardcoded random numbers to use for encryption IV and salt. Using the first 256 digits of Pi for the the IV, and first 256 digits of e for the salt (hashed) @@ -72,10 +73,10 @@ const uint32_t SecurePrint::kdfBytes_ = 16 * 1024 * 1024; //////////////////////////////////////////////////////////////////////////////// //// -//// BackupEasy16 +//// Easy16Codec //// //////////////////////////////////////////////////////////////////////////////// -BinaryData BackupEasy16::getHash(const BinaryDataRef& data, uint8_t hint) +BinaryData Easy16Codec::getHash(const BinaryDataRef& data, uint8_t hint) { if (hint == 0) { @@ -92,92 +93,111 @@ BinaryData BackupEasy16::getHash(const BinaryDataRef& data, uint8_t hint) } //////////////////////////////////////////////////////////////////////////////// -uint8_t BackupEasy16::verifyChecksum( +uint8_t Easy16Codec::verifyChecksum( const BinaryDataRef& data, const BinaryDataRef& checksum) { for (const auto& indexCandidate : eligibleIndexes_) { - auto hash = getHash(data, indexCandidate); + auto hash = getHash(data, (uint8_t)indexCandidate); if (hash.getSliceRef(0, EASY16_CHECKSUM_LEN) == checksum) - return indexCandidate; + return (uint8_t)indexCandidate; } return EASY16_INVALID_CHECKSUM_INDEX; } //////////////////////////////////////////////////////////////////////////////// -vector BackupEasy16::encode(const BinaryDataRef data, uint8_t index) +vector Easy16Codec::encode( + const BinaryDataRef data, BackupType bType) { + //TODO: use index pairs for a given backup type instead (one index per line) + uint8_t index = (uint8_t)bType; + if (index > EASY16_INDEX_MAX) { LOGERR << "index is too large"; throw runtime_error("index is too large"); } - auto encodeByte = [](stringstream& ss, uint8_t c)->void + auto encodeByte = [](char* ptr, uint8_t c)->void { uint8_t val1 = c >> 4; uint8_t val2 = c & 0x0F; - ss << e16chars_[val1] << e16chars_[val2]; + ptr[0] = e16chars_[val1]; + ptr[1] = e16chars_[val2]; }; auto encodeValue = [&encodeByte, &index]( - const BinaryDataRef& chunk16)->string + const BinaryDataRef& chunk16)->SecureBinaryData { //get hash auto h256 = getHash(chunk16, index); - + SecureBinaryData result(46); + //encode the chunk - stringstream ss; unsigned charCount = 0; + unsigned offset = 0; auto ptr = chunk16.getPtr(); for (unsigned i=0; i result; BinaryRefReader brr(data); + uint32_t count = (data.getSize() + EASY16_LINE_LENGTH - 1) / + EASY16_LINE_LENGTH; + vector result; + result.reserve(count); - uint32_t count = - (data.getSize() + EASY16_LINE_LENGTH - 1) / EASY16_LINE_LENGTH; for (unsigned i=0; i& lines) +BackupEasy16DecodeResult Easy16Codec::decode( + const vector& lines) { vector refVec; + refVec.reserve(lines.size()); for (const auto& line : lines) - refVec.emplace_back((const uint8_t*)line.c_str(), line.size()); + refVec.emplace_back(line.getRef()); return decode(refVec); } -//////////////////////////////////////////////////////////////////////////////// -BackupEasy16DecodeResult BackupEasy16::decode(const vector& lines) +//// +BackupEasy16DecodeResult Easy16Codec::decode(const vector& lines) { if (lines.size() == 0) throw runtime_error("empty easy16 code"); @@ -187,12 +207,9 @@ BackupEasy16DecodeResult BackupEasy16::decode(const vector& lines for (unsigned i=0; ibool + auto isSpace = [](const char* str)->bool { - if (str[0] == ' ') - return false; - - return true; + return (*str == ' '); }; auto decodeCharacters = [&easy16Vals]( @@ -206,7 +223,7 @@ BackupEasy16DecodeResult BackupEasy16::decode(const vector& lines auto iter2 = easy16Vals.find(str[1]); if (iter2 != easy16Vals.end()) - result += iter2->second; + result += iter2->second; }; /* @@ -218,8 +235,8 @@ BackupEasy16DecodeResult BackupEasy16::decode(const vector& lines . -2: invalid checksum data . -3: not enough room in the result buffer */ - auto decodeLine = [&checkSpace, &decodeCharacters]( - uint8_t* result, size_t& len, + auto decodeLine = [&isSpace, &decodeCharacters]( + uint8_t* result, size_t& len, const BinaryDataRef& line, BinaryData& checksum)->int { auto maxlen = len; @@ -230,12 +247,12 @@ BackupEasy16DecodeResult BackupEasy16::decode(const vector& lines for (; i= maxlen) return -3; - + decodeCharacters(result[len], ptr + i); //increment result length @@ -252,7 +269,7 @@ BackupEasy16DecodeResult BackupEasy16::decode(const vector& lines for (; i= EASY16_CHECKSUM_LEN) @@ -328,7 +345,7 @@ BackupEasy16DecodeResult BackupEasy16::decode(const vector& lines } //////////////////////////////////////////////////////////////////////////////// -bool BackupEasy16::repair(BackupEasy16DecodeResult& faultyBackup) +bool Easy16Codec::repair(BackupEasy16DecodeResult& faultyBackup) { //sanity check if (faultyBackup.data_.empty() || faultyBackup.checksums_.empty() || @@ -342,7 +359,7 @@ bool BackupEasy16::repair(BackupEasy16DecodeResult& faultyBackup) set validIndexes; for (auto index : faultyBackup.checksumIndexes_) { - auto indexIter = eligibleIndexes_.find(index); + auto indexIter = eligibleIndexes_.find((BackupType)index); if (indexIter == eligibleIndexes_.end()) { if (index == EASY16_INVALID_CHECKSUM_INDEX) @@ -403,10 +420,10 @@ bool BackupEasy16::repair(BackupEasy16DecodeResult& faultyBackup) //check all eligible indexes for (const auto& indexCandidate : eligibleIndexes_) { - auto hash = getHash(copied, indexCandidate); + auto hash = getHash(copied, (uint8_t)indexCandidate); if (hash.getSliceRef(0, 2) == checksum) { - auto& chkVal = result[indexCandidate]; + auto& chkVal = result[(uint8_t)indexCandidate]; auto& pos = chkVal[i]; pos.insert(y); } @@ -431,8 +448,8 @@ bool BackupEasy16::repair(BackupEasy16DecodeResult& faultyBackup) else if (validIndexes.size() == 1) { /* - Some lines are invalid but we have at least one that is valid. This - allows us to search for the expected checksum index in the invalid + Some lines are invalid but we have at least one that is valid. This + allows us to search for the expected checksum index in the invalid lines (they should all match) */ unsigned hint = *validIndexes.begin(); @@ -477,7 +494,7 @@ bool BackupEasy16::repair(BackupEasy16DecodeResult& faultyBackup) else { /* - All lines are invalid. There is no indication of what the checksum index + All lines are invalid. There is no indication of what the checksum index ought to be. We have to search all lines for a matching index. */ vector>>> resultMap; @@ -563,7 +580,46 @@ bool BackupEasy16::repair(BackupEasy16DecodeResult& faultyBackup) } return true; -} +} + +//////////////////////////////////////////////////////////////////////////////// +//// +//// BackupEasy16DecodeResult +//// +//////////////////////////////////////////////////////////////////////////////// +bool BackupEasy16DecodeResult::isInitialized() const +{ + return checksumIndexes_.size() == 2; +} + +//// +int BackupEasy16DecodeResult::getIndex() const +{ + if (!isInitialized()) + return -1; + + if (repairedIndexes_.size() == 2) + { + if (repairedIndexes_[0] == repairedIndexes_[1]) + return repairedIndexes_[0]; + } + else + { + if (checksumIndexes_[0] == checksumIndexes_[1]) + return checksumIndexes_[0]; + } + + return -1; +} + +bool BackupEasy16DecodeResult::isValid() const +{ + if (!isInitialized()) + return false; + + auto iter = Easy16Codec::eligibleIndexes_.find((BackupType)getIndex()); + return (iter != Easy16Codec::eligibleIndexes_.end()); +} //////////////////////////////////////////////////////////////////////////////// //// @@ -584,7 +640,7 @@ SecurePrint::SecurePrint() //////////////////////////////////////////////////////////////////////////////// pair SecurePrint::encrypt( - const SecureBinaryData& root, const SecureBinaryData& chaincode) + BinaryDataRef root, BinaryDataRef chaincode) { /* 1. generate passphrase from root and chaincode @@ -617,7 +673,8 @@ pair SecurePrint::encrypt( Concatenate root and chaincode then hmac */ - SecureBinaryData rootCopy = root; + SecureBinaryData rootCopy(64); + rootCopy.append(root); rootCopy.append(chaincode); auto rootHash = BtcUtils::getHash256(rootCopy); @@ -679,7 +736,7 @@ pair SecurePrint::encrypt( if (!chaincode.empty()) { - if (!encrypt(chaincode, result.second)) + if (!encrypt(chaincode, result.second)) { LOGERR << "SecurePrint encryption failure"; throw runtime_error("SecurePrint encryption failure"); @@ -694,6 +751,7 @@ SecureBinaryData SecurePrint::decrypt( const SecureBinaryData& ciphertext, const BinaryDataRef passphrase) const { //check passphrase checksum + //TODO: try with std::string_view instead string passStr(passphrase.toCharPtr(), passphrase.getSize()); BinaryData passBin; try @@ -770,420 +828,651 @@ SecureBinaryData SecurePrint::decrypt( //// Helpers //// //////////////////////////////////////////////////////////////////////////////// -WalletRootData Helpers::getRootData( - shared_ptr wltSingle) -{ - WalletRootData rootData; - rootData.wltId_ = wltSingle->getID(); - auto root = dynamic_pointer_cast( - wltSingle->getRoot()); - //lock wallet - auto lock = wltSingle->lockDecryptedContainer(); +/////////////////////////////// -- backup strings -- /////////////////////////// +unique_ptr Helpers::getWalletBackup( + shared_ptr wltPtr, BackupType bType) +{ + std::unique_ptr clearTextSeed; - //check root - auto rootBip32 = dynamic_pointer_cast(root); - if (rootBip32 == nullptr) + //grab encrypted seed from wallet + auto lock = wltPtr->lockDecryptedContainer(); + auto wltSeed = wltPtr->getEncryptedSeed(); + if (wltSeed != nullptr) { - /* - This isn't a bip32 root, therefor it's an Armory root. It may carry a - dedicated chaincode, let's check for that. - */ - + const auto& rawClearTextSeed = wltPtr->getDecryptedValue(wltSeed); + clearTextSeed = ClearTextSeed::deserialize(rawClearTextSeed); + } + else + { + //wallet has no seed, maybe it's a legacy Armory wallet, where + //the seed and root are the same + auto root = wltPtr->getRoot(); auto root135 = dynamic_pointer_cast(root); if (root135 == nullptr) + return {}; + + const auto& rootPrivKey = wltPtr->getDecryptedPrivateKeyForAsset( + root135); + clearTextSeed = unique_ptr(new ClearTextSeed_Armory135( + rootPrivKey, root135->getChaincode())); + } + + if (clearTextSeed == nullptr) + throw runtime_error("[getWalletBackup] could not get seed from wallet"); + + //pick default backup type for seed if not set explicitly + if (bType == BackupType::Invalid) + bType = clearTextSeed->getPreferedBackupType(); + + auto backup = getWalletBackup(move(clearTextSeed), bType); + backup->wltId_ = wltPtr->getID(); + return backup; +} + +//// +unique_ptr Helpers::getWalletBackup( + unique_ptr seed, BackupType bType) +{ + //sanity check + if (!seed->isBackupTypeEligible(bType)) + throw runtime_error("[getWalletBackup] ineligible backup type"); + + switch (bType) + { + case BackupType::Armory135: + case BackupType::Armory200a: + case BackupType::Armory200b: + case BackupType::Armory200c: + case BackupType::Armory200d: + return getEasy16BackupString(move(seed)); + + case BackupType::Base58: + return getBase58BackupString(move(seed)); + + case BackupType::BIP39: + return getBIP39BackupString(move(seed)); + + default: + throw runtime_error("[getWalletBackup] invalid backup type"); + } +} + +//////// +unique_ptr Helpers::getEasy16BackupString( + unique_ptr seed) +{ + BinaryDataRef primaryData; + BinaryDataRef secondaryData; + BackupType mode = BackupType::Invalid; + + switch (seed->type()) + { + case SeedType::Armory135: { - LOGERR << "unexpected wallet root type"; - throw runtime_error("unexpected wallet root type"); + auto seed135 = dynamic_cast(seed.get()); + primaryData = seed135->getRoot().getRef(); + secondaryData = seed135->getChaincode().getRef(); + mode = seed->getPreferedBackupType(); + break; } - rootData.root_ = wltSingle->getDecryptedPrivateKeyForAsset(root); - rootData.type_ = BackupType::Armory135; - - const auto& wltChaincode = root135->getChaincode(); - if (!wltChaincode.empty()) + case SeedType::BIP32_Structured: + case SeedType::BIP32_Virgin: + case SeedType::BIP39: { - /* - If the root carries a chaincode, it may be non deterministic. Let's - check. - */ + auto seedBip32 = dynamic_cast(seed.get()); + primaryData = seedBip32->getRawEntropy().getRef(); + mode = seed->getPreferedBackupType(); - auto computedChaincode = - BtcUtils::computeChainCode_Armory135(rootData.root_); - - if (computedChaincode != wltChaincode) - rootData.secondaryData_ = wltChaincode; + switch (seed->type()) + { + case SeedType::BIP39: + //force Armory200d for BIP39 seeds + mode = BackupType::Armory200d; + break; + + default: + mode = seed->getPreferedBackupType(); + } + break; } + + default: + throw runtime_error("[getEasy16BackupString] invalid seed type"); } - else + + //apply secureprint to seed data + SecurePrint sp; + auto encrRoot = sp.encrypt(primaryData, secondaryData); + + //set cleartext and encrypted root + auto lines_clear = Easy16Codec::encode(primaryData, mode); + auto lines_encr = Easy16Codec::encode(encrRoot.first, mode); + + auto result = make_unique(mode); + result->rootClear_ = move(lines_clear); + result->rootEncr_ = move(lines_encr); + if (mode == BackupType::Armory135) { - //bip32 wallet, grab the seed instead - auto seedPtr = wltSingle->getEncryptedSeed(); - if (seedPtr == nullptr) + //if there's a chaincode, set it too + if (!secondaryData.empty()) { - /* - For now, abort if bip32 wallet is missing its seed. May implement - root backups for bip32 wallets (privkey + chaincode) in the future. - */ - rootData.type_ = BackupType::BIP32_Root; - return rootData; + result->chaincodeClear_ = move(Easy16Codec::encode(secondaryData, mode)); + result->chaincodeEncr_ = move(Easy16Codec::encode(encrRoot.second, mode)); } + } + result->spPass_ = move(sp.getPassphrase()); + return result; +} + +//////// +unique_ptr Helpers::getBIP39BackupString( + unique_ptr seed) +{ + //sanity check + if (seed->type() != SeedType::BIP39) + throw runtime_error("[getBIP39BackupString] invalid seed type"); - rootData.type_ = BackupType::BIP32_Seed_Structured; + auto seedBip39 = dynamic_cast(seed.get()); + SecureBinaryData mnemonicString; + switch (seedBip39->getDictionnaryId()) + { + case 1: + { + //convert raw entropy to mnemonic string + break; + } - //decrypt the seed - rootData.root_ = wltSingle->getDecryptedValue(seedPtr); + default: + throw runtime_error("[getBIP39BackupString] invalid dictionnary id"); } - return rootData; + auto result = make_unique(move(mnemonicString)); + return result; } -//////////////////////////////////////////////////////////////////////////////// -WalletRootData Helpers::getRootData_Multisig( - shared_ptr) +//////// +unique_ptr Helpers::getBase58BackupString( + unique_ptr seed) { - throw runtime_error("TODO: needs implementation"); -} + auto seedBip32 = dynamic_cast(seed.get()); + if (seedBip32 == nullptr) + throw runtime_error("[getBase58BackupString] invalid seed object"); -//////////////////////////////////////////////////////////////////////////////// -WalletBackup Helpers::getWalletBackup( - std::shared_ptr wltPtr, BackupType type) -{ - auto rootData = getRootData(wltPtr); - return getWalletBackup(rootData, type); + if (seedBip32->type() != SeedType::BIP32_base58Root) + throw runtime_error("[getBase58BackupString] invalid seed type"); + + auto node = seedBip32->getRootNode(); + auto result = make_unique(move(node->getBase58())); + return result; } -//////////////////////////////////////////////////////////////////////////////// -WalletBackup Helpers::getWalletBackup(WalletRootData& rootData, - BackupType forceBackupType) +////////////////////////////// -- restore methods -- /////////////////////////// +shared_ptr Helpers::restoreFromBackup( + unique_ptr backup, const std::string& homedir, + const UserPrompt& callback) { - //apply secureprint - SecurePrint sp; - auto encrRoot = sp.encrypt(rootData.root_, rootData.secondaryData_); - - WalletBackup backup; + unique_ptr seed = nullptr; + auto bType = backup->type(); + switch (bType) + { + //easy16 backups + case BackupType::Armory135: + case BackupType::Armory200a: + case BackupType::Armory200b: + case BackupType::Armory200d: + case BackupType::Easy16_Unkonwn: + seed = restoreFromEasy16(move(backup), callback, bType); + break; - if (forceBackupType != BackupType::Invalid) - rootData.type_ = forceBackupType; + case BackupType::Base58: + seed = restoreFromBase58(move(backup)); + break; - unsigned mode = UINT32_MAX; - switch (rootData.type_) - { - case BackupType::Armory135: - case BackupType::BIP32_Seed_Structured: - case BackupType::BIP32_Root: - case BackupType::BIP32_Seed_Virgin: - { - mode = unsigned(rootData.type_); - break; - } + case BackupType::BIP39: + seed = restoreFromBIP39(move(backup), callback); + break; - default: - break; + default: + break; } - if (mode == UINT32_MAX) + if (seed == nullptr) { - LOGERR << "cannot create backup for unknown wallet type"; - throw runtime_error("cannot create backup for unknown wallet type"); + BridgeProto::RestorePrompt prompt; + prompt.mutable_type_error()->set_error( + "failed to create seed from backup"); + callback(move(prompt)); + return nullptr; } - //cleartext root easy16 - backup.rootClear_ = move(BackupEasy16::encode(rootData.root_, mode)); + //prompt user to verify id + { + BridgeProto::RestorePrompt prompt; + auto checkWltIdMsg = prompt.mutable_check_wallet_id(); + checkWltIdMsg->set_wallet_id(seed->getWalletId()); + checkWltIdMsg->set_backup_type((int)bType); - //encrypted root easy16 - backup.rootEncr_ = move(BackupEasy16::encode(encrRoot.first,mode)); + auto reply = callback(move(prompt)); + if (!reply.success()) + throw RestoreUserException("user rejected id"); + } - if (!rootData.secondaryData_.empty()) + //prompt for passwords + SecureBinaryData privkey, control; { - //cleartext chaincode easy16 - backup.chaincodeClear_ = move(BackupEasy16::encode( - rootData.secondaryData_, mode)); + BridgeProto::RestorePrompt prompt; + prompt.set_get_passphrases(true); + auto reply = callback(move(prompt)); - //encrypted chaincode easy16 - backup.chaincodeEncr_ = move(BackupEasy16::encode( - encrRoot.second, mode)); - } + if (!reply.success()) + throw RestoreUserException("user did not provide a passphrase"); - backup.spPass_ = sp.getPassphrase(); - backup.wltId_ = rootData.wltId_; + privkey = SecureBinaryData::fromString(reply.passphrases().privkey()); + control = SecureBinaryData::fromString(reply.passphrases().control()); + } - return backup; + //return wallet + return AssetWallet_Single::createFromSeed( + std::move(seed), privkey, control, homedir); } -//////////////////////////////////////////////////////////////////////////////// -shared_ptr Helpers::restoreFromBackup( - const vector& data, const BinaryDataRef passphrase, - const string& homedir, const UserPrompt& callerPrompt) +//////// +unique_ptr Helpers::restoreFromEasy16( + unique_ptr backup, const UserPrompt& callback, + BackupType& bType) { - vector bdrVec; - for (const auto& str : data) - bdrVec.emplace_back((const uint8_t*)str.c_str(), str.size()); + auto backupE16 = dynamic_cast(backup.get()); + if (backupE16 == nullptr) + return nullptr; + bool isEncrypted = !backupE16->getSpPass().empty(); - return restoreFromBackup(bdrVec, passphrase, homedir, callerPrompt); -} + /* decode data */ -//////////////////////////////////////////////////////////////////////////////// -shared_ptr Helpers::restoreFromBackup( - const vector& data, const BinaryDataRef passphrase, - const string& homedir, const UserPrompt& callerPrompt) -{ - SecureBinaryData promptDummy; - bool hasSecondaryData = false; + //root + vector first2Lines; + first2Lines.reserve(2); - //decode the data - BackupEasy16DecodeResult primaryData, secondaryData; - if (data.size() == 2) - { - primaryData = BackupEasy16::decode(data); - } - else if (data.size() > 2) - { - vector primarySlice; - primarySlice.insert(primarySlice.end(), data.begin(), data.begin() + 2); - primaryData = BackupEasy16::decode(primarySlice); + auto firstLine = backupE16->getRoot( + Backup_Easy16::LineIndex::One, isEncrypted); + first2Lines.emplace_back(BinaryDataRef( + (uint8_t*)firstLine.data(), firstLine.size())); - vector secondarySlice; - secondarySlice.insert(secondarySlice.end(), data.begin() + 2, data.end()); - secondaryData = BackupEasy16::decode(secondarySlice); + auto secondLine = backupE16->getRoot( + Backup_Easy16::LineIndex::Two, isEncrypted); + first2Lines.emplace_back(BinaryDataRef( + (uint8_t*)secondLine.data(), secondLine.size())); - hasSecondaryData = true; - } - else - { - callerPrompt(RestorePromptType::FormatError, {}, promptDummy); + auto primaryData = Easy16Codec::decode(first2Lines); + if (!primaryData.isInitialized()) return nullptr; - } - - if (primaryData.checksumIndexes_.empty() || - (hasSecondaryData && secondaryData.checksumIndexes_.empty())) - { - callerPrompt(RestorePromptType::Failure, {}, promptDummy); - return nullptr; - } - //sanity check - auto checksumIndexes = primaryData.checksumIndexes_; - if (hasSecondaryData) + //chaincode + BackupEasy16DecodeResult secondaryData; + if (backupE16->hasChaincode()) { - checksumIndexes.insert(checksumIndexes.end(), - secondaryData.checksumIndexes_.begin(), - secondaryData.checksumIndexes_.end()); + vector next2Lines; + auto thirdLine = backupE16->getChaincode( + Backup_Easy16::LineIndex::One, isEncrypted); + next2Lines.emplace_back(BinaryDataRef( + (uint8_t*)thirdLine.data(), thirdLine.size())); + + auto fourthLine = backupE16->getChaincode( + Backup_Easy16::LineIndex::Two, isEncrypted); + next2Lines.emplace_back(BinaryDataRef( + (uint8_t*)fourthLine.data(), fourthLine.size())); + + secondaryData = Easy16Codec::decode(next2Lines); + if (!secondaryData.isInitialized()) + return nullptr; } - bool checksumErrors; - int firstIndex; + /* checksums & repair */ - auto processChecksumIndexes = [&checksumErrors, &firstIndex]( - const vector& checksumValues) + //root + if (!primaryData.isValid()) { - /* - Set the common checksum result value and make sure all lines - carry the same value. - */ + if (!Easy16Codec::repair(primaryData)) + { + BridgeProto::RestorePrompt prompt; + auto checksumError = prompt.mutable_checksum_error(); + checksumError->add_index(primaryData.checksumIndexes_[0]); + checksumError->add_index(primaryData.checksumIndexes_[1]); + callback(move(prompt)); + return nullptr; + } - checksumErrors = false; - firstIndex = checksumValues[0]; - for (const auto& result : checksumValues) + if (!primaryData.isValid()) { - if (result < 0 || result != firstIndex) - { - checksumErrors = true; - break; - } + BridgeProto::RestorePrompt prompt; + auto checksumError = prompt.mutable_checksum_error(); + checksumError->add_index(primaryData.repairedIndexes_[0]); + checksumError->add_index(primaryData.repairedIndexes_[1]); + callback(move(prompt)); + return nullptr; } - }; - processChecksumIndexes(checksumIndexes); + } - if (checksumErrors) + //chaincode + if (secondaryData.isInitialized()) { - auto reportError = [&callerPrompt, &checksumIndexes](void) + if (!Easy16Codec::repair(secondaryData)) { - //prompt caller if we can't repair the error and throw - SecureBinaryData dummy; - callerPrompt( - RestorePromptType::ChecksumError, checksumIndexes, dummy); - throw RestoreUserException("checksum error"); - }; - - vector repairedIndexes; + BridgeProto::RestorePrompt prompt; + auto checksumError = prompt.mutable_checksum_error(); + checksumError->add_index(secondaryData.checksumIndexes_[0]); + checksumError->add_index(secondaryData.checksumIndexes_[1]); + callback(move(prompt)); + return nullptr; + } - auto repairData = [&reportError, &repairedIndexes]( - BackupEasy16DecodeResult& data) + if (!secondaryData.isValid()) { - //attempt to repair the data - auto result = BackupEasy16::repair(data); - if (!result) - reportError(); - - if (data.repairedIndexes_.size() != - data.checksumIndexes_.size()) - reportError(); + BridgeProto::RestorePrompt prompt; + auto checksumError = prompt.mutable_checksum_error(); + checksumError->add_index(secondaryData.repairedIndexes_[0]); + checksumError->add_index(secondaryData.repairedIndexes_[1]); + callback(move(prompt)); + return nullptr; + } - repairedIndexes.insert(repairedIndexes.end(), - data.repairedIndexes_.begin(), - data.repairedIndexes_.end()); - }; + //check chaincode index matches root index + if (primaryData.getIndex() != secondaryData.getIndex()) + { + BridgeProto::RestorePrompt prompt; + auto checksumError = prompt.mutable_checksum_mismatch(); + checksumError->add_index(primaryData.getIndex()); + checksumError->add_index(secondaryData.getIndex()); + callback(move(prompt)); + return nullptr; + } + } - //found some checksum errors, attempt to auto repair - repairData(primaryData); - if (hasSecondaryData) - repairData(secondaryData); + /* SecurePrint */ + if (isEncrypted) + try + { + SecurePrint sp; + auto pass = backupE16->getSpPass(); + BinaryDataRef passRef((uint8_t*)pass.data(), pass.size()); + primaryData.data_ = move(sp.decrypt(primaryData.data_, passRef)); - //check the repaired checksum result values - processChecksumIndexes(repairedIndexes); + if (secondaryData.isInitialized()) + secondaryData.data_ = move(sp.decrypt(secondaryData.data_, passRef)); + } + catch (const exception&) + { + BridgeProto::RestorePrompt prompt; + prompt.set_decrypt_error(true); + callback(move(prompt)); + throw RestoreUserException("invalid SP pass"); + } - if (checksumErrors) - reportError(); + /* backup type */ + if (bType == BackupType::Easy16_Unkonwn) + { + bType = (BackupType)primaryData.getIndex(); + } + else + { + if ((BackupType)primaryData.getIndex() != bType) + { + //mismatch between easy16 index and backup expected type + BridgeProto::RestorePrompt prompt; + auto checksumError = prompt.mutable_checksum_mismatch(); + checksumError->add_index(primaryData.getIndex()); + checksumError->add_index((int)bType); + callback(move(prompt)); + return nullptr; + } } - //check for encryption - if (!passphrase.empty()) + /* create seed */ + unique_ptr seedPtr = nullptr; + switch (bType) { - try + case BackupType::Armory135: { - SecurePrint sp; - auto decryptedData = sp.decrypt(primaryData.data_, passphrase); - primaryData.data_ = move(decryptedData); + /*legacy armory wallet, legacy backup string*/ + seedPtr = std::move(std::make_unique( + primaryData.data_, secondaryData.data_, + ClearTextSeed_Armory135::LegacyType::Armory135)); + break; + } - if (hasSecondaryData) - { - auto decryptedData = sp.decrypt(secondaryData.data_, passphrase); - secondaryData.data_ = move(decryptedData); - } + case BackupType::Armory200a: + { + /*legacy armory wallet, indexed backup string*/ + seedPtr = std::move(std::make_unique( + primaryData.data_, secondaryData.data_, + ClearTextSeed_Armory135::LegacyType::Armory200)); + break; } - catch (const exception&) + + //bip32 wallets + case BackupType::Armory200b: + { + /*BIP32 wallet with BIP44/49/84 accounts*/ + seedPtr = std::move(std::make_unique( + primaryData.data_, SeedType::BIP32_Structured)); + break; + } + + case BackupType::Armory200c: { - //prompt caller on decrypt error and return - callerPrompt(RestorePromptType::DecryptError, {}, promptDummy); - throw RestoreUserException("invalid SP pass"); + //empty BIP32 wallet + seedPtr = std::move(std::make_unique( + primaryData.data_, SeedType::BIP32_Virgin)); + break; } + + case BackupType::Armory200d: + { + //empty BIP32 wallet + seedPtr = std::move(std::make_unique( + primaryData.data_, 1)); + break; + } + + default: + return nullptr; } + return seedPtr; +} + +//////// +unique_ptr Helpers::restoreFromBase58( + unique_ptr backup) +{ + auto backupB58 = dynamic_cast(backup.get()); + if (backupB58 == nullptr) + return nullptr; - auto computeWalletId = []( - const SecureBinaryData& root, const SecureBinaryData& chaincode) - ->string + unique_ptr seed; + try { + auto b58StrView = backupB58->getBase58String(); + BinaryData b58Ref(b58StrView.data(), b58StrView.size()); + return ClearTextSeed_BIP32::fromBase58(b58Ref); + } + catch (const std::exception&) { - auto chaincodeCopy = chaincode; - if (chaincodeCopy.empty()) - chaincodeCopy = BtcUtils::computeChainCode_Armory135(root); + return nullptr; + } +} - auto derScheme = - make_shared(chaincodeCopy); +//////// +unique_ptr Helpers::restoreFromBIP39( + unique_ptr backup, const UserPrompt& callback) +{ + auto backupBIP39 = dynamic_cast(backup.get()); + if (backupBIP39 == nullptr) + return nullptr; - auto pubkey = CryptoECDSA().ComputePublicKey(root); - auto asset_single = make_shared( - Armory::Wallets::AssetId::getRootAssetId(), pubkey, nullptr); + //TODO: convert words to raw entropy + SecureBinaryData rawEntropy; - return AssetWallet_Single::computeWalletID(derScheme, asset_single); - }; + //entropy to seed + return make_unique(rawEntropy, 1); +} - auto promptForPassphrase = [&callerPrompt]( - SecureBinaryData& passphrase, SecureBinaryData& control)->bool - { - //prompt for wallet passphrase - if (!callerPrompt(RestorePromptType::Passphrase, {}, passphrase)) - return false; +//////////////////////////////////////////////////////////////////////////////// +// +//// WalletBackup +// +//////////////////////////////////////////////////////////////////////////////// +WalletBackup::WalletBackup(BackupType bType) : + type_(bType) +{} - //prompt for control passphrase - if (!callerPrompt(RestorePromptType::Control, {}, control)) - return false; +WalletBackup::~WalletBackup() +{} - return true; - }; +const string& WalletBackup::getWalletId() const +{ + return wltId_; +} - //generate wallet - shared_ptr wallet; - switch (firstIndex) - { - case BackupType::Armory135: - { - /*legacy armory wallet*/ +const BackupType& WalletBackup::type() const +{ + return type_; +} - auto id = SecureBinaryData::fromString( - computeWalletId(primaryData.data_, secondaryData.data_)); - if (!callerPrompt(RestorePromptType::Id, checksumIndexes, id)) - throw RestoreUserException("user rejected id"); +///////////////////////////////// Backup_Easy16 //////////////////////////////// +Backup_Easy16::Backup_Easy16(BackupType bType) : + WalletBackup(bType) +{} + +Backup_Easy16::~Backup_Easy16() +{} - //prompt for passwords - SecureBinaryData pass, control; - if (!promptForPassphrase(pass, control)) - throw RestoreUserException("user did not provide passphrase"); +bool Backup_Easy16::hasChaincode() const +{ + if (type() != BackupType::Armory135 && type() != BackupType::Easy16_Unkonwn) + return false; - //create wallet - wallet = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir, - primaryData.data_, - secondaryData.data_, - pass, control, - WALLET_RESTORE_LOOKUP); + return !chaincodeClear_.empty() || !chaincodeEncr_.empty() ; +} - break; +//// +string_view Backup_Easy16::getRoot(LineIndex li, bool encrypted) const +{ + auto lineIndex = (int)li; + std::vector::const_iterator iter; + if (!encrypted) + { + iter = rootClear_.begin() + lineIndex; + if (iter == rootClear_.end()) + { + throw runtime_error("[Backup_Easy16::getRoot]" + " missing cleartext line"); + } + } + else + { + iter = rootEncr_.begin() + lineIndex; + if (iter == rootEncr_.end()) + { + throw runtime_error("[Backup_Easy16::getRoot]" + " missing encrypted line"); + } } - //bip32 wallets - case BackupType::BIP32_Seed_Structured: + return string_view(iter->toCharPtr(), iter->getSize()); +} + +string_view Backup_Easy16::getChaincode(LineIndex li, bool encrypted) const +{ + auto lineIndex = (int)li; + std::vector::const_iterator iter; + if (!encrypted) + { + iter = chaincodeClear_.begin() + lineIndex; + if (iter == chaincodeClear_.end()) + { + throw runtime_error("[Backup_Easy16::getChaincode]" + " missing cleartext line"); + } + } + else { - /*BIP32 wallet with BIP44/49/84 accounts*/ + iter = chaincodeEncr_.begin() + lineIndex; + if (iter == chaincodeEncr_.end()) + { + throw runtime_error("[Backup_Easy16::getChaincode]" + " missing encrypted line"); + } + } - //create root node from seed - BIP32_Node rootNode; - rootNode.initFromSeed(primaryData.data_); + return string_view(iter->toCharPtr(), iter->getSize()); +} - //compute id and present to caller - auto id = SecureBinaryData::fromString( - computeWalletId(rootNode.getPrivateKey(), rootNode.getChaincode())); - if (!callerPrompt(RestorePromptType::Id, checksumIndexes, id)) - throw RestoreUserException("user rejected id"); +string_view Backup_Easy16::getSpPass() const +{ + return string_view(spPass_.toCharPtr(), spPass_.getSize()); +} - //prompt for passwords - SecureBinaryData pass, control; - if (!promptForPassphrase(pass, control)) - throw RestoreUserException("user did not provide passphrase"); +//// +unique_ptr Backup_Easy16::fromLines( + const vector& lines, string_view spPass) +{ + if (lines.size() % 2 != 0) + throw runtime_error("[Backup_Easy16::fromLines] invalid line count"); - //create wallet - wallet = AssetWallet_Single::createFromSeed_BIP32( - homedir, - primaryData.data_, - pass, control, - WALLET_RESTORE_LOOKUP); + auto result = make_unique(BackupType::Easy16_Unkonwn); + unsigned i=0; - break; + if (spPass.empty()) + { + for (const auto& line : lines) + { + auto lineSBD = SecureBinaryData::fromStringView(line); + if (i<2) + result->rootClear_.emplace_back(move(lineSBD)); + else + result->chaincodeClear_.emplace_back(move(lineSBD)); + ++i; + } } - - case BIP32_Seed_Virgin: + else { - /*empty BIP32 wallet*/ - - //create root node from seed - BIP32_Node rootNode; - rootNode.initFromSeed(primaryData.data_); - - //compute id and present to caller - auto id = SecureBinaryData::fromString( - computeWalletId(rootNode.getPrivateKey(), rootNode.getChaincode())); - if (!callerPrompt(RestorePromptType::Id, checksumIndexes, id)) - throw RestoreUserException("user rejected id"); + for (const auto& line : lines) + { + auto lineSBD = SecureBinaryData::fromStringView(line); + if (i<2) + result->rootEncr_.emplace_back(move(lineSBD)); + else + result->chaincodeEncr_.emplace_back(move(lineSBD)); + ++i; + } + result->spPass_ = SecureBinaryData::fromStringView(spPass); + } + + return result; +} - //prompt for passwords - SecureBinaryData pass, control; - if (!promptForPassphrase(pass, control)) - throw RestoreUserException("user did not provide passphrase"); +///////////////////////////////// Backup_Base58 //////////////////////////////// +Backup_Base58::Backup_Base58(SecureBinaryData b58String) : + WalletBackup(BackupType::Base58), b58String_(move(b58String)) +{} - //create wallet - wallet = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir, - primaryData.data_, - pass, control); +Backup_Base58::~Backup_Base58() +{} - break; - } +string_view Backup_Base58::getBase58String() const +{ + return string_view(b58String_.toCharPtr(), b58String_.getSize()); +} - //case BackupType::BIP32_Root: +unique_ptr Backup_Base58::fromString(const string_view& strV) +{ + return make_unique(SecureBinaryData::fromStringView(strV)); +} - default: - callerPrompt(RestorePromptType::TypeError, {}, promptDummy); - } +///////////////////////////////// Backup_BIP39 ///////////////////////////////// +Backup_BIP39::Backup_BIP39(SecureBinaryData mnemonicString) : + WalletBackup(BackupType::BIP39), mnemonicString_(move(mnemonicString)) +{} - return wallet; -} \ No newline at end of file +Backup_BIP39::~Backup_BIP39() +{} \ No newline at end of file diff --git a/cppForSwig/Wallets/Seeds/Backups.h b/cppForSwig/Wallets/Seeds/Backups.h index 43a952138..45587057b 100644 --- a/cppForSwig/Wallets/Seeds/Backups.h +++ b/cppForSwig/Wallets/Seeds/Backups.h @@ -11,6 +11,7 @@ #include #include +#include #include "SecureBinaryData.h" #include "EncryptionUtils.h" @@ -18,11 +19,21 @@ #define EASY16_INVALID_CHECKSUM_INDEX UINT8_MAX +namespace BridgeProto +{ + class RestorePrompt; + class RestoreReply; +} + namespace Armory { namespace Seeds { - //// + //forward declarations + class ClearTextSeed; + class ClearTextSeed_BIP39; + + ////////////////////////////////////////////////////////////////////////// class RestoreUserException : public std::runtime_error { public: @@ -31,6 +42,7 @@ namespace Armory {} }; + //// class Easy16RepairError : public std::runtime_error { public: @@ -40,52 +52,38 @@ namespace Armory }; //// - enum BackupType + enum class BackupType : int { - /* - Armory135: - For wallets using the Armory specific derivation scheme. - */ - Armory135 = 0, + //easy16, seed (2 or 4 lines), hash index is always 0 + Armory135 = 0, /* - BIP32_Seed_Structured: - For wallets carrying BIP44/49/84 accounts. Restores to a bip32 wallet - with all these accounts. + easy16, seed (2 lines), hash index defines seed type: + - a: Armory legacy derivation, P2PKH + P2WPK + P2SH-2WPKH + addresses in a single address account + - b: BIP32 with BIP44/49/84 chains, as individual address accounts + - c: BIP32 with no accounts + - d: BIP39 seed with BIP44/49/84 chains, as individual + address accounts */ - BIP32_Seed_Structured = 1, + Armory200a = 3, + Armory200b = 4, + Armory200c = 5, + Armory200d = 6, - /* - BIP32_Root: - For bip32 wallets that do not carry their own seed. Support for this is - not implemented at the moment. This type of backup would have to carry - the root privkey and chaincode generated through the seed's hmac. + //state of an easy16 backup prior to decode + Easy16_Unkonwn = 10, - May implement support in the future. - */ - BIP32_Root = 2, + //bip32 mnemonic phrase (12~24 words), english dictionnary + BIP39 = 0xFFFF, - /* - BIP32_Seed_Virgin: - No info is provided about the wallet's structure, restores to an empt - bip32 wallet. - */ - BIP32_Seed_Virgin = 15, - - /* - Default marker value. - */ - Invalid = UINT32_MAX - }; + Base58 = 58, - //// - struct WalletRootData - { - SecureBinaryData root_; - SecureBinaryData secondaryData_; + //raw binary of the seed in hexits, no extra info provided + Raw = INT32_MAX - 1, - BackupType type_; - std::string wltId_; + //end marker + Invalid = INT32_MAX }; //// @@ -95,10 +93,14 @@ namespace Armory std::vector repairedIndexes_; std::vector checksums_; SecureBinaryData data_; + + bool isInitialized(void) const; + bool isValid(void) const; + int getIndex(void) const; }; //// - struct BackupEasy16 + struct Easy16Codec { public: /*** @@ -111,7 +113,7 @@ namespace Armory one another. ***/ - static const std::set eligibleIndexes_; + static const std::set eligibleIndexes_; private: static BinaryData getHash(const BinaryDataRef&, uint8_t); @@ -120,8 +122,8 @@ namespace Armory public: const static std::vector e16chars_; - static std::vector encode(const BinaryDataRef, uint8_t); - static BackupEasy16DecodeResult decode(const std::vector&); + static std::vector encode(const BinaryDataRef, BackupType); + static BackupEasy16DecodeResult decode(const std::vector&); static BackupEasy16DecodeResult decode(const std::vector&); static bool repair(BackupEasy16DecodeResult&); }; @@ -144,27 +146,96 @@ namespace Armory SecurePrint(void); std::pair encrypt( - const SecureBinaryData&, const SecureBinaryData&); + BinaryDataRef, BinaryDataRef); SecureBinaryData decrypt( const SecureBinaryData&, const BinaryDataRef) const; const SecureBinaryData& getPassphrase(void) const { return passphrase_; } }; + ////////////////////////////////////////////////////////////////////////// + class WalletBackup + { + friend struct Helpers; + + protected: + const BackupType type_; + std::string wltId_; + + public: + WalletBackup(BackupType); + virtual ~WalletBackup(void) = 0; + + const BackupType& type(void) const; + const std::string& getWalletId(void) const; + }; + //// - struct WalletBackup + class Backup_Easy16 : public WalletBackup { - std::vector rootClear_; - std::vector chaincodeClear_; + friend class Helpers; + + public: + enum class LineIndex : int + { + One = 0, + Two = 1 + }; + + private: + std::vector rootClear_; + std::vector chaincodeClear_; - std::vector rootEncr_; - std::vector chaincodeEncr_; + std::vector rootEncr_; + std::vector chaincodeEncr_; SecureBinaryData spPass_; - std::string wltId_; + + public: + Backup_Easy16(BackupType); + ~Backup_Easy16(void) override; + + bool hasChaincode(void) const; + std::string_view getRoot(LineIndex, bool) const; + std::string_view getChaincode(LineIndex, bool) const; + std::string_view getSpPass(void) const; + + static std::unique_ptr fromLines( + const std::vector&, + std::string_view spPass = {}); + }; + + //// + class Backup_Base58 : public WalletBackup + { + private: + SecureBinaryData b58String_; + + public: + Backup_Base58(SecureBinaryData); + ~Backup_Base58(void) override; + + std::string_view getBase58String(void) const; + static std::unique_ptr fromString( + const std::string_view&); }; //// + class Backup_BIP39 : public WalletBackup + { + private: + SecureBinaryData mnemonicString_; + + public: + Backup_BIP39(SecureBinaryData); + ~Backup_BIP39(void) override; + + std::string_view getMnemonicString(void) const; + static std::unique_ptr fromMnemonics( + const std::vector&); + }; + + //////// enum RestorePromptType { //invalid backup format @@ -194,34 +265,31 @@ namespace Armory //// struct Helpers { - using UserPrompt = std::function&, - SecureBinaryData&)>; - - //getting root data from wallets - static WalletRootData getRootData( - std::shared_ptr); - static WalletRootData getRootData_Multisig( - std::shared_ptr); + using UserPrompt = std::function; //backup methods - static WalletBackup getWalletBackup( - std::shared_ptr, - BackupType bType = BackupType::Invalid); - - static WalletBackup getWalletBackup( - WalletRootData&, + static std::unique_ptr getWalletBackup( + std::shared_ptr, BackupType bType = BackupType::Invalid); + static std::unique_ptr getWalletBackup( + std::unique_ptr, BackupType); + static std::unique_ptr getEasy16BackupString( + std::unique_ptr); + static std::unique_ptr getBIP39BackupString( + std::unique_ptr); + static std::unique_ptr getBase58BackupString( + std::unique_ptr); //restore methods static std::shared_ptr restoreFromBackup( - const std::vector&, const BinaryDataRef, - const std::string&, const UserPrompt&); - - static std::shared_ptr restoreFromBackup( - const std::vector&, const BinaryDataRef, - const std::string&, const UserPrompt&); + std::unique_ptr, const std::string&, const UserPrompt&); + static std::unique_ptr restoreFromEasy16( + std::unique_ptr, const UserPrompt&, BackupType&); + static std::unique_ptr restoreFromBase58( + std::unique_ptr); + static std::unique_ptr restoreFromBIP39( + std::unique_ptr, const UserPrompt&); }; }; //namespace Backups }; //namespace Armory diff --git a/cppForSwig/Wallets/Seeds/Seeds.cpp b/cppForSwig/Wallets/Seeds/Seeds.cpp index b1186a591..98a0d6675 100644 --- a/cppForSwig/Wallets/Seeds/Seeds.cpp +++ b/cppForSwig/Wallets/Seeds/Seeds.cpp @@ -6,16 +6,25 @@ // // //////////////////////////////////////////////////////////////////////////////// -#include "Seeds.h" #include "../AssetEncryption.h" +#include "../DecryptedDataContainer.h" +#include "../DerivationScheme.h" +#include "../Assets.h" #include "../WalletIdTypes.h" +#include "../BIP32_Node.h" +#include "Seeds.h" +#include "Backups.h" +#include "BtcUtils.h" using namespace std; using namespace Armory; -using namespace Armory::Seed; +using namespace Armory::Seeds; using namespace Armory::Wallets; +using namespace Armory::Assets; -#define ENCRYPTED_SEED_VERSION 0x00000001 +#define ENCRYPTED_SEED_VERSION_1 0x00000001 +#define ENCRYPTED_SEED_VERSION_2 0x00000002 +#define WALLET_SEED_BYTE 0x84 //////////////////////////////////////////////////////////////////////////////// // @@ -24,12 +33,25 @@ using namespace Armory::Wallets; //////////////////////////////////////////////////////////////////////////////// const Wallets::AssetId EncryptedSeed::seedAssetId_(0x5EED, 0xDEE5, 0x5EED); -//////////////////////////////////////////////////////////////////////////////// +EncryptedSeed::EncryptedSeed(CipherText cipher, SeedType sType) : + Encryption::EncryptedAssetData(move(cipher)), type_(sType) +{} + +EncryptedSeed::~EncryptedSeed() +{} + +SeedType EncryptedSeed::type() const +{ + return type_; +} + +//////////////////////////////-- overrides --/////////////////////////////////// BinaryData EncryptedSeed::serialize() const { BinaryWriter bw; - bw.put_uint32_t(ENCRYPTED_SEED_VERSION); + bw.put_uint32_t(ENCRYPTED_SEED_VERSION_2); bw.put_uint8_t(WALLET_SEED_BYTE); + bw.put_int32_t((int32_t)type()); auto&& cipherData = getCipherDataPtr()->serialize(); bw.put_var_int(cipherData.getSize()); @@ -41,7 +63,7 @@ BinaryData EncryptedSeed::serialize() const return finalBw.getData(); } -//////////////////////////////////////////////////////////////////////////////// +//////// bool EncryptedSeed::isSame(Encryption::EncryptedAssetData* const seed) const { auto asset_ed = dynamic_cast(seed); @@ -51,13 +73,13 @@ bool EncryptedSeed::isSame(Encryption::EncryptedAssetData* const seed) const return Encryption::EncryptedAssetData::isSame(seed); } -//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////-- statics --///////////////////////////////////// const AssetId& EncryptedSeed::getAssetId() const { return seedAssetId_; } -//////////////////////////////////////////////////////////////////////////////// +//// unique_ptr EncryptedSeed::deserialize( const BinaryDataRef& data) { @@ -80,32 +102,598 @@ unique_ptr EncryptedSeed::deserialize( { case 0x00000001: { + //cipher data auto len = brr.get_var_int(); if (len > brr.getSizeRemaining()) - throw runtime_error("invalid serialized encrypted data len"); + throw runtime_error("[EncryptedSeed::deserialize]" + " invalid serialized encrypted data len"); auto cipherBdr = brr.get_BinaryDataRef(len); BinaryRefReader cipherBrr(cipherBdr); auto cipherData = Encryption::CipherData::deserialize(cipherBrr); - //ptr - assetPtr = make_unique(move(cipherData)); + //seed object + assetPtr = make_unique(move(cipherData), SeedType::Raw); + break; + } + + case 0x00000002: + { + //seed type + auto sType = (SeedType)brr.get_int32_t(); + + //cipher data + auto len = brr.get_var_int(); + if (len > brr.getSizeRemaining()) + throw runtime_error("[EncryptedSeed::deserialize]" + " invalid serialized encrypted data len"); + + auto cipherBdr = brr.get_BinaryDataRef(len); + BinaryRefReader cipherBrr(cipherBdr); + auto cipherData = Encryption::CipherData::deserialize(cipherBrr); + assetPtr = make_unique(move(cipherData), sType); break; } default: - throw runtime_error("unsupported seed version"); + throw runtime_error("[EncryptedSeed::deserialize]" + " unsupported seed version"); } break; } default: - throw runtime_error("unexpected encrypted data prefix"); + throw runtime_error("[EncryptedSeed::deserialize]" + " unexpected encrypted data prefix"); } if (assetPtr == nullptr) - throw runtime_error("failed to deserialize encrypted asset"); + { + throw runtime_error("[EncryptedSeed::deserialize]" + " failed to deserialize encrypted asset"); + } return assetPtr; } + +//// +std::unique_ptr EncryptedSeed::fromClearTextSeed( + std::unique_ptr seed, + std::unique_ptr cipher, + std::shared_ptr decrCont) +{ + //sanity checks + if (seed == nullptr) + throw runtime_error("[fromClearTextSeed] null seed"); + + if (cipher == nullptr) + throw runtime_error("[fromClearTextSeed] null cipher"); + + if (decrCont == nullptr) + throw runtime_error("[fromClearTextSeed] null decrypted data container"); + + //copy the cipher to cycle the IV + auto cipherCopy = cipher->getCopy(); + + //serialized clear text seed + BinaryWriter bw; //TODO: need SBD based bw + seed->serialize(bw); + + //encrypt it + auto cipherText = decrCont->encryptData(cipherCopy.get(), bw.getData()); + auto cipherData = make_unique( + cipherText, move(cipherCopy)); + + //instantiate encrypted seed object + return make_unique(move(cipherData), seed->type()); +} + +//////////////////////////////////////////////////////////////////////////////// +// +//// ClearTextSeed +// +//////////////////////////////////////////////////////////////////////////////// +ClearTextSeed::ClearTextSeed(SeedType sType) : + type_(sType) +{} + +ClearTextSeed::~ClearTextSeed() +{} + +SeedType ClearTextSeed::type() const +{ + return type_; +} + +//// +const string& ClearTextSeed::getWalletId() const +{ + if (walletId_.empty()) + walletId_ = computeWalletId(); + return walletId_; +} + +const string& ClearTextSeed::getMasterId() const +{ + if (masterId_.empty()) + masterId_ = computeMasterId(); + return masterId_; +} + +unique_ptr ClearTextSeed::deserialize( + const SecureBinaryData& serializedData) +{ + BinaryRefReader brr(serializedData); + auto type = (SeedType)brr.get_uint8_t(); + + //sanity check + auto len = brr.get_var_int(); + if (len != brr.getSizeRemaining()) + { + throw runtime_error("[ClearTextSeed::deserialize]" + " size mismatch in serialized seed"); + } + + switch (type) + { + case SeedType::Armory135: + { + ClearTextSeed_Armory135::LegacyType lType = + ClearTextSeed_Armory135::LegacyType::Armory200; + BinaryDataRef root; + BinaryDataRef chaincode; + while (!brr.isEndOfStream()) + { + auto prefix = (Prefix)brr.get_uint8_t(); + switch (prefix) + { + case Prefix::LegacyType: + { + lType = + (ClearTextSeed_Armory135::LegacyType)brr.get_uint8_t(); + break; + } + + case Prefix::Root: + { + auto rootLen = brr.get_var_int(); + root = brr.get_BinaryDataRef(rootLen); + break; + } + + case Prefix::Chaincode: + { + auto chLen = brr.get_var_int(); + chaincode = brr.get_BinaryDataRef(chLen); + break; + } + + default: + throw runtime_error("[ClearTextSeed::deserialize]" + " invalid prefix for Armory135 seed"); + } + } + return make_unique(root, chaincode, lType); + } + + case SeedType::BIP32_Structured: + case SeedType::BIP32_Virgin: + { + BinaryDataRef rawEntropy; + while (!brr.isEndOfStream()) + { + auto prefix = (Prefix)brr.get_uint8_t(); + switch (prefix) + { + case Prefix::RawEntropy: + { + auto entLen = brr.get_var_int(); + rawEntropy = brr.get_BinaryDataRef(entLen); + break; + } + + default: + throw runtime_error("[ClearTextSeed::deserialize]" + " invalid prefix for BIP32 seed"); + } + } + return make_unique(rawEntropy, type); + } + + case SeedType::BIP32_base58Root: + { + BinaryDataRef b58root; + while (!brr.isEndOfStream()) + { + auto prefix = (Prefix)brr.get_uint8_t(); + switch (prefix) + { + case Prefix::Base58Root: + { + auto entLen = brr.get_var_int(); + b58root = brr.get_BinaryDataRef(entLen); + break; + } + + default: + throw runtime_error("[ClearTextSeed::deserialize]" + " invalid prefix for BIP32 seed"); + } + } + return ClearTextSeed_BIP32::fromBase58(b58root); + } + + case SeedType::BIP39: + { + BinaryDataRef rawEntropy; + unsigned int dictionnary = 0; + while (!brr.isEndOfStream()) + { + auto prefix = (Prefix)brr.get_uint8_t(); + switch (prefix) + { + case Prefix::RawEntropy: + { + auto entLen = brr.get_var_int(); + rawEntropy = brr.get_BinaryDataRef(entLen); + break; + } + + case Prefix::Dictionnary: + { + dictionnary = brr.get_uint32_t(); + break; + } + + default: + throw runtime_error("[ClearTextSeed::deserialize]" + " invalid prefix for BIP39 seed"); + } + } + return make_unique(rawEntropy, dictionnary); + } + + default: + throw runtime_error("[ClearTextSeed::deserialize]" + " unexpected seed type"); + } +} + +//////////////////////////////////////////////////////////////////////////////// +ClearTextSeed_Armory135::ClearTextSeed_Armory135(LegacyType lType) : + ClearTextSeed_Armory135(CryptoPRNG::generateRandom(32), lType) +{} + +ClearTextSeed_Armory135::ClearTextSeed_Armory135( + const SecureBinaryData& root, LegacyType lType) : + ClearTextSeed(SeedType::Armory135), + root_(root), chaincode_({}), + legacyType_(lType) +{} + +ClearTextSeed_Armory135::ClearTextSeed_Armory135(const SecureBinaryData& root, + const SecureBinaryData& chaincode, LegacyType lType) : + ClearTextSeed(SeedType::Armory135), + root_(root), chaincode_(chaincode), + legacyType_(lType) +{} + +ClearTextSeed_Armory135::~ClearTextSeed_Armory135() +{} + +//// +const SecureBinaryData& ClearTextSeed_Armory135::getRoot() const +{ + return root_; +} + +const SecureBinaryData& ClearTextSeed_Armory135::getChaincode() const +{ + return chaincode_; +} + +//// +string ClearTextSeed_Armory135::computeWalletId() const +{ + auto chaincodeCopy = chaincode_; + if (chaincode_.empty()) + chaincodeCopy = BtcUtils::computeChainCode_Armory135(root_); + + auto pubkey = CryptoECDSA().ComputePublicKey(root_); + return Armory::Wallets::generateWalletId(pubkey, chaincodeCopy, type()); +} + +string ClearTextSeed_Armory135::computeMasterId() const +{ + //uncompressed pubkey + auto pubkey = CryptoECDSA().ComputePublicKey(root_); + return generateMasterId(pubkey, chaincode_); +} + +//// +void ClearTextSeed_Armory135::serialize(BinaryWriter& bw) const +{ + /* serialize the seed */ + BinaryWriter inner; + + //legacy type + inner.put_uint8_t((uint8_t)Prefix::LegacyType); + inner.put_uint8_t((uint8_t)legacyType_); + + //root + inner.put_uint8_t((uint8_t)Prefix::Root); + inner.put_var_int(root_.getSize()); + inner.put_BinaryData(root_); + + //chaincode + inner.put_uint8_t((uint8_t)Prefix::Chaincode); + inner.put_var_int(chaincode_.getSize()); + if (!chaincode_.empty()) + inner.put_BinaryData(chaincode_); + + /* append to writer */ + + //seed type + bw.put_uint8_t((uint8_t)type()); + + //packet size + bw.put_var_int(inner.getSize()); + + //packet + bw.put_BinaryData(inner.getData()); +} + +//// +bool ClearTextSeed_Armory135::isBackupTypeEligible(BackupType bType) const +{ + switch (legacyType_) + { + case LegacyType::Armory135: + return bType == BackupType::Armory135; + + case LegacyType::Armory200: + return bType == BackupType::Armory200a; + + default: + break; + } + return false; +} + +BackupType ClearTextSeed_Armory135::getPreferedBackupType() const +{ + switch (legacyType_) + { + case LegacyType::Armory135: + return BackupType::Armory135; + + case LegacyType::Armory200: + return BackupType::Armory200a; + + default: + break; + } + return BackupType::Invalid; +} + +//////////////////////////////////////////////////////////////////////////////// +ClearTextSeed_BIP32::ClearTextSeed_BIP32(SeedType sType) : + ClearTextSeed_BIP32(CryptoPRNG::generateRandom(32), sType) +{} + +ClearTextSeed_BIP32::ClearTextSeed_BIP32(const SecureBinaryData& raw, + SeedType sType) : + ClearTextSeed(sType), rawEntropy_(raw) +{ + switch (sType) + { + case SeedType::BIP32_Structured: + case SeedType::BIP32_Virgin: + case SeedType::BIP32_base58Root: + case SeedType::BIP39: + break; + + default: + throw runtime_error("invalid bip32 seed type"); + } +} + +ClearTextSeed_BIP32::~ClearTextSeed_BIP32() +{} + +unique_ptr ClearTextSeed_BIP32::fromBase58( + const BinaryDataRef& b58) +{ + auto result = make_unique(SeedType::BIP32_base58Root); + result->rootNode_ = make_shared(); + result->rootNode_->initFromBase58(b58); + return move(result); +} + +//// +string ClearTextSeed_BIP32::computeWalletId() const +{ + const auto& rootNode = getRootNode(); + return Armory::Wallets::generateWalletId(rootNode->getPublicKey(), + rootNode->getChaincode(), type()); +} + +string ClearTextSeed_BIP32::computeMasterId() const +{ + //uncompressed pubkey + const auto& rootNode = getRootNode(); + return generateMasterId(rootNode->getPublicKey(), rootNode->getChaincode()); +} + +//// +std::shared_ptr ClearTextSeed_BIP32::getRootNode() const +{ + if (rootNode_ == nullptr) + { + rootNode_ = make_shared(); + rootNode_->initFromSeed(rawEntropy_); + } + return rootNode_; +} + +const SecureBinaryData& ClearTextSeed_BIP32::getRawEntropy() const +{ + return rawEntropy_; +} + +//// +void ClearTextSeed_BIP32::serialize(BinaryWriter& bw) const +{ + /* serialize the seed */ + BinaryWriter inner; + + //root + switch (type()) + { + case SeedType::BIP32_Structured: + case SeedType::BIP32_Virgin: + { + inner.put_uint8_t((uint8_t)Prefix::RawEntropy); + inner.put_var_int(rawEntropy_.getSize()); + inner.put_BinaryData(rawEntropy_); + break; + } + + case SeedType::BIP32_base58Root: + { + inner.put_uint8_t((uint8_t)Prefix::Base58Root); + auto root = getRootNode()->getBase58(); + inner.put_var_int(root.getSize()); + inner.put_BinaryData(root); + break; + } + + default: + throw runtime_error("[ClearTextSeed_BIP32::serialize]" + " unexpected seed type"); + } + + /* append to writer */ + + //seed type + bw.put_uint8_t((uint8_t)type()); + + //packet size + bw.put_var_int(inner.getSize()); + + //packet + bw.put_BinaryData(inner.getData()); +} + +//// +bool ClearTextSeed_BIP32::isBackupTypeEligible(BackupType bType) const +{ + switch (type()) + { + case SeedType::BIP32_Structured: + return bType == BackupType::Armory200b; + + case SeedType::BIP32_Virgin: + return bType == BackupType::Armory200c; + + case SeedType::BIP32_base58Root: + return bType == BackupType::Base58; + + default: + break; + } + + return false; +} + +BackupType ClearTextSeed_BIP32::getPreferedBackupType() const +{ + switch (type()) + { + case SeedType::BIP32_Structured: + return BackupType::Armory200b; + + case SeedType::BIP32_Virgin: + return BackupType::Armory200c; + + case SeedType::BIP32_base58Root: + return BackupType::Base58; + + default: + break; + } + return BackupType::Invalid; +} + +//////////////////////////////////////////////////////////////////////////////// +ClearTextSeed_BIP39::ClearTextSeed_BIP39(const SecureBinaryData& raw, + unsigned int dictId) : + ClearTextSeed_BIP32(raw, SeedType::BIP39), dictionnaryId_(dictId) +{ + if (dictionnaryId_ == 0) + throw runtime_error("[ClearTextSeed_BIP39] invalid dictionnary id"); +} + +ClearTextSeed_BIP39::~ClearTextSeed_BIP39() +{} + +//// +std::shared_ptr ClearTextSeed_BIP39::getRootNode() const +{ + if (rootNode_ == nullptr) + { + //convert raw entropy to BIP39 mnemonic phrase based on dictionnary + + //convert mnemonic to seed + SecureBinaryData seed{}; + + rootNode_ = make_shared(); + rootNode_->initFromSeed(seed); + } + return rootNode_; +} + +unsigned int ClearTextSeed_BIP39::getDictionnaryId(void) const +{ + return dictionnaryId_; +} + +//// +void ClearTextSeed_BIP39::serialize(BinaryWriter& bw) const +{ + /* serialize the seed */ + BinaryWriter inner; + + //root + inner.put_uint8_t((uint8_t)Prefix::RawEntropy); + inner.put_var_int(rawEntropy_.getSize()); + inner.put_BinaryData(rawEntropy_); + + //dictionnary id + inner.put_uint32_t(dictionnaryId_); + + /* append to writer */ + + //seed type + bw.put_uint8_t((uint8_t)type()); + + //packet size + bw.put_var_int(inner.getSize()); + + //packet + bw.put_BinaryData(inner.getData()); +} + +//// +bool ClearTextSeed_BIP39::isBackupTypeEligible(BackupType bType) const +{ + //BIP39 seeds can be backed up to either the easy16 format or the + //mnemonic phrase, interchangeably + return bType == BackupType::Armory200d || bType == BackupType::BIP39; +} + +BackupType ClearTextSeed_BIP39::getPreferedBackupType() const +{ + return BackupType::BIP39; +} diff --git a/cppForSwig/Wallets/Seeds/Seeds.h b/cppForSwig/Wallets/Seeds/Seeds.h index ade6fa9c6..615856190 100644 --- a/cppForSwig/Wallets/Seeds/Seeds.h +++ b/cppForSwig/Wallets/Seeds/Seeds.h @@ -9,32 +9,257 @@ #pragma once #include "../AssetEncryption.h" +class BIP32_Node; namespace Armory { - namespace Seed + namespace Wallets { + namespace Encryption + { + class DecryptedDataContainer; + } + } + + /*** Wallet creation diagram *** + + WalletBackup <---> ClearTextSeed <-------- + | | + | | + v | + AssetWallet --> EncryptedSeed + ***/ + + namespace Seeds + { + ////////////////////////////////////////////////////////////////////////// + enum class SeedType : int + { + /* + Armory135: + For wallets using the legacy Armory derivation scheme. + */ + Armory135 = 0, + + /* + BIP32_Structured: + For wallets carrying BIP44/49/84 accounts. Restores to a bip32 wallet + with all these accounts. + */ + BIP32_Structured = 1, + + /* + BIP32_Virgin: + No info is provided about the wallet's structure, restores to an empty + bip32 wallet. + */ + BIP32_Virgin = 15, + + /* + BIP32_base58Root + From a base58 of the wallet root. No info about the wallet structure. + Cannot be extract as easy16. Mostly used to import HW roots. + */ + BIP32_base58Root = 16, + + /* + BIP39: + BIP39 seed. Can be outputed as either Easy16 or BIP39 english + dictionnary mnemonic. Backup string is always converted into the + BIP39 mnemonic then passed through PBKDF2 to generate the seed. + Yield a wallet with BIP44, 49 and 84 accounts. + */ + BIP39 = 8, + + /* + Raw: + Raw entropy. Used for wallet public data encryption and v1 seeds + */ + Raw = INT32_MAX - 1, + }; + enum class BackupType; + + ////////////////////////////////////////////////////////////////////////// + class ClearTextSeed + { + private: + const SeedType type_; + mutable std::string walletId_; + mutable std::string masterId_; + + protected: + enum class Prefix : int + { + Root = 0x11, + Chaincode = 0x22, + PublicKey = 0x33, + RawEntropy = 0x44, + Dictionnary = 0x55, + LegacyType = 0x66, + Base58Root = 0x77 + }; + + virtual std::string computeWalletId(void) const = 0; + virtual std::string computeMasterId(void) const = 0; + + public: + ClearTextSeed(SeedType); + virtual ~ClearTextSeed(void) = 0; + + SeedType type(void) const; + virtual bool isBackupTypeEligible(BackupType) const = 0; + virtual BackupType getPreferedBackupType(void) const = 0; + + const std::string& getWalletId(void) const; + const std::string& getMasterId(void) const; + + virtual void serialize(BinaryWriter&) const = 0; + static std::unique_ptr deserialize( + const SecureBinaryData&); + }; + + //////// + class ClearTextSeed_Armory135 : public ClearTextSeed + { + public: + enum class LegacyType : int + { + /* + Legacy type defines what kinda of backup can be created from this + seed. By default, legacy wallets would be created with a Armory200a + backup type, which would set the hash index to 3. + + A wallet restored from an older backup would then yield backups that + differ from the old paper. To avoid this, we track which legacy type + this seed is from. + + - seed type of LegacyType::Armory135 will generate + BackupType::Armory135 backups + - seed type of LegacyType::Armory200 will generate + BackupType::Armory200a backups + */ + Armory135 = 12, + Armory200 = 34 + }; + + private: + const SecureBinaryData root_; + const SecureBinaryData chaincode_; + const LegacyType legacyType_; + + protected: + std::string computeWalletId(void) const override; + std::string computeMasterId(void) const override; + + public: + //will generate random root + ClearTextSeed_Armory135(LegacyType lType = LegacyType::Armory200); + + //root + ClearTextSeed_Armory135(const SecureBinaryData&, + LegacyType lType = LegacyType::Armory200); + + //root + chaincode + ClearTextSeed_Armory135(const SecureBinaryData&, const SecureBinaryData&, + LegacyType lType = LegacyType::Armory135); + + //overrides + ~ClearTextSeed_Armory135(void) override; + void serialize(BinaryWriter&) const override; + bool isBackupTypeEligible(BackupType) const override; + BackupType getPreferedBackupType(void) const override; + + //local + const SecureBinaryData& getRoot(void) const; + const SecureBinaryData& getChaincode(void) const; + }; + + //////// + class ClearTextSeed_BIP32 : public ClearTextSeed + { + protected: + const SecureBinaryData rawEntropy_; + mutable std::shared_ptr rootNode_; + + protected: + std::string computeWalletId(void) const override; + std::string computeMasterId(void) const override; + + public: + //seed + ClearTextSeed_BIP32(SeedType); + ClearTextSeed_BIP32(const SecureBinaryData&, SeedType); + BackupType getPreferedBackupType(void) const override; + static std::unique_ptr fromBase58( + const BinaryDataRef&); + + //overrides + ~ClearTextSeed_BIP32(void) override; + virtual void serialize(BinaryWriter&) const override; + virtual bool isBackupTypeEligible(BackupType) const override; + + //locals + virtual std::shared_ptr getRootNode(void) const; + const SecureBinaryData& getRawEntropy(void) const; + }; + + //////// + class ClearTextSeed_BIP39 : public ClearTextSeed_BIP32 + { + private: + const unsigned int dictionnaryId_ = 1; + + public: + ClearTextSeed_BIP39(const SecureBinaryData&, unsigned int); + ~ClearTextSeed_BIP39(void) override; + + void serialize(BinaryWriter&) const override; + bool isBackupTypeEligible(BackupType) const override; + BackupType getPreferedBackupType(void) const override; + + std::shared_ptr getRootNode(void) const override; + unsigned int getDictionnaryId(void) const; + }; + ////////////////////////////////////////////////////////////////////////// class EncryptedSeed : public Wallets::Encryption::EncryptedAssetData { + /* + Carries the encrypted ClearTextSeed used to generate the wallet. + This class cannot be used to yield wallet seeds on its own, its + main purpose is disk IO. + + Convert to ClearTextSeed for seed/backup manipulations. + To convert, feed the decrypted the cipher text + to ClearTextSeed::deserialize + */ + private: + const SeedType type_; + public: + using CipherText = std::unique_ptr; static const Wallets::AssetId seedAssetId_; public: //tors - EncryptedSeed( - std::unique_ptr cipher) : - Wallets::Encryption::EncryptedAssetData(move(cipher)) - {} + EncryptedSeed(CipherText, SeedType); + ~EncryptedSeed(void) override; - //overrides + //utils + SeedType type(void) const; bool isSame(Wallets::Encryption::EncryptedAssetData* const) const override; - BinaryData serialize(void) const override; const Wallets::AssetId& getAssetId(void) const override; - //static + //for disk IO + BinaryData serialize(void) const override; static std::unique_ptr deserialize( const BinaryDataRef&); + + //used at wallet creation + static std::unique_ptr fromClearTextSeed( + std::unique_ptr, + std::unique_ptr, + std::shared_ptr); }; } } //namespace Armory \ No newline at end of file diff --git a/cppForSwig/Wallets/WalletFileInterface.cpp b/cppForSwig/Wallets/WalletFileInterface.cpp index 19d79f150..6a7ee1e27 100644 --- a/cppForSwig/Wallets/WalletFileInterface.cpp +++ b/cppForSwig/Wallets/WalletFileInterface.cpp @@ -14,7 +14,7 @@ #include "Seeds/Seeds.h" using namespace std; -using namespace Armory::Seed; +using namespace Armory::Seeds; using namespace Armory::Wallets::IO; using namespace Armory::Wallets::Encryption; @@ -486,7 +486,8 @@ shared_ptr WalletDBInterface::setupControlDB( auto cipherCopy = keyStruct.cipher_->getCopy(); auto cipherText = decryptedData->encryptData(cipherCopy.get(), seed); auto cipherData = make_unique(cipherText, move(cipherCopy)); - auto encrSeed = make_shared(move(cipherData)); + auto encrSeed = make_shared( + move(cipherData), SeedType::Raw); //write seed to disk auto&& tx = beginWriteTransaction(CONTROL_DB_NAME); diff --git a/cppForSwig/Wallets/WalletFileInterface.h b/cppForSwig/Wallets/WalletFileInterface.h index 41ce89fff..fbce882e3 100644 --- a/cppForSwig/Wallets/WalletFileInterface.h +++ b/cppForSwig/Wallets/WalletFileInterface.h @@ -31,7 +31,7 @@ class PRNG_Fortuna; namespace Armory { - namespace Seed + namespace Seeds { class EncryptedSeed; }; @@ -160,7 +160,7 @@ namespace Armory std::unique_ptr decryptedData_; std::unique_ptr controlLock_; - std::unique_ptr controlSeed_; + std::unique_ptr controlSeed_; unsigned encryptionVersion_ = UINT32_MAX; std::unique_ptr fortuna_; diff --git a/cppForSwig/Wallets/WalletIdTypes.cpp b/cppForSwig/Wallets/WalletIdTypes.cpp index 869fd7d9f..366207434 100644 --- a/cppForSwig/Wallets/WalletIdTypes.cpp +++ b/cppForSwig/Wallets/WalletIdTypes.cpp @@ -1,12 +1,15 @@ //////////////////////////////////////////////////////////////////////////////// // // -// Copyright (C) 2021-2021, goatpig // +// Copyright (C) 2021-2023, goatpig // // Distributed under the MIT license // // See LICENSE-MIT or https://opensource.org/licenses/MIT // // // //////////////////////////////////////////////////////////////////////////////// #include "WalletIdTypes.h" +#include "DerivationScheme.h" +#include "Assets.h" +#include "WalletHeader.h" using namespace Armory::Wallets; @@ -655,3 +658,63 @@ EncryptionKeyId EncryptionKeyId::deserializeValue(BinaryRefReader& brr) throw IdException("EncryptionKeyId::deserializeValue"); } } + +///////////////////////// - wallet & master id - /////////////////////////////// +std::string Armory::Wallets::generateWalletId( + std::shared_ptr derScheme, + std::shared_ptr rootEntry, + Armory::Seeds::SeedType sType) +{ + auto addrVec = derScheme->extendPublicChain(rootEntry, + 1, 1 + (int)sType, nullptr); + if (addrVec.size() != (int)sType+1) + throw WalletException("unexpected chain derivation output"); + + auto entry = std::dynamic_pointer_cast( + addrVec[int(sType)]); + if (entry == nullptr) + throw WalletException("unexpected asset entry type"); + + return BtcUtils::computeID(entry->getPubKey()->getUncompressedKey()); +} + +//// +std::string Armory::Wallets::generateWalletId( + SecureBinaryData pubkey, + SecureBinaryData chaincode, + Armory::Seeds::SeedType sType) +{ + //sanity checks + if (pubkey.empty()) + throw WalletException("[generateWalletId] empty pubkey"); + + if (chaincode.empty()) + throw WalletException("[generateWalletId] empty chaincode"); + + //create legacy armory derviation scheme from chaincode + auto derScheme = std::make_shared< + Armory::Assets::DerivationScheme_ArmoryLegacy>(chaincode); + + //create root pubkey asset + auto asset_single = std::make_shared< + Armory::Assets::AssetEntry_Single>( + Armory::Wallets::AssetId::getRootAssetId(), + pubkey, + nullptr); + + //derive '(int)sType' amount of addresses, use last one as id + return Armory::Wallets::generateWalletId(derScheme, asset_single, sType); +} + +//////// +std::string Armory::Wallets::generateMasterId(const SecureBinaryData& pubkey, + const SecureBinaryData& chaincode) +{ + BinaryWriter bw; + bw.put_BinaryData(pubkey); + bw.put_BinaryData(chaincode); + auto hmacMasterMsg = SecureBinaryData::fromString("MetaEntry"); + auto masterID_long = BtcUtils::getHMAC256( + bw.getData(), hmacMasterMsg); + return BtcUtils::computeID(masterID_long); +} diff --git a/cppForSwig/Wallets/WalletIdTypes.h b/cppForSwig/Wallets/WalletIdTypes.h index b1aeaad63..b6c61ebd4 100644 --- a/cppForSwig/Wallets/WalletIdTypes.h +++ b/cppForSwig/Wallets/WalletIdTypes.h @@ -19,7 +19,18 @@ namespace Armory namespace Bridge { class BridgePassphrasePrompt; - }; + } + + namespace Assets + { + class AssetEntry; + class DerivationScheme; + } + + namespace Seeds + { + enum class SeedType; + } namespace Wallets { @@ -183,6 +194,15 @@ namespace Armory BinaryData getSerializedKey(uint8_t) const; static EncryptionKeyId deserializeValue(BinaryRefReader&); }; - }; -}; + + //////////////////////////////////////////////////////////////////////// + std::string generateWalletId(std::shared_ptr, + std::shared_ptr, Seeds::SeedType); + std::string generateWalletId(SecureBinaryData, SecureBinaryData, + Seeds::SeedType); + std::string generateMasterId(const SecureBinaryData&, + const SecureBinaryData&); + + }// namespace Wallets +} #endif \ No newline at end of file diff --git a/cppForSwig/Wallets/Wallets.cpp b/cppForSwig/Wallets/Wallets.cpp index cbe5d5687..851b36592 100644 --- a/cppForSwig/Wallets/Wallets.cpp +++ b/cppForSwig/Wallets/Wallets.cpp @@ -17,7 +17,7 @@ using namespace Armory::Signer; using namespace Armory::Assets; using namespace Armory::Accounts; using namespace Armory::Wallets; -using namespace Armory::Seed; +using namespace Armory::Seeds; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -1122,36 +1122,71 @@ const AddressAccountId& AssetWallet_Single::createBIP32Account( return accountPtr->getID(); } +/////////////////////////////-- wallet creation --////////////////////////////// +shared_ptr AssetWallet_Single::createFromSeed( + std::unique_ptr seed, + const SecureBinaryData& passphrase, + const SecureBinaryData& controlPassphrase, + const string& folder, unsigned lookup) +{ + //sanity check + if (seed == nullptr) + throw WalletException("[AssetWallet_Single::createFromSeed] null seed"); + + //determine wallet type from seed type + shared_ptr result; + switch (seed->type()) + { + case Seeds::SeedType::Armory135: + { + auto seedA135 = dynamic_cast(seed.get()); + result = createFromSeed( + folder, seedA135, + passphrase, controlPassphrase, + lookup); + break; + } + + case Seeds::SeedType::BIP32_Structured: + case Seeds::SeedType::BIP32_Virgin: + case Seeds::SeedType::BIP32_base58Root: + case Seeds::SeedType::BIP39: + { + auto seedBip32 = dynamic_cast(seed.get()); + result = createFromSeed( + folder, seedBip32, + passphrase, controlPassphrase, + lookup); + break; + } + + default: + throw WalletException("[AssetWallet_Single::createFromSeed]" + " unexpected seed type"); + } + + //set the seed + result->setSeed(std::move(seed), passphrase); + return result; +} + //////////////////////////////////////////////////////////////////////////////// -shared_ptr AssetWallet_Single:: - createFromPrivateRoot_Armory135( +shared_ptr AssetWallet_Single::createFromSeed( const string& folder, - const SecureBinaryData& privateRoot, - SecureBinaryData chaincode, + ClearTextSeed_Armory135* seed, const SecureBinaryData& passphrase, const SecureBinaryData& controlPassphrase, unsigned lookup) { - /* - Pass the chaincode as it may be non deterministic for older Armory wallets. - To generate the chaincode from the private root, leave it empty. - */ + if (seed == nullptr) + throw WalletException("[createFromSeed] null root"); + const auto& privateRoot = seed->getRoot(); if (privateRoot.getSize() != 32) - throw WalletException("invalid root size"); - auto&& pubkey = CryptoECDSA().ComputePublicKey(privateRoot); + throw WalletException("[createFromSeed] invalid root size"); - //compute wallet ID - BinaryWriter masterIdPreimage; - masterIdPreimage.put_BinaryData(pubkey); - if (!chaincode.empty()) - masterIdPreimage.put_BinaryData(chaincode); - - //compute master ID as hmac256(root pubkey + chaincode, "MetaEntry") - auto hmacMasterMsg = SecureBinaryData::fromString("MetaEntry"); - auto&& masterID_long = BtcUtils::getHMAC256( - masterIdPreimage.getData(), hmacMasterMsg); - auto&& masterID = BtcUtils::computeID(masterID_long); + //TODO: need ID/backup pair tests (wallet ID should use fragments + //from derivation scheme, backup type should to different walletID) /* Create control passphrase lambda. It gets wiped after the wallet is setup @@ -1163,31 +1198,22 @@ shared_ptr AssetWallet_Single:: }; //create wallet file and dbenv - stringstream pathSS; - pathSS << folder << "/armory_" << masterID << "_wallet.lmdb"; - auto iface = getIfaceFromFile(pathSS.str(), false, controlPassLbd); + const auto& masterId = seed->getMasterId(); + std::string path{folder + "/armory_" + masterId + "_wallet.lmdb"}; + auto iface = getIfaceFromFile(path, false, controlPassLbd); - string walletID; + auto chaincode = seed->getChaincode(); + if (chaincode.empty()) { - //generate chaincode if it's not provided - if (chaincode.empty()) - chaincode = BtcUtils::computeChainCode_Armory135(privateRoot); - - auto chaincodeCopy = chaincode; - auto derScheme = make_shared( - chaincodeCopy); - - auto asset_single = make_shared( - AssetId::getRootAssetId(), - pubkey, nullptr); - - walletID = move(computeWalletID(derScheme, asset_single)); + //seed has no chaincode, generate deterministic one + chaincode = BtcUtils::computeChainCode_Armory135(privateRoot); } //create empty wallet + const auto& walletId = seed->getWalletId(); auto walletPtr = initWalletDb( iface, - masterID, walletID, + masterId, walletId, passphrase, controlPassphrase, privateRoot, @@ -1195,7 +1221,7 @@ shared_ptr AssetWallet_Single:: 0); //pass 0 for the fingerprint to signal legacy wallet //set as main - setMainWallet(iface, walletID); + setMainWallet(iface, walletId); //create account auto account135 = make_shared(); @@ -1222,82 +1248,16 @@ shared_ptr AssetWallet_Single:: } //////////////////////////////////////////////////////////////////////////////// -shared_ptr - AssetWallet_Single::createFromPublicRoot_Armory135( - const string& folder, - SecureBinaryData& pubRoot, - SecureBinaryData& chainCode, - const SecureBinaryData& controlPassphrase, - unsigned lookup) -{ - //compute master ID as hmac256(root pubkey, "MetaEntry") - auto hmacMasterMsg = SecureBinaryData::fromString("MetaEntry"); - auto&& masterID_long = BtcUtils::getHMAC256(pubRoot, hmacMasterMsg); - auto&& masterID = BtcUtils::computeID(masterID_long); - - /* - Create control passphrase lambda. It gets wiped after the wallet is setup - */ - auto controlPassLbd = - [&controlPassphrase](const set&)->SecureBinaryData - { - return controlPassphrase; - }; - - //create wallet file and dbenv - stringstream pathSS; - pathSS << folder << "/armory_" << masterID << "_WatchingOnly.lmdb"; - auto iface = getIfaceFromFile(pathSS.str(), false, controlPassLbd); - - string walletID; - shared_ptr rootPtr; - { - //walletID - auto chainCode_copy = chainCode; - auto derScheme = make_shared( - chainCode_copy); - - rootPtr = make_shared( - AssetId::getRootAssetId(), - pubRoot, nullptr, chainCode); - - walletID = move(computeWalletID(derScheme, rootPtr)); - } - - //create wallet - auto walletPtr = initWalletDbWithPubRoot( - iface, - controlPassphrase, - masterID, walletID, - rootPtr); - - //set as main - setMainWallet(iface, walletID); - - //add account - auto account135 = make_shared(); - account135->setMain(true); - - auto accountPtr = walletPtr->createAccount(account135); - accountPtr->extendPublicChain(iface, lookup - 1); - - return walletPtr; -} - -//////////////////////////////////////////////////////////////////////////////// -shared_ptr AssetWallet_Single::createFromSeed_BIP32( +shared_ptr AssetWallet_Single::createFromSeed( const string& folder, - const SecureBinaryData& seed, + Seeds::ClearTextSeed_BIP32* seed, const SecureBinaryData& passphrase, const SecureBinaryData& controlPassphrase, unsigned lookup) { - if (seed.empty()) - throw WalletException("[createFromSeed_BIP32] empty seed"); - - BIP32_Node rootNode; - rootNode.initFromSeed(seed); - + if (seed == nullptr) + throw WalletException("[createFromSeed] null seed"); + auto rootNode = seed->getRootNode(); auto coinType = Armory::Config::BitcoinSettings::getCoinType(); //address accounts @@ -1307,7 +1267,7 @@ shared_ptr AssetWallet_Single::createFromSeed_BIP32( //legacy account: 44 vector path = { 0x8000002C, coinType, 0x80000000 }; auto legacyAcc = AccountType_BIP32::makeFromDerPaths( - rootNode.getThisFingerprint(), {path}); + rootNode->getThisFingerprint(), {path}); //nodes legacyAcc->setNodes({ @@ -1335,7 +1295,7 @@ shared_ptr AssetWallet_Single::createFromSeed_BIP32( //nested sw account: 49 vector path = { 0x80000031, coinType, 0x80000000 }; auto nestedAcc = AccountType_BIP32::makeFromDerPaths( - rootNode.getThisFingerprint(), {path}); + rootNode->getThisFingerprint(), {path}); //nodes nestedAcc->setNodes({ @@ -1362,7 +1322,7 @@ shared_ptr AssetWallet_Single::createFromSeed_BIP32( //sw account: 84 vector path = { 0x80000054, coinType, 0x80000000 }; auto segwitAcc = AccountType_BIP32::makeFromDerPaths( - rootNode.getThisFingerprint(), {path}); + rootNode->getThisFingerprint(), {path}); //nodes segwitAcc->setNodes({ @@ -1383,76 +1343,78 @@ shared_ptr AssetWallet_Single::createFromSeed_BIP32( accountTypes.insert(segwitAcc); } + //create wallet file and dbenv + if (rootNode->isPublic()) + { + throw WalletException("[createFromSeed]" + " BIP32 seeds cannot lead to WO wallets"); + } - auto walletPtr = createFromBIP32Node( - rootNode, - accountTypes, - passphrase, - controlPassphrase, - folder); - - //save the seed - walletPtr->setSeed(seed, passphrase); + auto controlPassLbd = + [&controlPassphrase](const set&)->SecureBinaryData + { + return controlPassphrase; + }; - return walletPtr; -} + //db env + auto masterId = seed->getMasterId(); + auto walletId = seed->getWalletId(); + string path = folder + "/armory_" + masterId + "_wallet.lmdb"; + auto iface = getIfaceFromFile(path, false, controlPassLbd); -//////////////////////////////////////////////////////////////////////////////// -shared_ptr AssetWallet_Single::createFromSeed_BIP32_Blank( - const string& folder, - const SecureBinaryData& seed, - const SecureBinaryData& passphrase, - const SecureBinaryData& controlPassphrase) -{ - BIP32_Node rootNode; - if (seed.getSize() == 0) - throw WalletException("empty seed"); - rootNode.initFromSeed(seed); + //wallet object + auto walletPtr = initWalletDb(iface, + masterId, walletId, + passphrase, controlPassphrase, + rootNode->getPrivateKey(), + rootNode->getChaincode(), + rootNode->getThisFingerprint()); - //address accounts - set> accountTypes; + //set as main + setMainWallet(iface, walletId); - /* - no accounts are setup for a blank wallet - */ + //add accounts + auto passLbd =[&passphrase]( + const set&)->SecureBinaryData + { + return passphrase; + }; + walletPtr->setPassphrasePromptLambda(passLbd); - auto walletPtr = createFromBIP32Node( - rootNode, - accountTypes, - passphrase, - controlPassphrase, - folder); + switch (seed->type()) + { + case SeedType::BIP32_Structured: + case SeedType::BIP39: + { + for (auto accountPtr : accountTypes) + walletPtr->createBIP32Account(accountPtr); + break; + } - //save the seed - walletPtr->setSeed(seed, passphrase); + default: + //no accounts structure for these seeds + break; + } + walletPtr->resetPassphrasePromptLambda(); return walletPtr; } //////////////////////////////////////////////////////////////////////////////// -shared_ptr AssetWallet_Single::createFromBIP32Node( - const BIP32_Node& node, - set> accountTypes, - const SecureBinaryData& passphrase, +shared_ptr + AssetWallet_Single::createFromPublicRoot_Armory135( + const string& folder, + SecureBinaryData& pubRoot, + SecureBinaryData& chainCode, const SecureBinaryData& controlPassphrase, - const string& folder) + unsigned lookup) { - bool isPublic = false; - if (node.isPublic()) - isPublic = true; - - //compute wallet ID - auto pubkey = node.getPublicKey(); - - //compute master ID as hmac256(root pubkey, "MetaEntry") - auto hmacMasterMsg = SecureBinaryData::fromString("MetaEntry"); - auto&& masterID_long = BtcUtils::getHMAC256(pubkey, hmacMasterMsg); - auto&& masterID = BtcUtils::computeID(masterID_long); + auto masterID = generateMasterId(pubRoot, chainCode); /* Create control passphrase lambda. It gets wiped after the wallet is setup */ - auto controlPassLbd = + auto controlPassLbd = [&controlPassphrase](const set&)->SecureBinaryData { return controlPassphrase; @@ -1460,61 +1422,30 @@ shared_ptr AssetWallet_Single::createFromBIP32Node( //create wallet file and dbenv stringstream pathSS; - if (!isPublic) - pathSS << folder << "/armory_" << masterID << "_wallet.lmdb"; - else - pathSS << folder << "/armory_" << masterID << "_WatchingOnly.lmdb"; - + pathSS << folder << "/armory_" << masterID << "_WatchingOnly.lmdb"; auto iface = getIfaceFromFile(pathSS.str(), false, controlPassLbd); - string walletID; - { - //walletID - auto chaincode_copy = node.getChaincode(); - auto derScheme = - make_shared(chaincode_copy); - - auto asset_single = make_shared( - AssetId::getRootAssetId(), - pubkey, nullptr); - - walletID = move(computeWalletID(derScheme, asset_single)); - } + auto walletID = generateWalletId(pubRoot, chainCode, SeedType::Armory135); + auto rootPtr = make_shared( + AssetId::getRootAssetId(), pubRoot, nullptr, chainCode); //create wallet - shared_ptr walletPtr = nullptr; - - if (!isPublic) - { - walletPtr = initWalletDb( - iface, - masterID, walletID, - passphrase, - controlPassphrase, - node.getPrivateKey(), - node.getChaincode(), - node.getThisFingerprint()); - } - else - { - throw runtime_error("invalid for bip32 wallets"); - } + auto walletPtr = initWalletDbWithPubRoot( + iface, + controlPassphrase, + masterID, walletID, + rootPtr); //set as main setMainWallet(iface, walletID); - //add accounts - auto passLbd = - [&passphrase](const set&)->SecureBinaryData - { - return passphrase; - }; - walletPtr->setPassphrasePromptLambda(passLbd); + //add account + auto account135 = make_shared(); + account135->setMain(true); - for (auto accountPtr : accountTypes) - walletPtr->createBIP32Account(accountPtr); + auto accountPtr = walletPtr->createAccount(account135); + accountPtr->extendPublicChain(iface, lookup - 1); - walletPtr->resetPassphrasePromptLambda(); return walletPtr; } @@ -1528,7 +1459,7 @@ shared_ptr AssetWallet_Single::createBlank( /* Create control passphrase lambda. It gets wiped after the wallet is setup */ - auto controlPassLbd = + auto controlPassLbd = [&controlPassphrase](const set&)->SecureBinaryData { return controlPassphrase; @@ -1555,22 +1486,6 @@ shared_ptr AssetWallet_Single::createBlank( return walletPtr; } -//////////////////////////////////////////////////////////////////////////////// -string AssetWallet_Single::computeWalletID( - shared_ptr derScheme, - shared_ptr rootEntry) -{ - auto&& addrVec = derScheme->extendPublicChain(rootEntry, 1, 1, nullptr); - if (addrVec.size() != 1) - throw WalletException("unexpected chain derivation output"); - - auto firstEntry = dynamic_pointer_cast(addrVec[0]); - if (firstEntry == nullptr) - throw WalletException("unexpected asset entry type"); - - return BtcUtils::computeID(firstEntry->getPubKey()->getUncompressedKey()); -} - //////////////////////////////////////////////////////////////////////////////// shared_ptr AssetWallet_Single::initWalletDb( shared_ptr iface, @@ -2135,19 +2050,15 @@ WalletPublicData AssetWallet_Single::exportPublicData( //////////////////////////////////////////////////////////////////////////////// -void AssetWallet_Single::setSeed( - const SecureBinaryData& seed, +void AssetWallet_Single::setSeed(unique_ptr seedPtr, const SecureBinaryData& passphrase) { //copy root node cipher - auto rootPtr = dynamic_pointer_cast(root_); - if (rootPtr == nullptr) - throw WalletException("expected BIP32 root object"); - auto cipherCopy = - rootPtr->getPrivKey()->getCipherDataPtr()->cipher_->getCopy(); + auto cipherCopy = + root_->getPrivKey()->getCipherDataPtr()->cipher_->getCopy(); //if custom passphrase, set prompt lambda prior to encryption - if (passphrase.getSize() > 0) + if (!passphrase.empty()) { auto passphraseLambda = [&passphrase](const set&)->SecureBinaryData @@ -2161,11 +2072,8 @@ void AssetWallet_Single::setSeed( //create encrypted seed object { auto lock = lockDecryptedContainer(); - - auto cipherText = decryptedData_->encryptData(cipherCopy.get(), seed); - auto cipherData = make_unique( - cipherText, move(cipherCopy)); - seed_ = make_shared(move(cipherData)); + seed_ = EncryptedSeed::fromClearTextSeed(std::move(seedPtr), + std::move(cipherCopy), decryptedData_); } //write to disk diff --git a/cppForSwig/Wallets/Wallets.h b/cppForSwig/Wallets/Wallets.h index e5c69ace0..fd119d8ee 100644 --- a/cppForSwig/Wallets/Wallets.h +++ b/cppForSwig/Wallets/Wallets.h @@ -40,9 +40,12 @@ namespace Armory class BIP32_AssetPath; } - namespace Seed + namespace Seeds { class EncryptedSeed; + class ClearTextSeed; + class ClearTextSeed_Armory135; + class ClearTextSeed_BIP32; } namespace Wallets @@ -256,7 +259,7 @@ namespace Armory protected: std::shared_ptr root_ = nullptr; - std::shared_ptr seed_ = nullptr; + std::shared_ptr seed_ = nullptr; protected: //virtual @@ -284,7 +287,23 @@ namespace Armory static void importPublicData(const WalletPublicData&, std::shared_ptr); - void setSeed(const SecureBinaryData&, const SecureBinaryData&); + void setSeed(std::unique_ptr, + const SecureBinaryData&); + + //wallet creation private statics + static std::shared_ptr createFromSeed( + const std::string&, //folder + Seeds::ClearTextSeed_Armory135*, + const SecureBinaryData&, //pass + const SecureBinaryData&, //control pass + unsigned); //lookup + + static std::shared_ptr createFromSeed( + const std::string&, //folder + Seeds::ClearTextSeed_BIP32*, + const SecureBinaryData&, //pass + const SecureBinaryData&, //control pass + unsigned); //lookup public: //tors @@ -316,7 +335,7 @@ namespace Armory const SecureBinaryData& getDecryptedPrivateKeyForId( const AssetId&) const; - std::shared_ptr getEncryptedSeed(void) const; + std::shared_ptr getEncryptedSeed(void) const; Signer::BIP32_AssetPath getBip32PathForAsset( std::shared_ptr) const; @@ -332,21 +351,11 @@ namespace Armory std::shared_ptr); //static - static std::shared_ptr createFromBIP32Node( - const BIP32_Node& node, - std::set> accountTypes, - const SecureBinaryData& passphrase, - const SecureBinaryData& controlPassphrase, - const std::string& folder); - - static std::shared_ptr - createFromPrivateRoot_Armory135( - const std::string& folder, - const SecureBinaryData& privateRoot, - SecureBinaryData chaincode, - const SecureBinaryData& passphrase, - const SecureBinaryData& controlPassphrase, - unsigned lookup); + static std::shared_ptr createFromSeed( + std::unique_ptr, + const SecureBinaryData&, + const SecureBinaryData&, + const std::string&, unsigned lookup = 1000); static std::shared_ptr createFromPublicRoot_Armory135( @@ -356,28 +365,10 @@ namespace Armory const SecureBinaryData& controlPassphrase, unsigned lookup); - static std::shared_ptr createFromSeed_BIP32( - const std::string& folder, - const SecureBinaryData& seed, - const SecureBinaryData& passphrase, - const SecureBinaryData& controlPassphrase, - unsigned lookup); - - static std::shared_ptr - createFromSeed_BIP32_Blank( - const std::string& folder, - const SecureBinaryData& seed, - const SecureBinaryData& passphrase, - const SecureBinaryData& controlPassphrase); - static std::shared_ptr createBlank( const std::string& folder, const std::string& walletID, const SecureBinaryData& controlPassphrase); - - static std::string computeWalletID( - std::shared_ptr, - std::shared_ptr); }; ////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/gtest/SignerTests.cpp b/cppForSwig/gtest/SignerTests.cpp index 8f9db4b89..caa14db01 100644 --- a/cppForSwig/gtest/SignerTests.cpp +++ b/cppForSwig/gtest/SignerTests.cpp @@ -9,6 +9,7 @@ #include "TestUtils.h" #include "CoinSelection.h" +#include "../Wallets/Seeds/Seeds.h" using namespace std; using namespace Armory::Signer; @@ -388,14 +389,13 @@ TEST_F(SignerTest, SpendTest_SizeEstimates) //// create assetWlt //// - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), + {}, {}, homedir_, - move(wltRoot), //root as a r value - {}, - SecureBinaryData(), - SecureBinaryData(), - 5); //set lookup computation to 5 entries + 5); //register with db vector addrVec; @@ -790,13 +790,11 @@ TEST_F(SignerTest, SpendTest_P2WPKH) //// create assetWlt //// - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - move(wltRoot), //root as a rvalue - SecureBinaryData(), - SecureBinaryData(), - 5); //set lookup computation to 5 entries + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt = AssetWallet_Single::createFromSeed(move(seed), + {}, {}, homedir_, 5); //register with db vector> addrVec; @@ -1039,16 +1037,16 @@ TEST_F(SignerTest, SpendTest_MixedInputTypes) //// create assetWlt //// - auto&& wltRoot = CryptoPRNG::generateRandom(32); + auto rawEntropy = CryptoPRNG::generateRandom(32); BIP32_Node node; - node.initFromSeed(wltRoot); + node.initFromSeed(rawEntropy); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135(rawEntropy)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), + {}, {}, homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), 5); //set lookup computation to 3 entries //add a bip32 account @@ -1304,33 +1302,23 @@ TEST_F(SignerTest, SpendTest_MultipleSigners_1of3) scrAddrVec.push_back(TestChain::scrAddrE); //// create 3 assetWlt //// - - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt_1 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //set lookup computation to 3 entries - - wltRoot = move(CryptoPRNG::generateRandom(32)); - auto assetWlt_2 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //set lookup computation to 3 entries - - wltRoot = move(CryptoPRNG::generateRandom(32)); - auto assetWlt_3 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), {}, {}, homedir_, 3); + + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), {}, {}, homedir_, 3); + + unique_ptr seed3( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_3 = AssetWallet_Single::createFromSeed( + move(seed3), {}, {}, homedir_, 3); //create 1-of-3 multisig asset entry from 3 different wallets map> asset_single_map; @@ -1598,33 +1586,23 @@ TEST_F(SignerTest, SpendTest_MultipleSigners_2of3_NativeP2WSH) scrAddrVec.push_back(TestChain::scrAddrE); //// create 3 assetWlt //// - - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt_1 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //set lookup computation to 3 entries - - wltRoot = move(CryptoPRNG::generateRandom(32)); - auto assetWlt_2 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //set lookup computation to 3 entries - - wltRoot = move(CryptoPRNG::generateRandom(32)); - auto assetWlt_3 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), {}, {}, homedir_, 3); + + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), {}, {}, homedir_, 3); + + unique_ptr seed3( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_3 = AssetWallet_Single::createFromSeed( + move(seed3), {}, {}, homedir_, 3); //create 2-of-3 multisig asset entry from 3 different wallets map> asset_single_map; @@ -1992,22 +1970,15 @@ TEST_F(SignerTest, SpendTest_MultipleSigners_DifferentInputs) scrAddrVec.push_back(TestChain::scrAddrE); //// create 2 assetWlt //// + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), {}, {}, homedir_, 3); - auto assetWlt_1 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - CryptoPRNG::generateRandom(32), //root as rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //set lookup computation to 3 entries - - auto assetWlt_2 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(CryptoPRNG::generateRandom(32)), //root as rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), {}, {}, homedir_, 3); //register with db vector> addrVec_1; @@ -2289,22 +2260,15 @@ TEST_F(SignerTest, SpendTest_MultipleSigners_ParallelSigning) scrAddrVec.push_back(TestChain::scrAddrE); //// create 2 assetWlt //// + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), {}, {}, homedir_, 3); - auto assetWlt_1 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - CryptoPRNG::generateRandom(32), //root as rvalue - {}, - SecureBinaryData(), //empty passphrase - SecureBinaryData(), - 3); //set lookup computation to 3 entries - - auto assetWlt_2 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(CryptoPRNG::generateRandom(32)), //root as rvalue - {}, - SecureBinaryData(), //empty passphrase - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), {}, {}, homedir_, 3); //register with db vector> addrVec_1; @@ -2622,19 +2586,17 @@ TEST_F(SignerTest, SpendTest_MultipleSigners_ParallelSigning_GetUnsignedTx) scrAddrVec.push_back(TestChain::scrAddrE); //// create 2 assetWlt //// - auto assetWlt_1 = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - CryptoPRNG::generateRandom(32), //root as rvalue - SecureBinaryData(), //empty passphrase - SecureBinaryData(), - 3); //set lookup computation to 3 entries - - auto assetWlt_2 = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - move(CryptoPRNG::generateRandom(32)), //root as rvalue - SecureBinaryData(), //empty passphrase - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), {}, {}, homedir_, 3); + + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), {}, {}, homedir_, 3); //register with db vector> addrVec_1; @@ -2988,20 +2950,16 @@ TEST_F(SignerTest, SpendTest_MultipleSigners_ParallelSigning_GetUnsignedTx_Neste scrAddrVec.push_back(TestChain::scrAddrE); //// create 2 assetWlt //// - auto assetWlt_1 = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - CryptoPRNG::generateRandom(32), //root as rvalue - SecureBinaryData(), //empty passphrase - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), {}, {}, homedir_, 3); - auto assetWlt_2 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(CryptoPRNG::generateRandom(32)), //root as rvalue - {}, - SecureBinaryData(), //empty passphrase - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), {}, {}, homedir_, 3); //register with db auto addr_type_nested_p2sh = AddressEntryType(AddressEntryType_P2WPKH | AddressEntryType_P2SH); @@ -3412,20 +3370,16 @@ TEST_F(SignerTest, GetUnsignedTxId) scrAddrVec.push_back(TestChain::scrAddrE); //// create 2 assetWlt //// - auto assetWlt_1 = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - CryptoPRNG::generateRandom(32), //root as rvalue - SecureBinaryData(), //empty passphrase - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), {}, {}, homedir_, 3); - auto assetWlt_2 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(CryptoPRNG::generateRandom(32)), //root as rvalue - {}, - SecureBinaryData(), //empty passphrase - SecureBinaryData(), - 3); //set lookup computation to 3 entries + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), {}, {}, homedir_, 3); //register with db vector> addrVec_1; @@ -3796,12 +3750,11 @@ TEST_F(SignerTest, Wallet_SpendTest_Nested_P2WPKH) //// create assetWlt //// //create empty bip32 wallet - auto&& wltSeed = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, - wltSeed, - SecureBinaryData(), - SecureBinaryData()); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed(move(seed), + {}, {}, homedir_); //add p2sh-p2wpkh account vector derPath = { 0x800061a5, 0x80000000 }; @@ -4053,17 +4006,17 @@ TEST_F(SignerTest, Wallet_SpendTest_Nested_P2WPKH_WOResolution_fromWOCopy) //// create assetWlt //// - auto&& wltSeed = CryptoPRNG::generateRandom(32); + auto rawEntropy = CryptoPRNG::generateRandom(32); string woPath, wltPath; Signer signer3; { //create bip32 wallet - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, - wltSeed, - SecureBinaryData(), - SecureBinaryData()); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed(move(seed), + {}, {}, homedir_); //add p2sh-p2wpkh account vector derPath = { 0x800061a5, 0x80000000 }; @@ -4092,11 +4045,11 @@ TEST_F(SignerTest, Wallet_SpendTest_Nested_P2WPKH_WOResolution_fromWOCopy) AssetWallet::loadMainWalletFromFile(woPath, nullptr)); //recreate empty bip32 wallet - auto emptyWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, - wltSeed, - SecureBinaryData(), - SecureBinaryData()); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Virgin)); + auto emptyWlt = AssetWallet_Single::createFromSeed(move(seed), + {}, {}, homedir_); //// register with db //// vector addrVec; @@ -4343,12 +4296,12 @@ TEST_F(SignerTest, Wallet_SpendTest_Nested_P2WPKH_WOResolution_fromXPub) //// create assetWlt //// //create empty bip32 wallet - auto&& wltSeed = CryptoPRNG::generateRandom(32); - auto emptyWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, - wltSeed, - SecureBinaryData(), - SecureBinaryData()); + auto rawEntropy = CryptoPRNG::generateRandom(32); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Virgin)); + auto emptyWlt = AssetWallet_Single::createFromSeed(move(seed), + {}, {}, homedir_); //create empty WO wallet auto wltWO = AssetWallet_Single::createBlank( @@ -4357,7 +4310,7 @@ TEST_F(SignerTest, Wallet_SpendTest_Nested_P2WPKH_WOResolution_fromXPub) //derive public root vector derPath = { 0x800061a5, 0x80000000 }; BIP32_Node seedNode; - seedNode.initFromSeed(wltSeed); + seedNode.initFromSeed(rawEntropy); auto seedFingerprint = seedNode.getThisFingerprint(); for (auto& derId : derPath) seedNode.derivePrivate(derId); @@ -4613,15 +4566,13 @@ TEST_F(SignerTest, Wallet_SpendTest_Nested_P2PK) scrAddrVec.push_back(TestChain::scrAddrE); //// create assetWlt //// - - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), + {}, {}, homedir_, - move(wltRoot), //root as a r value - {}, - SecureBinaryData(), - SecureBinaryData(), - 3); //lookup computation + 3); //set lookup computation to 3 entries //register with db vector addrVec; @@ -4849,13 +4800,11 @@ TEST_F(SignerTest, SpendTest_FromAccount_Reload) scrAddrVec.push_back(TestChain::scrAddrE); //// create assetWlt //// - - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, - move(wltRoot), //root as a rvalue - SecureBinaryData(), - SecureBinaryData()); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed(move(seed), + {}, {}, homedir_); //add a bip32 account { @@ -5246,12 +5195,11 @@ TEST_F(SignerTest, SpendTest_BIP32_Accounts) //// create assetWlt //// auto passphrase = SecureBinaryData::fromString("test"); - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, - wltRoot, //root as a rvalue - SecureBinaryData(), - passphrase); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed(move(seed), + passphrase, {}, homedir_); auto rootBip32 = dynamic_pointer_cast< AssetEntry_BIP32Root>(assetWlt->getRoot()); @@ -5528,14 +5476,14 @@ TEST_F(SignerTest, SpendTest_FromExtendedAddress_Armory135) //// create assetWlt //// auto passphrase = SecureBinaryData::fromString("test"); - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, SecureBinaryData::fromString("control"), - 5); //set lookup computation to 5 entries + homedir_, + 5); //set lookup computation to 3 entries //register with db DBTestUtils::registerWallet(clients_, bdvID, scrAddrVec, "wallet1"); @@ -5777,13 +5725,11 @@ TEST_F(SignerTest, SpendTest_FromExtendedAddress_BIP32) //// create assetWlt //// auto passphrase = SecureBinaryData::fromString("test"); - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - move(wltRoot), //root as a rvalue - passphrase, - SecureBinaryData::fromString("control"), - 5); //set lookup computation to 5 entries + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt = AssetWallet_Single::createFromSeed(move(seed), + passphrase, SecureBinaryData::fromString("control"), homedir_, 5); //register with db DBTestUtils::registerWallet(clients_, bdvID, scrAddrVec, "wallet1"); @@ -6025,12 +5971,11 @@ TEST_F(SignerTest, SpendTest_FromExtendedAddress_Salted) //// create assetWlt //// auto passphrase = SecureBinaryData::fromString("test"); - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, - wltRoot, //root as a rvalue - passphrase, - SecureBinaryData::fromString("control")); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed(move(seed), + passphrase, SecureBinaryData::fromString("control"), homedir_); auto rootBip32 = dynamic_pointer_cast< AssetEntry_BIP32Root>(assetWlt->getRoot()); @@ -6305,12 +6250,11 @@ TEST_F(SignerTest, SpendTest_FromExtendedAddress_ECDH) //// create assetWlt //// auto passphrase = SecureBinaryData::fromString("test"); - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, - wltRoot, //root as a rvalue - passphrase, - SecureBinaryData::fromString("control")); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed(move(seed), + passphrase, SecureBinaryData::fromString("control"), homedir_); auto ecdhAccType = make_shared(privKey, pubKey); ecdhAccType->setDefaultAddressType( @@ -6576,14 +6520,11 @@ TEST_F(SignerTest, SpendTest_InjectSignature) scrAddrVec.push_back(TestChain::scrAddrE); //// create assetWlt //// - - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - move(wltRoot), //root as a rvalue - SecureBinaryData(), - SecureBinaryData(), - 5); //set lookup computation to 3 entries + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), {}, {}, homedir_, 5); //register with db vector> addrVec; @@ -6963,31 +6904,28 @@ TEST_F(SignerTest, SpendTest_InjectSignature_Multisig) //// create 3 assetWlt //// - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt_1 = AssetWallet_Single::createFromPrivateRoot_Armory135( + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), + {}, {}, homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), 3); //set lookup computation to 3 entries - wltRoot = move(CryptoPRNG::generateRandom(32)); - auto assetWlt_2 = AssetWallet_Single::createFromPrivateRoot_Armory135( + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), + {}, {}, homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), 3); //set lookup computation to 3 entries - wltRoot = move(CryptoPRNG::generateRandom(32)); - auto assetWlt_3 = AssetWallet_Single::createFromPrivateRoot_Armory135( + unique_ptr seed3( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_3 = AssetWallet_Single::createFromSeed( + move(seed3), + {}, {}, homedir_, - move(wltRoot), //root as a rvalue - {}, - SecureBinaryData(), - SecureBinaryData(), 3); //set lookup computation to 3 entries //create 2-of-3 multisig asset entry from 3 different wallets @@ -8652,8 +8590,8 @@ TEST_F(ExtrasTest, PSBT) auto txOut = tx.getTxOutCopy(index); UTXO utxo( - txOut.getValue(), - UINT32_MAX, UINT32_MAX, index, + txOut.getValue(), + UINT32_MAX, UINT32_MAX, index, hash, txOut.getScript()); return utxo; @@ -8713,16 +8651,17 @@ TEST_F(ExtrasTest, PSBT) auto b58seed = SecureBinaryData::fromString( "tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF"); + //create a wallet from that seed to test bip32 on the fly derivation + auto wallet = AssetWallet_Single::createFromSeed( + Armory::Seeds::ClearTextSeed_BIP32::fromBase58(b58seed), + SecureBinaryData(), SecureBinaryData(), + homedir_); + + //create node BIP32_Node node; node.initFromBase58(b58seed); auto masterFingerprint = node.getThisFingerprint(); - //create a wallet from that seed to test bip32 on the fly derivation - auto wallet = AssetWallet_Single::createFromBIP32Node( - node, {}, - SecureBinaryData(), SecureBinaryData(), - homedir_); - // 0'/0' node.derivePrivate(0x80000000); node.derivePrivate(0x80000000); @@ -8764,7 +8703,7 @@ TEST_F(ExtrasTest, PSBT) EXPECT_EQ(psbtTestVal, signer2.toPSBT()); Signer signer3(signer.serializeState()); - EXPECT_EQ(psbtTestVal, signer3.toPSBT()); + EXPECT_EQ(psbtTestVal, signer3.toPSBT()); } //resolve scripts @@ -8997,7 +8936,7 @@ TEST_F(ExtrasTest, PSBT) EXPECT_EQ(psbtHalf2, signer2.toPSBT()); Signer signer3(signer.serializeState()); - EXPECT_EQ(psbtHalf2, signer3.toPSBT()); + EXPECT_EQ(psbtHalf2, signer3.toPSBT()); } //combine sigs & finalize inputs @@ -9158,10 +9097,10 @@ class ExtrasTest_Mainnet : public ::testing::Test //////////////////////////////////////////////////////////////////////////////// TEST_F(ExtrasTest_Mainnet, Bip32PathDiscovery) { - auto seed = CryptoPRNG::generateRandom(32); + auto rawEntropy = CryptoPRNG::generateRandom(32); BIP32_Node node; - node.initFromSeed(seed); + node.initFromSeed(rawEntropy); auto masterFingerprint = node.getThisFingerprint(); vector derPath = { 0x8000002C, 0x80000000, 0x80000000 }; @@ -9191,10 +9130,11 @@ TEST_F(ExtrasTest_Mainnet, Bip32PathDiscovery) string wltPath; { - auto wallet = AssetWallet_Single::createFromSeed_BIP32( - homedir_, seed, - SecureBinaryData(), SecureBinaryData(), - 10); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Structured)); + auto wallet = AssetWallet_Single::createFromSeed( + move(seed), {}, {}, homedir_, 10); wltPath = wallet->getDbFilename(); auto woWalletPath = wallet->forkWatchingOnly(wltPath, passLbd); diff --git a/cppForSwig/gtest/SupernodeTests.cpp b/cppForSwig/gtest/SupernodeTests.cpp index 94f1296d7..db28f45b2 100644 --- a/cppForSwig/gtest/SupernodeTests.cpp +++ b/cppForSwig/gtest/SupernodeTests.cpp @@ -12,6 +12,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "TestUtils.h" +#include "../Wallets/Seeds/Seeds.h" using namespace std; using namespace Armory::Signer; using namespace Armory::Config; @@ -1762,31 +1763,31 @@ TEST_F(BlockUtilsWithWalletTest, MultipleSigners_2of3_NativeP2WSH) //// create 3 assetWlt //// //create a root private key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt_1 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, + unique_ptr seed1( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_1 = AssetWallet_Single::createFromSeed( + move(seed1), + SecureBinaryData(), SecureBinaryData(), - SecureBinaryData(), + homedir_, 3); //set lookup computation to 3 entries - wltRoot = move(CryptoPRNG::generateRandom(32)); - auto assetWlt_2 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, + unique_ptr seed2( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_2 = AssetWallet_Single::createFromSeed( + move(seed2), + SecureBinaryData(), SecureBinaryData(), - SecureBinaryData(), + homedir_, 3); //set lookup computation to 3 entries - wltRoot = move(CryptoPRNG::generateRandom(32)); - auto assetWlt_3 = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, + unique_ptr seed3( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt_3 = AssetWallet_Single::createFromSeed( + move(seed3), SecureBinaryData(), - SecureBinaryData(), + SecureBinaryData(), + homedir_, 3); //set lookup computation to 3 entries //create 2-of-3 multisig asset entry from 3 different wallets diff --git a/cppForSwig/gtest/WalletTests.cpp b/cppForSwig/gtest/WalletTests.cpp index d169d82ea..4bfb1415b 100644 --- a/cppForSwig/gtest/WalletTests.cpp +++ b/cppForSwig/gtest/WalletTests.cpp @@ -12,6 +12,7 @@ #include "../Wallets/Seeds/Backups.h" #include "../Wallets/Seeds/Seeds.h" #include "../Wallets/WalletFileInterface.h" +#include "protobuf/BridgeProto.pb.h" using namespace std; using namespace Armory::Signer; @@ -20,7 +21,7 @@ using namespace Armory::Assets; using namespace Armory::Accounts; using namespace Armory::Wallets; using namespace Armory::Wallets::Encryption; -using namespace Armory::Seed; +using namespace Armory::Seeds; //////////////////////////////////////////////////////////////////////////////// #define METHOD_ASSERT_EQ(a, b) \ @@ -4148,14 +4149,14 @@ TEST_F(WalletsTest, CreateCloseOpen_Test) //create 3 wallets for (unsigned i = 0; i < 1; i++) { - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), controlPass_, - 4); //set lookup computation to 4 entries + homedir_, + 4); //set lookup computation to 3 entries //get AddrVec auto&& hashSet = assetWlt->getAddrHashSet(); @@ -4197,14 +4198,14 @@ TEST_F(WalletsTest, CreateCloseOpen_Test) TEST_F(WalletsTest, CreateWOCopy_Test) { //create 1 wallet from priv key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), - 4); //set lookup computation to 4 entries + homedir_, + 4); //set lookup computation to 3 entries auto filename = assetWlt->getDbFilename(); //get AddrVec @@ -4276,35 +4277,24 @@ TEST_F(WalletsTest, CreateWOCopy_Test) //////////////////////////////////////////////////////////////////////////////// TEST_F(WalletsTest, IDs) { - auto computeId = []( - const SecureBinaryData& root, const SecureBinaryData& chaincode)->string + auto rawEntropy = CryptoPRNG::generateRandom(32); + auto rawPubkey = CryptoECDSA().ComputePublicKey(rawEntropy); + string id; { - auto ccCopy = chaincode; - if (chaincode.empty()) - ccCopy = BtcUtils::computeChainCode_Armory135(root); - - auto derScheme = - make_shared(ccCopy); - - auto pubkey = CryptoECDSA().ComputePublicKey(root); - auto asset_single = make_shared( - AssetId::getRootAssetId(), pubkey, nullptr); - - return AssetWallet_Single::computeWalletID(derScheme, asset_single); - }; - - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto id = computeId(wltRoot, {}); + auto chaincode = BtcUtils::computeChainCode_Armory135(rawEntropy); + id = generateWalletId(rawPubkey, chaincode, SeedType::Armory135); + } ASSERT_FALSE(id.empty()); //legacy wallet { - auto wlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135(rawEntropy)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries EXPECT_EQ(wlt->getID(), id); @@ -4312,18 +4302,23 @@ TEST_F(WalletsTest, IDs) //bip32 wallet { - auto wlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - wltRoot, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, + Armory::Seeds::SeedType::BIP32_Structured)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries //wallet id BIP32_Node node; - node.initFromSeed(wltRoot); + node.initFromSeed(rawEntropy); - auto idBip32 = computeId(node.getPrivateKey(), node.getChaincode()); + auto idBip32 = generateWalletId(node.getPublicKey(), node.getChaincode(), + SeedType::BIP32_Structured); EXPECT_EQ(wlt->getID(), idBip32); //account ids @@ -4403,41 +4398,103 @@ TEST_F(WalletsTest, IDs) //legacy with chaincode auto chaincode = CryptoPRNG::generateRandom(32); - auto idcc = computeId(wltRoot, chaincode); + auto idcc = generateWalletId(rawPubkey, chaincode, + SeedType::Armory135); ASSERT_NE(id, idcc); { - auto wlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, - chaincode, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135( + rawEntropy, chaincode)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries EXPECT_EQ(wlt->getID(), idcc); } } +//////////////////////////////////////////////////////////////////////////////// +TEST_F(WalletsTest, ID_fromSeeds) +{ + auto rawSBD = SecureBinaryData::CreateFromHex( + "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20"); + + //Armory135 + { + auto seed135 = make_unique( + rawSBD, ClearTextSeed_Armory135::LegacyType::Armory135); + EXPECT_EQ(seed135->getWalletId(), "2vrVAxyHR"); + EXPECT_EQ(seed135->getMasterId(), "LZxsEgeT"); + } + + //Armory200a + { + auto seed135 = make_unique( + rawSBD, ClearTextSeed_Armory135::LegacyType::Armory200); + EXPECT_EQ(seed135->getWalletId(), "2vrVAxyHR"); + EXPECT_EQ(seed135->getMasterId(), "LZxsEgeT"); + } + + //BIP32 structured + { + auto bip32 = make_unique( + rawSBD, SeedType::BIP32_Structured); + EXPECT_EQ(bip32->getWalletId(), "2BuhCGwV9"); + EXPECT_EQ(bip32->getMasterId(), "2d9H95rzK"); + } + + //BIP32 virgin + { + auto bip32 = make_unique( + rawSBD, SeedType::BIP32_Virgin); + EXPECT_EQ(bip32->getWalletId(), "22bd31PB5"); + EXPECT_EQ(bip32->getMasterId(), "2d9H95rzK"); + } + + //xpriv + { + BIP32_Node node; + node.initFromSeed(rawSBD); + auto xpriv = node.getBase58(); + + auto base58 = ClearTextSeed_BIP32::fromBase58(xpriv); + EXPECT_EQ(base58->getWalletId(), "33qBfTB51"); + EXPECT_EQ(base58->getMasterId(), "2d9H95rzK"); + } + + /* + //BIP39 + auto bip32 = make_unique(rawSBD, 1); + EXPECT_EQ(bip32->getWalletId(), "abcd"); + EXPECT_EQ(bip32->getMasterId(), "abcd"); + */ +} + //////////////////////////////////////////////////////////////////////////////// TEST_F(WalletsTest, Encryption_Test) { //#1: check deriving from an encrypted root yield correct chain //create 1 wallet from priv key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, - {}, + auto rawEntropy = CryptoPRNG::generateRandom(32); + + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135(rawEntropy)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries //derive private chain from root - auto&& chaincode = BtcUtils::computeChainCode_Armory135(wltRoot); + auto&& chaincode = BtcUtils::computeChainCode_Armory135(rawEntropy); vector privateKeys; - auto currentPrivKey = &wltRoot; + auto currentPrivKey = &rawEntropy; for (int i = 0; i < 4; i++) { @@ -4529,21 +4586,28 @@ TEST_F(WalletsTest, SeedEncryption) auto&& passphrase = SecureBinaryData::fromString("password"); //create regular wallet - auto&& seed = CryptoPRNG::generateRandom(32); - auto wlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, seed, passphrase, - SecureBinaryData::fromString("control"), 10); + auto rawEntropy = CryptoPRNG::generateRandom(32); + + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Structured)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), + passphrase, + SecureBinaryData::fromString("control"), + homedir_, + 10); //check clear text seed does not exist on disk auto filename = wlt->getDbFilename(); - ASSERT_FALSE(TestUtils::searchFile(filename, seed)); + ASSERT_FALSE(TestUtils::searchFile(filename, rawEntropy)); //grab without passphrase lbd, should fail try { auto lock = wlt->lockDecryptedContainer(); auto decryptedSeed = wlt->getDecryptedValue(wlt->getEncryptedSeed()); - EXPECT_EQ(decryptedSeed, seed); + EXPECT_EQ(decryptedSeed, rawEntropy); ASSERT_TRUE(false); } catch (DecryptedDataContainerException&) @@ -4562,7 +4626,7 @@ TEST_F(WalletsTest, SeedEncryption) try { auto decryptedSeed = wlt->getDecryptedValue(wlt->getEncryptedSeed()); - EXPECT_EQ(decryptedSeed, seed); + EXPECT_EQ(decryptedSeed, rawEntropy); ASSERT_TRUE(false); } catch (DecryptedDataContainerException&) @@ -4572,8 +4636,10 @@ TEST_F(WalletsTest, SeedEncryption) try { auto lock = wlt->lockDecryptedContainer(); - auto decryptedSeed = wlt->getDecryptedValue(wlt->getEncryptedSeed()); - EXPECT_EQ(decryptedSeed, seed); + auto clearTextSeed = ClearTextSeed::deserialize( + wlt->getDecryptedValue(wlt->getEncryptedSeed())); + auto seedBip32 = dynamic_cast(clearTextSeed.get()); + EXPECT_EQ(seedBip32->getRawEntropy(), rawEntropy); } catch (DecryptedDataContainerException&) { @@ -4586,7 +4652,7 @@ TEST_F(WalletsTest, SeedEncryption) { auto lock = wlt->lockDecryptedContainer(); auto decryptedSeed = wlt->getDecryptedValue(wlt->getEncryptedSeed()); - EXPECT_EQ(decryptedSeed, seed); + EXPECT_EQ(decryptedSeed, rawEntropy); ASSERT_TRUE(false); } catch (DecryptedDataContainerException&) @@ -4616,8 +4682,10 @@ TEST_F(WalletsTest, SeedEncryption) try { auto lock = wlt->lockDecryptedContainer(); - auto decryptedSeed = wlt->getDecryptedValue(wlt->getEncryptedSeed()); - EXPECT_EQ(decryptedSeed, seed); + auto clearTextSeed = ClearTextSeed::deserialize( + wlt->getDecryptedValue(wlt->getEncryptedSeed())); + auto seedBip32 = dynamic_cast(clearTextSeed.get()); + EXPECT_EQ(seedBip32->getRawEntropy(), rawEntropy); } catch (DecryptedDataContainerException&) { @@ -4629,13 +4697,15 @@ TEST_F(WalletsTest, SeedEncryption) TEST_F(WalletsTest, LockAndExtend_Test) { //create wallet from priv key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, //root as a r value - {}, + auto rawEntropy = CryptoPRNG::generateRandom(32); + + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135(rawEntropy)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), //root as a r value SecureBinaryData::fromString("passphrase"), //set passphrase to "test" controlPass_, + homedir_, 4); //set lookup computation to 4 entries auto passLbd = [] @@ -4647,10 +4717,10 @@ TEST_F(WalletsTest, LockAndExtend_Test) assetWlt->setPassphrasePromptLambda(passLbd); //derive private chain from root - auto&& chaincode = BtcUtils::computeChainCode_Armory135(wltRoot); + auto&& chaincode = BtcUtils::computeChainCode_Armory135(rawEntropy); vector privateKeys; - auto currentPrivKey = &wltRoot; + auto currentPrivKey = &rawEntropy; for (int i = 0; i < 10; i++) { @@ -4840,13 +4910,13 @@ TEST_F(WalletsTest, ControlPassphrase_Test) string filename; set addrSet; { - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("test"), //set passphrase to "test" SecureBinaryData::fromString("control"), //control passphrase + homedir_, 4); //set lookup computation to 4 entries filename = assetWlt->getDbFilename(); addrSet = assetWlt->getAddrHashSet(); @@ -5011,12 +5081,14 @@ TEST_F(WalletsTest, ControlPassphrase_Test) string filename2; { - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - wltRoot, //root as a r value + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("test"), //set passphrase to "test" SecureBinaryData(), //empty control passphrase + homedir_, 4); //set lookup computation to 4 entries filename2 = assetWlt->getDbFilename(); addrSet = assetWlt->getAddrHashSet(); @@ -5177,13 +5249,15 @@ TEST_F(WalletsTest, ControlPassphrase_Test) TEST_F(WalletsTest, SignPassphrase_Test) { //create wallet from priv key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, //root as a r value - {}, + auto rawEntropy = CryptoPRNG::generateRandom(32); + + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135(rawEntropy)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("test"), //set passphrase to "test" SecureBinaryData::fromString("control"), //control passphrase + homedir_, 4); //set lookup computation to 4 entries unsigned passphraseCount = 0; @@ -5247,9 +5321,9 @@ TEST_F(WalletsTest, SignPassphrase_Test) auto& privkey = assetWlt->getDecryptedValue(asset_single->getPrivKey()); //make sure decrypted privkey is valid - auto&& chaincode = BtcUtils::computeChainCode_Armory135(wltRoot); + auto&& chaincode = BtcUtils::computeChainCode_Armory135(rawEntropy); auto&& privkey_ex = - CryptoECDSA().ComputeChainedPrivateKey(wltRoot, chaincode); + CryptoECDSA().ComputeChainedPrivateKey(rawEntropy, chaincode); ASSERT_EQ(privkey, privkey_ex); } @@ -5265,13 +5339,16 @@ TEST_F(WalletsTest, SignPassphrase_Test) TEST_F(WalletsTest, WrongPassphrase_BIP32_Test) { //create wallet from priv key - auto&& wltRoot = CryptoPRNG::generateRandom(32); + auto rawEntropy = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - wltRoot, //root as a r value + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), //root as a r value SecureBinaryData::fromString("test"), //set passphrase to "test" SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries unsigned passphraseCount = 0; @@ -5336,7 +5413,7 @@ TEST_F(WalletsTest, WrongPassphrase_BIP32_Test) //make sure decrypted privkey is valid BIP32_Node node; - node.initFromSeed(wltRoot); + node.initFromSeed(rawEntropy); node.derivePrivate(0x8000002C); node.derivePrivate(0x80000000); @@ -5407,7 +5484,7 @@ TEST_F(WalletsTest, WrongPassphrase_BIP32_Test) //make sure decrypted privkey is valid BIP32_Node node; - node.initFromSeed(wltRoot); + node.initFromSeed(rawEntropy); for (auto& der : derPath2) node.derivePrivate(der); @@ -5428,18 +5505,20 @@ TEST_F(WalletsTest, WrongPassphrase_BIP32_Test) TEST_F(WalletsTest, ChangePassphrase_Test) { //create wallet from priv key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, //root as a r value - {}, + auto rawEntropy = CryptoPRNG::generateRandom(32); + + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135(rawEntropy)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("test"), //set passphrase to "test" controlPass_, + homedir_, 4); //set lookup computation to 4 entries - auto&& chaincode = BtcUtils::computeChainCode_Armory135(wltRoot); + auto&& chaincode = BtcUtils::computeChainCode_Armory135(rawEntropy); auto&& privkey_ex = - CryptoECDSA().ComputeChainedPrivateKey(wltRoot, chaincode); + CryptoECDSA().ComputeChainedPrivateKey(rawEntropy, chaincode); auto filename = assetWlt->getDbFilename(); @@ -5734,18 +5813,20 @@ TEST_F(WalletsTest, ChangePassphrase_Test) TEST_F(WalletsTest, ChangePassphrase_FromUnencryptedWallet_Test) { //create wallet from priv key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, //root as a r value - {}, + auto rawEntropy = CryptoPRNG::generateRandom(32); + + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135(rawEntropy)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData(), //set passphrase to "test" SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries - auto&& chaincode = BtcUtils::computeChainCode_Armory135(wltRoot); + auto&& chaincode = BtcUtils::computeChainCode_Armory135(rawEntropy); auto&& privkey_ex = - CryptoECDSA().ComputeChainedPrivateKey(wltRoot, chaincode); + CryptoECDSA().ComputeChainedPrivateKey(rawEntropy, chaincode); auto filename = assetWlt->getDbFilename(); auto newPass = SecureBinaryData::fromString("newpass"); @@ -5937,18 +6018,20 @@ TEST_F(WalletsTest, ChangePassphrase_FromUnencryptedWallet_Test) //////////////////////////////////////////////////////////////////////////////// TEST_F(WalletsTest, ChangeControlPassphrase_Test) { - + auto&& newPass = SecureBinaryData::fromString("newpass"); //create wallet string filename; { - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - wltRoot, //root as a r value + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), //root as a r value SecureBinaryData::fromString("test"), //set passphrase to "test" SecureBinaryData::fromString("control"), + homedir_, 40); //set lookup computation to 4 entries filename = assetWlt->getDbFilename(); @@ -6092,13 +6175,13 @@ TEST_F(WalletsTest, ChangeControlPassphrase_Test) TEST_F(WalletsTest, MultiplePassphrase_Test) { //create wallet from priv key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("test"), //set passphrase to "test" controlPass_, + homedir_, 4); //set lookup computation to 4 entries auto passLbd1 = [](const set&)->SecureBinaryData @@ -6201,9 +6284,14 @@ TEST_F(WalletsTest, BIP32_Chain) account->setMain(true); account->setAddressLookup(4); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, wltSeed, - SecureBinaryData::fromString("test"), controlPass_); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + wltSeed, Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), + SecureBinaryData::fromString("test"), + controlPass_, + homedir_); { auto passphraseLbd = [] @@ -6282,7 +6370,7 @@ TEST_F(WalletsTest, BIP32_Public_Chain) ASSERT_NE(assetSingle, nullptr); BIP32_Node pubNode; - auto&& pub_b58 = + auto&& pub_b58 = SecureBinaryData::fromString("xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"); pubNode.initFromBase58(pub_b58); @@ -6292,19 +6380,22 @@ TEST_F(WalletsTest, BIP32_Public_Chain) //////////////////////////////////////////////////////////////////////////////// TEST_F(WalletsTest, BIP32_ArmoryDefault) { - vector derivationPath = + vector derivationPath = { 0x8000002C, 0x80000000, 0x80000000 }; - auto&& seed = CryptoPRNG::generateRandom(32); + auto rawEntropy = CryptoPRNG::generateRandom(32); //create empty wallet auto&& passphrase = SecureBinaryData::fromString("password"); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, seed, passphrase, controlPass_, 5); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_, 5); auto mainAcc = assetWlt->getAccountForID(assetWlt->getMainAccountID()); auto outerAcc = mainAcc->getOuterAccount(); @@ -6312,7 +6403,7 @@ TEST_F(WalletsTest, BIP32_ArmoryDefault) outerAcc->getRoot()); BIP32_Node node; - node.initFromSeed(seed); + node.initFromSeed(rawEntropy); for (auto id : derivationPath) node.derivePrivate(id); node.derivePrivate(0); @@ -6344,12 +6435,15 @@ TEST_F(WalletsTest, BIP32_Chain_AddAccount) }; //random seed - auto&& seed = CryptoPRNG::generateRandom(32); + auto rawEntropy = CryptoPRNG::generateRandom(32); //create empty wallet - auto&& passphrase = SecureBinaryData::fromString("password"); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, seed, passphrase, controlPass_); + auto passphrase = SecureBinaryData::fromString("password"); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_); //this is a hard derivation scenario, the wallet needs to be able to //decrypt its root's private key @@ -6372,7 +6466,7 @@ TEST_F(WalletsTest, BIP32_Chain_AddAccount) //derive bip32 node BIP32_Node seedNode; - seedNode.initFromSeed(seed); + seedNode.initFromSeed(rawEntropy); for (auto& derId : derivationPath1) seedNode.derivePrivate(derId); @@ -6441,7 +6535,7 @@ TEST_F(WalletsTest, BIP32_Chain_AddAccount) auto accountID2 = assetWlt->createBIP32Account(accountTypePtr); BIP32_Node seedNode2; - seedNode2.initFromSeed(seed); + seedNode2.initFromSeed(rawEntropy); for (auto& derId : derivationPath2) seedNode2.derivePrivate(derId); seedNode2.derivePrivate(50); @@ -6530,9 +6624,11 @@ TEST_F(WalletsTest, BIP32_Fork_WatchingOnly) auto&& passphrase = SecureBinaryData::fromString("password"); //create regular wallet - auto&& seed = CryptoPRNG::generateRandom(32); - auto wlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, seed, passphrase, controlPass_, 10); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_, 10); //create WO copy auto woCopyPath = AssetWallet::forkWatchingOnly( @@ -6622,13 +6718,16 @@ TEST_F(WalletsTest, BIP32_WatchingOnly_FromXPub) auto&& passphrase = SecureBinaryData::fromString("password"); //create regular wallet - auto&& seed = CryptoPRNG::generateRandom(32); - auto wlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, seed, passphrase, controlPass_, 10); + auto rawEntropy = CryptoPRNG::generateRandom(32); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Structured)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_, 10); //get xpub for main account BIP32_Node seedNode; - seedNode.initFromSeed(seed); + seedNode.initFromSeed(rawEntropy); auto seedFingerprint = seedNode.getThisFingerprint(); for (auto& derId : derPath) seedNode.derivePrivate(derId); @@ -6692,9 +6791,11 @@ TEST_F(WalletsTest, AddressEntryTypes) auto&& passphrase = SecureBinaryData::fromString("password"); //create regular wallet - auto&& seed = CryptoPRNG::generateRandom(32); - auto wlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, seed, passphrase, controlPass_, 10); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_, 10); //grab a bunch of addresses of various types set addrHashes; @@ -6772,9 +6873,12 @@ TEST_F(WalletsTest, LegacyUncompressedAddressTypes) auto&& passphrase = SecureBinaryData::fromString("password"); //create regular wallet - auto&& seed = CryptoPRNG::generateRandom(32); - auto wlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, seed, passphrase, controlPass_); + auto rawEntropy = CryptoPRNG::generateRandom(32); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Virgin)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_); //create account with all common uncompressed address types & their //compressed counterparts @@ -6814,7 +6918,7 @@ TEST_F(WalletsTest, LegacyUncompressedAddressTypes) //derive the keys locally and reproduce the addresses BIP32_Node bip32Node; - bip32Node.initFromSeed(seed); + bip32Node.initFromSeed(rawEntropy); for (auto& der : derPath) bip32Node.derivePrivate(der); bip32Node.derivePublic(0); //spender leaf @@ -6884,9 +6988,9 @@ TEST_F(WalletsTest, BIP32_SaltedAccount) 327 }; - auto&& seed = CryptoPRNG::generateRandom(32); - auto&& salt1 = CryptoPRNG::generateRandom(32); - auto&& salt2 = CryptoPRNG::generateRandom(32); + auto rawEntropy = CryptoPRNG::generateRandom(32); + auto salt1 = CryptoPRNG::generateRandom(32); + auto salt2 = CryptoPRNG::generateRandom(32); string filename; AddressAccountId accountID1; @@ -6896,9 +7000,12 @@ TEST_F(WalletsTest, BIP32_SaltedAccount) { //create empty wallet + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Virgin)); auto&& passphrase = SecureBinaryData::fromString("password"); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, seed, passphrase, controlPass_); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_); auto rootbip32 = dynamic_pointer_cast( assetWlt->getRoot()); @@ -6944,7 +7051,7 @@ TEST_F(WalletsTest, BIP32_SaltedAccount) //derive from seed { BIP32_Node seedNode; - seedNode.initFromSeed(seed); + seedNode.initFromSeed(rawEntropy); for (auto& derId : derivationPath1) seedNode.derivePrivate(derId); @@ -6961,7 +7068,7 @@ TEST_F(WalletsTest, BIP32_SaltedAccount) { BIP32_Node seedNode; - seedNode.initFromSeed(seed); + seedNode.initFromSeed(rawEntropy); for (auto& derId : derivationPath2) seedNode.derivePrivate(derId); @@ -7002,7 +7109,7 @@ TEST_F(WalletsTest, BIP32_SaltedAccount) //derive from seed { BIP32_Node seedNode; - seedNode.initFromSeed(seed); + seedNode.initFromSeed(rawEntropy); for (auto& derId : derivationPath1) seedNode.derivePrivate(derId); @@ -7019,7 +7126,7 @@ TEST_F(WalletsTest, BIP32_SaltedAccount) { BIP32_Node seedNode; - seedNode.initFromSeed(seed); + seedNode.initFromSeed(rawEntropy); for (auto& derId : derivationPath2) seedNode.derivePrivate(derId); @@ -7064,7 +7171,7 @@ TEST_F(WalletsTest, BIP32_SaltedAccount) //derive from seed { BIP32_Node seedNode; - seedNode.initFromSeed(seed); + seedNode.initFromSeed(rawEntropy); for (auto& derId : derivationPath1) seedNode.derivePrivate(derId); @@ -7081,7 +7188,7 @@ TEST_F(WalletsTest, BIP32_SaltedAccount) { BIP32_Node seedNode; - seedNode.initFromSeed(seed); + seedNode.initFromSeed(rawEntropy); for (auto& derId : derivationPath2) seedNode.derivePrivate(derId); @@ -7104,8 +7211,6 @@ TEST_F(WalletsTest, ECDH_Account) //create blank wallet string filename, woFilename; - auto&& seed = CryptoPRNG::generateRandom(32); - auto&& privKey1 = READHEX( "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); auto&& pubKey1 = CryptoECDSA().ComputePublicKey(privKey1, true); @@ -7125,8 +7230,11 @@ TEST_F(WalletsTest, ECDH_Account) { //create empty wallet - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, seed, passphrase, controlPass_); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Virgin)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_); auto passphraseLbd = [&passphrase] (const set&) @@ -7374,7 +7482,7 @@ TEST_F(WalletsTest, ECDH_Account) TEST_F(WalletsTest, AssetPathResolution) { //seed shared across all wallet instances - auto seed = CryptoPRNG::generateRandom(32); + auto rawEntropy = CryptoPRNG::generateRandom(32); vector derPath = { 0x800012ab, @@ -7383,7 +7491,7 @@ TEST_F(WalletsTest, AssetPathResolution) }; BIP32_Node node; - node.initFromSeed(seed); + node.initFromSeed(rawEntropy); auto seedFingerprint = node.getThisFingerprint(); for (auto& step : derPath) @@ -7432,9 +7540,11 @@ TEST_F(WalletsTest, AssetPathResolution) { //empty wallet + custom account - auto wlt = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir_, seed, - SecureBinaryData(), SecureBinaryData()); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + rawEntropy, Armory::Seeds::SeedType::BIP32_Virgin)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), {}, {}, homedir_); auto account = wlt->makeNewBip32AccTypeObject(derPath); account->setMain(true); @@ -7472,26 +7582,9 @@ TEST_F(WalletsTest, AssetPathResolution) //empty WO wallet auto wltWO = AssetWallet_Single::createBlank(homedir_, "walletWO1", {}); - auto pubkey = pubNode.getPublicKey(); - auto chaincode = pubNode.getChaincode(); - - auto pubRootAsset = make_shared( - AssetId(0, 0, 0), //not relevant, this stuff is ignored in this context - - pubkey, //pub key - nullptr, //no priv key, this is a public node - chaincode, //have to pass the chaincode too - - //aesthetical stuff, not mandatory, not useful for the crypto side of things - pubNode.getDepth(), pubNode.getLeafID(), pubNode.getParentFingerprint(), seedFingerprint, - - //derivation path for this root, used for path discovery & PSBT - derPath - ); - //add account - auto mainAccType = - AccountType_BIP32::makeFromDerPaths(seedFingerprint, {derPath}); + auto mainAccType = AccountType_BIP32::makeFromDerPaths( + seedFingerprint, {derPath}); mainAccType->setMain(true); mainAccType->setAddressLookup(10); mainAccType->setNodes({0}); @@ -7515,9 +7608,11 @@ TEST_F(WalletsTest, isAssetIdInUse) auto passphrase = SecureBinaryData::fromString("password"); //create regular wallet - auto seed = CryptoPRNG::generateRandom(32); - auto wlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, seed, passphrase, controlPass_, 10); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass_, homedir_, 10); //grab a bunch of addresses of various types map addrHashesInUse; @@ -8330,9 +8425,11 @@ TEST_F(WalletMetaDataTest, Comments) //create regular wallet string filename; { - auto&& seed = CryptoPRNG::generateRandom(32); - auto wlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, seed, passphrase, controlPass, 10); + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto wlt = AssetWallet_Single::createFromSeed( + move(seed), passphrase, controlPass, homedir_, 10); filename = wlt->getDbFilename(); //set comments @@ -8402,7 +8499,8 @@ class BackupTests : public ::testing::Test Armory::Config::parseArgs({ "--offline", - "--datadir=./fakehomedir" }, + "--datadir=./fakehomedir", + "--testnet" }, Armory::Config::ProcessType::DB); } @@ -8417,7 +8515,7 @@ class BackupTests : public ::testing::Test ///////////////////////////////////////////////////////////////////////////// bool compareWalletWithBackup( std::shared_ptr assetWlt, - const string& path, + const string& path, const SecureBinaryData& pass, const SecureBinaryData& control) { unsigned controlPassCount = 0; @@ -8494,19 +8592,19 @@ class BackupTests : public ::testing::Test //////////////////////////////////////////////////////////////////////////////// TEST_F(BackupTests, Easy16) { - for (const auto& index : Armory::Seeds::BackupEasy16::eligibleIndexes_) + for (const auto& index : Armory::Seeds::Easy16Codec::eligibleIndexes_) { auto root = CryptoPRNG::generateRandom(32); //encode the root - auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), index); + auto encoded = Armory::Seeds::Easy16Codec::encode(root.getRef(), index); ASSERT_EQ(encoded.size(), 2ULL); - auto decoded = Armory::Seeds::BackupEasy16::decode(encoded); + auto decoded = Armory::Seeds::Easy16Codec::decode(encoded); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); - EXPECT_EQ(decoded.checksumIndexes_[0], index); - EXPECT_EQ(decoded.checksumIndexes_[1], index); + EXPECT_EQ(decoded.checksumIndexes_[0], (uint8_t)index); + EXPECT_EQ(decoded.checksumIndexes_[1], (uint8_t)index); EXPECT_EQ(decoded.data_, root); } @@ -8516,7 +8614,7 @@ TEST_F(BackupTests, Easy16) TEST_F(BackupTests, Easy16_Repair) { /*NOTE: this test will lead to a lot of hashing*/ - auto corruptLine = [](vector& lines, + auto corruptLine = [](vector& lines, uint8_t lineSelect, uint8_t wordSelect, uint8_t charSelect, uint8_t newVal) { auto& line = lines[lineSelect]; @@ -8530,7 +8628,7 @@ TEST_F(BackupTests, Easy16_Repair) char newChar; while (true) { - newChar = Armory::Seeds::BackupEasy16::e16chars_[newVal % 16]; + newChar = Armory::Seeds::Easy16Codec::e16chars_[newVal % 16]; if (newChar != val) break; @@ -8547,9 +8645,10 @@ TEST_F(BackupTests, Easy16_Repair) for (unsigned i=0; i<64; i++) { auto root = prng.generateRandom(32); - + //encode the root - auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), 0); + auto encoded = Armory::Seeds::Easy16Codec::encode(root.getRef(), + Armory::Seeds::BackupType::Armory135); ASSERT_EQ(encoded.size(), 2ULL); //corrupt one character in one line @@ -8565,7 +8664,7 @@ TEST_F(BackupTests, Easy16_Repair) ASSERT_NE(encoded[lineSelect], corrupted[lineSelect]); //decode the corrupted data, should yield an incorrect value - auto decoded = Armory::Seeds::BackupEasy16::decode(corrupted); + auto decoded = Armory::Seeds::Easy16Codec::decode(corrupted); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); if (lineSelect == 0) { @@ -8583,7 +8682,7 @@ TEST_F(BackupTests, Easy16_Repair) //attempt to repair, may fail because of collisions (no unique solution) try { - auto result = Armory::Seeds::BackupEasy16::repair(decoded); + auto result = Armory::Seeds::Easy16Codec::repair(decoded); if (result) { ASSERT_EQ(decoded.repairedIndexes_.size(), 2ULL); @@ -8606,7 +8705,8 @@ TEST_F(BackupTests, Easy16_Repair) auto root = prng.generateRandom(32); //encode the root - auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), 0); + auto encoded = Armory::Seeds::Easy16Codec::encode(root.getRef(), + Armory::Seeds::BackupType::Armory135); ASSERT_EQ(encoded.size(), 2ULL); //corrupt 2 characters in one line @@ -8629,7 +8729,7 @@ TEST_F(BackupTests, Easy16_Repair) ASSERT_NE(encoded[lineSelect], corrupted[lineSelect]); //decode, should yield an incorrect value - auto decoded = Armory::Seeds::BackupEasy16::decode(corrupted); + auto decoded = Armory::Seeds::Easy16Codec::decode(corrupted); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); if (lineSelect == 0) { @@ -8645,7 +8745,7 @@ TEST_F(BackupTests, Easy16_Repair) EXPECT_NE(root, decoded.data_); //attempt to repair, should fail - auto result = Armory::Seeds::BackupEasy16::repair(decoded); + auto result = Armory::Seeds::Easy16Codec::repair(decoded); if (result) { EXPECT_NE(decoded.data_, root); @@ -8659,7 +8759,8 @@ TEST_F(BackupTests, Easy16_Repair) auto root = prng.generateRandom(32); //encode the root - auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), 0); + auto encoded = Armory::Seeds::Easy16Codec::encode(root.getRef(), + Armory::Seeds::BackupType::Armory135); ASSERT_EQ(encoded.size(), 2ULL); //corrupt 1 character per line @@ -8678,7 +8779,7 @@ TEST_F(BackupTests, Easy16_Repair) corruptLine(corrupted, 1, wordSelect2, charSelect2, newVal2); //decode, should yield an incorrect value - auto decoded = Armory::Seeds::BackupEasy16::decode(corrupted); + auto decoded = Armory::Seeds::Easy16Codec::decode(corrupted); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); EXPECT_NE(decoded.checksumIndexes_[0], 0); EXPECT_NE(decoded.checksumIndexes_[1], 0); @@ -8686,7 +8787,7 @@ TEST_F(BackupTests, Easy16_Repair) //attempt to repair, may fail because of collisions (no evident solution) try { - auto result = Armory::Seeds::BackupEasy16::repair(decoded); + auto result = Armory::Seeds::Easy16Codec::repair(decoded); if (result) { ASSERT_EQ(decoded.repairedIndexes_.size(), 2ULL); @@ -8806,13 +8907,14 @@ TEST_F(BackupTests, SecurePrint) TEST_F(BackupTests, BackupStrings_Legacy) { //create a legacy wallet - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135( + Armory::Seeds::ClearTextSeed_Armory135::LegacyType::Armory135)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries auto passLbd = [](const set&)->SecureBinaryData @@ -8820,43 +8922,135 @@ TEST_F(BackupTests, BackupStrings_Legacy) return SecureBinaryData::fromString("passphrase"); }; assetWlt->setPassphrasePromptLambda(passLbd); - auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); + auto backupEasy16 = dynamic_cast(backupData.get()); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Seeds::RestorePromptType promptType, - const vector checksums, SecureBinaryData& extra)->bool + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply { - switch (promptType) + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) { - case Armory::Seeds::RestorePromptType::Passphrase: + case BridgeProto::RestorePrompt::kGetPassphrases: { - extra = newPass; - return true; + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass.toCharPtr(), newPass.getSize()); + passphrases->set_control(newCtrl.toCharPtr(), newCtrl.getSize()); + reply.set_success(true); + break; } - case Armory::Seeds::RestorePromptType::Control: + case BridgeProto::RestorePrompt::kCheckWalletId: { - extra = newCtrl; - return true; + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.wallet_id(), backupData->getWalletId()); + + EXPECT_EQ(checkWalleIdMsg.backup_type(), + (int)Armory::Seeds::BackupType::Armory135); + reply.set_success(true); + break; + } + + default: + reply.set_success(false); } + return reply; + }; + + string newHomeDir("./newhomedir"); + DBUtils::removeDirectory(newHomeDir); + mkdir(newHomeDir); + + string filename; + { + //restore wallet + auto backupCopy = Backup_Easy16::fromLines({ + backupEasy16->getRoot(Backup_Easy16::LineIndex::One, false), + backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, false), + }); + auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( + move(backupCopy), newHomeDir, callback); + EXPECT_NE(newWltPtr, nullptr); - case Armory::Seeds::RestorePromptType::Id: + auto passLbd2 = [&newPass](const set&)->SecureBinaryData { - EXPECT_EQ(extra, SecureBinaryData::fromString(backupData.wltId_)); - - EXPECT_EQ(checksums.size(), 2ULL); - for (const auto& chksum : checksums) - EXPECT_EQ(chksum, 0); + return newPass; + }; + newWltPtr->setPassphrasePromptLambda(passLbd2); - return true; + auto newWalletSingle = dynamic_pointer_cast(newWltPtr); + auto backupData2 = Armory::Seeds::Helpers::getWalletBackup(newWalletSingle); + auto backupEasy16_2 = dynamic_cast(backupData2.get()); + + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::One, false), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::One, false)); + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, false), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::Two, false)); + + EXPECT_EQ(backupEasy16->getWalletId(), backupEasy16_2->getWalletId()); + + filename = newWltPtr->getDbFilename(); + } + + EXPECT_TRUE(compareWalletWithBackup(assetWlt, filename, newPass, newCtrl)); + DBUtils::removeDirectory(newHomeDir); +} + +//////////////////////////////////////////////////////////////////////////////// +TEST_F(BackupTests, BackupStrings_Legacy_Armory200a) +{ + //create a legacy wallet + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), + SecureBinaryData::fromString("passphrase"), + SecureBinaryData::fromString("control"), + homedir_, + 4); //set lookup computation to 4 entries + + auto passLbd = [](const set&)->SecureBinaryData + { + return SecureBinaryData::fromString("passphrase"); + }; + assetWlt->setPassphrasePromptLambda(passLbd); + auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); + auto backupEasy16 = dynamic_cast(backupData.get()); + + auto newPass = CryptoPRNG::generateRandom(10); + auto newCtrl = CryptoPRNG::generateRandom(10); + auto callback = [&backupData, &newPass, &newCtrl]( + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply + { + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) + { + case BridgeProto::RestorePrompt::kGetPassphrases: + { + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass.toCharPtr(), newPass.getSize()); + passphrases->set_control(newCtrl.toCharPtr(), newCtrl.getSize()); + reply.set_success(true); + break; + } + + case BridgeProto::RestorePrompt::kCheckWalletId: + { + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.wallet_id(), backupData->getWalletId()); + + EXPECT_EQ(checkWalleIdMsg.backup_type(), + (int)Armory::Seeds::BackupType::Armory200a); + reply.set_success(true); + break; } default: - return false; + reply.set_success(false); } + return reply; }; string newHomeDir("./newhomedir"); @@ -8866,10 +9060,31 @@ TEST_F(BackupTests, BackupStrings_Legacy) string filename; { //restore wallet + auto backupCopy = Backup_Easy16::fromLines({ + backupEasy16->getRoot(Backup_Easy16::LineIndex::One, false), + backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, false), + }); auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( - backupData.rootClear_, {}, newHomeDir, callback); + move(backupCopy), newHomeDir, callback); EXPECT_NE(newWltPtr, nullptr); - + + auto passLbd2 = [&newPass](const set&)->SecureBinaryData + { + return newPass; + }; + newWltPtr->setPassphrasePromptLambda(passLbd2); + + auto newWalletSingle = dynamic_pointer_cast(newWltPtr); + auto backupData2 = Armory::Seeds::Helpers::getWalletBackup(newWalletSingle); + auto backupEasy16_2 = dynamic_cast(backupData2.get()); + + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::One, false), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::One, false)); + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, false), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::Two, false)); + + EXPECT_EQ(backupEasy16->getWalletId(), backupEasy16_2->getWalletId()); + filename = newWltPtr->getDbFilename(); } @@ -8877,18 +9092,18 @@ TEST_F(BackupTests, BackupStrings_Legacy) DBUtils::removeDirectory(newHomeDir); } - //////////////////////////////////////////////////////////////////////////////// TEST_F(BackupTests, BackupStrings_Legacy_SecurePrint) { //create a legacy wallet - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135( + Armory::Seeds::ClearTextSeed_Armory135::LegacyType::Armory135)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries auto passLbd = [](const set&)->SecureBinaryData @@ -8898,42 +9113,38 @@ TEST_F(BackupTests, BackupStrings_Legacy_SecurePrint) assetWlt->setPassphrasePromptLambda(passLbd); auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); + auto backupEasy16 = dynamic_cast(backupData.get()); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Seeds::RestorePromptType promptType, - const vector checksums, SecureBinaryData& extra)->bool + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply { - switch (promptType) + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) { - case Armory::Seeds::RestorePromptType::Passphrase: + case BridgeProto::RestorePrompt::kGetPassphrases: { - extra = newPass; - return true; + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass.toCharPtr(), newPass.getSize()); + passphrases->set_control(newCtrl.toCharPtr(), newCtrl.getSize()); + reply.set_success(true); + break; } - case Armory::Seeds::RestorePromptType::Control: + case BridgeProto::RestorePrompt::kCheckWalletId: { - extra = newCtrl; - return true; - } - - case Armory::Seeds::RestorePromptType::Id: - { - if (extra != SecureBinaryData::fromString(backupData.wltId_)) - return false; - - EXPECT_EQ(checksums.size(), 2ULL); - for (const auto& chksum : checksums) - EXPECT_EQ(chksum, 0); - - return true; + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.backup_type(), + (int)Armory::Seeds::BackupType::Armory135); + reply.set_success(checkWalleIdMsg.wallet_id() == backupData->getWalletId()); + break; } default: - return false; + reply.set_success(false); } + return reply; }; string newHomeDir("./newhomedir"); @@ -8945,8 +9156,12 @@ TEST_F(BackupTests, BackupStrings_Legacy_SecurePrint) //try without sp pass try { + auto backupCopy = Backup_Easy16::fromLines({ + backupEasy16->getRoot(Backup_Easy16::LineIndex::One, true), + backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, true), + }); Armory::Seeds::Helpers::restoreFromBackup( - backupData.rootEncr_, {}, newHomeDir, callback); + move(backupCopy), newHomeDir, callback); ASSERT_TRUE(false); } catch (const Armory::Seeds::RestoreUserException& e) @@ -8955,10 +9170,30 @@ TEST_F(BackupTests, BackupStrings_Legacy_SecurePrint) } //try with secure print now + auto backupCopy = Backup_Easy16::fromLines({ + backupEasy16->getRoot(Backup_Easy16::LineIndex::One, true), + backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, true)}, + backupEasy16->getSpPass()); auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( - backupData.rootEncr_, backupData.spPass_, newHomeDir, callback); + move(backupCopy), newHomeDir, callback); EXPECT_NE(newWltPtr, nullptr); - + + auto passLbd2 = [&newPass](const set&)->SecureBinaryData + { + return newPass; + }; + newWltPtr->setPassphrasePromptLambda(passLbd2); + + auto newWalletSingle = dynamic_pointer_cast(newWltPtr); + auto backupData2 = Armory::Seeds::Helpers::getWalletBackup(newWalletSingle); + auto backupEasy16_2 = dynamic_cast(backupData2.get()); + + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::One, true), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::One, true)); + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, true), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::Two, true)); + EXPECT_EQ(backupEasy16->getWalletId(), backupEasy16_2->getWalletId()); + filename = newWltPtr->getDbFilename(); } @@ -8970,7 +9205,7 @@ TEST_F(BackupTests, BackupStrings_Legacy_SecurePrint) TEST_F(BackupTests, Easy16_AutoRepair) { /*NOTE: this test will lead to a lot of hashing*/ - auto corruptLine = [](vector& lines, + auto corruptLine = [](vector& lines, uint8_t lineSelect, uint8_t wordSelect, uint8_t charSelect, uint8_t newVal) { auto& line = lines[lineSelect]; @@ -8984,7 +9219,7 @@ TEST_F(BackupTests, Easy16_AutoRepair) char newChar; while (true) { - newChar = Armory::Seeds::BackupEasy16::e16chars_[newVal % 16]; + newChar = Armory::Seeds::Easy16Codec::e16chars_[newVal % 16]; if (newChar != val) break; @@ -9003,7 +9238,15 @@ TEST_F(BackupTests, Easy16_AutoRepair) auto asset_single = make_shared( AssetId::getRootAssetId(), pubkey, nullptr); - return AssetWallet_Single::computeWalletID(derScheme, asset_single); + auto addrVec = derScheme->extendPublicChain(asset_single, 1, 1, nullptr); + if (addrVec.size() != 1) + throw runtime_error("unexpected chain derivation output"); + + auto firstEntry = dynamic_pointer_cast(addrVec[0]); + if (firstEntry == nullptr) + throw runtime_error("unexpected asset entry type"); + + return BtcUtils::computeID(firstEntry->getPubKey()->getUncompressedKey()); }; PRNG_Fortuna prng; @@ -9016,7 +9259,7 @@ TEST_F(BackupTests, Easy16_AutoRepair) auto wltID = computeWalletID(root); //encode the root - auto encoded = Armory::Seeds::BackupEasy16::encode(root.getRef(), 0); + auto encoded = Easy16Codec::encode(root.getRef(), BackupType::Armory135); ASSERT_EQ(encoded.size(), 2ULL); //corrupt one character in one line @@ -9032,7 +9275,7 @@ TEST_F(BackupTests, Easy16_AutoRepair) ASSERT_NE(encoded[lineSelect], corrupted[lineSelect]); //decode the corrupted data, should yield an incorrect value - auto decoded = Armory::Seeds::BackupEasy16::decode(corrupted); + auto decoded = Easy16Codec::decode(corrupted); ASSERT_EQ(decoded.checksumIndexes_.size(), 2ULL); if (lineSelect == 0) { @@ -9051,35 +9294,42 @@ TEST_F(BackupTests, Easy16_AutoRepair) try { auto userPrompt = [&wltID, &decoded, &succesfulRepairs]( - Armory::Seeds::RestorePromptType promptType, - const vector& chksumIndexes, - SecureBinaryData& extra)->bool + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply { - switch (promptType) + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) { - case Armory::Seeds::RestorePromptType::ChecksumError: + case BridgeProto::RestorePrompt::kChecksumError: { - EXPECT_EQ(chksumIndexes, decoded.checksumIndexes_); - return false; + EXPECT_EQ(prompt.checksum_error().index(0), decoded.checksumIndexes_[0]); + EXPECT_EQ(prompt.checksum_error().index(1), decoded.checksumIndexes_[1]); + reply.set_success(false); + break; } - case Armory::Seeds::RestorePromptType::Id: + case BridgeProto::RestorePrompt::kCheckWalletId: { - EXPECT_EQ(chksumIndexes, decoded.checksumIndexes_); - string extraStr(extra.toCharPtr(), extra.getSize()); - if (extraStr == wltID) + EXPECT_EQ(prompt.check_wallet_id().backup_type(), + (int)BackupType::Armory135); + if (prompt.check_wallet_id().wallet_id() == wltID) ++succesfulRepairs; - return false; + reply.set_success(false); + break; } default: - return true; + reply.set_success(true); } + return reply; }; + auto backup = Backup_Easy16::fromLines({ + string_view(corrupted[0].toCharPtr(), corrupted[0].getSize()), + string_view(corrupted[1].toCharPtr(), corrupted[1].getSize()) + }); Armory::Seeds::Helpers::restoreFromBackup( - corrupted, BinaryDataRef(), string(), userPrompt); + move(backup), homedir_, userPrompt); } catch (const exception&) {} @@ -9092,14 +9342,14 @@ TEST_F(BackupTests, Easy16_AutoRepair) TEST_F(BackupTests, BackupStrings_LegacyWithChaincode_SecurePrint) { //create a legacy wallet - auto wltRoot = CryptoPRNG::generateRandom(32); - auto chaincode = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - wltRoot, //root as a r value - chaincode, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135( + CryptoPRNG::generateRandom(32), CryptoPRNG::generateRandom(32))); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), //root as a r value SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries auto passLbd = [](const set&)->SecureBinaryData @@ -9109,42 +9359,38 @@ TEST_F(BackupTests, BackupStrings_LegacyWithChaincode_SecurePrint) assetWlt->setPassphrasePromptLambda(passLbd); auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); + auto backupEasy16 = dynamic_cast(backupData.get()); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Seeds::RestorePromptType promptType, - const vector checksums, SecureBinaryData& extra)->bool + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply { - switch (promptType) + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) { - case Armory::Seeds::RestorePromptType::Passphrase: + case BridgeProto::RestorePrompt::kGetPassphrases: { - extra = newPass; - return true; - } - - case Armory::Seeds::RestorePromptType::Control: - { - extra = newCtrl; - return true; + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass.toCharPtr(), newPass.getSize()); + passphrases->set_control(newCtrl.toCharPtr(), newCtrl.getSize()); + reply.set_success(true); + break; } - case Armory::Seeds::RestorePromptType::Id: + case BridgeProto::RestorePrompt::kCheckWalletId: { - if (extra != SecureBinaryData::fromString(backupData.wltId_)) - return false; - - EXPECT_EQ(checksums.size(), 4ULL); - for (const auto& chksum : checksums) - EXPECT_EQ(chksum, 0); - - return true; + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.backup_type(), + (int)Armory::Seeds::BackupType::Armory135); + reply.set_success(checkWalleIdMsg.wallet_id() == backupData->getWalletId()); + break; } default: - return false; + reply.set_success(false); } + return reply; }; string newHomeDir("./newhomedir"); @@ -9153,21 +9399,17 @@ TEST_F(BackupTests, BackupStrings_LegacyWithChaincode_SecurePrint) string filename; { - vector rootData; - auto insertVector = [&rootData](const vector& vec) - { - for (const auto& str : vec) - rootData.emplace_back((const uint8_t*)str.c_str(), str.size()); - }; - - insertVector(backupData.rootEncr_); - insertVector(backupData.chaincodeEncr_); - //try without sp pass try { + auto backupCopy = Backup_Easy16::fromLines({ + backupEasy16->getRoot(Backup_Easy16::LineIndex::One, true), + backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, true), + backupEasy16->getChaincode(Backup_Easy16::LineIndex::One, true), + backupEasy16->getChaincode(Backup_Easy16::LineIndex::Two, true), + }); Armory::Seeds::Helpers::restoreFromBackup( - backupData.rootEncr_, {}, newHomeDir, callback); + move(backupCopy), newHomeDir, callback); ASSERT_TRUE(false); } catch (const Armory::Seeds::RestoreUserException& e) @@ -9176,10 +9418,39 @@ TEST_F(BackupTests, BackupStrings_LegacyWithChaincode_SecurePrint) } //try with secure print now + auto backupCopy = Backup_Easy16::fromLines({ + backupEasy16->getRoot(Backup_Easy16::LineIndex::One, true), + backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, true), + backupEasy16->getChaincode(Backup_Easy16::LineIndex::One, true), + backupEasy16->getChaincode(Backup_Easy16::LineIndex::Two, true)}, + backupEasy16->getSpPass() + ); auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( - rootData, backupData.spPass_, newHomeDir, callback); + move(backupCopy), newHomeDir, callback); EXPECT_NE(newWltPtr, nullptr); - + + auto passLbd2 = [&newPass](const set&)->SecureBinaryData + { + return newPass; + }; + newWltPtr->setPassphrasePromptLambda(passLbd2); + + auto newWalletSingle = dynamic_pointer_cast(newWltPtr); + auto backupData2 = Armory::Seeds::Helpers::getWalletBackup(newWalletSingle); + auto backupEasy16_2 = dynamic_cast(backupData2.get()); + + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::One, true), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::One, true)); + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, true), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::Two, true)); + + EXPECT_EQ(backupEasy16->getChaincode(Backup_Easy16::LineIndex::One, true), + backupEasy16_2->getChaincode(Backup_Easy16::LineIndex::One, true)); + EXPECT_EQ(backupEasy16->getChaincode(Backup_Easy16::LineIndex::Two, true), + backupEasy16_2->getChaincode(Backup_Easy16::LineIndex::Two, true)); + + EXPECT_EQ(backupEasy16->getWalletId(), backupEasy16_2->getWalletId()); + filename = newWltPtr->getDbFilename(); } @@ -9191,12 +9462,14 @@ TEST_F(BackupTests, BackupStrings_LegacyWithChaincode_SecurePrint) TEST_F(BackupTests, BackupStrings_BIP32) { //create a legacy wallet - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - move(wltRoot), //root as a r value + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Structured)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), //root as a r value SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries auto passLbd = [](const set&)->SecureBinaryData @@ -9206,41 +9479,40 @@ TEST_F(BackupTests, BackupStrings_BIP32) assetWlt->setPassphrasePromptLambda(passLbd); auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); + auto backupEasy16 = dynamic_cast(backupData.get()); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Seeds::RestorePromptType promptType, - const vector checksums, SecureBinaryData& extra)->bool + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply { - switch (promptType) + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) { - case Armory::Seeds::RestorePromptType::Passphrase: + case BridgeProto::RestorePrompt::kGetPassphrases: { - extra = newPass; - return true; + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass.toCharPtr(), newPass.getSize()); + passphrases->set_control(newCtrl.toCharPtr(), newCtrl.getSize()); + reply.set_success(true); + break; } - case Armory::Seeds::RestorePromptType::Control: + case BridgeProto::RestorePrompt::kCheckWalletId: { - extra = newCtrl; - return true; - } + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.wallet_id(), backupData->getWalletId()); - case Armory::Seeds::RestorePromptType::Id: - { - EXPECT_EQ(extra, SecureBinaryData::fromString(backupData.wltId_)); - - EXPECT_EQ(checksums.size(), 2U); - for (const auto& chksum : checksums) - EXPECT_EQ(chksum, 1); - - return true; + EXPECT_EQ(checkWalleIdMsg.backup_type(), + (int)Armory::Seeds::BackupType::Armory200b); + reply.set_success(true); + break; } default: - return false; + reply.set_success(false); } + return reply; }; string newHomeDir("./newhomedir"); @@ -9250,10 +9522,31 @@ TEST_F(BackupTests, BackupStrings_BIP32) string filename; { //restore wallet + auto backupCopy = Backup_Easy16::fromLines({ + backupEasy16->getRoot(Backup_Easy16::LineIndex::One, false), + backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, false), + }); auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( - backupData.rootClear_, {}, newHomeDir, callback); - EXPECT_NE(newWltPtr, nullptr); - + move(backupCopy), newHomeDir, callback); + ASSERT_NE(newWltPtr, nullptr); + + auto passLbd2 = [&newPass](const set&)->SecureBinaryData + { + return newPass; + }; + newWltPtr->setPassphrasePromptLambda(passLbd2); + + auto newWalletSingle = dynamic_pointer_cast(newWltPtr); + auto backupData2 = Armory::Seeds::Helpers::getWalletBackup(newWalletSingle); + auto backupEasy16_2 = dynamic_cast(backupData2.get()); + + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::One, false), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::One, false)); + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, false), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::Two, false)); + + EXPECT_EQ(backupEasy16->getWalletId(), backupEasy16_2->getWalletId()); + filename = newWltPtr->getDbFilename(); } @@ -9262,15 +9555,18 @@ TEST_F(BackupTests, BackupStrings_BIP32) } //////////////////////////////////////////////////////////////////////////////// -TEST_F(BackupTests, BackupStrings_BIP32_Custom) +TEST_F(BackupTests, BackupStrings_BIP32_Virgin) { //create a legacy wallet - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromSeed_BIP32( - homedir_, - move(wltRoot), //root as a r value + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_BIP32( + Armory::Seeds::SeedType::BIP32_Virgin + )); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData::fromString("passphrase"), SecureBinaryData::fromString("control"), + homedir_, 4); //set lookup computation to 4 entries auto passLbd = [](const set&)->SecureBinaryData @@ -9279,43 +9575,42 @@ TEST_F(BackupTests, BackupStrings_BIP32_Custom) }; assetWlt->setPassphrasePromptLambda(passLbd); - auto backupData = Armory::Seeds::Helpers::getWalletBackup( - assetWlt, Armory::Seeds::BackupType::BIP32_Seed_Virgin); + auto backupData = Armory::Seeds::Helpers::getWalletBackup(assetWlt); + auto backupEasy16 = dynamic_cast( + backupData.get()); auto newPass = CryptoPRNG::generateRandom(10); auto newCtrl = CryptoPRNG::generateRandom(10); auto callback = [&backupData, &newPass, &newCtrl]( - const Armory::Seeds::RestorePromptType promptType, - const vector checksums, SecureBinaryData& extra)->bool + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply { - switch (promptType) - { - case Armory::Seeds::RestorePromptType::Passphrase: + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) { - extra = newPass; - return true; - } - - case Armory::Seeds::RestorePromptType::Control: + case BridgeProto::RestorePrompt::kGetPassphrases: { - extra = newCtrl; - return true; + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass.toCharPtr(), newPass.getSize()); + passphrases->set_control(newCtrl.toCharPtr(), newCtrl.getSize()); + reply.set_success(true); + break; } - case Armory::Seeds::RestorePromptType::Id: + case BridgeProto::RestorePrompt::kCheckWalletId: { - EXPECT_EQ(extra, SecureBinaryData::fromString(backupData.wltId_)); - - EXPECT_EQ(checksums.size(), 2U); - for (const auto& chksum : checksums) - EXPECT_EQ(chksum, 15); + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.wallet_id(), backupData->getWalletId()); - return true; + EXPECT_EQ(checkWalleIdMsg.backup_type(), + (int)Armory::Seeds::BackupType::Armory200c); + reply.set_success(true); + break; } default: - return false; + reply.set_success(false); } + return reply; }; string newHomeDir("./newhomedir"); @@ -9323,10 +9618,14 @@ TEST_F(BackupTests, BackupStrings_BIP32_Custom) mkdir(newHomeDir); //restore wallet + auto backupCopy = Backup_Easy16::fromLines({ + backupEasy16->getRoot(Backup_Easy16::LineIndex::One, false), + backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, false), + }); auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( - backupData.rootClear_, {}, newHomeDir, callback); - EXPECT_NE(newWltPtr, nullptr); - + move(backupCopy), newHomeDir, callback); + ASSERT_NE(newWltPtr, nullptr); + //check wallet id EXPECT_EQ(assetWlt->getID(), newWltPtr->getID()); @@ -9334,9 +9633,112 @@ TEST_F(BackupTests, BackupStrings_BIP32_Custom) auto loadedIDs = newWltPtr->getAccountIDs(); EXPECT_EQ(loadedIDs.size(), 0ULL); + auto passLbd2 = [&newPass](const set&)->SecureBinaryData + { + return newPass; + }; + newWltPtr->setPassphrasePromptLambda(passLbd2); + + auto newWalletSingle = dynamic_pointer_cast(newWltPtr); + auto backupData2 = Armory::Seeds::Helpers::getWalletBackup(newWalletSingle); + auto backupEasy16_2 = dynamic_cast( + backupData2.get()); + + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::One, false), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::One, false)); + EXPECT_EQ(backupEasy16->getRoot(Backup_Easy16::LineIndex::Two, false), + backupEasy16_2->getRoot(Backup_Easy16::LineIndex::Two, false)); + + EXPECT_EQ(backupEasy16->getWalletId(), backupEasy16_2->getWalletId()); + DBUtils::removeDirectory(newHomeDir); } +//////////////////////////////////////////////////////////////////////////////// +TEST_F(BackupTests, BackupStrings_BIP32_FromBase58) +{ + auto b58seed = string_view{ + "tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF"}; + + auto newPass = CryptoPRNG::generateRandom(10); + auto newCtrl = CryptoPRNG::generateRandom(10); + auto callback = [&newPass, &newCtrl]( + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply + { + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) + { + case BridgeProto::RestorePrompt::kGetPassphrases: + { + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass.toCharPtr(), newPass.getSize()); + passphrases->set_control(newCtrl.toCharPtr(), newCtrl.getSize()); + reply.set_success(true); + break; + } + + case BridgeProto::RestorePrompt::kCheckWalletId: + { + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.wallet_id(), "poUtmfmp"); + + EXPECT_EQ(checkWalleIdMsg.backup_type(), (int)BackupType::Base58); + reply.set_success(true); + break; + } + + default: + reply.set_success(false); + } + return reply; + }; + + //create bip32 wallet from xpriv, check it yields same xpriv + string filename; + { + auto backup = Backup_Base58::fromString(b58seed); + auto wallet = Helpers::restoreFromBackup( + move(backup), homedir_, callback); + ASSERT_NE(wallet, nullptr); + + auto passLbd = [newPass](const set&)->SecureBinaryData + { + return newPass; + }; + wallet->setPassphrasePromptLambda(passLbd); + + auto walletSingle = dynamic_pointer_cast(wallet); + auto backupData = Helpers::getWalletBackup(walletSingle); + auto backupBase58 = dynamic_cast(backupData.get()); + EXPECT_EQ(backupBase58->getBase58String(), b58seed); + filename = wallet->getDbFilename(); + } + + //load wallet from file and check xpriv again + { + auto controlPassLbd = [&newCtrl]( + const set&)->SecureBinaryData + { + return newCtrl; + }; + + //load it, newCtrl should work for the control passphrase + auto loadedWlt = AssetWallet::loadMainWalletFromFile( + filename, controlPassLbd); + ASSERT_NE(loadedWlt, nullptr); + + auto passLbd = [newPass](const set&)->SecureBinaryData + { + return newPass; + }; + loadedWlt->setPassphrasePromptLambda(passLbd); + + auto walletSingle = dynamic_pointer_cast(loadedWlt); + auto backupData = Helpers::getWalletBackup(walletSingle); + auto backupBase58 = dynamic_cast(backupData.get()); + EXPECT_EQ(backupBase58->getBase58String(), b58seed); + } +} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/gtest/ZeroConfTests.cpp b/cppForSwig/gtest/ZeroConfTests.cpp index 792b91898..a3834aa11 100644 --- a/cppForSwig/gtest/ZeroConfTests.cpp +++ b/cppForSwig/gtest/ZeroConfTests.cpp @@ -12,6 +12,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "TestUtils.h" +#include "../Wallets/Seeds/Seeds.h" using namespace std; using namespace Armory::Signer; using namespace Armory::Config; @@ -2364,15 +2365,15 @@ TEST_F(ZeroConfTests_FullNode, Replace_ZC_Test) //// create assetWlt //// //create a root private key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData(), SecureBinaryData(), + homedir_, 10); //set lookup computation to 5 entries - + //register with db vector addrVec; @@ -2877,13 +2878,13 @@ TEST_F(ZeroConfTests_FullNode, RegisterAddress_AfterZC) //// create assetWlt //// //create a root private key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData(), SecureBinaryData(), + homedir_, 3); //set lookup computation to 3 entries //register with db @@ -3095,14 +3096,14 @@ TEST_F(ZeroConfTests_FullNode, ChainZC_RBFchild_Test) //// create assetWlt //// //create a root private key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData(), - SecureBinaryData(), - 10); //set lookup computation to 10 entries + SecureBinaryData(), + homedir_, + 10); //set lookup computation to 3 entries //register with db vector addrVec; @@ -3576,14 +3577,14 @@ TEST_F(ZeroConfTests_FullNode, TwoZC_CheckLedgers) //// create assetWlt //// //create a root private key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), - {}, - SecureBinaryData(), //empty passphrase + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), + SecureBinaryData(), SecureBinaryData(), - 5); + homedir_, + 5); //set lookup computation to 3 entries //register with db vector addrVec; @@ -4375,13 +4376,13 @@ TEST_F(ZeroConfTests_Supernode, ZC_Reorg) theBDMt_->start(DBSettings::initMode()); auto&& bdvID = DBTestUtils::registerBDV(clients_, BitcoinSettings::getMagicBytes()); - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a rvalue - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData(), SecureBinaryData(), + homedir_, 3); //set lookup computation to 3 entries auto addr1_ptr = assetWlt->getNewAddress(); auto addr2_ptr = assetWlt->getNewAddress(); @@ -4546,14 +4547,14 @@ TEST_F(ZeroConfTests_Supernode, ChainZC_RBFchild_Test) //// create assetWlt //// //create a root private key - auto&& wltRoot = CryptoPRNG::generateRandom(32); - auto assetWlt = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir_, - move(wltRoot), //root as a r value - {}, + unique_ptr seed( + new Armory::Seeds::ClearTextSeed_Armory135()); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), SecureBinaryData(), - SecureBinaryData(), - 10); //set lookup computation to 5 entries + SecureBinaryData(), + homedir_, + 10); //set lookup computation to 3 entries //register with db vector addrVec; diff --git a/cppForSwig/protobuf/BridgeProto.proto b/cppForSwig/protobuf/BridgeProto.proto index 04ada3972..abbd43941 100644 --- a/cppForSwig/protobuf/BridgeProto.proto +++ b/cppForSwig/protobuf/BridgeProto.proto @@ -11,32 +11,47 @@ message RestoreWalletPayload optional string spPass = 3; } -enum RestorePromptType -{ - FormatError = 1; - Failure = 2; - ChecksumError = 3; - DecryptError = 4; - Passphrase = 5; - Control = 6; - Id = 7; - TypeError = 8; - Success = 9; - UnknownError = 10; -} - message RestorePrompt { + message TypeError + { + required string error = 1; + } + + message ChecksumIndexes + { + repeated int32 index = 1; + } + + message CheckWalletId + { + required string wallet_id = 1; + required int32 backup_type = 2; + } + + oneof prompt { + CheckWalletId check_wallet_id = 10; + bool get_passphrases = 11; + bool decrypt_error = 12; - required RestorePromptType promptType = 1; - repeated int32 checksums = 2; - optional string extra = 3; + TypeError type_error = 20; + ChecksumIndexes checksum_error = 21; + ChecksumIndexes checksum_mismatch = 22; + } } message RestoreReply { - required bool result = 1; - optional bytes extra = 2; + message Passphrases + { + required string control = 1; + required string privkey = 2; + } + + required bool success = 1; + oneof reply { + Passphrases passphrases = 11; + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/qtdialogs/DlgRestore.py b/qtdialogs/DlgRestore.py index a63090efc..2f535684b 100644 --- a/qtdialogs/DlgRestore.py +++ b/qtdialogs/DlgRestore.py @@ -160,7 +160,7 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): self.setWindowTitle(self.tr('Test Single-Sheet Backup')) else: self.setWindowTitle(self.tr('Restore Single-Sheet Backup')) - self.connect(self.chkEncrypt, SIGNAL("clicked()"), self.onEncryptCheckboxChange) + self.chkEncrypt.clicked.connect(self.onEncryptCheckboxChange) self.setMinimumWidth(500) self.layout().setSizeConstraint(QLayout.SetFixedSize) @@ -469,8 +469,8 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): self.btnAddFrag = QPushButton(self.tr('+Frag')) self.btnRmFrag = QPushButton(self.tr('-Frag')) self.btnRmFrag.setVisible(False) - self.connect(self.btnAddFrag, SIGNAL(CLICKED), self.addFragment) - self.connect(self.btnRmFrag, SIGNAL(CLICKED), self.removeFragment) + self.btnAddFrag.clicked.connect(self.addFragment) + self.btnRmFrag.clicked.connect(self.removeFragment) self.chkEncrypt = QCheckBox(self.tr('Encrypt Restored Wallet')) self.chkEncrypt.setChecked(True) frmAddRm = makeHorizFrame([self.chkEncrypt, STRETCH, self.btnRmFrag, self.btnAddFrag]) @@ -484,8 +484,8 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): btnExit = QPushButton(self.tr('Cancel')) self.btnRestore = QPushButton(doItText) - self.connect(btnExit, SIGNAL(CLICKED), self.reject) - self.connect(self.btnRestore, SIGNAL(CLICKED), self.processFrags) + btnExit.clicked.connect(self.reject) + self.btnRestore.clicked.connect(self.processFrags) frmBtns = makeHorizFrame([btnExit, STRETCH, self.btnRestore]) self.lblRightFrm = QRichLabel('', hAlign=Qt.AlignHCenter) @@ -538,7 +538,7 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): self.chkEncrypt.setVisible(not thisIsATest) self.advancedOptionsTab.setEnabled(not thisIsATest) if not thisIsATest: - self.connect(self.chkEncrypt, SIGNAL(CLICKED), self.onEncryptCheckboxChange) + self.chkEncrypt.clicked.connect(self.onEncryptCheckboxChange) layout = QVBoxLayout() layout.addWidget(walletRestoreTabs) @@ -578,12 +578,9 @@ def makeFragInputTable(self, addCount=0): lblFragID.setText('' + fid + '', color='TextWarn') - self.connect(btnEnter, SIGNAL(CLICKED), \ - functools.partial(self.dataEnter, fnum=i)) - self.connect(btnLoad, SIGNAL(CLICKED), \ - functools.partial(self.dataLoad, fnum=i)) - self.connect(btnClear, SIGNAL(CLICKED), \ - functools.partial(self.dataClear, fnum=i)) + btnEnter.clicked.connect(functools.partial(self.dataEnter, fnum=i)) + btnLoad.clicked.connect(functools.partial(self.dataLoad, fnum=i)) + btnClear.clicked.connect(functools.partial(self.dataClear, fnum=i)) newLayout.addWidget(btnEnter, 2 * i + 1, 0) @@ -1025,7 +1022,7 @@ def __init__(self, parent, main, fragList=[], wltType=UNKNOWN, securePrintCode=N self.backupTypeButtonGroup.addButton(self.version135cButton) self.backupTypeButtonGroup.addButton(self.version135cSPButton) self.version135cButton.setChecked(True) - self.connect(self.backupTypeButtonGroup, SIGNAL('buttonClicked(int)'), self.changeType) + self.backupTypeButtonGroup.buttonClicked.connect(self.changeType) # This value will be locked after the first fragment is entered. if wltType == UNKNOWN: @@ -1104,8 +1101,8 @@ def __init__(self, parent, main, fragList=[], wltType=UNKNOWN, securePrintCode=N self.btnAccept = QPushButton(self.tr("Done")) self.btnCancel = QPushButton(self.tr("Cancel")) - self.connect(self.btnAccept, SIGNAL(CLICKED), self.verifyUserInput) - self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) + self.btnAccept.clicked.connect(self.verifyUserInput) + self.btnCancel.clicked.connect(self.reject) buttonBox = QDialogButtonBox() buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) @@ -1291,9 +1288,9 @@ def __init__(self, parent, main, thisIsATest=False, expectWltID=None): self.btnLoad = QPushButton(self.tr("Load From Text File")) self.btnAccept = QPushButton(doItText) self.btnCancel = QPushButton(self.tr("Cancel")) - self.connect(self.btnLoad, SIGNAL("clicked()"), self.loadWODataFile) - self.connect(self.btnAccept, SIGNAL("clicked()"), self.verifyUserInput) - self.connect(self.btnCancel, SIGNAL("clicked()"), self.reject) + self.btnLoad.clicked.connect(self.loadWODataFile) + self.btnAccept.clicked.connect(self.verifyUserInput) + self.btnCancel.clicked.connect(self.reject) buttonBox = QDialogButtonBox() buttonBox.addButton(self.btnLoad, QDialogButtonBox.AcceptRole) buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) @@ -1490,8 +1487,8 @@ def __init__(self, parent, main): self.btnAccept = QPushButton(self.tr("Done")) self.btnCancel = QPushButton(self.tr("Cancel")) - self.connect(self.btnAccept, SIGNAL(CLICKED), self.verifySecurePrintCode) - self.connect(self.btnCancel, SIGNAL(CLICKED), self.reject) + self.btnAccept.clicked.connect(self.verifySecurePrintCode) + self.btnCancel.clicked.connect(self.reject) buttonBox = QDialogButtonBox() buttonBox.addButton(self.btnAccept, QDialogButtonBox.AcceptRole) buttonBox.addButton(self.btnCancel, QDialogButtonBox.RejectRole) diff --git a/qtdialogs/DlgUniversalRestoreSelect.py b/qtdialogs/DlgUniversalRestoreSelect.py index 6e74b8427..84dde4f1a 100644 --- a/qtdialogs/DlgUniversalRestoreSelect.py +++ b/qtdialogs/DlgUniversalRestoreSelect.py @@ -21,7 +21,7 @@ ################################################################################ class DlgUniversalRestoreSelect(ArmoryDialog): - ############################################################################# + ############################################################################# def __init__(self, parent, main): super(DlgUniversalRestoreSelect, self).__init__(parent, main) From 2a94305f771bb6caeb0141faa3a1451ecf3e1ca1 Mon Sep 17 00:00:00 2001 From: goatpig Date: Mon, 29 May 2023 14:59:29 +0200 Subject: [PATCH 43/47] [ARMORY-29] BIP39 legacy English Trezor dictionnary support --- Makefile.am | 2 +- cppForSwig/Makefile.am | 31 +++- cppForSwig/Makefile.tests.include | 6 + cppForSwig/Wallets/Seeds/Backups.cpp | 79 ++++++++-- cppForSwig/Wallets/Seeds/Backups.h | 17 ++- cppForSwig/Wallets/Seeds/Seeds.cpp | 83 ++++++++--- cppForSwig/Wallets/Seeds/Seeds.h | 16 ++- cppForSwig/chacha20poly1305/Makefile.am | 3 - cppForSwig/gtest/WalletTests.cpp | 184 +++++++++++++++++++++++- 9 files changed, 365 insertions(+), 56 deletions(-) diff --git a/Makefile.am b/Makefile.am index fbb97f743..9f1816c5a 100755 --- a/Makefile.am +++ b/Makefile.am @@ -16,7 +16,7 @@ copy-script: cp cppForSwig/BIP150KeyManager ./BIP150KeyManager cp cppForSwig/CppBridge ./CppBridge -# SWIG code and requirements. +# protobuf formas for python if BUILD_CLIENT protoc --python_out=$(srcdir)/armoryengine --proto_path=$(srcdir)/cppForSwig/protobuf $(srcdir)/cppForSwig/protobuf/BridgeProto.proto #TODO: build c20p1305 CFFI python lib diff --git a/cppForSwig/Makefile.am b/cppForSwig/Makefile.am index 04ad99b48..d32666d78 100644 --- a/cppForSwig/Makefile.am +++ b/cppForSwig/Makefile.am @@ -8,6 +8,8 @@ BUILT_SOURCES = LIBBTC = $(LIBBTC_LIBDIR)/libbtc.la LIBCHACHA20POLY1305 = chacha20poly1305/libchacha20poly1305.la +LIBHKDF = hkdf/libhkdf.la +LIBTREZORCRYPTO = libTrezorCrypto.la LIBARMORYWALLETS = libArmoryWallets.la LIBARMORYSIGNER = libArmorySigner.la @@ -16,7 +18,6 @@ LIBARMORYCOMMON = libArmoryCommon.la LIBARMORYGUI = libArmoryGUI.la LIBARMORYCLI = libArmoryCLI.la LIBLMDBPP = liblmdbpp.la -LIBHKDF = hkdf/libhkdf.la LIBSECP256K1 = $(LIBBTC_LIBDIR)/src/secp256k1/libsecp256k1.la LIBWEBSOCKETS_STATIC = $(WEBSOCKETS_LIBDIR)/libwebsockets.a LIBPROTOBUF_STATIC = -lprotobuf @@ -57,11 +58,10 @@ AM_CXXFLAGS = #LDFLAGS += -Wl,-rpath,. -Wl,-rpath,'$$ORIGIN/../more/libs/here' -Wl,-z,origin protobuf/%.pb.cc protobuf/%.pb.h: protobuf/%.proto -# @test -f proto protoc --cpp_out=protobuf --proto_path=$(srcdir)/protobuf $< INCLUDE_FILES = -I$(srcdir)/lmdbpp \ - -I$(srcdir)/chacha20poly1305 -I$(LIBBTC_LIBDIR)/src/secp256k1/include \ + -I$(srcdir)/chacha20poly1305 -I$(LIBBTC_LIBDIR)/src/secp256k1/include -I$(LIBBTC_LIBDIR)/src \ -I$(LIBBTC_LIBDIR)/include $(LWS_CFLAGS) -I$(srcdir)/hkdf -I$(srcdir)/Wallets -I$(srcdir)/Signer # Files should *not* be marked as "common" if at all possible. Also, a refactor @@ -78,6 +78,13 @@ CPPBRIDGE_SOURCE_FILES = BridgeAPI/CppBridge.cpp \ BridgeAPI/ProtobufCommandParser.cpp \ BridgeAPI/PassphrasePrompt.cpp +LIBTREZORCRYPTO_SOURCE_FILES = $(LIBBTC_LIBDIR)/src/trezor-crypto/bip39.c \ + $(LIBBTC_LIBDIR)/src/trezor-crypto/pbkdf2.c \ + $(LIBBTC_LIBDIR)/src/trezor-crypto/memzero.c \ + $(LIBBTC_LIBDIR)/src/trezor-crypto/hmac.c \ + $(LIBBTC_LIBDIR)/src/trezor-crypto/sha2.c \ + $(LIBBTC_LIBDIR)/src/trezor-crypto/rand.c + ARMORYWALLETS_SOURCE_FILES = Wallets/BIP32_Node.cpp \ Wallets/Addresses.cpp \ Wallets/DecryptedDataContainer.cpp \ @@ -210,6 +217,13 @@ liblmdbpp_la_CPPFLAGS = -Ilmdbpp -fPIC liblmdbpp_la_LIBADD = -llmdb liblmdbpp_la_LDFLAGS = -static +# libTrezorCrypto +noinst_LTLIBRARIES += $(LIBTREZORCRYPTO) +libTrezorCrypto_la_SOURCES = $(LIBTREZORCRYPTO_SOURCE_FILES) +libTrezorCrypto_la_CPPFLAGS = $(AM_CPPFLAGS) +libTrezorCrypto_la_CXXFLAGS = $(AM_CXXFLAGS) -fPIC +libTrezorCrypto_la_LDFLAGS = $(LDFLAGS) -static + # libArmoryWallets noinst_LTLIBRARIES += $(LIBARMORYWALLETS) libArmoryWallets_la_SOURCES = $(ARMORYWALLETS_SOURCE_FILES) @@ -217,7 +231,8 @@ libArmoryWallets_la_CPPFLAGS = $(AM_CPPFLAGS) $(INCLUDE_FILES) libArmoryWallets_la_CXXFLAGS = $(AM_CXXFLAGS) $(LIBBTC_FLAGS) \ $(UNIT_TEST_CXXFLAGS) -D__STDC_LIMIT_MACROS libArmoryWallets_la_LIBADD = $(LIBBTC) \ - $(LIBSECP256K1) + $(LIBSECP256K1) \ + $(LIBTREZORCRYPTO) libArmoryWallets_la_LDFLAGS = $(LDFLAGS) # libArmorySigner @@ -227,7 +242,8 @@ libArmorySigner_la_CPPFLAGS = $(AM_CPPFLAGS) $(INCLUDE_FILES) libArmorySigner_la_CXXFLAGS = $(AM_CXXFLAGS) $(LIBBTC_FLAGS) \ $(UNIT_TEST_CXXFLAGS) -D__STDC_LIMIT_MACROS libArmorySigner_la_LIBADD = $(LIBBTC) \ - $(LIBSECP256K1) + $(LIBSECP256K1) \ + $(LIBTREZORCRYPTO) libArmorySigner_la_LDFLAGS = $(LDFLAGS) @@ -253,6 +269,7 @@ libArmoryCommon_la_CXXFLAGS = $(AM_CXXFLAGS) $(LIBBTC_FLAGS) \ libArmoryCommon_la_LIBADD = $(LIBHKDF) \ $(LIBBTC) \ $(LIBSECP256K1) \ + $(LIBTREZORCRYPTO) \ $(LIBARMORYWALLETS) \ $(LIBARMORYSIGNER) \ $(LIBBLOCKCHAINDB) \ @@ -275,6 +292,7 @@ libArmoryCLI_la_LIBADD = $(LIBCHACHA20POLY1305) \ $(LIBHKDF) \ $(LIBBTC) \ $(LIBSECP256K1) \ + $(LIBTREZORCRYPTO) \ -llmdb \ -lpthread libArmoryCLI_la_LDFLAGS = $(LDFLAGS) $(LWSLDFLAGS) @@ -293,6 +311,7 @@ ArmoryDB_LDADD = $(LIBARMORYWALLETS) \ $(LIBHKDF) \ $(LIBBTC) \ $(LIBSECP256K1) \ + $(LIBTREZORCRYPTO) \ $(LIBCHACHA20POLY1305) \ $(LIBLMDBPP) \ -llmdb \ @@ -327,6 +346,7 @@ BIP150KeyManager_LDADD = $(LIBARMORYCOMMON) \ $(LIBHKDF) \ $(LIBBTC) \ $(LIBCHACHA20POLY1305) \ + $(LIBTREZORCRYPTO) \ $(LIBLMDBPP) \ -llmdb \ $(LIBPROTOBUF_STATIC) \ @@ -344,6 +364,7 @@ CppBridge_LDADD = $(LIBARMORYCOMMON) \ $(LIBHKDF) \ $(LIBBTC) \ $(LIBCHACHA20POLY1305) \ + $(LIBTREZORCRYPTO) \ $(LIBLMDBPP) \ -llmdb \ $(LIBPROTOBUF_STATIC) \ diff --git a/cppForSwig/Makefile.tests.include b/cppForSwig/Makefile.tests.include index b94cc05d6..c14c8d5fd 100644 --- a/cppForSwig/Makefile.tests.include +++ b/cppForSwig/Makefile.tests.include @@ -46,6 +46,7 @@ gtest_UtilsTests_LDADD = gtest/libgtest.la \ $(LIBARMORYCLI) \ $(LIBHKDF) \ $(LIBBTC) \ + $(LIBTREZORCRYPTO) \ $(LIBLMDBPP) \ -llmdb \ $(LIBPROTOBUF_STATIC) \ @@ -116,6 +117,7 @@ gtest_SupernodeTests_LDADD = gtest/libgtest.la \ -llmdb \ $(LIBHKDF) \ $(LIBBTC) \ + $(LIBTREZORCRYPTO) \ $(LIBCHACHA20POLY1305) \ $(LIBPROTOBUF_STATIC) \ $(LIBWEBSOCKETS_STATIC) @@ -147,6 +149,7 @@ gtest_CppBlockUtilsTests_LDADD = gtest/libgtest.la \ $(LIBHKDF) \ $(LIBBTC) \ $(LIBLMDBPP) \ + $(LIBTREZORCRYPTO) \ -llmdb \ $(LIBPROTOBUF_STATIC) \ $(LIBWEBSOCKETS_STATIC) @@ -178,6 +181,7 @@ gtest_ZeroConfTests_LDADD = gtest/libgtest.la \ -llmdb \ $(LIBHKDF) \ $(LIBBTC) \ + $(LIBTREZORCRYPTO) \ $(LIBCHACHA20POLY1305) \ $(LIBPROTOBUF_STATIC) \ $(LIBWEBSOCKETS_STATIC) @@ -209,6 +213,7 @@ gtest_WalletTests_LDADD = gtest/libgtest.la \ $(LIBHKDF) \ $(LIBBTC) \ $(LIBLMDBPP) \ + $(LIBTREZORCRYPTO) \ -llmdb \ $(LIBPROTOBUF_STATIC) \ $(LIBWEBSOCKETS_STATIC) @@ -240,6 +245,7 @@ gtest_SignerTests_LDADD = gtest/libgtest.la \ $(LIBBTC) \ $(LIBCHACHA20POLY1305) \ $(LIBLMDBPP) \ + $(LIBTREZORCRYPTO) \ -llmdb \ $(LIBPROTOBUF_STATIC) \ $(LIBWEBSOCKETS_STATIC) diff --git a/cppForSwig/Wallets/Seeds/Backups.cpp b/cppForSwig/Wallets/Seeds/Backups.cpp index c8caf140f..28341e433 100644 --- a/cppForSwig/Wallets/Seeds/Backups.cpp +++ b/cppForSwig/Wallets/Seeds/Backups.cpp @@ -12,6 +12,9 @@ #include "../WalletIdTypes.h" #include "Seeds.h" #include "protobuf/BridgeProto.pb.h" +extern "C" { +#include +} #define EASY16_CHECKSUM_LEN 2 #define EASY16_INDEX_MAX 15 @@ -975,20 +978,31 @@ unique_ptr Helpers::getBIP39BackupString( throw runtime_error("[getBIP39BackupString] invalid seed type"); auto seedBip39 = dynamic_cast(seed.get()); - SecureBinaryData mnemonicString; + unique_ptr result; switch (seedBip39->getDictionnaryId()) { - case 1: + case ClearTextSeed_BIP39::Dictionnary::English_Trezor: { - //convert raw entropy to mnemonic string + //clear libbtc/trezor bip39 mnemonic buffer + mnemonic_clear(); + + //convert raw entropy to mnemonic phrase + auto mnemonicPtr = mnemonic_from_data( + seedBip39->getRawEntropy().getPtr(), + seedBip39->getRawEntropy().getSize()); + string_view mnemonicView(mnemonicPtr, strlen(mnemonicPtr)); + + //copy mnemonic phrase + result = Backup_BIP39::fromMnemonicString(mnemonicView); + + //clear libbtc/trezor bip39 mnemonic buffer + mnemonic_clear(); break; } default: throw runtime_error("[getBIP39BackupString] invalid dictionnary id"); } - - auto result = make_unique(move(mnemonicString)); return result; } @@ -1031,7 +1045,7 @@ shared_ptr Helpers::restoreFromBackup( break; case BackupType::BIP39: - seed = restoreFromBIP39(move(backup), callback); + seed = restoreFromBIP39(move(backup)); break; default: @@ -1044,7 +1058,7 @@ shared_ptr Helpers::restoreFromBackup( prompt.mutable_type_error()->set_error( "failed to create seed from backup"); callback(move(prompt)); - return nullptr; + throw RestoreUserException("failed to create seed from backup"); } //prompt user to verify id @@ -1271,7 +1285,8 @@ unique_ptr Helpers::restoreFromEasy16( { //empty BIP32 wallet seedPtr = std::move(std::make_unique( - primaryData.data_, 1)); + primaryData.data_, + ClearTextSeed_BIP39::Dictionnary::English_Trezor)); break; } @@ -1303,17 +1318,33 @@ unique_ptr Helpers::restoreFromBase58( //////// unique_ptr Helpers::restoreFromBIP39( - unique_ptr backup, const UserPrompt& callback) + unique_ptr backup) { auto backupBIP39 = dynamic_cast(backup.get()); if (backupBIP39 == nullptr) return nullptr; - //TODO: convert words to raw entropy - SecureBinaryData rawEntropy; + const char* mnemonic = backupBIP39->getMnemonicString().data(); + + //check mnemonic phrase + if (mnemonic_check(mnemonic) == 0) + return nullptr; + + //convert mnemonic phrase to raw entropy + SecureBinaryData rawEntropy(33); //max entropy size + checksum + auto lenInBits = mnemonic_to_bits(mnemonic, rawEntropy.getPtr()); + + if (lenInBits == 0) + return nullptr; + + //strip out checksum bits + auto lenInBytes = lenInBits / 8; + lenInBytes -= lenInBytes % 8; + rawEntropy.resize(lenInBytes); //entropy to seed - return make_unique(rawEntropy, 1); + return make_unique(rawEntropy, + ClearTextSeed_BIP39::Dictionnary::English_Trezor); } //////////////////////////////////////////////////////////////////////////////// @@ -1470,9 +1501,27 @@ unique_ptr Backup_Base58::fromString(const string_view& strV) } ///////////////////////////////// Backup_BIP39 ///////////////////////////////// -Backup_BIP39::Backup_BIP39(SecureBinaryData mnemonicString) : - WalletBackup(BackupType::BIP39), mnemonicString_(move(mnemonicString)) +Backup_BIP39::Backup_BIP39() : + WalletBackup(BackupType::BIP39), mnemonicString_() {} Backup_BIP39::~Backup_BIP39() -{} \ No newline at end of file +{} + +unique_ptr Backup_BIP39::fromMnemonicString(string_view strV) +{ + //create a SBD with 1 extra byte to account for terminating 0, + //as trezor-crypto expects null terminate strings + SecureBinaryData mnemonicSBD(strV.size() + 1); + memset(mnemonicSBD.getPtr(), 0, strV.size() + 1); + memcpy(mnemonicSBD.getPtr(), strV.data(), strV.size()); + + unique_ptr result(new Backup_BIP39()); + result->mnemonicString_ = move(mnemonicSBD); + return result; +} + +string_view Backup_BIP39::getMnemonicString() const +{ + return string_view(mnemonicString_.toCharPtr(), mnemonicString_.getSize()); +} diff --git a/cppForSwig/Wallets/Seeds/Backups.h b/cppForSwig/Wallets/Seeds/Backups.h index 45587057b..5df91014d 100644 --- a/cppForSwig/Wallets/Seeds/Backups.h +++ b/cppForSwig/Wallets/Seeds/Backups.h @@ -64,15 +64,16 @@ namespace Armory - b: BIP32 with BIP44/49/84 chains, as individual address accounts - c: BIP32 with no accounts - d: BIP39 seed with BIP44/49/84 chains, as individual - address accounts + address accounts, Trezor English dicionnary */ Armory200a = 3, Armory200b = 4, Armory200c = 5, - Armory200d = 6, + + Armory200d = 10, //state of an easy16 backup prior to decode - Easy16_Unkonwn = 10, + Easy16_Unkonwn = 30, //bip32 mnemonic phrase (12~24 words), english dictionnary BIP39 = 0xFFFF, @@ -226,13 +227,15 @@ namespace Armory private: SecureBinaryData mnemonicString_; + private: + Backup_BIP39(void); + public: - Backup_BIP39(SecureBinaryData); ~Backup_BIP39(void) override; std::string_view getMnemonicString(void) const; - static std::unique_ptr fromMnemonics( - const std::vector&); + static std::unique_ptr fromMnemonicString( + std::string_view); }; //////// @@ -289,7 +292,7 @@ namespace Armory static std::unique_ptr restoreFromBase58( std::unique_ptr); static std::unique_ptr restoreFromBIP39( - std::unique_ptr, const UserPrompt&); + std::unique_ptr); }; }; //namespace Backups }; //namespace Armory diff --git a/cppForSwig/Wallets/Seeds/Seeds.cpp b/cppForSwig/Wallets/Seeds/Seeds.cpp index 98a0d6675..5cc43ffc5 100644 --- a/cppForSwig/Wallets/Seeds/Seeds.cpp +++ b/cppForSwig/Wallets/Seeds/Seeds.cpp @@ -15,6 +15,9 @@ #include "Seeds.h" #include "Backups.h" #include "BtcUtils.h" +extern "C" { +#include +} using namespace std; using namespace Armory; @@ -327,7 +330,7 @@ unique_ptr ClearTextSeed::deserialize( case SeedType::BIP39: { BinaryDataRef rawEntropy; - unsigned int dictionnary = 0; + ClearTextSeed_BIP39::Dictionnary dictionnary; while (!brr.isEndOfStream()) { auto prefix = (Prefix)brr.get_uint8_t(); @@ -342,7 +345,8 @@ unique_ptr ClearTextSeed::deserialize( case Prefix::Dictionnary: { - dictionnary = brr.get_uint32_t(); + dictionnary = + (ClearTextSeed_BIP39::Dictionnary)brr.get_uint32_t(); break; } @@ -628,12 +632,14 @@ BackupType ClearTextSeed_BIP32::getPreferedBackupType() const //////////////////////////////////////////////////////////////////////////////// ClearTextSeed_BIP39::ClearTextSeed_BIP39(const SecureBinaryData& raw, - unsigned int dictId) : - ClearTextSeed_BIP32(raw, SeedType::BIP39), dictionnaryId_(dictId) -{ - if (dictionnaryId_ == 0) - throw runtime_error("[ClearTextSeed_BIP39] invalid dictionnary id"); -} + Dictionnary dictType) : + ClearTextSeed_BIP32(raw, SeedType::BIP39), dictionnary_(dictType) +{} + +ClearTextSeed_BIP39::ClearTextSeed_BIP39(Dictionnary dictType) : + ClearTextSeed_BIP32(CryptoPRNG::generateRandom(32), SeedType::BIP39), + dictionnary_(dictType) +{} ClearTextSeed_BIP39::~ClearTextSeed_BIP39() {} @@ -642,21 +648,63 @@ ClearTextSeed_BIP39::~ClearTextSeed_BIP39() std::shared_ptr ClearTextSeed_BIP39::getRootNode() const { if (rootNode_ == nullptr) + setupRootNode(); + return rootNode_; +} + +//// +void ClearTextSeed_BIP39::setupRootNode() const +{ + //sanity checks + if (rawEntropy_.empty()) { - //convert raw entropy to BIP39 mnemonic phrase based on dictionnary + throw runtime_error("[ClearTextSeed_BIP39::setupRootNode]" + " missing raw entropy"); + } - //convert mnemonic to seed - SecureBinaryData seed{}; + if (rootNode_ != nullptr) + { + throw runtime_error("[ClearTextSeed_BIP39::setupRootNode]" + " already have root node"); + } - rootNode_ = make_shared(); - rootNode_->initFromSeed(seed); + switch (dictionnary_) + { + case Dictionnary::English_Trezor: + { + //clear libbtc/trezor bip39 mnemonic buffer + mnemonic_clear(); + + //convert raw entropy to mnemonic phrase + auto mnemonicPtr = mnemonic_from_data( + rawEntropy_.getPtr(), rawEntropy_.getSize()); + + //convert mnemonic phrase to seed + SecureBinaryData seed64(64); + mnemonic_to_seed( + mnemonicPtr, //the mnemonic string + "", //passphrase, null for now + seed64.getPtr(), //result buffer + nullptr); //progress callback, dont care for now + + //clean up libbtc buffer + mnemonic_clear(); + + //setup root node + rootNode_ = make_shared(); + rootNode_->initFromSeed(seed64); + break; + } + + default: + throw runtime_error("[ClearTextSeed_BIP39::setupRootNode]" + " unexpected dictionnary id"); } - return rootNode_; } -unsigned int ClearTextSeed_BIP39::getDictionnaryId(void) const +ClearTextSeed_BIP39::Dictionnary ClearTextSeed_BIP39::getDictionnaryId() const { - return dictionnaryId_; + return dictionnary_; } //// @@ -671,7 +719,8 @@ void ClearTextSeed_BIP39::serialize(BinaryWriter& bw) const inner.put_BinaryData(rawEntropy_); //dictionnary id - inner.put_uint32_t(dictionnaryId_); + inner.put_uint8_t((uint8_t)Prefix::Dictionnary); + inner.put_uint32_t((uint32_t)dictionnary_); /* append to writer */ diff --git a/cppForSwig/Wallets/Seeds/Seeds.h b/cppForSwig/Wallets/Seeds/Seeds.h index 615856190..cea70b5bc 100644 --- a/cppForSwig/Wallets/Seeds/Seeds.h +++ b/cppForSwig/Wallets/Seeds/Seeds.h @@ -205,11 +205,21 @@ namespace Armory //////// class ClearTextSeed_BIP39 : public ClearTextSeed_BIP32 { + public: + enum class Dictionnary : int + { + English_Trezor = 1, + }; + + private: + const Dictionnary dictionnary_; + private: - const unsigned int dictionnaryId_ = 1; + void setupRootNode(void) const; public: - ClearTextSeed_BIP39(const SecureBinaryData&, unsigned int); + ClearTextSeed_BIP39(const SecureBinaryData&, Dictionnary); + ClearTextSeed_BIP39(Dictionnary); ~ClearTextSeed_BIP39(void) override; void serialize(BinaryWriter&) const override; @@ -217,7 +227,7 @@ namespace Armory BackupType getPreferedBackupType(void) const override; std::shared_ptr getRootNode(void) const override; - unsigned int getDictionnaryId(void) const; + Dictionnary getDictionnaryId(void) const; }; ////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/chacha20poly1305/Makefile.am b/cppForSwig/chacha20poly1305/Makefile.am index 6b81633d8..af66d209a 100644 --- a/cppForSwig/chacha20poly1305/Makefile.am +++ b/cppForSwig/chacha20poly1305/Makefile.am @@ -10,7 +10,6 @@ CHACHA20POLY1305_SOURCE_FILES = poly1305.c chacha.c chachapoly_aead.c libchacha20poly1305_la_SOURCES = $(CHACHA20POLY1305_SOURCE_FILES) libchacha20poly1305_la_CPPFLAGS = $(AM_CPPFLAGS) $(INCLUDE_FILES) -fPIC libchacha20poly1305_la_CFLAGS = $(AM_CFLAGS) -libchacha20poly1305_la_LIBADD = $(LIBCHACHAPOLY1305) libchacha20poly1305_la_LDFLAGS = $(LDFLAGS) -static if BUILD_TESTS @@ -19,7 +18,6 @@ TESTS += chacha20poly1305tests chacha20poly1305tests_SOURCES = $(CHACHA20POLY1305_SOURCE_FILES) tests.c chacha20poly1305tests_CFLAGS = $(AM_CFLAGS) chacha20poly1305tests_CPPFLAGS = $(AM_CPPFLAGS) -fPIC -chacha20poly1305tests_LDADD = $(LIBCHACHA20POLY1305) chacha20poly1305tests_LDFLAGS = $(LDFLAGS) -static endif @@ -29,6 +27,5 @@ BENCH += chacha20poly1305bench chacha20poly1305bench_SOURCES = $(CHACHA20POLY1305_SOURCE_FILES) bench.c chacha20poly1305bench_CFLAGS = $(AM_CFLAGS) chacha20poly1305bench_CPPFLAGS = $(AM_CPPFLAGS) -fPIC -chacha20poly1305bench_LDADD = $(LIBCHACHA20POLY1305) chacha20poly1305bench_LDFLAGS = $(LDFLAGS) -static endif diff --git a/cppForSwig/gtest/WalletTests.cpp b/cppForSwig/gtest/WalletTests.cpp index 4bfb1415b..906b60625 100644 --- a/cppForSwig/gtest/WalletTests.cpp +++ b/cppForSwig/gtest/WalletTests.cpp @@ -4466,12 +4466,14 @@ TEST_F(WalletsTest, ID_fromSeeds) EXPECT_EQ(base58->getMasterId(), "2d9H95rzK"); } - /* + //BIP39 - auto bip32 = make_unique(rawSBD, 1); - EXPECT_EQ(bip32->getWalletId(), "abcd"); - EXPECT_EQ(bip32->getMasterId(), "abcd"); - */ + { + auto bip32 = make_unique(rawSBD, + ClearTextSeed_BIP39::Dictionnary::English_Trezor); + EXPECT_EQ(bip32->getWalletId(), "vUXT83m9"); + EXPECT_EQ(bip32->getMasterId(), "WLKZBhnX"); + } } //////////////////////////////////////////////////////////////////////////////// @@ -8582,6 +8584,7 @@ class BackupTests : public ::testing::Test // EXPECT_EQ(address->getPrefixedHash(), newAddr->getPrefixedHash()); EXPECT_EQ(privKey, newKey); + EXPECT_EQ(assetID.first, newID.first); } METHOD_ASSERT_EQ(keyPassCount, 10U); @@ -9740,6 +9743,177 @@ TEST_F(BackupTests, BackupStrings_BIP32_FromBase58) } } +//////////////////////////////////////////////////////////////////////////////// +TEST_F(BackupTests, BackupStrings_BIP39) +{ + //create a legacy wallet + string filename; + unique_ptr seed( + new ClearTextSeed_BIP39( + ClearTextSeed_BIP39::Dictionnary::English_Trezor)); + auto assetWlt = AssetWallet_Single::createFromSeed( + move(seed), //root as a r value + SecureBinaryData::fromString("passphrase"), + SecureBinaryData::fromString("control"), + homedir_, + 4); //set lookup computation to 4 entries + + auto passLbd = [](const set&)->SecureBinaryData + { + return SecureBinaryData::fromString("passphrase"); + }; + auto walletId = assetWlt->getID(); + assetWlt->setPassphrasePromptLambda(passLbd); + auto backupDataBIP39 = Armory::Seeds::Helpers::getWalletBackup( + assetWlt, BackupType::BIP39); + auto backupDataArmory200d = Armory::Seeds::Helpers::getWalletBackup( + assetWlt, BackupType::Armory200d); + + EXPECT_EQ(walletId, backupDataBIP39->getWalletId()); + EXPECT_EQ(walletId, backupDataArmory200d->getWalletId()); + + //restore Armory200d lambda + auto newPass = CryptoPRNG::generateRandom(10); + auto newCtrl = CryptoPRNG::generateRandom(10); + auto callback = [&walletId, &newPass, &newCtrl]( + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply + { + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) + { + case BridgeProto::RestorePrompt::kGetPassphrases: + { + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass.toCharPtr(), newPass.getSize()); + passphrases->set_control(newCtrl.toCharPtr(), newCtrl.getSize()); + reply.set_success(true); + break; + } + + case BridgeProto::RestorePrompt::kCheckWalletId: + { + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.wallet_id(), walletId); + EXPECT_EQ(checkWalleIdMsg.backup_type(), (int)BackupType::Armory200d); + reply.set_success(true); + break; + } + + default: + reply.set_success(false); + } + return reply; + }; + + string newHomeDir("./newhomedir"); + DBUtils::removeDirectory(newHomeDir); + mkdir(newHomeDir); + + { + auto backupE16 = dynamic_cast(backupDataArmory200d.get()); + ASSERT_NE(backupE16, nullptr); + auto backupE16Copy = Backup_Easy16::fromLines({ + backupE16->getRoot(Backup_Easy16::LineIndex::One, false), + backupE16->getRoot(Backup_Easy16::LineIndex::Two, false), + }); + auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( + move(backupE16Copy), newHomeDir, callback); + ASSERT_NE(newWltPtr, nullptr); + + auto passLbd2 = [&newPass](const set&)->SecureBinaryData + { + return newPass; + }; + newWltPtr->setPassphrasePromptLambda(passLbd2); + + auto newWalletSingle = dynamic_pointer_cast(newWltPtr); + auto backupData2 = Armory::Seeds::Helpers::getWalletBackup( + newWalletSingle, BackupType::Armory200d); + auto backupE16_2 = dynamic_cast(backupData2.get()); + + EXPECT_EQ(backupE16->getRoot(Backup_Easy16::LineIndex::One, false), + backupE16_2->getRoot(Backup_Easy16::LineIndex::One, false)); + EXPECT_EQ(backupE16->getRoot(Backup_Easy16::LineIndex::Two, false), + backupE16_2->getRoot(Backup_Easy16::LineIndex::Two, false)); + EXPECT_EQ(backupE16->getWalletId(), backupE16_2->getWalletId()); + + filename = newWltPtr->getDbFilename(); + } + + EXPECT_TRUE(compareWalletWithBackup(assetWlt, filename, newPass, newCtrl)); + DBUtils::removeDirectory(newHomeDir); + mkdir(newHomeDir); + + //restore BIP39 lambda + auto newPass2 = CryptoPRNG::generateRandom(10); + auto newCtrl2 = CryptoPRNG::generateRandom(10); + auto callbackBip39 = [&walletId, &newPass2, &newCtrl2]( + BridgeProto::RestorePrompt prompt)->BridgeProto::RestoreReply + { + BridgeProto::RestoreReply reply; + switch (prompt.prompt_case()) + { + case BridgeProto::RestorePrompt::kGetPassphrases: + { + auto passphrases = reply.mutable_passphrases(); + passphrases->set_privkey(newPass2.toCharPtr(), newPass2.getSize()); + passphrases->set_control(newCtrl2.toCharPtr(), newCtrl2.getSize()); + reply.set_success(true); + break; + } + + case BridgeProto::RestorePrompt::kCheckWalletId: + { + auto checkWalleIdMsg = prompt.check_wallet_id(); + EXPECT_EQ(checkWalleIdMsg.wallet_id(), walletId); + EXPECT_EQ(checkWalleIdMsg.backup_type(), (int)BackupType::BIP39); + reply.set_success(true); + break; + } + + default: + reply.set_success(false); + } + return reply; + }; + + //restore from mnemonic string + { + //restore wallet + auto backupBIP39 = dynamic_cast(backupDataBIP39.get()); + ASSERT_NE(backupBIP39, nullptr); + + auto backupBIP39Copy = Backup_BIP39::fromMnemonicString( + backupBIP39->getMnemonicString()); + auto newWltPtr = Armory::Seeds::Helpers::restoreFromBackup( + move(backupBIP39Copy), newHomeDir, callbackBip39); + ASSERT_NE(newWltPtr, nullptr); + + auto passLbd2 = [&newPass2](const set&)->SecureBinaryData + { + return newPass2; + }; + newWltPtr->setPassphrasePromptLambda(passLbd2); + + auto newWalletSingle = dynamic_pointer_cast(newWltPtr); + auto backupData2 = Armory::Seeds::Helpers::getWalletBackup(newWalletSingle); + auto backupBIP39_2 = dynamic_cast(backupData2.get()); + + EXPECT_EQ(backupBIP39->getMnemonicString(), backupBIP39_2->getMnemonicString()); + EXPECT_EQ(backupBIP39->getWalletId(), backupBIP39_2->getWalletId()); + + filename = newWltPtr->getDbFilename(); + + //grab 10 addresses from restored wallet to get in sync with original + //otherwise the comparision will fail + for (int i=0; i<10; i++) + newWltPtr->getNewAddress(); + } + + EXPECT_TRUE(compareWalletWithBackup(assetWlt, filename, newPass2, newCtrl2)); + DBUtils::removeDirectory(newHomeDir); +} + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Now actually execute all the tests From 07be576d12c3c5bab2cc47a7dbed988b5d14662f Mon Sep 17 00:00:00 2001 From: goatpig Date: Mon, 29 May 2023 15:00:53 +0200 Subject: [PATCH 44/47] [ARMORY-29] update wallet creation diagram --- cppForSwig/Wallets/Seeds/Seeds.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cppForSwig/Wallets/Seeds/Seeds.h b/cppForSwig/Wallets/Seeds/Seeds.h index cea70b5bc..59e579ba7 100644 --- a/cppForSwig/Wallets/Seeds/Seeds.h +++ b/cppForSwig/Wallets/Seeds/Seeds.h @@ -21,7 +21,9 @@ namespace Armory } /*** Wallet creation diagram *** - + Raw Entropy + | + v WalletBackup <---> ClearTextSeed <-------- | | | | From a8dcce560ff6ef8f38be1edfe3538077d8faa13b Mon Sep 17 00:00:00 2001 From: Sergey Chernikov Date: Wed, 31 May 2023 19:43:04 +0300 Subject: [PATCH 45/47] file templates for regular wallets --- cppForSwig/ArmoryBackups.cpp | 1188 -------------------- cppForSwig/Signer/ResolverFeed_Wallets.cpp | 2 +- cppForSwig/Wallets/Wallets.cpp | 87 +- 3 files changed, 65 insertions(+), 1212 deletions(-) delete mode 100644 cppForSwig/ArmoryBackups.cpp diff --git a/cppForSwig/ArmoryBackups.cpp b/cppForSwig/ArmoryBackups.cpp deleted file mode 100644 index 7f0d34459..000000000 --- a/cppForSwig/ArmoryBackups.cpp +++ /dev/null @@ -1,1188 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// // -// Copyright (C) 2020, goatpig // -// Distributed under the MIT license // -// See LICENSE-MIT or https://opensource.org/licenses/MIT // -// // -//////////////////////////////////////////////////////////////////////////////// - -#include "ArmoryBackups.h" -#include "EncryptionUtils.h" -#include "BtcUtils.h" -#include "Wallets/WalletIdTypes.h" - -constexpr uint32_t EASY16_CHECKSUM_LEN = 2; -constexpr uint32_t EASY16_INDEX_MAX = 15; -constexpr uint32_t EASY16_LINE_LENGTH = 16; - -constexpr uint32_t WALLET_RESTORE_LOOKUP = 1000; - -using namespace std; -using namespace Armory::Backups; -using namespace Armory::Assets; -using namespace Armory::Wallets; - -//////////////////////////////////////////////////////////////////////////////// -const vector BackupEasy16::e16chars_ = -{ - 'a', 's', 'd', 'f', - 'g', 'h', 'j', 'k', - 'w', 'e', 'r', 't', - 'u', 'i', 'o', 'n' -}; - -//////////////////////////////////////////////////////////////////////////////// -const set BackupEasy16::eligibleIndexes_ = -{ - (uint8_t)BackupType::Armory135, - (uint8_t)BackupType::BIP32_Seed_Structured, - (uint8_t)BackupType::BIP32_Root, - (uint8_t)BackupType::BIP32_Seed_Virgin, -}; - - -//////////////////////////////////////////////////////////////////////////////// - -/* -Nothing up my sleeve! Need some hardcoded random numbers to use for -encryption IV and salt. Using the first 256 digits of Pi for the -the IV, and first 256 digits of e for the salt (hashed) -*/ - -const string SecurePrint::digits_pi_ = -{ - "ARMORY_ENCRYPTION_INITIALIZATION_VECTOR_" - "1415926535897932384626433832795028841971693993751058209749445923" - "0781640628620899862803482534211706798214808651328230664709384460" - "9550582231725359408128481117450284102701938521105559644622948954" - "9303819644288109756659334461284756482337867831652712019091456485" -}; - -const string SecurePrint::digits_e_ = -{ - "ARMORY_KEY_DERIVATION_FUNCTION_SALT_" - "7182818284590452353602874713526624977572470936999595749669676277" - "2407663035354759457138217852516642742746639193200305992181741359" - "6629043572900334295260595630738132328627943490763233829880753195" - "2510190115738341879307021540891499348841675092447614606680822648" -}; - -const uint32_t SecurePrint::kdfBytes_ = 16 * 1024 * 1024; - -//////////////////////////////////////////////////////////////////////////////// -//// -//// BackupEasy16 -//// -//////////////////////////////////////////////////////////////////////////////// -BinaryData BackupEasy16::getHash(const BinaryDataRef& data, uint8_t hint) -{ - if (hint == 0) - { - return BtcUtils::getHash256(data); - } - else - { - SecureBinaryData dataCopy(data.getSize() + 1); - memcpy(dataCopy.getPtr(), data.getPtr(), data.getSize()); - dataCopy.getPtr()[data.getSize()] = hint; - - return BtcUtils::getHash256(dataCopy); - } -} - -//////////////////////////////////////////////////////////////////////////////// -uint8_t BackupEasy16::verifyChecksum( - const BinaryDataRef& data, const BinaryDataRef& checksum) -{ - for (const auto& indexCandidate : eligibleIndexes_) - { - auto hash = getHash(data, indexCandidate); - if (hash.getSliceRef(0, EASY16_CHECKSUM_LEN) == checksum) - return indexCandidate; - } - - return EASY16_INVALID_CHECKSUM_INDEX; -} - -//////////////////////////////////////////////////////////////////////////////// -vector BackupEasy16::encode(const BinaryDataRef data, uint8_t index) -{ - if (index > EASY16_INDEX_MAX) - { - LOGERR << "index is too large"; - throw runtime_error("index is too large"); - } - - auto encodeByte = [](stringstream& ss, uint8_t c)->void - { - uint8_t val1 = c >> 4; - uint8_t val2 = c & 0x0F; - ss << e16chars_[val1] << e16chars_[val2]; - }; - - auto encodeValue = [&encodeByte, &index]( - const BinaryDataRef& chunk16)->string - { - //get hash - auto h256 = getHash(chunk16, index); - - //encode the chunk - stringstream ss; - unsigned charCount = 0; - auto ptr = chunk16.getPtr(); - for (unsigned i=0; i result; - BinaryRefReader brr(data); - - uint32_t count = - ((uint32_t)data.getSize() + EASY16_LINE_LENGTH - 1) / EASY16_LINE_LENGTH; - for (unsigned i=0; i& lines) -{ - vector refVec; - for (const auto& line : lines) - refVec.emplace_back((const uint8_t*)line.c_str(), line.size()); - - return decode(refVec); -} - -//////////////////////////////////////////////////////////////////////////////// -BackupEasy16DecodeResult BackupEasy16::decode(const vector& lines) -{ - if (lines.size() == 0) - throw runtime_error("empty easy16 code"); - - //setup character to value lookup map - map easy16Vals; - for (unsigned i=0; ibool - { - if (str[0] == ' ') - return false; - - return true; - }; - - auto decodeCharacters = [&easy16Vals]( - uint8_t& result, const char* str)->void - { - //convert characters to value, ignore effect of invalid ones - result = 0; - auto iter1 = easy16Vals.find(str[0]); - if (iter1 != easy16Vals.end()) - result = iter1->second << 4; - - auto iter2 = easy16Vals.find(str[1]); - if (iter2 != easy16Vals.end()) - result += iter2->second; - }; - - /* - Converts line to binary, appends into result. - Returns the hash index matching the checksum. - - Error values: - . -1: checksum mismatch - . -2: invalid checksum data - . -3: not enough room in the result buffer - */ - auto decodeLine = [&checkSpace, &decodeCharacters]( - uint8_t* result, size_t& len, - const BinaryDataRef& line, BinaryData& checksum)->int - { - auto maxlen = len; - len = 0; - auto ptr = line.toCharPtr(); - - unsigned i=0; - for (; i= maxlen) - return -3; - - decodeCharacters(result[len], ptr + i); - - //increment result length - ++len; - - //increment i to skip 2 characters - ++i; - } - - //grab checksum - checksum.resize(EASY16_CHECKSUM_LEN); - uint8_t* checksumPtr = checksum.getPtr(); - size_t checksumLen = 0; - for (; i= EASY16_CHECKSUM_LEN) - return -2; - - decodeCharacters(*(checksumPtr + checksumLen), ptr + i); - ++checksumLen; - ++i; - } - - if (checksumLen != EASY16_CHECKSUM_LEN) - return -2; - - //hash data - BinaryDataRef decodedChunk(result, len); - return verifyChecksum(decodedChunk, checksum); - }; - - size_t fullSize = lines.size() * EASY16_LINE_LENGTH; - SecureBinaryData data(fullSize); - vector checksumIndexes; - vector checksums(lines.size()); - - auto dataPtr = data.getPtr(); - size_t pos = 0; - for (unsigned i=0; i EASY16_LINE_LENGTH) - { - throw runtime_error("easy16 line is too long"); - } - else if (len < EASY16_LINE_LENGTH) - { - if (i != lines.size() - 1) - throw runtime_error("easy16 line is too short"); - - //last line doesn't have to be EASY16_LINE_LENGTH bytes long - data.resize(pos); - } - } - - BackupEasy16DecodeResult result; - result.checksumIndexes_ = move(checksumIndexes); - result.checksums_ = move(checksums); - result.data_ = move(data); - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -bool BackupEasy16::repair(BackupEasy16DecodeResult& faultyBackup) -{ - //sanity check - if (faultyBackup.data_.empty() || faultyBackup.checksums_.empty() || - faultyBackup.checksums_.size() != faultyBackup.checksumIndexes_.size()) - { - throw Easy16RepairError("invalid arugments"); - } - - //is there an error? - bool hasError = false; - set validIndexes; - for (auto index : faultyBackup.checksumIndexes_) - { - auto indexIter = eligibleIndexes_.find(index); - if (indexIter == eligibleIndexes_.end()) - { - if (index == EASY16_INVALID_CHECKSUM_INDEX) - { - hasError = true; - continue; - } - else - { - //these errors cannot be repaired - throw Easy16RepairError("fatal checksum error"); - } - } - - validIndexes.insert(index); - } - - if (!hasError && validIndexes.size() == 1) - return true; - - /* checksum search function */ - auto searchChecksum = []( - const BinaryDataRef& data, const BinaryData& checksum, uint8_t hint) - ->map>> - { - map>> result; - - //copy the data - SecureBinaryData copied(data); - - //run through each byte of data - for (unsigned i=0; i 1) - { - //there's more than one checksum index, cannot proceed - throw Easy16RepairError("checksum results mismatch"); - } - else if (validIndexes.size() == 1) - { - /* - Some lines are invalid but we have at least one that is valid. This - allows us to search for the expected checksum index in the invalid - lines (they should all match) - */ - unsigned hint = *validIndexes.begin(); - - BinaryRefReader brr(faultyBackup.data_); - for (unsigned i=0; isecond.size() != 1) - return false; - - const auto& repairPair = *repairIter->second.begin(); - if (repairPair.second.size() != 1) - return false; - - //apply repair on the fly - auto ptr = (uint8_t*)(dataRef.getPtr() + repairPair.first); - *ptr = *repairPair.second.begin(); - - //update the repaired line checksum result - faultyBackup.repairedIndexes_.push_back(hint); - } - } - else - { - /* - All lines are invalid. There is no indication of what the checksum index - ought to be. We have to search all lines for a matching index. - */ - vector>>> resultMap; - - BinaryRefReader brr(faultyBackup.data_); - for (unsigned i=0; i> chksumIndexes; - for (unsigned i=0; isecond.size() != 1) - continue; - - auto& chkValueSet = chksumIndexes[lineData.first]; - chkValueSet.insert(i); - } - } - - //only those indexes represented across all lines are eligible - auto iter = chksumIndexes.begin(); - while (iter != chksumIndexes.end()) - { - if (iter->second.size() != faultyBackup.checksumIndexes_.size()) - { - chksumIndexes.erase(iter++); - continue; - } - - ++iter; - } - - //fail if we have several repair candidates - if (chksumIndexes.size() != 1) - return false; - - //repair the data - brr.resetPosition(); - auto repairIndex = chksumIndexes.begin()->first; - for (unsigned i=0; isecond.size() != 1) - return false; - - auto valIter = lineIter->second.begin(); - if (valIter->second.size() != 1) - return false; - - auto dataRef = brr.get_BinaryDataRef( - std::min(EASY16_LINE_LENGTH, (uint32_t)brr.getSizeRemaining())); - - auto ptr = (uint8_t*)(dataRef.getPtr() + valIter->first); - *ptr = *valIter->second.begin(); - - //update the repaired line checksum result - faultyBackup.repairedIndexes_.push_back(repairIndex); - } - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -//// -//// SecurePrint -//// -//////////////////////////////////////////////////////////////////////////////// -SecurePrint::SecurePrint() -{ - //setup aes IV and kdf - auto iv32 = BtcUtils::getHash256( - (const uint8_t*)digits_pi_.c_str(), (uint32_t)digits_pi_.size()); - iv16_ = move(iv32.getSliceCopy(0, AES_BLOCK_SIZE)); - - salt_ = move(BtcUtils::getHash256( - (const uint8_t*)digits_e_.c_str(), (uint32_t)digits_e_.size())); - kdf_.usePrecomputedKdfParams(kdfBytes_, 1, salt_); -} - -//////////////////////////////////////////////////////////////////////////////// -pair SecurePrint::encrypt( - const SecureBinaryData& root, const SecureBinaryData& chaincode) -{ - /* - 1. generate passphrase from root and chaincode - */ - - //sanity check - if (root.getSize() != 32) - { - LOGERR << "invalid root size for secureprint"; - throw runtime_error("invalid root size for secureprint"); - } - - SecureBinaryData hmacPhrase(64); - if (chaincode.empty()) - { - /* - The passphrase is the hmac of the root and the chaincode. If the - chaincode is empty, we only hmac the root. - */ - - auto rootHash = BtcUtils::getHash256(root); - BtcUtils::getHMAC512( - rootHash.getPtr(), rootHash.getSize(), - salt_.getPtr(), salt_.getSize(), - hmacPhrase.getPtr()); - } - else - { - /* - Concatenate root and chaincode then hmac - */ - - SecureBinaryData rootCopy = root; - rootCopy.append(chaincode); - - auto rootHash = BtcUtils::getHash256(rootCopy); - BtcUtils::getHMAC512( - rootHash.getPtr(), rootHash.getSize(), - salt_.getPtr(), salt_.getSize(), - hmacPhrase.getPtr()); - } - - //passphrase is first 7 bytes of the hmac - BinaryWriter bw; - bw.put_BinaryDataRef(hmacPhrase.getSliceRef(0, 7)); - auto passChecksum = BtcUtils::getHash256(bw.getData()); - bw.put_uint8_t(passChecksum[0]); - - passphrase_ = SecureBinaryData::fromString( - BtcUtils::base58_encode(bw.getData())); - - /* - 2. extend the passphrase - */ - - auto encryptionKey = kdf_.DeriveKey(passphrase_); - - /* - 3. Encrypt the data. We use the libbtc call directly because - we do not want padding - */ - - auto encrypt = [this, &encryptionKey]( - const SecureBinaryData& cleartext, SecureBinaryData& result)->bool - { - //this exclusively encrypt 32 bytes of data - if (cleartext.getSize() != 32) - return false; - - //make sure result buffer is large enough - result.resize(32); - - //encrypt with CBC - auto encrLen = aes256_cbc_encrypt( - encryptionKey.getPtr(), iv16_.getPtr(), - cleartext.getPtr(), (int)cleartext.getSize(), - 0, //no padding - result.getPtr()); - - if (encrLen != 32) - return false; - - return true; - }; - - pair result; - if (!encrypt(root, result.first)) - { - LOGERR << "SecurePrint encryption failure"; - throw runtime_error("SecurePrint encryption failure"); - } - - if (!chaincode.empty()) - { - if (!encrypt(chaincode, result.second)) - { - LOGERR << "SecurePrint encryption failure"; - throw runtime_error("SecurePrint encryption failure"); - } - } - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -SecureBinaryData SecurePrint::decrypt( - const SecureBinaryData& ciphertext, const BinaryDataRef passphrase) const -{ - //check passphrase checksum - string passStr(passphrase.toCharPtr(), passphrase.getSize()); - BinaryData passBin; - try - { - passBin = move(BtcUtils::base58_decode(passStr)); - } - catch (const exception&) - { - LOGERR << "invalid SecurePrint passphrase"; - throw runtime_error("invalid SecurePrint passphrase"); - } - - if (passBin.getSize() != 8) - { - LOGERR << "invalid SecurePrint passphrase"; - throw runtime_error("invalid SecurePrint passphrase"); - } - - BinaryRefReader brr(passBin); - auto passBase = brr.get_BinaryDataRef(7); - auto checksum = brr.get_uint8_t(); - - auto passHash = BtcUtils::getHash256(passBase); - if (passHash[0] != checksum) - { - LOGERR << "invalid SecurePrint passphrase"; - throw runtime_error("invalid SecurePrint passphrase"); - } - - if (ciphertext.getSize() < 32) - { - LOGERR << "invalid ciphertext size for SecurePrint"; - throw runtime_error("invalid ciphertext size for SecurePrint"); - } - - //kdf the passphrase - auto encryptionKey = kdf_.DeriveKey(passphrase); - - // - auto decrypt = [this, &encryptionKey]( - const BinaryDataRef& ciphertext, SecureBinaryData& result)->bool - { - //works exclusively on 32 byte packets - if (ciphertext.getSize() != 32) - return false; - - result.resize(32); - - auto size = aes256_cbc_decrypt( - encryptionKey.getPtr(), iv16_.getPtr(), - ciphertext.getPtr(), (int)ciphertext.getSize(), - 0, //no padding - result.getPtr()); - - if (size != 32) - return false; - - return true; - }; - - //decrypt the root - SecureBinaryData result; - if (!decrypt(ciphertext, result)) - { - LOGERR << "failed to decrypt SecurePrint string"; - throw runtime_error("failed to decrypt SecurePrint string"); - } - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -//// -//// Helpers -//// -//////////////////////////////////////////////////////////////////////////////// -WalletRootData Helpers::getRootData( - shared_ptr wltSingle) -{ - WalletRootData rootData; - rootData.wltId_ = wltSingle->getID(); - auto root = dynamic_pointer_cast( - wltSingle->getRoot()); - - //lock wallet - auto lock = wltSingle->lockDecryptedContainer(); - - //check root - auto rootBip32 = dynamic_pointer_cast(root); - if (rootBip32 == nullptr) - { - /* - This isn't a bip32 root, therefor it's an Armory root. It may carry a - dedicated chaincode, let's check for that. - */ - - auto root135 = dynamic_pointer_cast(root); - if (root135 == nullptr) - { - LOGERR << "unexpected wallet root type"; - throw runtime_error("unexpected wallet root type"); - } - - rootData.root_ = wltSingle->getDecryptedPrivateKeyForAsset(root); - rootData.type_ = BackupType::Armory135; - - const auto& wltChaincode = root135->getChaincode(); - if (!wltChaincode.empty()) - { - /* - If the root carries a chaincode, it may be non deterministic. Let's - check. - */ - - auto computedChaincode = - BtcUtils::computeChainCode_Armory135(rootData.root_); - - if (computedChaincode != wltChaincode) - rootData.secondaryData_ = wltChaincode; - } - } - else - { - //bip32 wallet, grab the seed instead - auto seedPtr = wltSingle->getEncryptedSeed(); - if (seedPtr == nullptr) - { - /* - For now, abort if bip32 wallet is missing its seed. May implement - root backups for bip32 wallets (privkey + chaincode) in the future. - */ - rootData.type_ = BackupType::BIP32_Root; - return rootData; - } - - rootData.type_ = BackupType::BIP32_Seed_Structured; - - //decrypt the seed - rootData.root_ = wltSingle->getDecryptedValue(seedPtr); - } - - return rootData; -} - -//////////////////////////////////////////////////////////////////////////////// -WalletRootData Helpers::getRootData_Multisig( - shared_ptr) -{ - throw runtime_error("TODO: needs implementation"); -} - -//////////////////////////////////////////////////////////////////////////////// -WalletBackup Helpers::getWalletBackup( - std::shared_ptr wltPtr, BackupType type) -{ - auto rootData = getRootData(wltPtr); - return getWalletBackup(rootData, type); -} - -//////////////////////////////////////////////////////////////////////////////// -WalletBackup Helpers::getWalletBackup(WalletRootData& rootData, - BackupType forceBackupType) -{ - //apply secureprint - SecurePrint sp; - auto encrRoot = sp.encrypt(rootData.root_, rootData.secondaryData_); - - WalletBackup backup; - - if (forceBackupType != BackupType::Invalid) - rootData.type_ = forceBackupType; - - unsigned mode = UINT32_MAX; - switch (rootData.type_) - { - case BackupType::Armory135: - case BackupType::BIP32_Seed_Structured: - case BackupType::BIP32_Root: - case BackupType::BIP32_Seed_Virgin: - { - mode = unsigned(rootData.type_); - break; - } - - default: - break; - } - - if (mode == UINT32_MAX) - { - LOGERR << "cannot create backup for unknown wallet type"; - throw runtime_error("cannot create backup for unknown wallet type"); - } - - //cleartext root easy16 - backup.rootClear_ = move(BackupEasy16::encode(rootData.root_, mode)); - - //encrypted root easy16 - backup.rootEncr_ = move(BackupEasy16::encode(encrRoot.first,mode)); - - if (!rootData.secondaryData_.empty()) - { - //cleartext chaincode easy16 - backup.chaincodeClear_ = move(BackupEasy16::encode( - rootData.secondaryData_, mode)); - - //encrypted chaincode easy16 - backup.chaincodeEncr_ = move(BackupEasy16::encode( - encrRoot.second, mode)); - } - - backup.spPass_ = sp.getPassphrase(); - backup.wltId_ = rootData.wltId_; - - return backup; -} - -//////////////////////////////////////////////////////////////////////////////// -shared_ptr Helpers::restoreFromBackup( - const vector& data, const BinaryDataRef passphrase, - const string& homedir, const UserPrompt& callerPrompt) -{ - vector bdrVec; - for (const auto& str : data) - bdrVec.emplace_back((const uint8_t*)str.c_str(), str.size()); - - return restoreFromBackup(bdrVec, passphrase, homedir, callerPrompt); -} - -//////////////////////////////////////////////////////////////////////////////// -shared_ptr Helpers::restoreFromBackup( - const vector& data, const BinaryDataRef passphrase, - const string& homedir, const UserPrompt& callerPrompt) -{ - SecureBinaryData promptDummy; - bool hasSecondaryData = false; - - //decode the data - BackupEasy16DecodeResult primaryData, secondaryData; - if (data.size() == 2) - { - primaryData = BackupEasy16::decode(data); - } - else if (data.size() > 2) - { - vector primarySlice; - primarySlice.insert(primarySlice.end(), data.begin(), data.begin() + 2); - primaryData = BackupEasy16::decode(primarySlice); - - vector secondarySlice; - secondarySlice.insert(secondarySlice.end(), data.begin() + 2, data.end()); - secondaryData = BackupEasy16::decode(secondarySlice); - - hasSecondaryData = true; - } - else - { - callerPrompt(RestorePromptType::FormatError, {}, promptDummy); - return nullptr; - } - - if (primaryData.checksumIndexes_.empty() || - (hasSecondaryData && secondaryData.checksumIndexes_.empty())) - { - callerPrompt(RestorePromptType::Failure, {}, promptDummy); - return nullptr; - } - - //sanity check - auto checksumIndexes = primaryData.checksumIndexes_; - if (hasSecondaryData) - { - checksumIndexes.insert(checksumIndexes.end(), - secondaryData.checksumIndexes_.begin(), - secondaryData.checksumIndexes_.end()); - } - - bool checksumErrors; - int firstIndex; - - auto processChecksumIndexes = [&checksumErrors, &firstIndex]( - const vector& checksumValues) - { - /* - Set the common checksum result value and make sure all lines - carry the same value. - */ - - checksumErrors = false; - firstIndex = checksumValues[0]; - for (const auto& result : checksumValues) - { - if (result < 0 || result != firstIndex) - { - checksumErrors = true; - break; - } - } - }; - processChecksumIndexes(checksumIndexes); - - if (checksumErrors) - { - auto reportError = [&callerPrompt, &checksumIndexes](void) - { - //prompt caller if we can't repair the error and throw - SecureBinaryData dummy; - callerPrompt( - RestorePromptType::ChecksumError, checksumIndexes, dummy); - throw RestoreUserException("checksum error"); - }; - - vector repairedIndexes; - - auto repairData = [&reportError, &repairedIndexes]( - BackupEasy16DecodeResult& data) - { - //attempt to repair the data - auto result = BackupEasy16::repair(data); - if (!result) - reportError(); - - if (data.repairedIndexes_.size() != - data.checksumIndexes_.size()) - reportError(); - - repairedIndexes.insert(repairedIndexes.end(), - data.repairedIndexes_.begin(), - data.repairedIndexes_.end()); - }; - - //found some checksum errors, attempt to auto repair - repairData(primaryData); - if (hasSecondaryData) - repairData(secondaryData); - - //check the repaired checksum result values - processChecksumIndexes(repairedIndexes); - - if (checksumErrors) - reportError(); - } - - //check for encryption - if (!passphrase.empty()) - { - try - { - SecurePrint sp; - auto decryptedData = sp.decrypt(primaryData.data_, passphrase); - primaryData.data_ = move(decryptedData); - - if (hasSecondaryData) - { - auto decryptedData = sp.decrypt(secondaryData.data_, passphrase); - secondaryData.data_ = move(decryptedData); - } - } - catch (const exception&) - { - //prompt caller on decrypt error and return - callerPrompt(RestorePromptType::DecryptError, {}, promptDummy); - throw RestoreUserException("invalid SP pass"); - } - } - - auto computeWalletId = []( - const SecureBinaryData& root, const SecureBinaryData& chaincode) - ->string - { - auto chaincodeCopy = chaincode; - if (chaincodeCopy.empty()) - chaincodeCopy = BtcUtils::computeChainCode_Armory135(root); - - auto derScheme = - make_shared(chaincodeCopy); - - auto pubkey = CryptoECDSA().ComputePublicKey(root); - auto asset_single = make_shared( - Armory::Wallets::AssetId::getRootAssetId(), pubkey, nullptr); - - return AssetWallet_Single::computeWalletID(derScheme, asset_single); - }; - - auto promptForPassphrase = [&callerPrompt]( - SecureBinaryData& passphrase, SecureBinaryData& control)->bool - { - //prompt for wallet passphrase - if (!callerPrompt(RestorePromptType::Passphrase, {}, passphrase)) - return false; - - //prompt for control passphrase - if (!callerPrompt(RestorePromptType::Control, {}, control)) - return false; - - return true; - }; - - //generate wallet - shared_ptr wallet; - switch (firstIndex) - { - case BackupType::Armory135: - { - /*legacy armory wallet*/ - - auto id = SecureBinaryData::fromString( - computeWalletId(primaryData.data_, secondaryData.data_)); - if (!callerPrompt(RestorePromptType::Id, checksumIndexes, id)) - throw RestoreUserException("user rejected id"); - - //prompt for passwords - SecureBinaryData pass, control; - if (!promptForPassphrase(pass, control)) - throw RestoreUserException("user did not provide passphrase"); - - //create wallet - wallet = AssetWallet_Single::createFromPrivateRoot_Armory135( - homedir, - primaryData.data_, - secondaryData.data_, - pass, control, - WALLET_RESTORE_LOOKUP); - - break; - } - - //bip32 wallets - case BackupType::BIP32_Seed_Structured: - { - /*BIP32 wallet with BIP44/49/84 accounts*/ - - //create root node from seed - BIP32_Node rootNode; - rootNode.initFromSeed(primaryData.data_); - - //compute id and present to caller - auto id = SecureBinaryData::fromString( - computeWalletId(rootNode.getPrivateKey(), rootNode.getChaincode())); - if (!callerPrompt(RestorePromptType::Id, checksumIndexes, id)) - throw RestoreUserException("user rejected id"); - - //prompt for passwords - SecureBinaryData pass, control; - if (!promptForPassphrase(pass, control)) - throw RestoreUserException("user did not provide passphrase"); - - //create wallet - wallet = AssetWallet_Single::createFromSeed_BIP32( - homedir, - primaryData.data_, - pass, control, - WALLET_RESTORE_LOOKUP); - - break; - } - - case BIP32_Seed_Virgin: - { - /*empty BIP32 wallet*/ - - //create root node from seed - BIP32_Node rootNode; - rootNode.initFromSeed(primaryData.data_); - - //compute id and present to caller - auto id = SecureBinaryData::fromString( - computeWalletId(rootNode.getPrivateKey(), rootNode.getChaincode())); - if (!callerPrompt(RestorePromptType::Id, checksumIndexes, id)) - throw RestoreUserException("user rejected id"); - - //prompt for passwords - SecureBinaryData pass, control; - if (!promptForPassphrase(pass, control)) - throw RestoreUserException("user did not provide passphrase"); - - //create wallet - wallet = AssetWallet_Single::createFromSeed_BIP32_Blank( - homedir, - primaryData.data_, - pass, control); - - break; - } - - //case BackupType::BIP32_Root: - - default: - callerPrompt(RestorePromptType::TypeError, {}, promptDummy); - } - - return wallet; -} \ No newline at end of file diff --git a/cppForSwig/Signer/ResolverFeed_Wallets.cpp b/cppForSwig/Signer/ResolverFeed_Wallets.cpp index 82f8859d2..65a5d51c9 100644 --- a/cppForSwig/Signer/ResolverFeed_Wallets.cpp +++ b/cppForSwig/Signer/ResolverFeed_Wallets.cpp @@ -151,7 +151,7 @@ BinaryData Armory::Signer::ResolverFeed_AssetWalletSingle::getByVal(const Binary } //////////////////////////////////////////////////////////////////////////////// -const SecureBinaryData& ResolverFeed_AssetWalletSingle::getPrivKeyForPubkey( +const SecureBinaryData& Armory::Signer::ResolverFeed_AssetWalletSingle::getPrivKeyForPubkey( const BinaryData& pubkey) { //check cache first diff --git a/cppForSwig/Wallets/Wallets.cpp b/cppForSwig/Wallets/Wallets.cpp index be60bc86a..eec26fc19 100644 --- a/cppForSwig/Wallets/Wallets.cpp +++ b/cppForSwig/Wallets/Wallets.cpp @@ -121,9 +121,11 @@ shared_ptr AssetWallet::createAccount( dbName_, accountType, decryptedData_, move(cipher), getRootLbd); auto accID = account_ptr->getID(); - if (accounts_.find(accID) != accounts_.end()) - throw WalletException("already have an address account with this path"); - + const auto& itAccount = accounts_.find(accID); + if (itAccount != accounts_.end()) { + //throw WalletException("already have an address account with path " + accID.toHexStr()); + return itAccount->second; + } //commit to disk account_ptr->commit(iface_); @@ -1274,9 +1276,57 @@ shared_ptr AssetWallet_Single::createFromSeed( return walletPtr; } +/* substitutes variables from vars into tmpl with syntax "" -> "var_value" +* Available var names: +* * master_id +* * wallet_id +* * is_public = WatchingOnly if this flag is set +*/ +static string substFileTemplate(const string& tmpl + , const unordered_map& vars) +{ + if (tmpl.empty()) { + return {}; + } + if (tmpl[tmpl.size() - 1] == '/') { // compatibility mode - folder only + string result = tmpl; + string masterId; + const bool isPublic = (vars.find("is_public") != vars.end()); + try { + masterId = vars.at("master_id"); + } + catch (const exception&) {} + if (!isPublic) { + result += "armory_" + masterId + "_wallet.lmdb"; + } + else { + result += "armory_" + masterId + "_" + vars.at("is_public") + ".lmdb"; + } + return result; + } + string str = tmpl; + size_t itStart = string::npos; + while ((itStart = str.find('<')) != string::npos) { + const auto itEnd = str.find('>', itStart); + if (itEnd == string::npos) { + break; + } + const auto& key = str.substr(itStart + 1, itEnd - itStart - 1); + string value; + try { + value = vars.at(key); + } + catch (const exception&) { + value = "_"; + } + str.replace(itStart, itEnd - itStart + 1, value); + } + return str; +} + //////////////////////////////////////////////////////////////////////////////// shared_ptr AssetWallet_Single::createFromSeed( - const string& folder, + const string& fileTmpl, Seeds::ClearTextSeed_BIP32* seed, const SecureBinaryData& passphrase, const SecureBinaryData& controlPassphrase, @@ -1386,8 +1436,13 @@ shared_ptr AssetWallet_Single::createFromSeed( //db env auto masterId = seed->getMasterId(); auto walletId = seed->getWalletId(); - string path = folder + "/armory_" + masterId + "_wallet.lmdb"; - auto iface = getIfaceFromFile(path, false, controlPassLbd); + //string path = folder + "/armory_" + masterId + "_wallet.lmdb"; + const unordered_map tmplVars{ {"master_id", masterId}, {"wallet_id", walletId} }; + const auto& walletPathName = substFileTemplate(fileTmpl, tmplVars); + if (walletPathName.empty()) { + throw runtime_error("invalid wallet pathname"); + } + auto iface = getIfaceFromFile(walletPathName, false, controlPassLbd); //wallet object auto walletPtr = initWalletDb(iface, @@ -1430,7 +1485,7 @@ shared_ptr AssetWallet_Single::createFromSeed( //////////////////////////////////////////////////////////////////////////////// shared_ptr AssetWallet_Single::createFromPublicRoot_Armory135( - const string& folder, + const string& fileTmpl, SecureBinaryData& pubRoot, SecureBinaryData& chainCode, const SecureBinaryData& controlPassphrase, @@ -1448,23 +1503,9 @@ shared_ptr }; unordered_map tmplVars{ {"master_id", masterID} }; - if (isPublic) { - tmplVars["is_public"] = "WatchingOnly"; - } + tmplVars["is_public"] = "WatchingOnly"; const auto walletID = generateWalletId(pubRoot, chainCode, SeedType::Armory135); - { - //walletID - auto chaincode_copy = node.getChaincode(); - auto derScheme = - make_shared(chaincode_copy); - - auto asset_single = make_shared( - AssetId::getRootAssetId(), - pubkey, nullptr); - - walletID = move(computeWalletID(derScheme, asset_single)); - tmplVars["wallet_id"] = walletID; - } + tmplVars["wallet_id"] = walletID; const auto& walletPathName = substFileTemplate(fileTmpl, tmplVars); if (walletPathName.empty()) { From f895833ff45b874025492f36eb0af9810c1f5c60 Mon Sep 17 00:00:00 2001 From: Sergey Chernikov Date: Thu, 29 Aug 2024 19:19:36 +0300 Subject: [PATCH 46/47] ifdef'ed BUILD_PROTOBUF --- cppForSwig/AsyncClient.cpp | 179 +++++++++++++++++- cppForSwig/AsyncClient.h | 14 +- cppForSwig/BDM_Server.cpp | 42 +++- cppForSwig/BDM_Server.h | 27 ++- cppForSwig/BDVCodec.h | 2 + cppForSwig/BlockDataViewer.cpp | 6 + cppForSwig/BlockDataViewer.h | 4 + .../BlockchainDatabase/ScrAddrFilter.cpp | 3 +- cppForSwig/BridgeAPI/CppBridge.h | 2 + cppForSwig/BridgeAPI/PassphrasePrompt.cpp | 2 + cppForSwig/BridgeAPI/ProtobufConversions.h | 3 +- cppForSwig/CMakeLists.txt | 2 + cppForSwig/DBClientClasses.cpp | 128 ++++++++++++- cppForSwig/DBClientClasses.h | 31 +-- cppForSwig/LedgerEntry.cpp | 2 + cppForSwig/LedgerEntry.h | 3 +- cppForSwig/Server.cpp | 14 +- cppForSwig/Server.h | 5 +- cppForSwig/Signer/ResolverFeed.cpp | 3 +- cppForSwig/Signer/ResolverFeed.h | 6 +- cppForSwig/Signer/Script.cpp | 2 + cppForSwig/Signer/Script.h | 16 +- cppForSwig/Signer/ScriptRecipient.cpp | 2 + cppForSwig/Signer/ScriptRecipient.h | 8 +- cppForSwig/Signer/Signer.cpp | 10 + cppForSwig/Signer/Signer.h | 23 ++- cppForSwig/SocketObject.cpp | 5 +- cppForSwig/SocketWritePayload.h | 4 + cppForSwig/TxClasses.cpp | 2 + cppForSwig/TxClasses.h | 5 +- cppForSwig/WebSocketClient.cpp | 7 +- cppForSwig/WebSocketMessage.cpp | 8 + cppForSwig/WebSocketMessage.h | 7 +- cppForSwig/ZeroConfNotifications.cpp | 6 +- cppForSwig/ZeroConfNotifications.h | 3 +- cppForSwig/gtest/WalletTests.cpp | 2 + 36 files changed, 516 insertions(+), 72 deletions(-) diff --git a/cppForSwig/AsyncClient.cpp b/cppForSwig/AsyncClient.cpp index a019b0f12..1ea64b41b 100755 --- a/cppForSwig/AsyncClient.cpp +++ b/cppForSwig/AsyncClient.cpp @@ -10,13 +10,17 @@ #include "AsyncClient.h" #include "EncryptionUtils.h" #include "BDVCodec.h" +#ifdef BUILD_PROTOBUF #include "google/protobuf/io/zero_copy_stream_impl_lite.h" #include "google/protobuf/text_format.h" +#endif #include "ArmoryErrors.h" using namespace std; using namespace AsyncClient; +#ifdef BUILD_PROTOBUF using namespace Codec_BDVCommand; +#endif using namespace DBClientClasses; /////////////////////////////////////////////////////////////////////////////// @@ -24,6 +28,7 @@ using namespace DBClientClasses; // BlockDataViewer // /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF unique_ptr BlockDataViewer::make_payload(Methods method) { auto payload = make_unique(); @@ -45,6 +50,7 @@ unique_ptr BlockDataViewer::make_payload( payload->message_ = move(message); return payload; } +#endif /////////////////////////////////////////////////////////////////////////////// bool BlockDataViewer::hasRemoteDB(void) @@ -100,10 +106,11 @@ void BlockDataViewer::registerWithDB(BinaryData magic_word) //get bdvID try { +#ifdef BUILD_PROTOBUF auto payload = make_payload(StaticMethods::registerBDV); auto command = dynamic_cast(payload->message_.get()); command->set_magicword(magic_word.getPtr(), magic_word.getSize()); - +#endif //registration is always blocking as it needs to guarantee the bdvID auto promPtr = make_shared>(); @@ -124,8 +131,9 @@ void BlockDataViewer::registerWithDB(BinaryData magic_word) auto read_payload = make_shared(); read_payload->callbackReturn_ = make_unique(getResult); +#ifdef BUILD_PROTOBUF sock_->pushPayload(move(payload), read_payload); - +#endif bdvID_ = move(fut.get()); } catch (runtime_error &e) @@ -150,16 +158,19 @@ void BlockDataViewer::unregisterFromDB() sockws->shutdown(); return; } - +#ifdef BUILD_PROTOBUF auto payload = make_payload(StaticMethods::unregisterBDV); sock_->pushPayload(move(payload), nullptr); +#endif } /////////////////////////////////////////////////////////////////////////////// void BlockDataViewer::goOnline() { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::goOnline); sock_->pushPayload(move(payload), nullptr); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -182,6 +193,7 @@ BlockDataViewer::~BlockDataViewer() /////////////////////////////////////////////////////////////////////////////// void BlockDataViewer::shutdown(const string& cookie) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(StaticMethods::shutdown); auto command = dynamic_cast(payload->message_.get()); @@ -189,11 +201,13 @@ void BlockDataViewer::shutdown(const string& cookie) command->set_cookie(cookie); sock_->pushPayload(move(payload), nullptr); +#endif } /////////////////////////////////////////////////////////////////////////////// void BlockDataViewer::shutdownNode(const string& cookie) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(StaticMethods::shutdownNode); auto command = dynamic_cast(payload->message_.get()); @@ -201,6 +215,7 @@ void BlockDataViewer::shutdownNode(const string& cookie) command->set_cookie(cookie); sock_->pushPayload(move(payload), nullptr); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -219,22 +234,26 @@ Lockbox BlockDataViewer::instantiateLockbox(const string& id) void BlockDataViewer::getLedgerDelegateForWallets( function)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getLedgerDelegateForWallets); auto read_payload = make_shared(); read_payload->callbackReturn_ = make_unique(sock_, bdvID_, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void BlockDataViewer::getLedgerDelegateForLockboxes( function)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getLedgerDelegateForLockboxes); auto read_payload = make_shared(); read_payload->callbackReturn_ = make_unique(sock_, bdvID_, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -248,7 +267,7 @@ string BlockDataViewer::broadcastZC(const BinaryData& rawTx) { auto tx = make_shared(rawTx); cache_->insertTx(tx); - +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::broadcastZC); auto command = dynamic_cast(payload->message_.get()); command->add_bindata(rawTx.getPtr(), rawTx.getSize()); @@ -259,11 +278,15 @@ string BlockDataViewer::broadcastZC(const BinaryData& rawTx) sock_->pushPayload(move(payload), nullptr); return broadcastId; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// string BlockDataViewer::broadcastZC(const vector& rawTxVec) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::broadcastZC); auto command = dynamic_cast(payload->message_.get()); @@ -281,6 +304,9 @@ string BlockDataViewer::broadcastZC(const vector& rawTxVec) sock_->pushPayload(move(payload), nullptr); return broadcastId; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -288,7 +314,7 @@ string BlockDataViewer::broadcastThroughRPC(const BinaryData& rawTx) { auto tx = make_shared(rawTx); cache_->insertTx(tx); - +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::broadcastThroughRPC); auto command = dynamic_cast(payload->message_.get()); command->add_bindata(rawTx.getPtr(), rawTx.getSize()); @@ -299,6 +325,9 @@ string BlockDataViewer::broadcastThroughRPC(const BinaryData& rawTx) sock_->pushPayload(move(payload), nullptr); return broadcastId; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -353,7 +382,7 @@ void BlockDataViewer::getTxByHash( } catch(NoMatch&) {} - +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getTxByHash); auto command = dynamic_cast(payload->message_.get()); command->set_hash(bdRef.getPtr(), bdRef.getSize()); @@ -363,6 +392,7 @@ void BlockDataViewer::getTxByHash( read_payload->callbackReturn_ = make_unique(cache_, txHash, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -370,9 +400,10 @@ void BlockDataViewer::getTxBatchByHash( const set& hashes, const TxBatchCallback& callback) { //only accepts hashes in binary format +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getTxBatchByHash); auto command = dynamic_cast(payload->message_.get()); - +#endif map hashesToFetch; TxBatchResult cachedTxs; for (auto& hash : hashes) @@ -407,6 +438,7 @@ void BlockDataViewer::getTxBatchByHash( return; } +#ifdef BUILD_PROTOBUF for (auto& hash : hashesToFetch) { if (!hash.second) @@ -427,6 +459,7 @@ void BlockDataViewer::getTxBatchByHash( make_unique( cache_, cachedTxs, hashesToFetch, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -456,6 +489,7 @@ void BlockDataViewer::getRawHeaderForTxHash(const BinaryData& txHash, catch(NoMatch&) { } +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getHeaderByHash); auto command = dynamic_cast(payload->message_.get()); command->add_bindata(txHash.getPtr(), txHash.getSize()); @@ -465,6 +499,7 @@ void BlockDataViewer::getRawHeaderForTxHash(const BinaryData& txHash, make_unique( cache_, UINT32_MAX, txHash, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -480,6 +515,7 @@ void BlockDataViewer::getHeaderByHeight(unsigned height, catch(NoMatch&) { } +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getHeaderByHeight); auto command = dynamic_cast(payload->message_.get()); command->set_height(height); @@ -490,6 +526,7 @@ void BlockDataViewer::getHeaderByHeight(unsigned height, make_unique( cache_, height, txhash, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -497,6 +534,7 @@ void BlockDataViewer::getLedgerDelegateForScrAddr( const string& walletID, BinaryDataRef scrAddr, function)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getLedgerDelegateForScrAddr); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID); @@ -506,29 +544,34 @@ void BlockDataViewer::getLedgerDelegateForScrAddr( read_payload->callbackReturn_ = make_unique(sock_, bdvID_, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void BlockDataViewer::updateWalletsLedgerFilter( const vector& wltIdVec) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::updateWalletsLedgerFilter); auto command = dynamic_cast(payload->message_.get()); for (auto bd : wltIdVec) command->add_bindata(bd.getPtr(), bd.getSize()); sock_->pushPayload(move(payload), nullptr); +#endif } /////////////////////////////////////////////////////////////////////////////// void BlockDataViewer::getNodeStatus(function< void(ReturnMessage>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getNodeStatus); auto read_payload = make_shared(); read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -536,6 +579,7 @@ void BlockDataViewer::estimateFee(unsigned blocksToConfirm, const string& strategy, function)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::estimateFee); auto command = dynamic_cast(payload->message_.get()); command->set_value(blocksToConfirm); @@ -545,12 +589,14 @@ void BlockDataViewer::estimateFee(unsigned blocksToConfirm, read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void BlockDataViewer::getFeeSchedule(const string& strategy, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getFeeSchedule); auto command = dynamic_cast(payload->message_.get()); command->add_bindata(strategy); @@ -559,6 +605,7 @@ void BlockDataViewer::getFeeSchedule(const string& strategy, functioncallbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } @@ -567,6 +614,7 @@ void BlockDataViewer::getHistoryForWalletSelection( const vector& wldIDs, const string& orderingStr, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getHistoryForWalletSelection); auto command = dynamic_cast(payload->message_.get()); if (orderingStr == "ascending") @@ -583,6 +631,7 @@ void BlockDataViewer::getHistoryForWalletSelection( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -591,6 +640,7 @@ void BlockDataViewer::getSpentnessForOutputs( function>>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getSpentnessForOutputs); auto command = dynamic_cast(payload->message_.get()); @@ -610,6 +660,7 @@ void BlockDataViewer::getSpentnessForOutputs( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -618,6 +669,7 @@ void BlockDataViewer::getSpentnessForZcOutputs( function>>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getSpentnessForZcOutputs); auto command = dynamic_cast(payload->message_.get()); @@ -637,6 +689,7 @@ void BlockDataViewer::getSpentnessForZcOutputs( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -655,6 +708,7 @@ void BlockDataViewer::getOutputsForOutpoints( const map>& outpoints, bool withZc, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = make_payload(Methods::getOutputsForOutpoints); auto command = dynamic_cast(payload->message_.get()); @@ -676,6 +730,7 @@ void BlockDataViewer::getOutputsForOutpoints( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -692,6 +747,7 @@ LedgerDelegate::LedgerDelegate(shared_ptr sock, void LedgerDelegate::getHistoryPage(uint32_t id, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getHistoryPage); auto command = dynamic_cast(payload->message_.get()); command->set_delegateid(delegateID_); @@ -701,12 +757,14 @@ void LedgerDelegate::getHistoryPage(uint32_t id, read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void LedgerDelegate::getPageCount( function)> callback) const { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getPageCountForLedgerDelegate); auto command = dynamic_cast(payload->message_.get()); @@ -716,6 +774,7 @@ void LedgerDelegate::getPageCount( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -731,6 +790,7 @@ AsyncClient::BtcWallet::BtcWallet(const BlockDataViewer& bdv, const string& id) string AsyncClient::BtcWallet::registerAddresses( const vector& addrVec, bool isNew) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::registerWallet); auto command = dynamic_cast(payload->message_.get()); command->set_flag(isNew); @@ -745,11 +805,15 @@ string AsyncClient::BtcWallet::registerAddresses( sock_->pushPayload(move(payload), nullptr); return registrationId; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// string AsyncClient::BtcWallet::setUnconfirmedTarget(unsigned confTarget) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::setWalletConfTarget); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -761,12 +825,16 @@ string AsyncClient::BtcWallet::setUnconfirmedTarget(unsigned confTarget) sock_->pushPayload(move(payload), nullptr); return registrationId; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// string AsyncClient::BtcWallet::unregisterAddresses( const set& addrSet) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::unregisterAddresses); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -780,6 +848,9 @@ string AsyncClient::BtcWallet::unregisterAddresses( sock_->pushPayload(move(payload), nullptr); return registrationId; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -792,6 +863,7 @@ string AsyncClient::BtcWallet::unregister() void AsyncClient::BtcWallet::getBalancesAndCount(uint32_t blockheight, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getBalancesAndCount); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -801,12 +873,14 @@ void AsyncClient::BtcWallet::getBalancesAndCount(uint32_t blockheight, read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void AsyncClient::BtcWallet::getSpendableTxOutListForValue(uint64_t val, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getSpendableTxOutListForValue); auto command = dynamic_cast(payload->message_.get()); @@ -817,12 +891,14 @@ void AsyncClient::BtcWallet::getSpendableTxOutListForValue(uint64_t val, read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void AsyncClient::BtcWallet::getSpendableZCList( function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getSpendableZCList); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -831,12 +907,14 @@ void AsyncClient::BtcWallet::getSpendableZCList( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void AsyncClient::BtcWallet::getRBFTxOutList( function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getRBFTxOutList); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -845,12 +923,14 @@ void AsyncClient::BtcWallet::getRBFTxOutList( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void AsyncClient::BtcWallet::getAddrTxnCountsFromDB( function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getAddrTxnCounts); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -859,12 +939,14 @@ void AsyncClient::BtcWallet::getAddrTxnCountsFromDB( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void AsyncClient::BtcWallet::getAddrBalancesFromDB( function>>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getAddrBalances); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -873,12 +955,14 @@ void AsyncClient::BtcWallet::getAddrBalancesFromDB( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void AsyncClient::BtcWallet::getHistoryPage(uint32_t id, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getHistoryPage); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -888,6 +972,7 @@ void AsyncClient::BtcWallet::getHistoryPage(uint32_t id, read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -897,7 +982,7 @@ void AsyncClient::BtcWallet::getLedgerEntryForTxHash( { //get history page with a hash as argument instead of an int will return //the ledger entry for the tx instead of a page - +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getHistoryPage); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -907,6 +992,7 @@ void AsyncClient::BtcWallet::getLedgerEntryForTxHash( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -921,6 +1007,7 @@ ScrAddrObj AsyncClient::BtcWallet::getScrAddrObjByKey(const BinaryData& scrAddr, void AsyncClient::BtcWallet::createAddressBook( function>)> callback) const { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::createAddressBook); auto command = dynamic_cast(payload->message_.get()); command->set_walletid(walletID_); @@ -929,6 +1016,7 @@ void AsyncClient::BtcWallet::createAddressBook( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -958,6 +1046,7 @@ void Lockbox::getBalancesAndCountFromDB(uint32_t topBlockHeight) string AsyncClient::Lockbox::registerAddresses( const vector& addrVec, bool isNew) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::registerLockbox); auto command = dynamic_cast(payload->message_.get()); command->set_flag(isNew); @@ -972,6 +1061,9 @@ string AsyncClient::Lockbox::registerAddresses( sock_->pushPayload(move(payload), nullptr); return registrationId; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1001,6 +1093,7 @@ ScrAddrObj::ScrAddrObj(AsyncClient::BtcWallet* wlt, const BinaryData& scrAddr, void ScrAddrObj::getSpendableTxOutList( function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getSpendableTxOutListForAddr); auto command = dynamic_cast(payload->message_.get()); @@ -1011,6 +1104,7 @@ void ScrAddrObj::getSpendableTxOutList( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1026,6 +1120,7 @@ AsyncClient::Blockchain::Blockchain(const BlockDataViewer& bdv) : void AsyncClient::Blockchain::getHeaderByHash(const BinaryData& hash, function)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getHeaderByHash); auto command = dynamic_cast(payload->message_.get()); command->set_hash(hash.getPtr(), hash.getSize()); @@ -1034,12 +1129,14 @@ void AsyncClient::Blockchain::getHeaderByHash(const BinaryData& hash, read_payload->callbackReturn_ = make_unique(UINT32_MAX, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// void AsyncClient::Blockchain::getHeaderByHeight(unsigned height, function)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getHeaderByHeight); auto command = dynamic_cast(payload->message_.get()); command->set_height(height); @@ -1048,6 +1145,7 @@ void AsyncClient::Blockchain::getHeaderByHeight(unsigned height, read_payload->callbackReturn_ = make_unique(height, callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1065,6 +1163,7 @@ void AsyncClient::BlockDataViewer::getCombinedBalances( const vector& wltIDs, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload(Methods::getCombinedBalances); auto command = dynamic_cast(payload->message_.get()); @@ -1075,6 +1174,7 @@ void AsyncClient::BlockDataViewer::getCombinedBalances( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1082,6 +1182,7 @@ void AsyncClient::BlockDataViewer::getCombinedAddrTxnCounts( const vector& wltIDs, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getCombinedAddrTxnCounts); auto command = dynamic_cast(payload->message_.get()); @@ -1093,6 +1194,7 @@ void AsyncClient::BlockDataViewer::getCombinedAddrTxnCounts( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1100,6 +1202,7 @@ void AsyncClient::BlockDataViewer::getCombinedSpendableTxOutListForValue( const vector& wltIDs, uint64_t value, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getCombinedSpendableTxOutListForValue); auto command = dynamic_cast(payload->message_.get()); @@ -1113,6 +1216,7 @@ void AsyncClient::BlockDataViewer::getCombinedSpendableTxOutListForValue( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1120,6 +1224,7 @@ void AsyncClient::BlockDataViewer::getCombinedSpendableZcOutputs( const vector& wltIDs, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getCombinedSpendableZcOutputs); auto command = dynamic_cast(payload->message_.get()); @@ -1131,6 +1236,7 @@ void AsyncClient::BlockDataViewer::getCombinedSpendableZcOutputs( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1138,6 +1244,7 @@ void AsyncClient::BlockDataViewer::getCombinedRBFTxOuts( const vector& wltIDs, function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getCombinedRBFTxOuts); auto command = dynamic_cast(payload->message_.get()); @@ -1149,6 +1256,7 @@ void AsyncClient::BlockDataViewer::getCombinedRBFTxOuts( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1157,6 +1265,7 @@ void AsyncClient::BlockDataViewer::getOutpointsForAddresses( unsigned startHeight, unsigned zcIndexCutoff, std::function)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getOutpointsForAddresses); auto command = dynamic_cast(payload->message_.get()); @@ -1171,6 +1280,7 @@ void AsyncClient::BlockDataViewer::getOutpointsForAddresses( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1178,6 +1288,7 @@ void AsyncClient::BlockDataViewer::getUTXOsForAddress( const BinaryData& scrAddr, bool withZc, std::function>)> callback) { +#ifdef BUILD_PROTOBUF auto payload = BlockDataViewer::make_payload( Methods::getUTXOsForAddress); auto command = dynamic_cast(payload->message_.get()); @@ -1189,6 +1300,7 @@ void AsyncClient::BlockDataViewer::getUTXOsForAddress( read_payload->callbackReturn_ = make_unique(callback); sock_->pushPayload(move(payload), read_payload); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1196,6 +1308,7 @@ void AsyncClient::BlockDataViewer::getUTXOsForAddress( // CallbackReturn children // /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void AsyncClient::deserialize( google::protobuf::Message* ptr, const WebSocketMessagePartial& partialMsg) { @@ -1208,11 +1321,13 @@ void AsyncClient::deserialize( throw ClientMessageError(errorMsg.errstr(), errorMsg.code()); } } +#endif /////////////////////////////////////////////////////////////////////////////// void CallbackReturn_BinaryDataRef::callback( const WebSocketMessagePartial& partialMsg) { +#ifdef BUILD_PROTOBUF auto msg = make_shared<::Codec_CommonTypes::BinaryData>(); AsyncClient::deserialize(msg.get(), partialMsg); @@ -1234,6 +1349,7 @@ void CallbackReturn_BinaryDataRef::callback( if (thr.joinable()) thr.detach(); } +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1242,6 +1358,7 @@ void CallbackReturn_String::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::Strings msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1262,6 +1379,7 @@ void CallbackReturn_String::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1276,6 +1394,7 @@ void CallbackReturn_LedgerDelegate::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::Strings msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1298,6 +1417,7 @@ void CallbackReturn_LedgerDelegate::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1312,6 +1432,7 @@ void CallbackReturn_Tx::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::TxWithMetaData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1352,6 +1473,7 @@ void CallbackReturn_Tx::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1366,6 +1488,7 @@ void CallbackReturn_TxBatch::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::ManyTxWithMetaData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1443,6 +1566,7 @@ void CallbackReturn_TxBatch::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1457,6 +1581,7 @@ void CallbackReturn_RawHeader::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::BinaryData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1484,6 +1609,7 @@ void CallbackReturn_RawHeader::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1504,6 +1630,7 @@ void CallbackReturn_NodeStatus::callback( { try { +#ifdef BUILD_PROTOBUF auto msg = make_shared(); AsyncClient::deserialize(msg.get(), partialMsg); @@ -1521,6 +1648,7 @@ void CallbackReturn_NodeStatus::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1535,6 +1663,7 @@ void CallbackReturn_FeeEstimateStruct::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_FeeEstimate::FeeEstimate msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1553,6 +1682,7 @@ void CallbackReturn_FeeEstimateStruct::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1567,6 +1697,7 @@ void CallbackReturn_FeeSchedule::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_FeeEstimate::FeeSchedule msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1594,6 +1725,7 @@ void CallbackReturn_FeeSchedule::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1608,10 +1740,10 @@ void CallbackReturn_VectorLedgerEntry::callback( { try { +#ifdef BUILD_PROTOBUF auto msg = make_shared<::Codec_LedgerEntry::ManyLedgerEntry>(); AsyncClient::deserialize(msg.get(), partialMsg); - vector lev; for (int i = 0; i < msg->values_size(); i++) @@ -1632,6 +1764,7 @@ void CallbackReturn_VectorLedgerEntry::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1646,6 +1779,7 @@ void CallbackReturn_UINT64::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::OneUnsigned msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1663,6 +1797,7 @@ void CallbackReturn_UINT64::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1677,6 +1812,7 @@ void CallbackReturn_VectorUTXO::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_Utxo::ManyUtxo utxos; AsyncClient::deserialize(&utxos, partialMsg); @@ -1700,6 +1836,7 @@ void CallbackReturn_VectorUTXO::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1714,6 +1851,7 @@ void CallbackReturn_VectorUINT64::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::ManyUnsigned msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1733,6 +1871,7 @@ void CallbackReturn_VectorUINT64::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1747,6 +1886,7 @@ void CallbackReturn_Map_BD_U32::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_AddressData::ManyAddressData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1777,6 +1917,7 @@ void CallbackReturn_Map_BD_U32::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1791,6 +1932,7 @@ void CallbackReturn_Map_BD_VecU64::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_AddressData::ManyAddressData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1819,6 +1961,7 @@ void CallbackReturn_Map_BD_VecU64::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1833,6 +1976,7 @@ void CallbackReturn_LedgerEntry::callback( { try { +#ifdef BUILD_PROTOBUF auto msg = make_shared<::Codec_LedgerEntry::LedgerEntry>(); AsyncClient::deserialize(msg.get(), partialMsg); @@ -1850,6 +1994,7 @@ void CallbackReturn_LedgerEntry::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1864,6 +2009,7 @@ void CallbackReturn_VectorAddressBookEntry::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_AddressBook::AddressBook addressBook; AsyncClient::deserialize(&addressBook, partialMsg); @@ -1896,6 +2042,7 @@ void CallbackReturn_VectorAddressBookEntry::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1909,6 +2056,7 @@ void CallbackReturn_Bool::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::OneUnsigned msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1924,6 +2072,7 @@ void CallbackReturn_Bool::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1938,6 +2087,7 @@ void CallbackReturn_BlockHeader::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_CommonTypes::BinaryData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -1959,6 +2109,7 @@ void CallbackReturn_BlockHeader::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -1971,10 +2122,12 @@ void CallbackReturn_BlockHeader::callback( void CallbackReturn_BDVCallback::callback( const WebSocketMessagePartial& partialMsg) { +#ifdef BUILD_PROTOBUF auto msg = make_shared<::Codec_BDVCommand::BDVCallback>(); AsyncClient::deserialize(msg.get(), partialMsg); userCallbackLambda_(msg); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1983,6 +2136,7 @@ void CallbackReturn_CombinedBalances::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_AddressData::ManyCombinedData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -2025,6 +2179,7 @@ void CallbackReturn_CombinedBalances::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -2039,6 +2194,7 @@ void CallbackReturn_CombinedCounts::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_AddressData::ManyCombinedData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -2075,6 +2231,7 @@ void CallbackReturn_CombinedCounts::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -2089,6 +2246,7 @@ void CallbackReturn_AddrOutpoints::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_Utxo::AddressOutpointsData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -2132,6 +2290,7 @@ void CallbackReturn_AddrOutpoints::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { @@ -2146,6 +2305,7 @@ void CallbackReturn_SpentnessData::callback( { try { +#ifdef BUILD_PROTOBUF ::Codec_Utxo::Spentness_BatchData msg; AsyncClient::deserialize(&msg, partialMsg); @@ -2194,6 +2354,7 @@ void CallbackReturn_SpentnessData::callback( if (thr.joinable()) thr.detach(); } +#endif } catch (ClientMessageError& e) { diff --git a/cppForSwig/AsyncClient.h b/cppForSwig/AsyncClient.h index 2e0adbf2a..753d79245 100755 --- a/cppForSwig/AsyncClient.h +++ b/cppForSwig/AsyncClient.h @@ -403,11 +403,12 @@ namespace AsyncClient ~BlockDataViewer(void); //utility +#ifdef BUILD_PROTOBUF static std::unique_ptr make_payload( ::Codec_BDVCommand::Methods); static std::unique_ptr make_payload( ::Codec_BDVCommand::StaticMethods); - +#endif BtcWallet instantiateWallet(const std::string& id); Lockbox instantiateLockbox(const std::string& id); @@ -527,9 +528,10 @@ namespace AsyncClient }; //////////////////////////////////////////////////////////////////////////// - void deserialize(::google::protobuf::Message*, +#ifdef BUILD_PROTOBUF + void deserialize(::google::protobuf::Message*, const WebSocketMessagePartial&); - +#endif /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //// callback structs for async networking @@ -863,16 +865,18 @@ namespace AsyncClient /////////////////////////////////////////////////////////////////////////////// struct CallbackReturn_BDVCallback : public CallbackReturn_WebSocket { +#ifdef BUILD_PROTOBUF private: std::function)> userCallbackLambda_; - +#endif public: +#ifdef BUILD_PROTOBUF CallbackReturn_BDVCallback( std::function)> lbd) : userCallbackLambda_(lbd) {} - +#endif //virtual void callback(const WebSocketMessagePartial&); }; diff --git a/cppForSwig/BDM_Server.cpp b/cppForSwig/BDM_Server.cpp index c70ee7e8b..81f11701b 100644 --- a/cppForSwig/BDM_Server.cpp +++ b/cppForSwig/BDM_Server.cpp @@ -10,8 +10,10 @@ #include "ArmoryErrors.h" using namespace std; +#ifdef BUILD_PROTOBUF using namespace ::google::protobuf; using namespace ::Codec_BDVCommand; +#endif using namespace ::Armory::Threading; /////////////////////////////////////////////////////////////////////////////// @@ -19,6 +21,7 @@ using namespace ::Armory::Threading; // BDV_Server_Object // /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF BDVCommandProcessingResultType BDV_Server_Object::processCommand( shared_ptr command, shared_ptr& resultingPayload) { @@ -1804,6 +1807,7 @@ BDVCommandProcessingResultType BDV_Server_Object::processCommand( return BDVCommandProcess_Success; } +#endif /////////////////////////////////////////////////////////////////////////////// shared_ptr Clients::get(const string& id) const @@ -1918,6 +1922,7 @@ void BDV_Server_Object::init() //fill with addresses from protobuf payloads for (auto& wlt : wltMap) { +#ifdef BUILD_PROTOBUF for (int i = 0; i < wlt.second.command_->bindata_size(); i++) { auto& addrStr = wlt.second.command_->bindata(i); @@ -1927,6 +1932,7 @@ void BDV_Server_Object::init() BinaryDataRef addrRef; addrRef.setRef(addrStr); batch->scrAddrSet_.insert(move(addrRef)); } +#endif } //callback only serves to wait on the registration event @@ -1964,7 +1970,7 @@ void BDV_Server_Object::init() //mark bdv object as ready isReadyPromise_->set_value(true); - +#ifdef BUILD_PROTOBUF //callback client with BDM_Ready packet auto message = make_shared(); auto notif = message->add_notification(); @@ -1972,6 +1978,7 @@ void BDV_Server_Object::init() auto newBlockNotif = notif->mutable_newblock(); newBlockNotif->set_height(blockchain().top()->getBlockHeight()); cb_->callback(message); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -1987,7 +1994,7 @@ void BDV_Server_Object::processNotification( } scanWallets(notifPtr); - +#ifdef BUILD_PROTOBUF auto callbackPtr = make_shared(); switch (action) @@ -2130,9 +2137,11 @@ void BDV_Server_Object::processNotification( if(callbackPtr->notification_size() > 0) cb_->callback(callbackPtr); +#endif } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void BDV_Server_Object::registerWallet( shared_ptr<::Codec_BDVCommand::BDVCommand> command) { @@ -2192,6 +2201,7 @@ void BDV_Server_Object::registerLockbox( auto bdvPtr = (BlockDataViewer*)this; bdvPtr->registerLockbox(command); } +#endif /////////////////////////////////////////////////////////////////////////////// void BDV_Server_Object::populateWallets(map& wltMap) @@ -2201,6 +2211,8 @@ void BDV_Server_Object::populateWallets(map& wltMap) for (auto& wlt : wltMap) { + map> newAddrMap; +#ifdef BUILD_PROTOBUF auto& walletId = wlt.second.command_->walletid(); shared_ptr theWallet; @@ -2215,7 +2227,6 @@ void BDV_Server_Object::populateWallets(map& wltMap) continue; } - map> newAddrMap; for (int i = 0; i < wlt.second.command_->bindata_size(); i++) { auto& addrStr = wlt.second.command_->bindata(i); @@ -2232,11 +2243,12 @@ void BDV_Server_Object::populateWallets(map& wltMap) db_, &blockchain(), zeroConfCont_.get(), iter->first); newAddrMap.insert(move(make_pair(iter->first, addrObj))); } - +#endif if (newAddrMap.size() == 0) continue; - +#ifdef BUILD_PROTOBUF theWallet->scrAddrMap_.update(newAddrMap); +#endif } } @@ -2255,6 +2267,7 @@ void BDV_Server_Object::flagRefresh( } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF BDVCommandProcessingResultType BDV_Server_Object::processPayload( shared_ptr& packet, shared_ptr& result) { @@ -2373,9 +2386,9 @@ BDVCommandProcessingResultType BDV_Server_Object::processPayload( result = errMsg; } - return BDVCommandProcess_Failure; } +#endif /////////////////////////////////////////////////////////////////////////////// // @@ -2525,6 +2538,7 @@ void Clients::bdvMaintenanceThread() } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void Clients::processShutdownCommand(shared_ptr command) { const auto& thisCookie = Armory::Config::NetworkSettings::cookie(); @@ -2573,6 +2587,7 @@ void Clients::processShutdownCommand(shared_ptr command) LOGWARN << "unexpected command in processShutdownCommand"; } } +#endif /////////////////////////////////////////////////////////////////////////////// void Clients::shutdown() @@ -2646,6 +2661,7 @@ void Clients::unregisterAllBDVs() } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF shared_ptr Clients::registerBDV( shared_ptr command, string bdvID) { @@ -2689,6 +2705,7 @@ shared_ptr Clients::registerBDV( response->set_data(newID); return response; } +#endif /////////////////////////////////////////////////////////////////////////////// void Clients::unregisterBDV(std::string bdvId) @@ -2838,8 +2855,9 @@ void Clients::messageParserThread(void) the object's process mutex */ unique_lock lock(bdvPtr->processPacketMutex_); +#ifdef BUILD_PROTOBUF auto result = processCommand(payloadPtr); - +#endif //check if the map has the next message { auto msgIter = bdvPtr->messageMap_.find( @@ -2869,10 +2887,12 @@ void Clients::messageParserThread(void) lock.unlock(); bdvPtr->packetProcess_threadLock_.store(0); +#ifdef BUILD_PROTOBUF //write return value if any if (result != nullptr) WebSocketServer::write( payloadPtr->bdvID_, payloadPtr->messageID_, result); +#endif } } @@ -3043,6 +3063,7 @@ void Clients::broadcastThroughRPC() } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF shared_ptr Clients::processCommand(shared_ptr payload) { //clear bdvPtr from the payload to avoid circular ownership @@ -3420,6 +3441,7 @@ shared_ptr Clients::processUnregisteredCommand(const uint64_t& bdvId, return nullptr; } +#endif /////////////////////////////////////////////////////////////////////////////// // @@ -3430,6 +3452,7 @@ Callback::~Callback() {} /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void WS_Callback::callback(shared_ptr command) { //write to socket @@ -3455,6 +3478,7 @@ shared_ptr<::Codec_BDVCommand::BDVCallback> UnitTest_Callback::getNotification() return nullptr; } +#endif /////////////////////////////////////////////////////////////////////////////// // @@ -3480,6 +3504,7 @@ void BDV_PartialMessage::reset() } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF bool BDV_PartialMessage::getMessage(shared_ptr msgPtr) { if (!isReady()) @@ -3487,6 +3512,7 @@ bool BDV_PartialMessage::getMessage(shared_ptr msgPtr) return partialMessage_.getMessage(msgPtr.get()); } +#endif /////////////////////////////////////////////////////////////////////////////// size_t BDV_PartialMessage::topId() const @@ -3502,4 +3528,4 @@ size_t BDV_PartialMessage::topId() const unsigned BDV_PartialMessage::getMessageId(shared_ptr packet) { return WebSocketMessagePartial::getMessageId(packet->packetData_.getRef()); -} \ No newline at end of file +} diff --git a/cppForSwig/BDM_Server.h b/cppForSwig/BDM_Server.h index c4932c311..457e51c32 100644 --- a/cppForSwig/BDM_Server.h +++ b/cppForSwig/BDM_Server.h @@ -52,8 +52,10 @@ class BDV_Server_Object; namespace DBTestUtils { +#ifdef BUILD_PROTOBUF std::tuple, unsigned> waitOnSignal( Clients*, const std::string&, ::Codec_BDVCommand::NotificationType); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -97,7 +99,9 @@ class Callback virtual ~Callback() = 0; +#ifdef BUILD_PROTOBUF virtual void callback(std::shared_ptr<::Codec_BDVCommand::BDVCallback>) = 0; +#endif virtual bool isValid(void) = 0; virtual void shutdown(void) = 0; }; @@ -113,7 +117,9 @@ class WS_Callback : public Callback bdvID_(bdvid) {} +#ifdef BUILD_PROTOBUF void callback(std::shared_ptr<::Codec_BDVCommand::BDVCallback>); +#endif bool isValid(void) { return true; } void shutdown(void) {} }; @@ -121,26 +127,32 @@ class WS_Callback : public Callback /////////////////////////////////////////////////////////////////////////////// class UnitTest_Callback : public Callback { +#ifdef BUILD_PROTOBUF private: Armory::Threading::BlockingQueue< std::shared_ptr<::Codec_BDVCommand::BDVCallback>> notifQueue_; - +#endif public: +#ifdef BUILD_PROTOBUF void callback(std::shared_ptr<::Codec_BDVCommand::BDVCallback>); +#endif bool isValid(void) { return true; } void shutdown(void) {} +#ifdef BUILD_PROTOBUF std::shared_ptr<::Codec_BDVCommand::BDVCallback> getNotification(void); +#endif }; /////////////////////////////////////////////////////////////////////////////// class BDV_Server_Object : public BlockDataViewer { friend class Clients; +#ifdef BUILD_PROTOBUF friend std::tuple, unsigned> DBTestUtils::waitOnSignal( Clients*, const std::string&, ::Codec_BDVCommand::NotificationType); - +#endif private: std::atomic started_; std::thread initT_; @@ -153,7 +165,9 @@ class BDV_Server_Object : public BlockDataViewer struct walletRegStruct { +#ifdef BUILD_PROTOBUF std::shared_ptr<::Codec_BDVCommand::BDVCommand> command_; +#endif WalletType type_; }; @@ -173,13 +187,17 @@ class BDV_Server_Object : public BlockDataViewer private: BDV_Server_Object(BDV_Server_Object&) = delete; //no copies +#ifdef BUILD_PROTOBUF BDVCommandProcessingResultType processCommand( std::shared_ptr<::Codec_BDVCommand::BDVCommand>, std::shared_ptr<::google::protobuf::Message>&); +#endif void startThreads(void); +#ifdef BUILD_PROTOBUF void registerWallet(std::shared_ptr<::Codec_BDVCommand::BDVCommand>); void registerLockbox(std::shared_ptr<::Codec_BDVCommand::BDVCommand>); +#endif void populateWallets(std::map&); void setup(void); @@ -255,10 +273,12 @@ class Clients std::shared_ptr get(const std::string& id) const; +#ifdef BUILD_PROTOBUF void processShutdownCommand( std::shared_ptr<::Codec_BDVCommand::StaticCommand>); std::shared_ptr<::google::protobuf::Message> registerBDV( std::shared_ptr<::Codec_BDVCommand::StaticCommand>, std::string bdvID); +#endif void unregisterBDV(std::string bdvId); void shutdown(void); void exitRequestLoop(void); @@ -267,12 +287,13 @@ class Clients { packetQueue_.push_back(move(payload)); } - +#ifdef BUILD_PROTOBUF std::shared_ptr<::google::protobuf::Message> processUnregisteredCommand( const uint64_t& bdvId, std::shared_ptr<::Codec_BDVCommand::StaticCommand>); std::shared_ptr<::google::protobuf::Message> processCommand( std::shared_ptr); +#endif }; #endif diff --git a/cppForSwig/BDVCodec.h b/cppForSwig/BDVCodec.h index 95dda046a..3e09cb9f5 100644 --- a/cppForSwig/BDVCodec.h +++ b/cppForSwig/BDVCodec.h @@ -29,6 +29,7 @@ #endif #endif // __GNUC__ +#ifdef BUILD_PROTOBUF #include "protobuf/AddressBook.pb.h" #include "protobuf/AddressData.pb.h" #include "protobuf/CommonTypes.pb.h" @@ -37,6 +38,7 @@ #include "protobuf/Utxo.pb.h" #include "protobuf/NodeStatus.pb.h" #include "protobuf/BDVCommand.pb.h" +#endif #ifdef _MSC_VER #pragma warning(pop) diff --git a/cppForSwig/BlockDataViewer.cpp b/cppForSwig/BlockDataViewer.cpp index 3cb15b299..88dc409b6 100644 --- a/cppForSwig/BlockDataViewer.cpp +++ b/cppForSwig/BlockDataViewer.cpp @@ -33,6 +33,7 @@ BlockDataViewer::~BlockDataViewer() } ///////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void BlockDataViewer::registerWallet( shared_ptr<::Codec_BDVCommand::BDVCommand> msg) { @@ -45,6 +46,7 @@ void BlockDataViewer::registerLockbox( { groups_[group_lockbox].registerAddresses(msg); } +#endif ///////////////////////////////////////////////////////////////////////////// void BlockDataViewer::unregisterWallet(const string& IDstr) @@ -224,6 +226,7 @@ bool BlockDataViewer::hasWallet(const string& ID) const } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void BlockDataViewer::registerAddresses( shared_ptr<::Codec_BDVCommand::BDVCommand> msg) { @@ -234,6 +237,7 @@ void BlockDataViewer::registerAddresses( group.registerAddresses(msg); } } +#endif //////////////////////////////////////////////////////////////////////////////// Tx BlockDataViewer::getTxByHash(BinaryData const & txhash) const @@ -1197,6 +1201,7 @@ void WalletGroup::unregisterWallet(const string& id) } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void WalletGroup::registerAddresses( shared_ptr<::Codec_BDVCommand::BDVCommand> msg) { @@ -1299,6 +1304,7 @@ void WalletGroup::registerAddresses( saf_->pushAddressBatch(batch); theWallet->resetCounters(); } +#endif //////////////////////////////////////////////////////////////////////////////// bool WalletGroup::hasID(const string& ID) const diff --git a/cppForSwig/BlockDataViewer.h b/cppForSwig/BlockDataViewer.h index 43e7e845f..61a727814 100644 --- a/cppForSwig/BlockDataViewer.h +++ b/cppForSwig/BlockDataViewer.h @@ -72,9 +72,11 @@ class BlockDataViewer // blockchain in RAM, each scan will take 30-120 seconds. Registering makes // sure that the intial blockchain scan picks up wallet-relevant stuff as // it goes, and does a full [re-]scan of the blockchain only if necessary. +#ifdef BUILD_PROTOBUF void registerWallet(std::shared_ptr<::Codec_BDVCommand::BDVCommand>); void registerLockbox(std::shared_ptr<::Codec_BDVCommand::BDVCommand>); void registerAddresses(std::shared_ptr<::Codec_BDVCommand::BDVCommand>); +#endif void unregisterWallet(const std::string& ID); void unregisterLockbox(const std::string& ID); @@ -265,7 +267,9 @@ class WalletGroup ~WalletGroup(); std::shared_ptr getOrSetWallet(const std::string&); +#ifdef BUILD_PROTOBUF void registerAddresses(std::shared_ptr<::Codec_BDVCommand::BDVCommand>); +#endif void unregisterWallet(const std::string& IDstr); bool hasID(const std::string &ID) const; diff --git a/cppForSwig/BlockchainDatabase/ScrAddrFilter.cpp b/cppForSwig/BlockchainDatabase/ScrAddrFilter.cpp index e822edacb..a30aa34cd 100644 --- a/cppForSwig/BlockchainDatabase/ScrAddrFilter.cpp +++ b/cppForSwig/BlockchainDatabase/ScrAddrFilter.cpp @@ -17,8 +17,9 @@ #include "TxOutScrRef.h" #include +#ifdef BUILD_PROTOBUF #include - +#endif using namespace std; diff --git a/cppForSwig/BridgeAPI/CppBridge.h b/cppForSwig/BridgeAPI/CppBridge.h index 2a09385de..d09a21621 100755 --- a/cppForSwig/BridgeAPI/CppBridge.h +++ b/cppForSwig/BridgeAPI/CppBridge.h @@ -12,7 +12,9 @@ #include "../ArmoryConfig.h" #include "WalletManager.h" #include "btc/ecc.h" +#ifdef BUILD_PROTOBUF #include "../protobuf/BridgeProto.pb.h" +#endif #include "../AsyncClient.h" namespace BridgeProto diff --git a/cppForSwig/BridgeAPI/PassphrasePrompt.cpp b/cppForSwig/BridgeAPI/PassphrasePrompt.cpp index 4d9c48f73..a293d582d 100644 --- a/cppForSwig/BridgeAPI/PassphrasePrompt.cpp +++ b/cppForSwig/BridgeAPI/PassphrasePrompt.cpp @@ -7,7 +7,9 @@ //////////////////////////////////////////////////////////////////////////////// #include "log.h" +#ifdef BUILD_PROTOBUF #include "../protobuf/BridgeProto.pb.h" +#endif #include "PassphrasePrompt.h" using namespace Armory::Bridge; diff --git a/cppForSwig/BridgeAPI/ProtobufConversions.h b/cppForSwig/BridgeAPI/ProtobufConversions.h index 7e03ba03c..87e4339ee 100644 --- a/cppForSwig/BridgeAPI/ProtobufConversions.h +++ b/cppForSwig/BridgeAPI/ProtobufConversions.h @@ -10,8 +10,9 @@ #define _BRIDGE_PROTOBUF_CONVERSION_H #include - +#ifdef BUILD_PROTOBUF #include "../protobuf/BridgeProto.pb.h" +#endif #define PROTO_ASSETID_PREFIX 0xAFu diff --git a/cppForSwig/CMakeLists.txt b/cppForSwig/CMakeLists.txt index da66c7204..2e1514275 100644 --- a/cppForSwig/CMakeLists.txt +++ b/cppForSwig/CMakeLists.txt @@ -197,6 +197,7 @@ endif() file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/protobuf) +IF (BUILD_PROTOBUF) foreach(proto ${PROTOBUF_FILES}) string(REGEX REPLACE "\\.proto$" ".pb.cc" proto_cc ${proto}) @@ -208,6 +209,7 @@ foreach(proto ${PROTOBUF_FILES}) list(APPEND LIBARMORYCOMMON_SOURCES ${PROJECT_BINARY_DIR}/protobuf/${proto_cc}) endforeach() +ENDIF (BUILD_PROTOBUF) add_library(ArmoryCommon STATIC diff --git a/cppForSwig/DBClientClasses.cpp b/cppForSwig/DBClientClasses.cpp index b14f27690..381d471fd 100644 --- a/cppForSwig/DBClientClasses.cpp +++ b/cppForSwig/DBClientClasses.cpp @@ -13,8 +13,9 @@ using namespace std; using namespace DBClientClasses; +#ifdef BUILD_PROTOBUF using namespace Codec_BDVCommand; - +#endif /////////////////////////////////////////////////////////////////////////////// void initLibrary() @@ -54,28 +55,35 @@ void DBClientClasses::BlockHeader::unserialize(uint8_t const * ptr, uint32_t siz // LedgerEntry // /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF LedgerEntry::LedgerEntry(shared_ptr<::Codec_LedgerEntry::LedgerEntry> msg) : msgPtr_(msg), ptr_(msg.get()) {} +#endif /////////////////////////////////////////////////////////////////////////////// LedgerEntry::LedgerEntry(BinaryDataRef bdr) { +#ifdef BUILD_PROTOBUF auto msg = make_shared<::Codec_LedgerEntry::LedgerEntry>(); msg->ParseFromArray(bdr.getPtr(), (int)bdr.getSize()); ptr_ = msg.get(); msgPtr_ = msg; +#endif } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF LedgerEntry::LedgerEntry( shared_ptr<::Codec_LedgerEntry::ManyLedgerEntry> msg, unsigned index) : msgPtr_(msg) { ptr_ = &msg->values(index); } +#endif /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF LedgerEntry::LedgerEntry( shared_ptr<::Codec_BDVCommand::BDVCallback> msg, unsigned i, unsigned y) : msgPtr_(msg) @@ -84,107 +92,153 @@ LedgerEntry::LedgerEntry( auto& ledgers = notif.ledgers(); ptr_ = &ledgers.values(y); } - +#endif /////////////////////////////////////////////////////////////////////////////// string LedgerEntry::getID() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); if (ptr_->has_id()) return ptr_->id(); +#endif return string(); } /////////////////////////////////////////////////////////////////////////////// int64_t LedgerEntry::getValue() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->balance(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// uint32_t LedgerEntry::getBlockNum() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->txheight(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// BinaryDataRef LedgerEntry::getTxHash() const { +#if 0 if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); auto& val = ptr_->txhash(); BinaryDataRef bdr; bdr.setRef(val); return bdr; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// uint32_t LedgerEntry::getIndex() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->index(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// uint32_t LedgerEntry::getTxTime() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->txtime(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// bool LedgerEntry::isCoinbase() const { +#if 0 if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->iscoinbase(); +#else + return false; +#endif } /////////////////////////////////////////////////////////////////////////////// bool LedgerEntry::isSentToSelf() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->issts(); +#else + return false; +#endif } /////////////////////////////////////////////////////////////////////////////// bool LedgerEntry::isChangeBack() const { +#if 0 if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->ischangeback(); +#else + return false; +#endif } /////////////////////////////////////////////////////////////////////////////// bool LedgerEntry::isOptInRBF() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->optinrbf(); +#else + return false; +#endif } /////////////////////////////////////////////////////////////////////////////// bool LedgerEntry::isChainedZC() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->ischainedzc(); +#else + return false; +#endif } /////////////////////////////////////////////////////////////////////////////// bool LedgerEntry::isWitness() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); return ptr_->iswitness(); +#else + return false; +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -202,6 +256,7 @@ bool LedgerEntry::operator==(const LedgerEntry& rhs) /////////////////////////////////////////////////////////////////////////////// vector LedgerEntry::getScrAddrList() const { +#ifdef BUILD_PROTOBUF if (ptr_ == nullptr) throw runtime_error("uninitialized ledger entry"); @@ -215,6 +270,9 @@ vector LedgerEntry::getScrAddrList() const } return addrList; +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -226,6 +284,7 @@ RemoteCallback::~RemoteCallback(void) {} /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF bool RemoteCallback::processNotifications( shared_ptr callback) { @@ -398,6 +457,7 @@ bool RemoteCallback::processNotifications( return true; } +#endif /////////////////////////////////////////////////////////////////////////////// // @@ -406,14 +466,17 @@ bool RemoteCallback::processNotifications( /////////////////////////////////////////////////////////////////////////////// NodeStatus::NodeStatus(BinaryDataRef bdr) { +#ifdef BUILD_PROTOBUF auto msg = make_shared(); if (!msg->ParseFromArray(bdr.getPtr(), (int)bdr.getSize())) throw runtime_error("invalid node status protobuf msg"); ptr_ = msg.get(); msgPtr_ = move(msg); +#endif } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF NodeStatus::NodeStatus( shared_ptr msg) { @@ -429,36 +492,50 @@ NodeStatus::NodeStatus( auto& notif = msg->notification(i); ptr_ = ¬if.nodestatus(); } +#endif /////////////////////////////////////////////////////////////////////////////// CoreRPC::NodeState NodeStatus::state() const { +#ifdef BUILD_PROTOBUF return (CoreRPC::NodeState)ptr_->state(); +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// bool NodeStatus::isSegWitEnabled() const { +#ifdef BUILD_PROTOBUF if (ptr_->has_segwitenabled()) return ptr_->segwitenabled(); +#endif return false; } /////////////////////////////////////////////////////////////////////////////// CoreRPC::RpcState NodeStatus::rpcState() const { +#ifdef BUILD_PROTOBUF if (ptr_->has_rpcstate()) return (CoreRPC::RpcState)ptr_->rpcstate(); +#endif return CoreRPC::RpcState::RpcState_Disabled; } /////////////////////////////////////////////////////////////////////////////// NodeChainStatus NodeStatus::chainStatus() const { +#ifdef BUILD_PROTOBUF return NodeChainStatus(ptr_); +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF shared_ptr NodeStatus::make_new( shared_ptr msg, unsigned i) { @@ -475,35 +552,56 @@ NodeChainStatus::NodeChainStatus( const Codec_NodeStatus::NodeStatus* ptr) : ptr_(&ptr->chainstatus()) {} +#endif /////////////////////////////////////////////////////////////////////////////// CoreRPC::ChainState NodeChainStatus::state() const { +#ifdef BUILD_PROTOBUF return (CoreRPC::ChainState)ptr_->state(); +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// float NodeChainStatus::getBlockSpeed() const { +#ifdef BUILD_PROTOBUF return ptr_->blockspeed(); +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// float NodeChainStatus::getProgressPct() const { +#ifdef BUILD_PROTOBUF return ptr_->pct(); +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// uint64_t NodeChainStatus::getETA() const { +#ifdef BUILD_PROTOBUF return ptr_->eta(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// unsigned NodeChainStatus::getBlocksLeft() const { +#ifdef BUILD_PROTOBUF return ptr_->blocksleft(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -513,12 +611,15 @@ unsigned NodeChainStatus::getBlocksLeft() const /////////////////////////////////////////////////////////////////////////////// ProgressData::ProgressData(BinaryDataRef) { +#ifdef BUILD_PROTOBUF auto msg = make_shared<::Codec_NodeStatus::ProgressData>(); ptr_ = msg.get(); msgPtr_ = msg; +#endif } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF ProgressData::ProgressData( shared_ptr<::Codec_BDVCommand::BDVCallback> msg, unsigned i) : msgPtr_(msg) @@ -526,46 +627,65 @@ ProgressData::ProgressData( auto& notif = msg->notification(i); ptr_ = ¬if.progress(); } +#endif /////////////////////////////////////////////////////////////////////////////// BDMPhase ProgressData::phase() const { +#ifdef BUILD_PROTOBUF return (BDMPhase)ptr_->phase(); +#else + return {}; +#endif } /////////////////////////////////////////////////////////////////////////////// double ProgressData::progress() const { +#ifdef BUILD_PROTOBUF return ptr_->progress(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// unsigned ProgressData::time() const { +#ifdef BUILD_PROTOBUF return ptr_->time(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// unsigned ProgressData::numericProgress() const { +#ifdef BUILD_PROTOBUF return ptr_->numericprogress(); +#else + return 0; +#endif } /////////////////////////////////////////////////////////////////////////////// vector ProgressData::wltIDs() const { vector vec; +#ifdef BUILD_PROTOBUF for (int i = 0; i < ptr_->id_size(); i++) vec.push_back(ptr_->id(i)); - +#endif return vec; } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF std::shared_ptr ProgressData::make_new( std::shared_ptr<::Codec_BDVCommand::BDVCallback> msg, unsigned i) { auto pd = make_shared(ProgressData(msg, i)); return pd; } - +#endif diff --git a/cppForSwig/DBClientClasses.h b/cppForSwig/DBClientClasses.h index 0f768eae5..8eceea992 100644 --- a/cppForSwig/DBClientClasses.h +++ b/cppForSwig/DBClientClasses.h @@ -122,18 +122,20 @@ namespace DBClientClasses //////////////////////////////////////////////////////////////////////////// class LedgerEntry { +#ifdef BUILD_PROTOBUF private: std::shared_ptr<::google::protobuf::Message> msgPtr_; const ::Codec_LedgerEntry::LedgerEntry* ptr_ = nullptr; - +#endif public: LedgerEntry(BinaryDataRef bdr); +#ifdef BUILD_PROTOBUF LedgerEntry(std::shared_ptr<::Codec_LedgerEntry::LedgerEntry>); LedgerEntry(std::shared_ptr<::Codec_LedgerEntry::ManyLedgerEntry>, unsigned); LedgerEntry(std::shared_ptr<::Codec_BDVCommand::BDVCallback>, unsigned, unsigned); - +#endif std::string getID(void) const; int64_t getValue(void) const; uint32_t getBlockNum(void) const; @@ -155,13 +157,15 @@ namespace DBClientClasses //////////////////////////////////////////////////////////////////////////// class NodeChainStatus { +#ifdef BUILD_PROTOBUF private: std::shared_ptr<::google::protobuf::Message> msgPtr_; const Codec_NodeStatus::NodeChainStatus* ptr_; - +#endif public: +#ifdef BUILD_PROTOBUF NodeChainStatus(const Codec_NodeStatus::NodeStatus*); - +#endif CoreRPC::ChainState state(void) const; float getBlockSpeed(void) const; @@ -173,36 +177,39 @@ namespace DBClientClasses //////////////////////////////////////////////////////////////////////////// class NodeStatus { +#ifdef BUILD_PROTOBUF private: std::shared_ptr<::google::protobuf::Message> msgPtr_; const Codec_NodeStatus::NodeStatus* ptr_; - private: NodeStatus(std::shared_ptr, unsigned); - +#endif public: NodeStatus(BinaryDataRef); +#ifdef BUILD_PROTOBUF NodeStatus(std::shared_ptr); - +#endif CoreRPC::NodeState state(void) const; bool isSegWitEnabled(void) const; CoreRPC::RpcState rpcState(void) const; NodeChainStatus chainStatus(void) const; - +#ifdef BUILD_PROTOBUF static std::shared_ptr make_new( std::shared_ptr, unsigned); +#endif }; //////////////////////////////////////////////////////////////////////////// class ProgressData { +#ifdef BUILD_PROTOBUF private: std::shared_ptr<::google::protobuf::Message> msgPtr_; const ::Codec_NodeStatus::ProgressData* ptr_; private: ProgressData(std::shared_ptr<::Codec_BDVCommand::BDVCallback>, unsigned); - +#endif public: ProgressData(BinaryDataRef); @@ -211,9 +218,10 @@ namespace DBClientClasses unsigned time(void) const; unsigned numericProgress(void) const; std::vector wltIDs(void) const; - +#ifdef BUILD_PROTOBUF static std::shared_ptr make_new( std::shared_ptr<::Codec_BDVCommand::BDVCallback>, unsigned); +#endif }; }; //namespace DBClientClasses @@ -255,8 +263,9 @@ class RemoteCallback unsigned progressNumeric ) = 0; virtual void disconnected(void) = 0; - +#ifdef BUILD_PROTOBUF bool processNotifications(std::shared_ptr<::Codec_BDVCommand::BDVCallback>); +#endif }; #endif diff --git a/cppForSwig/LedgerEntry.cpp b/cppForSwig/LedgerEntry.cpp index 22ed3f483..03b550297 100644 --- a/cppForSwig/LedgerEntry.cpp +++ b/cppForSwig/LedgerEntry.cpp @@ -348,6 +348,7 @@ map LedgerEntry::computeLedgerMap( } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void LedgerEntry::fillMessage(::Codec_LedgerEntry::LedgerEntry* msg) const { if (msg == nullptr) @@ -376,3 +377,4 @@ void LedgerEntry::fillMessage(::Codec_LedgerEntry::LedgerEntry* msg) const for (auto& scrAddr : scrAddrSet_) msg->add_scraddr(scrAddr.getPtr(), scrAddr.getSize()); } +#endif diff --git a/cppForSwig/LedgerEntry.h b/cppForSwig/LedgerEntry.h index f6bae23eb..3da075959 100644 --- a/cppForSwig/LedgerEntry.h +++ b/cppForSwig/LedgerEntry.h @@ -144,8 +144,9 @@ class LedgerEntry const std::set& getScrAddrList(void) const { return scrAddrSet_; } - +#ifdef BUILD_PROTOBUF void fillMessage(::Codec_LedgerEntry::LedgerEntry* msg) const; +#endif public: diff --git a/cppForSwig/Server.cpp b/cppForSwig/Server.cpp index 1c6bc2d96..063d309ac 100644 --- a/cppForSwig/Server.cpp +++ b/cppForSwig/Server.cpp @@ -516,7 +516,11 @@ void WebSocketServer::write(const uint64_t& id, const uint32_t& msgid, if (message == nullptr) return; +#ifdef BUILD_PROTOBUF auto msg = make_unique(id, msgid, message); +#else + auto msg = make_unique(PendingMessage{ id, msgid }); +#endif auto instance = getInstance(); instance->msgQueue_.push_back(move(msg)); } @@ -563,7 +567,7 @@ void WebSocketServer::prepareWriteThread() { bool needs_rekey = false; auto rightnow = chrono::system_clock::now(); - +#ifdef BUILD_PROTOBUF if (statePtr->bip151Connection_->rekeyNeeded(msg->message_->ByteSizeLong())) { needs_rekey = true; @@ -575,7 +579,7 @@ void WebSocketServer::prepareWriteThread() if (time_sec.count() >= AEAD_REKEY_INVERVAL_SECONDS) needs_rekey = true; } - +#endif if (needs_rekey) { //create rekey packet @@ -601,6 +605,7 @@ void WebSocketServer::prepareWriteThread() //serialize arg vector serializedData; +#ifdef BUILD_PROTOBUF if (msg->message_->ByteSizeLong() > 0) { serializedData.resize(msg->message_->ByteSizeLong()); @@ -612,7 +617,7 @@ void WebSocketServer::prepareWriteThread() return; } } - +#endif SerializedMessage ws_msg; ws_msg.construct( serializedData, statePtr->bip151Connection_.get(), @@ -896,7 +901,7 @@ void ClientConnection::processReadQueue(shared_ptr clients) //invalid msg, kill connection continue; } - +#ifdef BUILD_PROTOBUF //process command auto message = make_shared<::Codec_BDVCommand::StaticCommand>(); if (!message->ParseFromArray(messageRef.getPtr(), (int)messageRef.getSize())) @@ -910,6 +915,7 @@ void ClientConnection::processReadQueue(shared_ptr clients) //reply WebSocketServer::write(id_, msgObj.getId(), reply); +#endif } } } diff --git a/cppForSwig/Server.h b/cppForSwig/Server.h index b4317da22..a85f3ee0d 100644 --- a/cppForSwig/Server.h +++ b/cppForSwig/Server.h @@ -27,8 +27,9 @@ #include "AuthorizedPeers.h" #include "BIP150_151.h" +#ifdef BUILD_PROTOBUF #include - +#endif #define SERVER_AUTH_PEER_FILENAME "server.peers" class Clients; @@ -73,12 +74,14 @@ struct PendingMessage { const uint64_t id_; const uint32_t msgid_; +#ifdef BUILD_PROTOBUF std::shared_ptr <::google::protobuf::Message> message_; PendingMessage(uint64_t id, uint32_t msgid, std::shared_ptr<::google::protobuf::Message> msg) : id_(id), msgid_(msgid), message_(msg) {} +#endif }; /////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/Signer/ResolverFeed.cpp b/cppForSwig/Signer/ResolverFeed.cpp index 6e4eec490..d9ad27a01 100644 --- a/cppForSwig/Signer/ResolverFeed.cpp +++ b/cppForSwig/Signer/ResolverFeed.cpp @@ -180,6 +180,7 @@ BIP32_AssetPath BIP32_AssetPath::fromPSBT( } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void BIP32_AssetPath::toProtobuf( Codec_SignerState::PubkeyBIP32Path& protoMsg) const { @@ -200,7 +201,7 @@ BIP32_AssetPath BIP32_AssetPath::fromProtobuf( return BIP32_AssetPath(pubkey, path, protoMsg.fingerprint(), nullptr); } - +#endif //////////////////////////////////////////////////////////////////////////////// //// //// ResolverFeed diff --git a/cppForSwig/Signer/ResolverFeed.h b/cppForSwig/Signer/ResolverFeed.h index edd9be0e7..5fa4450f0 100644 --- a/cppForSwig/Signer/ResolverFeed.h +++ b/cppForSwig/Signer/ResolverFeed.h @@ -16,8 +16,9 @@ #include "../BinaryData.h" #include "../SecureBinaryData.h" #include "../Wallets/BIP32_Node.h" - +#ifdef BUILD_PROTOBUF #include "protobuf/Signer.pb.h" +#endif //// class NoAssetException : public std::runtime_error @@ -102,10 +103,11 @@ namespace Armory static BIP32_AssetPath fromPSBT( const BinaryDataRef&, const BinaryDataRef&); - //// +#ifdef BUILD_PROTOBUF void toProtobuf(Codec_SignerState::PubkeyBIP32Path&) const; static BIP32_AssetPath fromProtobuf( const Codec_SignerState::PubkeyBIP32Path&); +#endif }; ////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/Signer/Script.cpp b/cppForSwig/Signer/Script.cpp index b884e2086..4f21ca43b 100644 --- a/cppForSwig/Signer/Script.cpp +++ b/cppForSwig/Signer/Script.cpp @@ -105,6 +105,7 @@ void StackItem_MultiSig::merge(const StackItem* obj) } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void StackItem_PushData::serialize( Codec_SignerState::StackEntryState& protoMsg) const { @@ -255,6 +256,7 @@ shared_ptr StackItem::deserialize( return itemPtr; } +#endif //////////////////////////////////////////////////////////////////////////////// StackItem_MultiSig::StackItem_MultiSig(unsigned id, BinaryData& script) : diff --git a/cppForSwig/Signer/Script.h b/cppForSwig/Signer/Script.h index 7f18717cd..24445704e 100644 --- a/cppForSwig/Signer/Script.h +++ b/cppForSwig/Signer/Script.h @@ -30,9 +30,9 @@ #include "SigHashEnum.h" #include "TxEvalState.h" #include "ResolverFeed.h" - +#ifdef BUILD_PROTOBUF #include "protobuf/Signer.pb.h" - +#endif namespace Armory { @@ -965,10 +965,12 @@ namespace Armory unsigned getId(void) const { return id_; } virtual bool isValid(void) const { return true; } +#ifdef BUILD_PROTOBUF virtual void serialize(Codec_SignerState::StackEntryState&) const = 0; static std::shared_ptr deserialize( const Codec_SignerState::StackEntryState&); +#endif }; //// @@ -981,7 +983,9 @@ namespace Armory {} bool isSame(const StackItem* obj) const override; +#ifdef BUILD_PROTOBUF void serialize(Codec_SignerState::StackEntryState&) const override; +#endif bool isValid(void) const override { return !data_.empty(); } }; @@ -1000,7 +1004,9 @@ namespace Armory bool isSame(const StackItem* obj) const override; void merge(const StackItem* obj); +#ifdef BUILD_PROTOBUF void serialize(Codec_SignerState::StackEntryState&) const override; +#endif void injectSig(SecureBinaryData& sig) { sig_ = std::move(sig); @@ -1032,7 +1038,9 @@ namespace Armory void merge(const StackItem* obj); bool isValid(void) const override { return sigs_.size() == m_; } +#ifdef BUILD_PROTOBUF void serialize(Codec_SignerState::StackEntryState&) const override; +#endif }; //// @@ -1046,7 +1054,9 @@ namespace Armory {} bool isSame(const StackItem* obj) const override; +#ifdef BUILD_PROTOBUF void serialize(Codec_SignerState::StackEntryState&) const override; +#endif }; //// @@ -1060,7 +1070,9 @@ namespace Armory {} bool isSame(const StackItem* obj) const; +#ifdef BUILD_PROTOBUF void serialize(Codec_SignerState::StackEntryState&) const; +#endif }; ////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/Signer/ScriptRecipient.cpp b/cppForSwig/Signer/ScriptRecipient.cpp index 99cf6665b..ad161eefa 100644 --- a/cppForSwig/Signer/ScriptRecipient.cpp +++ b/cppForSwig/Signer/ScriptRecipient.cpp @@ -170,6 +170,7 @@ void ScriptRecipient::toPSBT(BinaryWriter& bw) const } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void ScriptRecipient::toProtobuf( Codec_SignerState::RecipientState& protoMsg, unsigned group) const { @@ -200,6 +201,7 @@ shared_ptr ScriptRecipient::fromProtobuf( return recipient; } +#endif //////////////////////////////////////////////////////////////////////////////// void ScriptRecipient::addBip32Path(const BIP32_AssetPath& bip32Path) diff --git a/cppForSwig/Signer/ScriptRecipient.h b/cppForSwig/Signer/ScriptRecipient.h index e15ea999c..3e243e428 100644 --- a/cppForSwig/Signer/ScriptRecipient.h +++ b/cppForSwig/Signer/ScriptRecipient.h @@ -13,8 +13,9 @@ #include "BinaryData.h" #include "BtcUtils.h" #include "ResolverFeed.h" - +#ifdef BUILD_PROTOBUF #include "protobuf/Signer.pb.h" +#endif class TxOut; @@ -83,8 +84,9 @@ namespace Armory void addBip32Path(const BIP32_AssetPath&); const std::map& getBip32Paths(void) const; - +#ifdef BUILD_PROTOBUF void toProtobuf(Codec_SignerState::RecipientState&, unsigned) const; +#endif void toPSBT(BinaryWriter&) const; void merge(std::shared_ptr); @@ -103,8 +105,10 @@ namespace Armory static std::shared_ptr fromScript(BinaryDataRef); static std::shared_ptr fromPSBT( BinaryRefReader& brr, const TxOut&); +#ifdef BUILD_PROTOBUF static std::shared_ptr fromProtobuf( const Codec_SignerState::RecipientState&); +#endif }; ////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/Signer/Signer.cpp b/cppForSwig/Signer/Signer.cpp index 39d035976..3b1481bf4 100644 --- a/cppForSwig/Signer/Signer.cpp +++ b/cppForSwig/Signer/Signer.cpp @@ -725,6 +725,7 @@ void ScriptSpender::processStacks() } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void ScriptSpender::serializeStateHeader( Codec_SignerState::ScriptSpenderState& protoMsg) const { @@ -903,6 +904,7 @@ shared_ptr ScriptSpender::deserializeState( return resultPtr; } +#endif //////////////////////////////////////////////////////////////////////////////// void ScriptSpender::merge(const ScriptSpender& obj) @@ -2397,12 +2399,14 @@ void ScriptSpender::prettyPrint(ostream& os) const //// Signer //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF Signer::Signer(const Codec_SignerState::SignerState& protoMsg) : TransactionStub() { supportingTxMap_ = std::make_shared>(); deserializeState(protoMsg); } +#endif //////////////////////////////////////////////////////////////////////////////// BinaryDataRef Signer::getSerializedOutputScripts(void) const @@ -3015,6 +3019,7 @@ bool Signer::verifyRawTx(const BinaryData& rawTx, } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF Codec_SignerState::SignerState Signer::serializeState() const { Codec_SignerState::SignerState protoMsg; @@ -3140,6 +3145,7 @@ void Signer::deserializeState( merge(new_signer); } +#endif //////////////////////////////////////////////////////////////////////////////// void Signer::merge(const Signer& rhs) @@ -3760,6 +3766,7 @@ string Signer::toTxSigCollect(bool isLegacy) const signerState.put_uint32_t(TXSIGCOLLECT_VER_LEGACY); signerState.put_BinaryData(legacyState); } +#ifdef BUILD_PROTOBUF else { auto protoState = serializeState(); @@ -3772,6 +3779,7 @@ string Signer::toTxSigCollect(bool isLegacy) const signerState.put_uint32_t(TXSIGCOLLECT_VER_MODERN); signerState.put_BinaryData(stateBD); } +#endif //get sigcollect b58id auto legacyB58ID = getSigCollectID(); @@ -3927,6 +3935,7 @@ Signer Signer::fromString(const string& signerState) case TXSIGCOLLECT_VER_MODERN: { +#ifdef BUILD_PROTOBUF //regular protobuf packet Codec_SignerState::SignerState signerProto; if (!signerProto.ParseFromArray( @@ -3939,6 +3948,7 @@ Signer Signer::fromString(const string& signerState) theSigner.deserializeState(signerProto); theSigner.fromType_ = SignerStringFormat::TxSigCollect_Modern; +#endif break; } diff --git a/cppForSwig/Signer/Signer.h b/cppForSwig/Signer/Signer.h index 8a354eec7..58e04ad46 100644 --- a/cppForSwig/Signer/Signer.h +++ b/cppForSwig/Signer/Signer.h @@ -17,8 +17,9 @@ #include "Transactions.h" #include "ScriptRecipient.h" #include "ResolverFeed.h" - +#ifdef BUILD_PROTOBUF #include "protobuf/Signer.pb.h" +#endif #define SCRIPT_SPENDER_VERSION_MAX 1 #define SCRIPT_SPENDER_VERSION_MIN 0 @@ -140,7 +141,7 @@ namespace Armory BinaryDataRef getRedeemScriptFromStack( const std::map>*) const; std::map getPartialSigs(void) const; - +#ifdef BUILD_PROTOBUF protected: virtual void serializeStateHeader( Codec_SignerState::ScriptSpenderState&) const; @@ -153,7 +154,7 @@ namespace Armory void serializePathData( Codec_SignerState::ScriptSpenderState&) const; - +#endif private: ScriptSpender(void) {} @@ -242,11 +243,11 @@ namespace Armory bool isResolved(void) const; bool isSigned(void) const; bool isInitialized(void) const; - +#ifdef BUILD_PROTOBUF void serializeState(Codec_SignerState::ScriptSpenderState&) const; static std::shared_ptr deserializeState( const Codec_SignerState::ScriptSpenderState&); - +#endif bool canBeResolved(void) const; bool operator==(const ScriptSpender& rhs) @@ -326,9 +327,10 @@ namespace Armory BinaryData serializeAvailableResolvedData(void) const; static Signer createFromState(const std::string&); +#ifdef BUILD_PROTOBUF static Signer createFromState(const Codec_SignerState::SignerState&); void deserializeSupportingTxMap(const Codec_SignerState::SignerState&); - +#endif void parseScripts(bool); void addBip32Root(std::shared_ptr); void matchAssetPathsWithRoots(void); @@ -342,9 +344,9 @@ namespace Armory { supportingTxMap_ = std::make_shared>(); } - +#ifdef BUILD_PROTOBUF Signer(const Codec_SignerState::SignerState&); - +#endif /*sigs*/ //create sigs @@ -397,11 +399,14 @@ namespace Armory BinaryData getTxId_const(void) const; //state import/export +#ifdef BUILD_PROTOBUF void deserializeState(const Codec_SignerState::SignerState&); +#endif void deserializeState_Legacy(const BinaryDataRef&); void merge(const Signer& rhs); - +#ifdef BUILD_PROTOBUF Codec_SignerState::SignerState serializeState(void) const; +#endif BinaryData serializeState_Legacy(void) const; std::string getSigCollectID(void) const; diff --git a/cppForSwig/SocketObject.cpp b/cppForSwig/SocketObject.cpp index b0c97b021..7da546f53 100644 --- a/cppForSwig/SocketObject.cpp +++ b/cppForSwig/SocketObject.cpp @@ -10,8 +10,9 @@ #include "SocketWritePayload.h" #include #include - +#ifdef BUILD_PROTOBUF #include "google/protobuf/text_format.h" +#endif using namespace std; @@ -1091,6 +1092,7 @@ void WritePayload_Raw::serialize(vector& data) } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF string WritePayload_Protobuf::serializeToText(void) { if (message_ == nullptr) @@ -1110,3 +1112,4 @@ void WritePayload_Protobuf::serialize(vector& data) data.resize(message_->ByteSizeLong()); message_->SerializeToArray(&data[0], (int)data.size()); } +#endif diff --git a/cppForSwig/SocketWritePayload.h b/cppForSwig/SocketWritePayload.h index d6b6e6fcd..42920ab9c 100644 --- a/cppForSwig/SocketWritePayload.h +++ b/cppForSwig/SocketWritePayload.h @@ -10,11 +10,14 @@ #define _SOCKET_WRITE_PAYLOAD_H #include "SocketObject.h" +#ifdef BUILD_PROTOBUF #include +#endif /////////////////////////////////////////////////////////////////////////////// struct WritePayload_Protobuf : public Socket_WritePayload { +#ifdef BUILD_PROTOBUF std::unique_ptr<::google::protobuf::Message> message_; void serialize(std::vector&); @@ -22,6 +25,7 @@ struct WritePayload_Protobuf : public Socket_WritePayload size_t getSerializedSize(void) const { return message_->ByteSizeLong(); } +#endif }; /////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/TxClasses.cpp b/cppForSwig/TxClasses.cpp index aec4a2b97..f017a6015 100644 --- a/cppForSwig/TxClasses.cpp +++ b/cppForSwig/TxClasses.cpp @@ -680,6 +680,7 @@ unsigned UTXO::getWitnessDataSize(void) const } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void UTXO::toProtobuf(Codec_Utxo::Utxo& utxoProto) const { utxoProto.set_value(value_); @@ -715,6 +716,7 @@ UTXO UTXO::fromProtobuf(const Codec_Utxo::Utxo& utxoProto) return result; } +#endif //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/TxClasses.h b/cppForSwig/TxClasses.h index b17a69e7d..94bbbec4e 100644 --- a/cppForSwig/TxClasses.h +++ b/cppForSwig/TxClasses.h @@ -17,7 +17,9 @@ #include "BtcUtils.h" #include "DBUtils.h" +#ifdef BUILD_PROTOBUF #include "protobuf/Utxo.pb.h" +#endif //PayStruct flags #define USE_FULL_CUSTOM_LIST 1 @@ -473,9 +475,10 @@ struct UTXO } bool isInitialized(void) const { return !script_.empty(); } - +#ifdef BUILD_PROTOBUF void toProtobuf(Codec_Utxo::Utxo&) const; static UTXO fromProtobuf(const Codec_Utxo::Utxo&); +#endif }; namespace AsyncClient diff --git a/cppForSwig/WebSocketClient.cpp b/cppForSwig/WebSocketClient.cpp index 8df1b8af6..a1c51f1b5 100644 --- a/cppForSwig/WebSocketClient.cpp +++ b/cppForSwig/WebSocketClient.cpp @@ -302,6 +302,7 @@ void WebSocketClient::cleanUp() readPackets_.clear(); //create error message to send to all outsanding read callbacks +#ifdef BUILD_PROTOBUF ::Codec_BDVCommand::BDV_Error errMsg; errMsg.set_code(-1); errMsg.set_errstr("LWS client disconnected"); @@ -350,7 +351,7 @@ void WebSocketClient::cleanUp() if (thr.joinable()) thr.join(); } - +#endif LOGINFO << "lws client cleaned up"; } @@ -557,7 +558,7 @@ void WebSocketClient::readService() currentReadMessage_.reset(); continue; } - +#ifdef BUILD_PROTOBUF auto msgptr = make_shared<::Codec_BDVCommand::BDVCallback>(); if (!currentReadMessage_.message_.getMessage(msgptr.get())) { @@ -566,8 +567,8 @@ void WebSocketClient::readService() } callbackPtr_->processNotifications(msgptr); +#endif currentReadMessage_.reset(); - break; } diff --git a/cppForSwig/WebSocketMessage.cpp b/cppForSwig/WebSocketMessage.cpp index 76795714c..754db18c8 100644 --- a/cppForSwig/WebSocketMessage.cpp +++ b/cppForSwig/WebSocketMessage.cpp @@ -9,11 +9,15 @@ #include "BtcUtils.h" #include "WebSocketMessage.h" #include "libwebsockets.h" +#ifdef BUILD_PROTOBUF #include +#endif #include "BIP15x_Handshake.h" using namespace std; +#ifdef BUILD_PROTOBUF using namespace ::google::protobuf::io; +#endif //////////////////////////////////////////////////////////////////////////////// // @@ -249,6 +253,7 @@ vector WebSocketMessageCodec::serialize( } //////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF bool WebSocketMessageCodec::reconstructFragmentedMessage( const map& payloadMap, ::google::protobuf::Message* msg) @@ -293,6 +298,7 @@ bool WebSocketMessageCodec::reconstructFragmentedMessage( return result; } +#endif //////////////////////////////////////////////////////////////////////////////// uint32_t WebSocketMessageCodec::getMessageId(const BinaryDataRef& packet) @@ -509,6 +515,7 @@ bool WebSocketMessagePartial::parseMessageWithoutId(const BinaryDataRef& bdr) } /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF bool WebSocketMessagePartial::getMessage( ::google::protobuf::Message* msgPtr) const { @@ -525,6 +532,7 @@ bool WebSocketMessagePartial::getMessage( return WebSocketMessageCodec::reconstructFragmentedMessage(packets_, msgPtr); } } +#endif /////////////////////////////////////////////////////////////////////////////// bool WebSocketMessagePartial::isReady() const diff --git a/cppForSwig/WebSocketMessage.h b/cppForSwig/WebSocketMessage.h index d798b3a8d..b0a238d73 100644 --- a/cppForSwig/WebSocketMessage.h +++ b/cppForSwig/WebSocketMessage.h @@ -14,7 +14,9 @@ #include #include "BinaryData.h" +#ifdef BUILD_PROTOBUF #include +#endif #include "SocketObject.h" #include "BIP150_151.h" @@ -56,10 +58,11 @@ class WebSocketMessageCodec ArmoryAEAD::BIP151_PayloadType); static uint32_t getMessageId(const BinaryDataRef&); - +#ifdef BUILD_PROTOBUF static bool reconstructFragmentedMessage( const std::map&, ::google::protobuf::Message*); +#endif }; /////////////////////////////////////////////////////////////////////////////// @@ -106,7 +109,9 @@ class WebSocketMessagePartial void reset(void); bool parsePacket(const BinaryDataRef&); bool isReady(void) const; +#ifdef BUILD_PROTOBUF bool getMessage(::google::protobuf::Message*) const; +#endif BinaryDataRef getSingleBinaryMessage(void) const; const uint32_t& getId(void) const { return id_; } ArmoryAEAD::BIP151_PayloadType getType(void) const { return type_; } diff --git a/cppForSwig/ZeroConfNotifications.cpp b/cppForSwig/ZeroConfNotifications.cpp index 3c549af07..093a12615 100644 --- a/cppForSwig/ZeroConfNotifications.cpp +++ b/cppForSwig/ZeroConfNotifications.cpp @@ -16,7 +16,9 @@ #include "BlockchainDatabase/txio.h" using namespace std; +#ifdef BUILD_PROTOBUF using namespace ::Codec_BDVCommand; +#endif /////////////////////////////////////////////////////////////////////////////// // @@ -258,6 +260,7 @@ ZeroConfCallbacks_BDV::ZcNotifRequest::~ZcNotifRequest() // ZcNotificationPacket // /////////////////////////////////////////////////////////////////////////////// +#ifdef BUILD_PROTOBUF void ZcNotificationPacket::toProtobufNotification( std::shared_ptr<::Codec_BDVCommand::BDVCallback> protoPtr, const std::vector& leVec) const @@ -317,4 +320,5 @@ void ZcNotificationPacket::toProtobufNotification( idPtr->set_data(id.second.getPtr(), id.second.getSize()); } } -} \ No newline at end of file +} +#endif diff --git a/cppForSwig/ZeroConfNotifications.h b/cppForSwig/ZeroConfNotifications.h index 166689600..523365a14 100644 --- a/cppForSwig/ZeroConfNotifications.h +++ b/cppForSwig/ZeroConfNotifications.h @@ -212,10 +212,11 @@ struct ZcNotificationPacket ZcNotificationPacket(const std::string& bdvID) : bdvID_(bdvID) {} - +#ifdef BUILD_PROTOBUF void toProtobufNotification( std::shared_ptr<::Codec_BDVCommand::BDVCallback>, const std::vector&) const; +#endif }; //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/gtest/WalletTests.cpp b/cppForSwig/gtest/WalletTests.cpp index 906b60625..f2f76ebbf 100644 --- a/cppForSwig/gtest/WalletTests.cpp +++ b/cppForSwig/gtest/WalletTests.cpp @@ -12,7 +12,9 @@ #include "../Wallets/Seeds/Backups.h" #include "../Wallets/Seeds/Seeds.h" #include "../Wallets/WalletFileInterface.h" +#ifdef BUILD_PROTOBUF #include "protobuf/BridgeProto.pb.h" +#endif using namespace std; using namespace Armory::Signer; From 641063b975dde219570857cf30dd6335293bfbd5 Mon Sep 17 00:00:00 2001 From: Sergey Chernikov Date: Thu, 29 Aug 2024 23:16:16 +0300 Subject: [PATCH 47/47] superfluous include --- cppForSwig/BtcUtils.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cppForSwig/BtcUtils.cpp b/cppForSwig/BtcUtils.cpp index 249f50978..68e82b26b 100644 --- a/cppForSwig/BtcUtils.cpp +++ b/cppForSwig/BtcUtils.cpp @@ -7,7 +7,6 @@ //////////////////////////////////////////////////////////////////////////////// #include "BtcUtils.h" -#include "EncryptionUtils.h" #include "ArmoryConfig.h" #include "btc/segwit_addr.h" #include "TxOutScrRef.h"