revealer.py (3555B)
1 import random 2 import os 3 from hashlib import sha256 4 from typing import NamedTuple, Optional, Dict, Tuple 5 6 from electrum.plugin import BasePlugin 7 from electrum.util import to_bytes, bh2u, bfh 8 9 from .hmac_drbg import DRBG 10 11 12 class VersionedSeed(NamedTuple): 13 version: str 14 seed: str 15 checksum: str 16 17 def get_ui_string_version_plus_seed(self): 18 version, seed = self.version, self.seed 19 assert isinstance(version, str) and len(version) == 1, version 20 assert isinstance(seed, str) and len(seed) >= 32 21 ret = version + seed 22 ret = ret.upper() 23 return ' '.join(ret[i : i+4] for i in range(0, len(ret), 4)) 24 25 26 class RevealerPlugin(BasePlugin): 27 28 LATEST_VERSION = '1' 29 KNOWN_VERSIONS = ('0', '1') 30 assert LATEST_VERSION in KNOWN_VERSIONS 31 32 SIZE = (159, 97) 33 34 def __init__(self, parent, config, name): 35 BasePlugin.__init__(self, parent, config, name) 36 37 @classmethod 38 def code_hashid(cls, txt: str) -> str: 39 txt = txt.lower() 40 x = to_bytes(txt, 'utf8') 41 hash = sha256(x).hexdigest() 42 return hash[-3:].upper() 43 44 @classmethod 45 def get_versioned_seed_from_user_input(cls, txt: str) -> Optional[VersionedSeed]: 46 if len(txt) < 34: 47 return None 48 try: 49 int(txt, 16) 50 except: 51 return None 52 version = txt[0] 53 if version not in cls.KNOWN_VERSIONS: 54 return None 55 checksum = cls.code_hashid(txt[:-3]) 56 if txt[-3:].upper() != checksum.upper(): 57 return None 58 return VersionedSeed(version=version.upper(), 59 seed=txt[1:-3].upper(), 60 checksum=checksum.upper()) 61 62 @classmethod 63 def get_noise_map(cls, versioned_seed: VersionedSeed) -> Dict[Tuple[int, int], int]: 64 """Returns a map from (x,y) coordinate to pixel value 0/1, to be used as rawnoise.""" 65 w, h = cls.SIZE 66 version = versioned_seed.version 67 hex_seed = versioned_seed.seed 68 checksum = versioned_seed.checksum 69 noise_map = {} 70 if version == '0': 71 random.seed(int(hex_seed, 16)) 72 for x in range(w): 73 for y in range(h): 74 noise_map[(x, y)] = random.randint(0, 1) 75 elif version == '1': 76 prng_seed = bfh(hex_seed + version + checksum) 77 drbg = DRBG(prng_seed) 78 num_noise_bytes = 1929 # ~ w*h 79 noise_array = bin(int.from_bytes(drbg.generate(num_noise_bytes), 'big'))[2:] 80 # there's an approx 1/1024 chance that the generated number is 'too small' 81 # and we would get IndexError below. easiest backwards compat fix: 82 noise_array += '0' * (w * h - len(noise_array)) 83 i = 0 84 for x in range(w): 85 for y in range(h): 86 noise_map[(x, y)] = int(noise_array[i]) 87 i += 1 88 else: 89 raise Exception(f"unexpected revealer version: {version}") 90 return noise_map 91 92 @classmethod 93 def gen_random_versioned_seed(cls): 94 version = cls.LATEST_VERSION 95 hex_seed = bh2u(os.urandom(16)) 96 checksum = cls.code_hashid(version + hex_seed) 97 return VersionedSeed(version=version.upper(), 98 seed=hex_seed.upper(), 99 checksum=checksum.upper()) 100 101 102 if __name__ == '__main__': 103 for i in range(10**4): 104 vs = RevealerPlugin.gen_random_versioned_seed() 105 nm = RevealerPlugin.get_noise_map(vs)