Browse Source

revealer: clean-up noise-generation. support regeneration of v0 again

3.3.3.1
SomberNight 6 years ago
parent
commit
94afd7a9ea
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 171
      electrum/plugins/revealer/qt.py

171
electrum/plugins/revealer/qt.py

@ -15,22 +15,42 @@ import qrcode
import traceback
from hashlib import sha256
from decimal import Decimal
import binascii
from typing import NamedTuple, Optional, Dict, Tuple
from PyQt5.QtPrintSupport import QPrinter
from electrum.plugin import BasePlugin, hook
from electrum.i18n import _
from electrum.util import to_bytes, make_dir, InvalidPassword, UserCancelled
from electrum.util import to_bytes, make_dir, InvalidPassword, UserCancelled, bh2u, bfh
from electrum.gui.qt.util import *
from electrum.gui.qt.qrtextedit import ScanQRTextEdit
from electrum.gui.qt.main_window import StatusBarButton
from .hmac_drbg import DRBG
class VersionedSeed(NamedTuple):
version: str
seed: str
checksum: str
def get_ui_string_version_plus_seed(self):
version, seed = self.version, self.seed
assert isinstance(version, str) and len(version) == 1, version
assert isinstance(seed, str) and len(seed) >= 32
ret = version + seed
ret = ret.upper()
return ' '.join(ret[i : i+4] for i in range(0, len(ret), 4))
class Plugin(BasePlugin):
LATEST_VERSION = '1'
KNOWN_VERSIONS = ('0', '1')
assert LATEST_VERSION in KNOWN_VERSIONS
MAX_PLAINTEXT_LEN = 189 # chars
SIZE = (159, 97)
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
@ -44,8 +64,6 @@ class Plugin(BasePlugin):
self.calibration_h = self.config.get('calibration_h')
self.calibration_v = self.config.get('calibration_v')
self.version = '1'
self.size = (159, 97)
self.f_size = QSize(1014*2, 642*2)
self.abstand_h = 21
self.abstand_v = 34
@ -90,7 +108,6 @@ class Plugin(BasePlugin):
self.wallet = window.parent().wallet
self.update_wallet_name(self.wallet)
self.user_input = False
self.noise_seed = False
self.d = WindowModalDialog(window, "Setup Dialog")
self.d.setMinimumWidth(500)
@ -145,39 +162,36 @@ class Plugin(BasePlugin):
return ''.join(text.split()).lower()
def on_edit(self):
s = self.get_noise()
b = self.is_noise(s)
if b:
self.noise_seed = s[1:-3]
self.user_input = True
self.next_button.setEnabled(b)
def code_hashid(self, txt):
txt = self.get_noise()
versioned_seed = self.get_versioned_seed_from_user_input(txt)
if versioned_seed:
self.versioned_seed = versioned_seed
self.user_input = bool(versioned_seed)
self.next_button.setEnabled(bool(versioned_seed))
@classmethod
def code_hashid(cls, txt: str) -> str:
x = to_bytes(txt, 'utf8')
hash = sha256(x).hexdigest()
return hash[-3:].upper()
def is_noise(self, txt):
if (len(txt) >= 34):
@classmethod
def get_versioned_seed_from_user_input(cls, txt: str) -> Optional[VersionedSeed]:
if len(txt) < 34:
return None
try:
int(txt, 16)
except:
self.user_input = False
return False
else:
id = self.code_hashid(txt[:-3])
if (txt[-3:].upper() == id.upper()):
self.code_id = id
self.user_input = True
return True
else:
return False
else:
if (len(txt)>0 and txt[0]=='0'):
self.d.show_message(''.join(["<b>",_("Warning: "), "</b>", _("Revealers starting with 0 had a vulnerability and are not supported.")]))
self.user_input = False
return False
return None
version = txt[0]
if version not in cls.KNOWN_VERSIONS:
return None
checksum = cls.code_hashid(txt[:-3])
if txt[-3:].upper() != checksum.upper():
return None
return VersionedSeed(version=version.upper(),
seed=txt[1:-3].upper(),
checksum=checksum.upper())
def make_digital(self, dialog):
self.make_rawnoise(True)
@ -185,7 +199,9 @@ class Plugin(BasePlugin):
self.d.close()
def get_path_to_revealer_file(self, ext: str= '') -> str:
filename = self.filename_prefix + self.version + "_" + self.code_id + ext
version = self.versioned_seed.version
code_id = self.versioned_seed.checksum
filename = self.filename_prefix + version + "_" + code_id + ext
path = os.path.join(self.base_dir, filename)
return os.path.normcase(os.path.abspath(path))
@ -195,7 +211,9 @@ class Plugin(BasePlugin):
def bcrypt(self, dialog):
self.rawnoise = False
dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, self.version, self.code_id),
version = self.versioned_seed.version
code_id = self.versioned_seed.checksum
dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, version, code_id),
"<b>", self.get_path_to_revealer_file(), "</b>", "<br/>",
"<br/>", "<b>", _("Always check you backups.")]))
dialog.close()
@ -206,7 +224,9 @@ class Plugin(BasePlugin):
dialog.close()
def bdone(self, dialog):
dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(self.version, self.code_id),
version = self.versioned_seed.version
code_id = self.versioned_seed.checksum
dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(version, code_id),
"<br/>","<b>", self.get_path_to_revealer_file(), '</b>']))
@ -241,7 +261,8 @@ class Plugin(BasePlugin):
logo.setAlignment(Qt.AlignLeft)
hbox.addSpacing(16)
self.vbox.addWidget(WWLabel("<b>" + _("Revealer Secret Backup Plugin") + "</b><br>"
+ _("Ready to encrypt for revealer {}").format(self.version+'_'+self.code_id )))
+ _("Ready to encrypt for revealer {}")
.format(self.versioned_seed.version+'_'+self.versioned_seed.checksum)))
self.vbox.addSpacing(11)
hbox.addLayout(self.vbox)
grid = QGridLayout()
@ -294,7 +315,7 @@ class Plugin(BasePlugin):
else:
txt = self.txt.upper()
img = QImage(self.size[0],self.size[1], QImage.Format_Mono)
img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono)
bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
bitmap.fill(Qt.white)
painter = QPainter()
@ -325,7 +346,7 @@ class Plugin(BasePlugin):
while len(' '.join(map(str, temp_seed))) > max_letters:
nwords = nwords - 1
temp_seed = seed_array[:nwords]
painter.drawText(QRect(0, linespace*n , self.size[0], self.size[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed)))
painter.drawText(QRect(0, linespace*n , self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed)))
del seed_array[:nwords]
painter.end()
@ -337,43 +358,55 @@ class Plugin(BasePlugin):
return img
def make_rawnoise(self, create_revealer=False):
w = self.size[0]
h = self.size[1]
if not self.user_input:
version = self.LATEST_VERSION
hex_seed = bh2u(os.urandom(16))
checksum = self.code_hashid(version + hex_seed)
self.versioned_seed = VersionedSeed(version=version.upper(),
seed=hex_seed.upper(),
checksum=checksum.upper())
assert self.versioned_seed
w, h = self.SIZE
rawnoise = QImage(w, h, QImage.Format_Mono)
if(self.noise_seed == False):
self.noise_seed = random.SystemRandom().getrandbits(128)
self.hex_noise = format(self.noise_seed, '032x')
self.hex_noise = self.version + str(self.hex_noise)
noise_map = self.get_noise_map(self.versioned_seed)
for (x,y), pixel in noise_map.items():
rawnoise.setPixel(x, y, pixel)
if (self.user_input == True):
self.noise_seed = int(self.noise_seed, 16)
self.hex_noise = self.version + str(format(self.noise_seed, '032x'))
self.code_id = self.code_hashid(self.hex_noise)
self.hex_noise = ' '.join(self.hex_noise[i:i+4] for i in range(0,len(self.hex_noise),4))
entropy = binascii.unhexlify(str(format(self.noise_seed, '032x')))
code_id = binascii.unhexlify(self.version + self.code_id)
drbg = DRBG(entropy + code_id)
noise_array=bin(int.from_bytes(drbg.generate(1929), 'big'))[2:]
self.rawnoise = rawnoise
if create_revealer:
self.make_revealer()
@classmethod
def get_noise_map(self, versioned_seed: VersionedSeed) -> Dict[Tuple[int, int], int]:
"""Returns a map from (x,y) coordinate to pixel value 0/1, to be used as noise."""
w, h = self.SIZE
version = versioned_seed.version
hex_seed = versioned_seed.seed
checksum = versioned_seed.checksum
noise_map = {}
if version == '0':
random.seed(int(hex_seed, 16))
for x in range(w):
for y in range(h):
noise_map[(x, y)] = random.randint(0, 1)
elif version == '1':
prng_seed = bfh(hex_seed + version + checksum)
drbg = DRBG(prng_seed)
num_noise_bytes = 1929 # ~ w*h
noise_array = bin(int.from_bytes(drbg.generate(num_noise_bytes), 'big'))[2:]
i = 0
for x in range(w):
for y in range(h):
rawnoise.setPixel(x,y,int(noise_array[i]))
noise_map[(x, y)] = int(noise_array[i])
i += 1
self.rawnoise = rawnoise
if create_revealer==True:
self.make_revealer()
self.noise_seed = False
else:
raise Exception(f"unexpected revealer version: {version}")
return noise_map
def make_calnoise(self):
random.seed(self.calibration_noise)
w = self.size[0]
h = self.size[1]
w, h = self.SIZE
rawnoise = QImage(w, h, QImage.Format_Mono)
for x in range(w):
for y in range(h):
@ -422,7 +455,7 @@ class Plugin(BasePlugin):
return cypherseed
def calibration(self):
img = QImage(self.size[0],self.size[1], QImage.Format_Mono)
img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono)
bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
bitmap.fill(Qt.black)
self.make_calnoise()
@ -586,7 +619,8 @@ class Plugin(BasePlugin):
(base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2)
painter.setPen(QColor(0,0,0,255))
painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11,
base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.version + '_'+self.code_id)
base_img.height()-total_distance_h - border_thick), Qt.AlignRight,
self.versioned_seed.version + '_'+self.versioned_seed.checksum)
painter.end()
else: # revealer
@ -635,12 +669,13 @@ class Plugin(BasePlugin):
painter.setPen(QColor(0,0,0,255))
painter.drawText(QRect(((base_img.width()/2) +21)-qr_size, base_img.height()-107,
base_img.width()-total_distance_h - border_thick -93,
base_img.height()-total_distance_h - border_thick), Qt.AlignLeft, self.hex_noise.upper())
base_img.height()-total_distance_h - border_thick), Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed())
painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size,
base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.code_id)
base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum)
# draw qr code
qr_qt = self.paintQR(self.hex_noise.upper() +self.code_id)
qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed()
+ self.versioned_seed.checksum)
target = QRectF(base_img.width()-65-qr_size,
base_img.height()-65-qr_size,
qr_size, qr_size )

Loading…
Cancel
Save