You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
3.5 KiB
105 lines
3.5 KiB
import random |
|
import os |
|
from hashlib import sha256 |
|
from typing import NamedTuple, Optional, Dict, Tuple |
|
|
|
from electrum.plugin import BasePlugin |
|
from electrum.util import to_bytes, bh2u, bfh |
|
|
|
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 RevealerPlugin(BasePlugin): |
|
|
|
LATEST_VERSION = '1' |
|
KNOWN_VERSIONS = ('0', '1') |
|
assert LATEST_VERSION in KNOWN_VERSIONS |
|
|
|
SIZE = (159, 97) |
|
|
|
def __init__(self, parent, config, name): |
|
BasePlugin.__init__(self, parent, config, name) |
|
|
|
@classmethod |
|
def code_hashid(cls, txt: str) -> str: |
|
txt = txt.lower() |
|
x = to_bytes(txt, 'utf8') |
|
hash = sha256(x).hexdigest() |
|
return hash[-3:].upper() |
|
|
|
@classmethod |
|
def get_versioned_seed_from_user_input(cls, txt: str) -> Optional[VersionedSeed]: |
|
if len(txt) < 34: |
|
return None |
|
try: |
|
int(txt, 16) |
|
except: |
|
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()) |
|
|
|
@classmethod |
|
def get_noise_map(cls, versioned_seed: VersionedSeed) -> Dict[Tuple[int, int], int]: |
|
"""Returns a map from (x,y) coordinate to pixel value 0/1, to be used as rawnoise.""" |
|
w, h = cls.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:] |
|
# there's an approx 1/1024 chance that the generated number is 'too small' |
|
# and we would get IndexError below. easiest backwards compat fix: |
|
noise_array += '0' * (w * h - len(noise_array)) |
|
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 |
|
|
|
@classmethod |
|
def gen_random_versioned_seed(cls): |
|
version = cls.LATEST_VERSION |
|
hex_seed = bh2u(os.urandom(16)) |
|
checksum = cls.code_hashid(version + hex_seed) |
|
return VersionedSeed(version=version.upper(), |
|
seed=hex_seed.upper(), |
|
checksum=checksum.upper()) |
|
|
|
|
|
if __name__ == '__main__': |
|
for i in range(10**4): |
|
vs = RevealerPlugin.gen_random_versioned_seed() |
|
nm = RevealerPlugin.get_noise_map(vs)
|
|
|