electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

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)