trevealer.py - electrum - Electrum Bitcoin wallet
HTML git clone https://git.parazyd.org/electrum
DIR Log
DIR Files
DIR Refs
DIR Submodules
---
trevealer.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)