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. 183
      electrum/plugins/revealer/qt.py

183
electrum/plugins/revealer/qt.py

@ -15,22 +15,42 @@ import qrcode
import traceback import traceback
from hashlib import sha256 from hashlib import sha256
from decimal import Decimal from decimal import Decimal
import binascii from typing import NamedTuple, Optional, Dict, Tuple
from PyQt5.QtPrintSupport import QPrinter from PyQt5.QtPrintSupport import QPrinter
from electrum.plugin import BasePlugin, hook from electrum.plugin import BasePlugin, hook
from electrum.i18n import _ 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.util import *
from electrum.gui.qt.qrtextedit import ScanQRTextEdit from electrum.gui.qt.qrtextedit import ScanQRTextEdit
from electrum.gui.qt.main_window import StatusBarButton from electrum.gui.qt.main_window import StatusBarButton
from .hmac_drbg import DRBG 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): class Plugin(BasePlugin):
LATEST_VERSION = '1'
KNOWN_VERSIONS = ('0', '1')
assert LATEST_VERSION in KNOWN_VERSIONS
MAX_PLAINTEXT_LEN = 189 # chars MAX_PLAINTEXT_LEN = 189 # chars
SIZE = (159, 97)
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
BasePlugin.__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_h = self.config.get('calibration_h')
self.calibration_v = self.config.get('calibration_v') self.calibration_v = self.config.get('calibration_v')
self.version = '1'
self.size = (159, 97)
self.f_size = QSize(1014*2, 642*2) self.f_size = QSize(1014*2, 642*2)
self.abstand_h = 21 self.abstand_h = 21
self.abstand_v = 34 self.abstand_v = 34
@ -90,7 +108,6 @@ class Plugin(BasePlugin):
self.wallet = window.parent().wallet self.wallet = window.parent().wallet
self.update_wallet_name(self.wallet) self.update_wallet_name(self.wallet)
self.user_input = False self.user_input = False
self.noise_seed = False
self.d = WindowModalDialog(window, "Setup Dialog") self.d = WindowModalDialog(window, "Setup Dialog")
self.d.setMinimumWidth(500) self.d.setMinimumWidth(500)
@ -145,39 +162,36 @@ class Plugin(BasePlugin):
return ''.join(text.split()).lower() return ''.join(text.split()).lower()
def on_edit(self): def on_edit(self):
s = self.get_noise() txt = self.get_noise()
b = self.is_noise(s) versioned_seed = self.get_versioned_seed_from_user_input(txt)
if b: if versioned_seed:
self.noise_seed = s[1:-3] self.versioned_seed = versioned_seed
self.user_input = True self.user_input = bool(versioned_seed)
self.next_button.setEnabled(b) self.next_button.setEnabled(bool(versioned_seed))
def code_hashid(self, txt): @classmethod
def code_hashid(cls, txt: str) -> str:
x = to_bytes(txt, 'utf8') x = to_bytes(txt, 'utf8')
hash = sha256(x).hexdigest() hash = sha256(x).hexdigest()
return hash[-3:].upper() return hash[-3:].upper()
def is_noise(self, txt): @classmethod
if (len(txt) >= 34): def get_versioned_seed_from_user_input(cls, txt: str) -> Optional[VersionedSeed]:
try: if len(txt) < 34:
int(txt, 16) return None
except: try:
self.user_input = False int(txt, 16)
return False except:
else: return None
id = self.code_hashid(txt[:-3]) version = txt[0]
if (txt[-3:].upper() == id.upper()): if version not in cls.KNOWN_VERSIONS:
self.code_id = id return None
self.user_input = True checksum = cls.code_hashid(txt[:-3])
return True if txt[-3:].upper() != checksum.upper():
else: return None
return False return VersionedSeed(version=version.upper(),
else: seed=txt[1:-3].upper(),
checksum=checksum.upper())
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
def make_digital(self, dialog): def make_digital(self, dialog):
self.make_rawnoise(True) self.make_rawnoise(True)
@ -185,7 +199,9 @@ class Plugin(BasePlugin):
self.d.close() self.d.close()
def get_path_to_revealer_file(self, ext: str= '') -> str: 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) path = os.path.join(self.base_dir, filename)
return os.path.normcase(os.path.abspath(path)) return os.path.normcase(os.path.abspath(path))
@ -195,7 +211,9 @@ class Plugin(BasePlugin):
def bcrypt(self, dialog): def bcrypt(self, dialog):
self.rawnoise = False 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/>", "<b>", self.get_path_to_revealer_file(), "</b>", "<br/>",
"<br/>", "<b>", _("Always check you backups.")])) "<br/>", "<b>", _("Always check you backups.")]))
dialog.close() dialog.close()
@ -206,7 +224,9 @@ class Plugin(BasePlugin):
dialog.close() dialog.close()
def bdone(self, dialog): 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>'])) "<br/>","<b>", self.get_path_to_revealer_file(), '</b>']))
@ -241,7 +261,8 @@ class Plugin(BasePlugin):
logo.setAlignment(Qt.AlignLeft) logo.setAlignment(Qt.AlignLeft)
hbox.addSpacing(16) hbox.addSpacing(16)
self.vbox.addWidget(WWLabel("<b>" + _("Revealer Secret Backup Plugin") + "</b><br>" 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) self.vbox.addSpacing(11)
hbox.addLayout(self.vbox) hbox.addLayout(self.vbox)
grid = QGridLayout() grid = QGridLayout()
@ -294,7 +315,7 @@ class Plugin(BasePlugin):
else: else:
txt = self.txt.upper() 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 = QBitmap.fromImage(img, Qt.MonoOnly)
bitmap.fill(Qt.white) bitmap.fill(Qt.white)
painter = QPainter() painter = QPainter()
@ -325,7 +346,7 @@ class Plugin(BasePlugin):
while len(' '.join(map(str, temp_seed))) > max_letters: while len(' '.join(map(str, temp_seed))) > max_letters:
nwords = nwords - 1 nwords = nwords - 1
temp_seed = seed_array[:nwords] 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] del seed_array[:nwords]
painter.end() painter.end()
@ -337,43 +358,55 @@ class Plugin(BasePlugin):
return img return img
def make_rawnoise(self, create_revealer=False): def make_rawnoise(self, create_revealer=False):
w = self.size[0] if not self.user_input:
h = self.size[1] 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) rawnoise = QImage(w, h, QImage.Format_Mono)
if(self.noise_seed == False): noise_map = self.get_noise_map(self.versioned_seed)
self.noise_seed = random.SystemRandom().getrandbits(128) for (x,y), pixel in noise_map.items():
self.hex_noise = format(self.noise_seed, '032x') rawnoise.setPixel(x, y, pixel)
self.hex_noise = self.version + str(self.hex_noise)
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:]
i=0
for x in range(w):
for y in range(h):
rawnoise.setPixel(x,y,int(noise_array[i]))
i+=1
self.rawnoise = rawnoise self.rawnoise = rawnoise
if create_revealer==True: if create_revealer:
self.make_revealer() self.make_revealer()
self.noise_seed = False
@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):
noise_map[(x, y)] = int(noise_array[i])
i += 1
else:
raise Exception(f"unexpected revealer version: {version}")
return noise_map
def make_calnoise(self): def make_calnoise(self):
random.seed(self.calibration_noise) random.seed(self.calibration_noise)
w = self.size[0] w, h = self.SIZE
h = self.size[1]
rawnoise = QImage(w, h, QImage.Format_Mono) rawnoise = QImage(w, h, QImage.Format_Mono)
for x in range(w): for x in range(w):
for y in range(h): for y in range(h):
@ -422,7 +455,7 @@ class Plugin(BasePlugin):
return cypherseed return cypherseed
def calibration(self): 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 = QBitmap.fromImage(img, Qt.MonoOnly)
bitmap.fill(Qt.black) bitmap.fill(Qt.black)
self.make_calnoise() self.make_calnoise()
@ -586,7 +619,8 @@ class Plugin(BasePlugin):
(base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2) (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2)
painter.setPen(QColor(0,0,0,255)) painter.setPen(QColor(0,0,0,255))
painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11, 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() painter.end()
else: # revealer else: # revealer
@ -635,12 +669,13 @@ class Plugin(BasePlugin):
painter.setPen(QColor(0,0,0,255)) painter.setPen(QColor(0,0,0,255))
painter.drawText(QRect(((base_img.width()/2) +21)-qr_size, base_img.height()-107, 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.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, 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 # 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, target = QRectF(base_img.width()-65-qr_size,
base_img.height()-65-qr_size, base_img.height()-65-qr_size,
qr_size, qr_size ) qr_size, qr_size )

Loading…
Cancel
Save