From ca8b2dd83efeab0260f17fba0064e94187ef0265 Mon Sep 17 00:00:00 2001 From: Lex Berezhny Date: Sun, 25 Mar 2018 22:59:57 -0400 Subject: [PATCH] wip: initial import of twisted based wallet --- lbrynet/{ => tests/unit}/txlbryum/__init__.py | 0 lbrynet/txlbryum/client.py | 177 -- lbrynet/txlbryum/errors.py | 18 - lbrynet/txlbryum/factory.py | 110 - lbrynet/wallet/__init__.py | 0 lbrynet/wallet/account.py | 83 + lbrynet/wallet/bcd_data_stream.py | 133 ++ lbrynet/wallet/blockchain.py | 243 ++ lbrynet/wallet/coinchooser.py | 313 +++ lbrynet/wallet/constants.py | 76 + lbrynet/wallet/enumeration.py | 47 + lbrynet/wallet/errors.py | 43 + lbrynet/wallet/hashing.py | 50 + lbrynet/wallet/lbrycrd.py | 633 +++++ lbrynet/wallet/manager.py | 88 + lbrynet/wallet/mnemonic.py | 157 ++ lbrynet/wallet/msqr.py | 96 + lbrynet/wallet/opcodes.py | 76 + lbrynet/wallet/protocol.py | 282 +++ lbrynet/wallet/store.py | 31 + lbrynet/wallet/stream.py | 127 + lbrynet/wallet/transaction.py | 702 ++++++ lbrynet/wallet/util.py | 117 + lbrynet/wallet/wallet.py | 1499 ++++++++++++ .../wallet/wordlist/chinese_simplified.txt | 2048 +++++++++++++++++ lbrynet/wallet/wordlist/english.txt | 2048 +++++++++++++++++ lbrynet/wallet/wordlist/japanese.txt | 2048 +++++++++++++++++ lbrynet/wallet/wordlist/portuguese.txt | 1654 +++++++++++++ lbrynet/wallet/wordlist/spanish.txt | 2048 +++++++++++++++++ 29 files changed, 14642 insertions(+), 305 deletions(-) rename lbrynet/{ => tests/unit}/txlbryum/__init__.py (100%) delete mode 100644 lbrynet/txlbryum/client.py delete mode 100644 lbrynet/txlbryum/errors.py delete mode 100644 lbrynet/txlbryum/factory.py create mode 100644 lbrynet/wallet/__init__.py create mode 100644 lbrynet/wallet/account.py create mode 100644 lbrynet/wallet/bcd_data_stream.py create mode 100644 lbrynet/wallet/blockchain.py create mode 100644 lbrynet/wallet/coinchooser.py create mode 100644 lbrynet/wallet/constants.py create mode 100644 lbrynet/wallet/enumeration.py create mode 100644 lbrynet/wallet/errors.py create mode 100644 lbrynet/wallet/hashing.py create mode 100644 lbrynet/wallet/lbrycrd.py create mode 100644 lbrynet/wallet/manager.py create mode 100644 lbrynet/wallet/mnemonic.py create mode 100644 lbrynet/wallet/msqr.py create mode 100644 lbrynet/wallet/opcodes.py create mode 100644 lbrynet/wallet/protocol.py create mode 100644 lbrynet/wallet/store.py create mode 100644 lbrynet/wallet/stream.py create mode 100644 lbrynet/wallet/transaction.py create mode 100644 lbrynet/wallet/util.py create mode 100644 lbrynet/wallet/wallet.py create mode 100644 lbrynet/wallet/wordlist/chinese_simplified.txt create mode 100644 lbrynet/wallet/wordlist/english.txt create mode 100644 lbrynet/wallet/wordlist/japanese.txt create mode 100644 lbrynet/wallet/wordlist/portuguese.txt create mode 100644 lbrynet/wallet/wordlist/spanish.txt diff --git a/lbrynet/txlbryum/__init__.py b/lbrynet/tests/unit/txlbryum/__init__.py similarity index 100% rename from lbrynet/txlbryum/__init__.py rename to lbrynet/tests/unit/txlbryum/__init__.py diff --git a/lbrynet/txlbryum/client.py b/lbrynet/txlbryum/client.py deleted file mode 100644 index d01b5eeb6..000000000 --- a/lbrynet/txlbryum/client.py +++ /dev/null @@ -1,177 +0,0 @@ -import json -import logging -import socket - -from twisted.internet import defer, error -from twisted.protocols.basic import LineOnlyReceiver -from errors import RemoteServiceException, ProtocolException, ServiceException - -log = logging.getLogger(__name__) - - -class StratumClientProtocol(LineOnlyReceiver): - delimiter = '\n' - - def __init__(self): - self._connected = defer.Deferred() - - def _get_id(self): - self.request_id += 1 - return self.request_id - - def _get_ip(self): - return self.transport.getPeer().host - - def get_session(self): - return self.session - - def connectionMade(self): - try: - self.transport.setTcpNoDelay(True) - self.transport.setTcpKeepAlive(True) - if hasattr(socket, "TCP_KEEPIDLE"): - self.transport.socket.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, - 120) # Seconds before sending keepalive probes - else: - log.debug("TCP_KEEPIDLE not available") - if hasattr(socket, "TCP_KEEPINTVL"): - self.transport.socket.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, - 1) # Interval in seconds between keepalive probes - else: - log.debug("TCP_KEEPINTVL not available") - if hasattr(socket, "TCP_KEEPCNT"): - self.transport.socket.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, - 5) # Failed keepalive probles before declaring other end dead - else: - log.debug("TCP_KEEPCNT not available") - - except Exception as err: - # Supported only by the socket transport, - # but there's really no better place in code to trigger this. - log.warning("Error setting up socket: %s", err) - - self.request_id = 0 - self.lookup_table = {} - - self._connected.callback(True) - - # Initiate connection session - self.session = {} - - log.debug("Connected %s" % self.transport.getPeer().host) - - def transport_write(self, data): - '''Overwrite this if transport needs some extra care about data written - to the socket, like adding message format in websocket.''' - try: - self.transport.write(data) - except AttributeError: - # Transport is disconnected - log.warning("transport is disconnected") - - def writeJsonRequest(self, method, params, is_notification=False): - request_id = None if is_notification else self._get_id() - serialized = json.dumps({'id': request_id, 'method': method, 'params': params}) - self.transport_write("%s\n" % serialized) - return request_id - - def writeJsonResponse(self, data, message_id): - serialized = json.dumps({'id': message_id, 'result': data, 'error': None}) - self.transport_write("%s\n" % serialized) - - def writeJsonError(self, code, message, traceback, message_id): - serialized = json.dumps( - {'id': message_id, 'result': None, 'error': (code, message, traceback)} - ) - self.transport_write("%s\n" % serialized) - - def writeGeneralError(self, message, code=-1): - log.error(message) - return self.writeJsonError(code, message, None, None) - - def process_response(self, data, message_id): - self.writeJsonResponse(data.result, message_id) - - def process_failure(self, failure, message_id): - if not isinstance(failure.value, ServiceException): - # All handled exceptions should inherit from ServiceException class. - # Throwing other exception class means that it is unhandled error - # and we should log it. - log.exception(failure) - code = getattr(failure.value, 'code', -1) - if message_id != None: - tb = failure.getBriefTraceback() - self.writeJsonError(code, failure.getErrorMessage(), tb, message_id) - - def dataReceived(self, data): - '''Original code from Twisted, hacked for request_counter proxying. - request_counter is hack for HTTP transport, didn't found cleaner solution how - to indicate end of request processing in asynchronous manner. - - TODO: This would deserve some unit test to be sure that future twisted versions - will work nicely with this.''' - - lines = (self._buffer + data).split(self.delimiter) - self._buffer = lines.pop(-1) - - for line in lines: - if self.transport.disconnecting: - return - if len(line) > self.MAX_LENGTH: - return self.lineLengthExceeded(line) - else: - try: - self.lineReceived(line) - except Exception as exc: - # log.exception("Processing of message failed") - log.warning("Failed message: %s from %s" % (str(exc), self._get_ip())) - return error.ConnectionLost('Processing of message failed') - - if len(self._buffer) > self.MAX_LENGTH: - return self.lineLengthExceeded(self._buffer) - - def lineReceived(self, line): - try: - message = json.loads(line) - except (ValueError, TypeError): - # self.writeGeneralError("Cannot decode message '%s'" % line) - raise ProtocolException("Cannot decode message '%s'" % line.strip()) - msg_id = message.get('id', 0) - msg_result = message.get('result') - msg_error = message.get('error') - if msg_id: - # It's a RPC response - # Perform lookup to the table of waiting requests. - try: - meta = self.lookup_table[msg_id] - del self.lookup_table[msg_id] - except KeyError: - # When deferred object for given message ID isn't found, it's an error - raise ProtocolException( - "Lookup for deferred object for message ID '%s' failed." % msg_id) - # If there's an error, handle it as errback - # If both result and error are null, handle it as a success with blank result - if msg_error != None: - meta['defer'].errback( - RemoteServiceException(msg_error[0], msg_error[1], msg_error[2]) - ) - else: - meta['defer'].callback(msg_result) - else: - raise ProtocolException("Cannot handle message '%s'" % line) - - def rpc(self, method, params, is_notification=False): - ''' - This method performs remote RPC call. - - If method should expect an response, it store - request ID to lookup table and wait for corresponding - response message. - ''' - - request_id = self.writeJsonRequest(method, params, is_notification) - if is_notification: - return - d = defer.Deferred() - self.lookup_table[request_id] = {'defer': d, 'method': method, 'params': params} - return d diff --git a/lbrynet/txlbryum/errors.py b/lbrynet/txlbryum/errors.py deleted file mode 100644 index eaa8723dc..000000000 --- a/lbrynet/txlbryum/errors.py +++ /dev/null @@ -1,18 +0,0 @@ -class TransportException(Exception): - pass - - -class ServiceException(Exception): - code = -2 - - -class RemoteServiceException(Exception): - pass - - -class ProtocolException(Exception): - pass - - -class MethodNotFoundException(ServiceException): - code = -3 diff --git a/lbrynet/txlbryum/factory.py b/lbrynet/txlbryum/factory.py deleted file mode 100644 index 6c59d83a3..000000000 --- a/lbrynet/txlbryum/factory.py +++ /dev/null @@ -1,110 +0,0 @@ -import logging -from twisted.internet import defer -from twisted.internet.protocol import ClientFactory -from client import StratumClientProtocol -from errors import TransportException - -log = logging.getLogger() - - -class StratumClient(ClientFactory): - protocol = StratumClientProtocol - - def __init__(self, connected_d=None): - self.client = None - self.connected_d = connected_d or defer.Deferred() - - def buildProtocol(self, addr): - client = self.protocol() - client.factory = self - self.client = client - self.client._connected.addCallback(lambda _: self.connected_d.callback(self)) - return client - - def _rpc(self, method, params, *args, **kwargs): - if not self.client: - raise TransportException("Not connected") - - return self.client.rpc(method, params, *args, **kwargs) - - def blockchain_claimtrie_getvaluesforuris(self, block_hash, *uris): - return self._rpc('blockchain.claimtrie.getvaluesforuris', - [block_hash] + list(uris)) - - def blockchain_claimtrie_getvaluesforuri(self, block_hash, uri): - return self._rpc('blockchain.claimtrie.getvaluesforuri', [block_hash, uri]) - - def blockchain_claimtrie_getclaimssignedbynthtoname(self, name, n): - return self._rpc('blockchain.claimtrie.getclaimssignedbynthtoname', [name, n]) - - def blockchain_claimtrie_getclaimssignedbyid(self, certificate_id): - return self._rpc('blockchain.claimtrie.getclaimssignedbyid', [certificate_id]) - - def blockchain_claimtrie_getclaimssignedby(self, name): - return self._rpc('blockchain.claimtrie.getclaimssignedby', [name]) - - def blockchain_claimtrie_getnthclaimforname(self, name, n): - return self._rpc('blockchain.claimtrie.getnthclaimforname', [name, n]) - - def blockchain_claimtrie_getclaimsbyids(self, *claim_ids): - return self._rpc('blockchain.claimtrie.getclaimsbyids', list(claim_ids)) - - def blockchain_claimtrie_getclaimbyid(self, claim_id): - return self._rpc('blockchain.claimtrie.getclaimbyid', [claim_id]) - - def blockchain_claimtrie_get(self): - return self._rpc('blockchain.claimtrie.get', []) - - def blockchain_block_get_block(self, block_hash): - return self._rpc('blockchain.block.get_block', [block_hash]) - - def blockchain_claimtrie_getclaimsforname(self, name): - return self._rpc('blockchain.claimtrie.getclaimsforname', [name]) - - def blockchain_claimtrie_getclaimsintx(self, txid): - return self._rpc('blockchain.claimtrie.getclaimsintx', [txid]) - - def blockchain_claimtrie_getvalue(self, name, block_hash=None): - return self._rpc('blockchain.claimtrie.getvalue', [name, block_hash]) - - def blockchain_relayfee(self): - return self._rpc('blockchain.relayfee', []) - - def blockchain_estimatefee(self): - return self._rpc('blockchain.estimatefee', []) - - def blockchain_transaction_get(self, txid): - return self._rpc('blockchain.transaction.get', [txid]) - - def blockchain_transaction_get_merkle(self, tx_hash, height, cache_only=False): - return self._rpc('blockchain.transaction.get_merkle', [tx_hash, height, cache_only]) - - def blockchain_transaction_broadcast(self, raw_transaction): - return self._rpc('blockchain.transaction.broadcast', [raw_transaction]) - - def blockchain_block_get_chunk(self, index, cache_only=False): - return self._rpc('blockchain.block.get_chunk', [index, cache_only]) - - def blockchain_block_get_header(self, height, cache_only=False): - return self._rpc('blockchain.block.get_header', [height, cache_only]) - - def blockchain_utxo_get_address(self, txid, pos): - return self._rpc('blockchain.utxo.get_address', [txid, pos]) - - def blockchain_address_listunspent(self, address): - return self._rpc('blockchain.address.listunspent', [address]) - - def blockchain_address_get_proof(self, address): - return self._rpc('blockchain.address.get_proof', [address]) - - def blockchain_address_get_balance(self, address): - return self._rpc('blockchain.address.get_balance', [address]) - - def blockchain_address_get_mempool(self, address): - return self._rpc('blockchain.address.get_mempool', [address]) - - def blockchain_address_get_history(self, address): - return self._rpc('blockchain.address.get_history', [address]) - - def blockchain_block_get_server_height(self): - return self._rpc('blockchain.block.get_server_height', []) diff --git a/lbrynet/wallet/__init__.py b/lbrynet/wallet/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lbrynet/wallet/account.py b/lbrynet/wallet/account.py new file mode 100644 index 000000000..437ec358b --- /dev/null +++ b/lbrynet/wallet/account.py @@ -0,0 +1,83 @@ +import logging + +from lbryschema.address import public_key_to_address + +from .lbrycrd import deserialize_xkey +from .lbrycrd import CKD_pub + +log = logging.getLogger(__name__) + + +def get_key_chain_from_xpub(xpub): + _, _, _, chain, key = deserialize_xkey(xpub) + return key, chain + + +def derive_key(parent_key, chain, sequence): + return CKD_pub(parent_key, chain, sequence)[0] + + +class AddressSequence: + + def __init__(self, derived_keys, gap, age_checker, pub_key, chain_key): + self.gap = gap + self.is_old = age_checker + self.pub_key = pub_key + self.chain_key = chain_key + self.derived_keys = derived_keys + self.addresses = [ + public_key_to_address(key.decode('hex')) + for key in derived_keys + ] + + def generate_next_address(self): + new_key, _ = derive_key(self.pub_key, self.chain_key, len(self.derived_keys)) + address = public_key_to_address(new_key) + self.derived_keys.append(new_key.encode('hex')) + self.addresses.append(address) + return address + + def has_gap(self): + if len(self.addresses) < self.gap: + return False + for address in self.addresses[-self.gap:]: + if self.is_old(address): + return False + return True + + def ensure_enough_addresses(self): + starting_length = len(self.addresses) + while not self.has_gap(): + self.generate_next_address() + return self.addresses[starting_length:] + + +class Account: + + def __init__(self, data, receiving_gap, change_gap, age_checker): + self.xpub = data['xpub'] + master_key, master_chain = get_key_chain_from_xpub(data['xpub']) + self.receiving = AddressSequence( + data.get('receiving', []), receiving_gap, age_checker, + *derive_key(master_key, master_chain, 0) + ) + self.change = AddressSequence( + data.get('change', []), change_gap, age_checker, + *derive_key(master_key, master_chain, 1) + ) + self.is_old = age_checker + + def as_dict(self): + return { + 'receiving': self.receiving.derived_keys, + 'change': self.change.derived_keys, + 'xpub': self.xpub + } + + def ensure_enough_addresses(self): + return self.receiving.ensure_enough_addresses() + \ + self.change.ensure_enough_addresses() + + @property + def sequences(self): + return self.receiving, self.change diff --git a/lbrynet/wallet/bcd_data_stream.py b/lbrynet/wallet/bcd_data_stream.py new file mode 100644 index 000000000..163044641 --- /dev/null +++ b/lbrynet/wallet/bcd_data_stream.py @@ -0,0 +1,133 @@ +import struct + + +class SerializationError(Exception): + """ Thrown when there's a problem deserializing or serializing """ + + +class BCDataStream(object): + def __init__(self): + self.input = None + self.read_cursor = 0 + + def clear(self): + self.input = None + self.read_cursor = 0 + + def write(self, bytes): # Initialize with string of bytes + if self.input is None: + self.input = bytes + else: + self.input += bytes + + def read_string(self): + # Strings are encoded depending on length: + # 0 to 252 : 1-byte-length followed by bytes (if any) + # 253 to 65,535 : byte'253' 2-byte-length followed by bytes + # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes + # ... and the Bitcoin client is coded to understand: + # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string + # ... but I don't think it actually handles any strings that big. + if self.input is None: + raise SerializationError("call write(bytes) before trying to deserialize") + + try: + length = self.read_compact_size() + except IndexError: + raise SerializationError("attempt to read past end of buffer") + + return self.read_bytes(length) + + def write_string(self, string): + # Length-encoded as with read-string + self.write_compact_size(len(string)) + self.write(string) + + def read_bytes(self, length): + try: + result = self.input[self.read_cursor:self.read_cursor + length] + self.read_cursor += length + return result + except IndexError: + raise SerializationError("attempt to read past end of buffer") + + return '' + + def read_boolean(self): + return self.read_bytes(1)[0] != chr(0) + + def read_int16(self): + return self._read_num(' 0: + previous_header = self[height-1] + self._verify_header(height, header, previous_header) + previous_header = header + + with open(self.path, 'r+b') as f: + f.seek(start * HEADER_SIZE) + f.write(headers) + f.truncate() + + _old_size = self._size + self._size = self.sync_read_length() + change = self._size - _old_size + log.info('saved {} header blocks'.format(change)) + self._on_change_controller.add(change) + + def _iterate_headers(self, height, headers): + assert len(headers) % HEADER_SIZE == 0 + for idx in range(len(headers) / HEADER_SIZE): + start, end = idx * HEADER_SIZE, (idx + 1) * HEADER_SIZE + header = headers[start:end] + yield self._deserialize(height+idx, header) + + def _verify_header(self, height, header, previous_header): + previous_hash = self._hash_header(previous_header) + assert previous_hash == header['prev_block_hash'], \ + "prev hash mismatch: {} vs {}".format(previous_hash, header['prev_block_hash']) + + bits, target = self._calculate_lbry_next_work_required(height, previous_header, header) + assert bits == header['bits'], \ + "bits mismatch: {} vs {} (hash: {})".format( + bits, header['bits'], self._hash_header(header)) + + _pow_hash = self._pow_hash_header(header) + assert int('0x' + _pow_hash, 16) <= target, \ + "insufficient proof of work: {} vs target {}".format( + int('0x' + _pow_hash, 16), target) + + @staticmethod + def _serialize(header): + return ''.join([ + int_to_hex(header['version'], 4), + rev_hex(header['prev_block_hash']), + rev_hex(header['merkle_root']), + rev_hex(header['claim_trie_root']), + int_to_hex(int(header['timestamp']), 4), + int_to_hex(int(header['bits']), 4), + int_to_hex(int(header['nonce']), 4) + ]) + + @staticmethod + def _deserialize(height, header): + return { + 'version': hex_to_int(header[0:4]), + 'prev_block_hash': hash_encode(header[4:36]), + 'merkle_root': hash_encode(header[36:68]), + 'claim_trie_root': hash_encode(header[68:100]), + 'timestamp': hex_to_int(header[100:104]), + 'bits': hex_to_int(header[104:108]), + 'nonce': hex_to_int(header[108:112]), + 'block_height': height + } + + def _hash_header(self, header): + if header is None: + return '0' * 64 + return hash_encode(Hash(self._serialize(header).decode('hex'))) + + def _pow_hash_header(self, header): + if header is None: + return '0' * 64 + return hash_encode(PoWHash(self._serialize(header).decode('hex'))) + + def _calculate_lbry_next_work_required(self, height, first, last): + """ See: lbrycrd/src/lbry.cpp """ + + if height == 0: + return self.genesis_bits, self.max_target + + # bits to target + if self.chain != 'lbrycrd_regtest': + bits = last['bits'] + bitsN = (bits >> 24) & 0xff + assert 0x03 <= bitsN <= 0x1f, \ + "First part of bits should be in [0x03, 0x1d], but it was {}".format(hex(bitsN)) + bitsBase = bits & 0xffffff + assert 0x8000 <= bitsBase <= 0x7fffff, \ + "Second part of bits should be in [0x8000, 0x7fffff] but it was {}".format(bitsBase) + + # new target + retargetTimespan = self.target_timespan + nActualTimespan = last['timestamp'] - first['timestamp'] + + nModulatedTimespan = retargetTimespan + (nActualTimespan - retargetTimespan) // 8 + + nMinTimespan = retargetTimespan - (retargetTimespan // 8) + nMaxTimespan = retargetTimespan + (retargetTimespan // 2) + + # Limit adjustment step + if nModulatedTimespan < nMinTimespan: + nModulatedTimespan = nMinTimespan + elif nModulatedTimespan > nMaxTimespan: + nModulatedTimespan = nMaxTimespan + + # Retarget + bnPowLimit = _ArithUint256(self.max_target) + bnNew = _ArithUint256.SetCompact(last['bits']) + bnNew *= nModulatedTimespan + bnNew //= nModulatedTimespan + if bnNew > bnPowLimit: + bnNew = bnPowLimit + + return bnNew.GetCompact(), bnNew._value + + +class _ArithUint256: + """ See: lbrycrd/src/arith_uint256.cpp """ + + def __init__(self, value): + self._value = value + + def __str__(self): + return hex(self._value) + + @staticmethod + def fromCompact(nCompact): + """Convert a compact representation into its value""" + nSize = nCompact >> 24 + # the lower 23 bits + nWord = nCompact & 0x007fffff + if nSize <= 3: + return nWord >> 8 * (3 - nSize) + else: + return nWord << 8 * (nSize - 3) + + @classmethod + def SetCompact(cls, nCompact): + return cls(cls.fromCompact(nCompact)) + + def bits(self): + """Returns the position of the highest bit set plus one.""" + bn = bin(self._value)[2:] + for i, d in enumerate(bn): + if d: + return (len(bn) - i) + 1 + return 0 + + def GetLow64(self): + return self._value & 0xffffffffffffffff + + def GetCompact(self): + """Convert a value into its compact representation""" + nSize = (self.bits() + 7) // 8 + nCompact = 0 + if nSize <= 3: + nCompact = self.GetLow64() << 8 * (3 - nSize) + else: + bn = _ArithUint256(self._value >> 8 * (nSize - 3)) + nCompact = bn.GetLow64() + # The 0x00800000 bit denotes the sign. + # Thus, if it is already set, divide the mantissa by 256 and increase the exponent. + if nCompact & 0x00800000: + nCompact >>= 8 + nSize += 1 + assert (nCompact & ~0x007fffff) == 0 + assert nSize < 256 + nCompact |= nSize << 24 + return nCompact + + def __mul__(self, x): + # Take the mod because we are limited to an unsigned 256 bit number + return _ArithUint256((self._value * x) % 2 ** 256) + + def __ifloordiv__(self, x): + self._value = (self._value // x) + return self + + def __gt__(self, x): + return self._value > x diff --git a/lbrynet/wallet/coinchooser.py b/lbrynet/wallet/coinchooser.py new file mode 100644 index 000000000..72c725fdf --- /dev/null +++ b/lbrynet/wallet/coinchooser.py @@ -0,0 +1,313 @@ +import struct +import logging +from collections import defaultdict, namedtuple +from math import floor, log10 + +from .hashing import sha256 +from .constants import COIN, TYPE_ADDRESS +from .transaction import Transaction +from .errors import NotEnoughFunds + +log = logging.getLogger() + + +class PRNG(object): + """ + A simple deterministic PRNG. Used to deterministically shuffle a + set of coins - the same set of coins should produce the same output. + Although choosing UTXOs "randomly" we want it to be deterministic, + so if sending twice from the same UTXO set we choose the same UTXOs + to spend. This prevents attacks on users by malicious or stale + servers. + """ + + def __init__(self, seed): + self.sha = sha256(seed) + self.pool = bytearray() + + def get_bytes(self, n): + while len(self.pool) < n: + self.pool.extend(self.sha) + self.sha = sha256(self.sha) + result, self.pool = self.pool[:n], self.pool[n:] + return result + + def random(self): + # Returns random double in [0, 1) + four = self.get_bytes(4) + return struct.unpack("I", four)[0] / 4294967296.0 + + def randint(self, start, end): + # Returns random integer in [start, end) + return start + int(self.random() * (end - start)) + + def choice(self, seq): + return seq[int(self.random() * len(seq))] + + def shuffle(self, x): + for i in reversed(xrange(1, len(x))): + # pick an element in x[:i+1] with which to exchange x[i] + j = int(self.random() * (i + 1)) + x[i], x[j] = x[j], x[i] + + +Bucket = namedtuple('Bucket', ['desc', 'size', 'value', 'coins']) + + +def strip_unneeded(bkts, sufficient_funds): + '''Remove buckets that are unnecessary in achieving the spend amount''' + bkts = sorted(bkts, key=lambda bkt: bkt.value) + for i in range(len(bkts)): + if not sufficient_funds(bkts[i + 1:]): + return bkts[i:] + # Shouldn't get here + return bkts + + +class CoinChooserBase: + def keys(self, coins): + raise NotImplementedError + + def bucketize_coins(self, coins): + keys = self.keys(coins) + buckets = defaultdict(list) + for key, coin in zip(keys, coins): + buckets[key].append(coin) + + def make_Bucket(desc, coins): + size = sum(Transaction.estimated_input_size(coin) + for coin in coins) + value = sum(coin['value'] for coin in coins) + return Bucket(desc, size, value, coins) + + return map(make_Bucket, buckets.keys(), buckets.values()) + + def penalty_func(self, tx): + def penalty(candidate): + return 0 + + return penalty + + def change_amounts(self, tx, count, fee_estimator, dust_threshold): + # Break change up if bigger than max_change + output_amounts = [o[2] for o in tx.outputs()] + # Don't split change of less than 0.02 BTC + max_change = max(max(output_amounts) * 1.25, 0.02 * COIN) + + # Use N change outputs + for n in range(1, count + 1): + # How much is left if we add this many change outputs? + change_amount = max(0, tx.get_fee() - fee_estimator(n)) + if change_amount // n <= max_change: + break + + # Get a handle on the precision of the output amounts; round our + # change to look similar + def trailing_zeroes(val): + s = str(val) + return len(s) - len(s.rstrip('0')) + + zeroes = map(trailing_zeroes, output_amounts) + min_zeroes = min(zeroes) + max_zeroes = max(zeroes) + zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1) + + # Calculate change; randomize it a bit if using more than 1 output + remaining = change_amount + amounts = [] + while n > 1: + average = remaining // n + amount = self.p.randint(int(average * 0.7), int(average * 1.3)) + precision = min(self.p.choice(zeroes), int(floor(log10(amount)))) + amount = int(round(amount, -precision)) + amounts.append(amount) + remaining -= amount + n -= 1 + + # Last change output. Round down to maximum precision but lose + # no more than 100 satoshis to fees (2dp) + N = pow(10, min(2, zeroes[0])) + amount = (remaining // N) * N + amounts.append(amount) + + assert sum(amounts) <= change_amount + + return amounts + + def change_outputs(self, tx, change_addrs, fee_estimator, dust_threshold): + amounts = self.change_amounts(tx, len(change_addrs), fee_estimator, + dust_threshold) + assert min(amounts) >= 0 + assert len(change_addrs) >= len(amounts) + # If change is above dust threshold after accounting for the + # size of the change output, add it to the transaction. + dust = sum(amount for amount in amounts if amount < dust_threshold) + amounts = [amount for amount in amounts if amount >= dust_threshold] + change = [(TYPE_ADDRESS, addr, amount) + for addr, amount in zip(change_addrs, amounts)] + log.debug('change: %s', change) + if dust: + log.debug('not keeping dust %s', dust) + return change + + def make_tx(self, coins, outputs, change_addrs, fee_estimator, + dust_threshold, abandon_txid=None): + '''Select unspent coins to spend to pay outputs. If the change is + greater than dust_threshold (after adding the change output to + the transaction) it is kept, otherwise none is sent and it is + added to the transaction fee.''' + + # Deterministic randomness from coins + utxos = [c['prevout_hash'] + str(c['prevout_n']) for c in coins] + self.p = PRNG(''.join(sorted(utxos))) + + # Copy the ouputs so when adding change we don't modify "outputs" + tx = Transaction.from_io([], outputs[:]) + # Size of the transaction with no inputs and no change + base_size = tx.estimated_size() + spent_amount = tx.output_value() + + claim_coin = None + if abandon_txid is not None: + claim_coins = [coin for coin in coins if coin['is_claim']] + assert len(claim_coins) >= 1 + claim_coin = claim_coins[0] + spent_amount -= claim_coin['value'] + coins = [coin for coin in coins if not coin['is_claim']] + + def sufficient_funds(buckets): + '''Given a list of buckets, return True if it has enough + value to pay for the transaction''' + total_input = sum(bucket.value for bucket in buckets) + total_size = sum(bucket.size for bucket in buckets) + base_size + return total_input >= spent_amount + fee_estimator(total_size) + + # Collect the coins into buckets, choose a subset of the buckets + buckets = self.bucketize_coins(coins) + buckets = self.choose_buckets(buckets, sufficient_funds, + self.penalty_func(tx)) + + if claim_coin is not None: + tx.add_inputs([claim_coin]) + tx.add_inputs([coin for b in buckets for coin in b.coins]) + tx_size = base_size + sum(bucket.size for bucket in buckets) + + # This takes a count of change outputs and returns a tx fee; + # each pay-to-bitcoin-address output serializes as 34 bytes + def fee(count): + return fee_estimator(tx_size + count * 34) + + change = self.change_outputs(tx, change_addrs, fee, dust_threshold) + tx.add_outputs(change) + + log.debug("using %i inputs", len(tx.inputs())) + log.info("using buckets: %s", [bucket.desc for bucket in buckets]) + + return tx + + +class CoinChooserOldestFirst(CoinChooserBase): + '''Maximize transaction priority. Select the oldest unspent + transaction outputs in your wallet, that are sufficient to cover + the spent amount. Then, remove any unneeded inputs, starting with + the smallest in value. + ''' + + def keys(self, coins): + return [coin['prevout_hash'] + ':' + str(coin['prevout_n']) + for coin in coins] + + def choose_buckets(self, buckets, sufficient_funds, penalty_func): + '''Spend the oldest buckets first.''' + # Unconfirmed coins are young, not old + def adj_height(height): + return 99999999 if height == 0 else height + + buckets.sort(key=lambda b: max(adj_height(coin['height']) + for coin in b.coins)) + selected = [] + for bucket in buckets: + selected.append(bucket) + if sufficient_funds(selected): + return strip_unneeded(selected, sufficient_funds) + raise NotEnoughFunds() + + +class CoinChooserRandom(CoinChooserBase): + def keys(self, coins): + return [coin['prevout_hash'] + ':' + str(coin['prevout_n']) + for coin in coins] + + def bucket_candidates(self, buckets, sufficient_funds): + '''Returns a list of bucket sets.''' + candidates = set() + + # Add all singletons + for n, bucket in enumerate(buckets): + if sufficient_funds([bucket]): + candidates.add((n,)) + + # And now some random ones + attempts = min(100, (len(buckets) - 1) * 10 + 1) + permutation = range(len(buckets)) + for i in range(attempts): + # Get a random permutation of the buckets, and + # incrementally combine buckets until sufficient + self.p.shuffle(permutation) + bkts = [] + for count, index in enumerate(permutation): + bkts.append(buckets[index]) + if sufficient_funds(bkts): + candidates.add(tuple(sorted(permutation[:count + 1]))) + break + else: + raise NotEnoughFunds() + + candidates = [[buckets[n] for n in c] for c in candidates] + return [strip_unneeded(c, sufficient_funds) for c in candidates] + + def choose_buckets(self, buckets, sufficient_funds, penalty_func): + candidates = self.bucket_candidates(buckets, sufficient_funds) + penalties = [penalty_func(cand) for cand in candidates] + winner = candidates[penalties.index(min(penalties))] + log.debug("Bucket sets: %i", len(buckets)) + log.debug("Winning penalty: %s", min(penalties)) + return winner + + +class CoinChooserPrivacy(CoinChooserRandom): + '''Attempts to better preserve user privacy. First, if any coin is + spent from a user address, all coins are. Compared to spending + from other addresses to make up an amount, this reduces + information leakage about sender holdings. It also helps to + reduce blockchain UTXO bloat, and reduce future privacy loss that + would come from reusing that address' remaining UTXOs. Second, it + penalizes change that is quite different to the sent amount. + Third, it penalizes change that is too big.''' + + def keys(self, coins): + return [coin['address'] for coin in coins] + + def penalty_func(self, tx): + min_change = min(o[2] for o in tx.outputs()) * 0.75 + max_change = max(o[2] for o in tx.outputs()) * 1.33 + spent_amount = sum(o[2] for o in tx.outputs()) + + def penalty(buckets): + badness = len(buckets) - 1 + total_input = sum(bucket.value for bucket in buckets) + change = float(total_input - spent_amount) + # Penalize change not roughly in output range + if change < min_change: + badness += (min_change - change) / (min_change + 10000) + elif change > max_change: + badness += (change - max_change) / (max_change + 10000) + # Penalize large change; 5 BTC excess ~= using 1 more input + badness += change / (COIN * 5) + return badness + + return penalty + + +COIN_CHOOSERS = {'Priority': CoinChooserOldestFirst, + 'Privacy': CoinChooserPrivacy} diff --git a/lbrynet/wallet/constants.py b/lbrynet/wallet/constants.py new file mode 100644 index 000000000..55a166d0e --- /dev/null +++ b/lbrynet/wallet/constants.py @@ -0,0 +1,76 @@ +from lbrynet import __version__ +LBRYUM_VERSION = __version__ +PROTOCOL_VERSION = '0.10' # protocol version requested +NEW_SEED_VERSION = 11 # lbryum versions >= 2.0 +OLD_SEED_VERSION = 4 # lbryum versions < 2.0 + +# The hash of the mnemonic seed must begin with this +SEED_PREFIX = '01' # Electrum standard wallet +SEED_PREFIX_2FA = '101' # extended seed for two-factor authentication + + +RECOMMENDED_FEE = 50000 +COINBASE_MATURITY = 100 +COIN = 100000000 + +# supported types of transaction outputs +TYPE_ADDRESS = 1 +TYPE_PUBKEY = 2 +TYPE_SCRIPT = 4 +TYPE_CLAIM = 8 +TYPE_SUPPORT = 16 +TYPE_UPDATE = 32 + +# claim related constants +EXPIRATION_BLOCKS = 262974 +RECOMMENDED_CLAIMTRIE_HASH_CONFIRMS = 1 + +NO_SIGNATURE = 'ff' + +NULL_HASH = '0000000000000000000000000000000000000000000000000000000000000000' +HEADER_SIZE = 112 +BLOCKS_PER_CHUNK = 96 +CLAIM_ID_SIZE = 20 + +HEADERS_URL = "https://s3.amazonaws.com/lbry-blockchain-headers/blockchain_headers_latest" + +DEFAULT_PORTS = {'t': '50001', 's': '50002', 'h': '8081', 'g': '8082'} +NODES_RETRY_INTERVAL = 60 +SERVER_RETRY_INTERVAL = 10 +MAX_BATCH_QUERY_SIZE = 500 +proxy_modes = ['socks4', 'socks5', 'http'] + +# Main network and testnet3 definitions +# these values follow the parameters in lbrycrd/src/chainparams.cpp +blockchain_params = { + 'lbrycrd_main': { + 'pubkey_address': 0, + 'script_address': 5, + 'pubkey_address_prefix': 85, + 'script_address_prefix': 122, + 'genesis_hash': '9c89283ba0f3227f6c03b70216b9f665f0118d5e0fa729cedf4fb34d6a34f463', + 'max_target': 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 'genesis_bits': 0x1f00ffff, + 'target_timespan': 150 + }, + 'lbrycrd_testnet': { + 'pubkey_address': 0, + 'script_address': 5, + 'pubkey_address_prefix': 111, + 'script_address_prefix': 196, + 'genesis_hash': '9c89283ba0f3227f6c03b70216b9f665f0118d5e0fa729cedf4fb34d6a34f463', + 'max_target': 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 'genesis_bits': 0x1f00ffff, + 'target_timespan': 150 + }, + 'lbrycrd_regtest': { + 'pubkey_address': 0, + 'script_address': 5, + 'pubkey_address_prefix': 111, + 'script_address_prefix': 196, + 'genesis_hash': '6e3fcf1299d4ec5d79c3a4c91d624a4acf9e2e173d95a1a0504f677669687556', + 'max_target': 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, + 'genesis_bits': 0x207fffff, + 'target_timespan': 1 + } +} diff --git a/lbrynet/wallet/enumeration.py b/lbrynet/wallet/enumeration.py new file mode 100644 index 000000000..497805a84 --- /dev/null +++ b/lbrynet/wallet/enumeration.py @@ -0,0 +1,47 @@ +import exceptions +import types + + +class EnumException(exceptions.Exception): + pass + + +class Enumeration(object): + """ + enum-like type + From the Python Cookbook, downloaded from http://code.activestate.com/recipes/67107/ + """ + + def __init__(self, name, enumList): + self.__doc__ = name + lookup = {} + reverseLookup = {} + i = 0 + uniqueNames = [] + uniqueValues = [] + for x in enumList: + if isinstance(x, types.TupleType): + x, i = x + if not isinstance(x, types.StringType): + raise EnumException, "enum name is not a string: " + x + if not isinstance(i, types.IntType): + raise EnumException, "enum value is not an integer: " + i + if x in uniqueNames: + raise EnumException, "enum name is not unique: " + x + if i in uniqueValues: + raise EnumException, "enum value is not unique for " + x + uniqueNames.append(x) + uniqueValues.append(i) + lookup[x] = i + reverseLookup[i] = x + i = i + 1 + self.lookup = lookup + self.reverseLookup = reverseLookup + + def __getattr__(self, attr): + if attr not in self.lookup: + raise AttributeError(attr) + return self.lookup[attr] + + def whatis(self, value): + return self.reverseLookup[value] diff --git a/lbrynet/wallet/errors.py b/lbrynet/wallet/errors.py new file mode 100644 index 000000000..70e4cd3ba --- /dev/null +++ b/lbrynet/wallet/errors.py @@ -0,0 +1,43 @@ +class TransportException(Exception): + pass + + +class ServiceException(Exception): + code = -2 + + +class RemoteServiceException(Exception): + pass + + +class ProtocolException(Exception): + pass + + +class MethodNotFoundException(ServiceException): + code = -3 + + +class NotEnoughFunds(Exception): + pass + + +class InvalidPassword(Exception): + def __str__(self): + return "Incorrect password" + + +class Timeout(Exception): + pass + + +class InvalidProofError(Exception): + pass + + +class ChainValidationError(Exception): + pass + + +class InvalidClaimId(Exception): + pass diff --git a/lbrynet/wallet/hashing.py b/lbrynet/wallet/hashing.py new file mode 100644 index 000000000..ed50ee750 --- /dev/null +++ b/lbrynet/wallet/hashing.py @@ -0,0 +1,50 @@ +import hashlib +import hmac + + +def sha256(x): + return hashlib.sha256(x).digest() + + +def sha512(x): + return hashlib.sha512(x).digest() + + +def ripemd160(x): + h = hashlib.new('ripemd160') + h.update(x) + return h.digest() + + +def Hash(x): + if type(x) is unicode: + x = x.encode('utf-8') + return sha256(sha256(x)) + + +def PoWHash(x): + if type(x) is unicode: + x = x.encode('utf-8') + r = sha512(Hash(x)) + r1 = ripemd160(r[:len(r) / 2]) + r2 = ripemd160(r[len(r) / 2:]) + r3 = Hash(r1 + r2) + return r3 + + +def hash_encode(x): + return x[::-1].encode('hex') + + +def hash_decode(x): + return x.decode('hex')[::-1] + + +def hmac_sha_512(x, y): + return hmac.new(x, y, hashlib.sha512).digest() + + +def hash_160(public_key): + md = hashlib.new('ripemd160') + md.update(sha256(public_key)) + return md.digest() diff --git a/lbrynet/wallet/lbrycrd.py b/lbrynet/wallet/lbrycrd.py new file mode 100644 index 000000000..d4bafe9bb --- /dev/null +++ b/lbrynet/wallet/lbrycrd.py @@ -0,0 +1,633 @@ +import base64 +import hashlib +import hmac +import struct +import logging +import aes +import ecdsa +from ecdsa import numbertheory, util +from ecdsa.curves import SECP256k1 +from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1 +from ecdsa.ellipticcurve import Point +from ecdsa.util import number_to_string, string_to_number + +from lbryschema.address import public_key_to_address +from lbryschema.schema import B58_CHARS +from lbryschema.base import b58encode_with_checksum, b58decode_strip_checksum + +from . import msqr +from .util import rev_hex, var_int, int_to_hex +from .hashing import Hash, sha256, hash_160 +from .errors import InvalidPassword, InvalidClaimId +from .constants import CLAIM_ID_SIZE + +log = logging.getLogger(__name__) + +# AES encryption +EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret, s)) +DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e)) + + +# get the claim id hash from txid bytes and int n +def claim_id_hash(txid, n): + return hash_160(txid + struct.pack('>I', n)) + + +# deocde a claim_id hex string +def decode_claim_id_hex(claim_id_hex): + claim_id = rev_hex(claim_id_hex).decode('hex') + if len(claim_id) != CLAIM_ID_SIZE: + raise InvalidClaimId() + return claim_id + + +# encode claim id bytes into hex string +def encode_claim_id_hex(claim_id): + return rev_hex(claim_id.encode('hex')) + + +def strip_PKCS7_padding(s): + """return s stripped of PKCS7 padding""" + if len(s) % 16 or not s: + raise ValueError("String of len %d can't be PCKS7-padded" % len(s)) + numpads = ord(s[-1]) + if numpads > 16: + raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1]) + if s[-numpads:] != numpads * chr(numpads): + raise ValueError("Invalid PKCS7 padding") + return s[:-numpads] + + +# backport padding fix to AES module +aes.strip_PKCS7_padding = strip_PKCS7_padding + + +def aes_encrypt_with_iv(key, iv, data): + mode = aes.AESModeOfOperation.modeOfOperation["CBC"] + key = map(ord, key) + iv = map(ord, iv) + data = aes.append_PKCS7_padding(data) + keysize = len(key) + assert keysize in aes.AES.keySize.values(), 'invalid key size: %s' % keysize + moo = aes.AESModeOfOperation() + (mode, length, ciph) = moo.encrypt(data, mode, key, keysize, iv) + return ''.join(map(chr, ciph)) + + +def aes_decrypt_with_iv(key, iv, data): + mode = aes.AESModeOfOperation.modeOfOperation["CBC"] + key = map(ord, key) + iv = map(ord, iv) + keysize = len(key) + assert keysize in aes.AES.keySize.values(), 'invalid key size: %s' % keysize + data = map(ord, data) + moo = aes.AESModeOfOperation() + decr = moo.decrypt(data, None, mode, key, keysize, iv) + decr = strip_PKCS7_padding(decr) + return decr + + +def pw_encode(s, password): + if password: + secret = Hash(password) + return EncodeAES(secret, s.encode("utf8")) + else: + return s + + +def pw_decode(s, password): + if password is not None: + secret = Hash(password) + try: + d = DecodeAES(secret, s).decode("utf8") + except Exception: + raise InvalidPassword() + return d + else: + return s + + +def op_push(i): + if i < 0x4c: + return int_to_hex(i) + elif i < 0xff: + return '4c' + int_to_hex(i) + elif i < 0xffff: + return '4d' + int_to_hex(i, 2) + else: + return '4e' + int_to_hex(i, 4) + + +# pywallet openssl private key implementation + +def i2o_ECPublicKey(pubkey, compressed=False): + # public keys are 65 bytes long (520 bits) + # 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate + # 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed + # compressed keys: where is 0x02 if y is even and 0x03 if y is odd + if compressed: + if pubkey.point.y() & 1: + key = '03' + '%064x' % pubkey.point.x() + else: + key = '02' + '%064x' % pubkey.point.x() + else: + key = '04' + \ + '%064x' % pubkey.point.x() + \ + '%064x' % pubkey.point.y() + + return key.decode('hex') + + +# end pywallet openssl private key implementation +# functions from pywallet + + +def PrivKeyToSecret(privkey): + return privkey[9:9 + 32] + + +def SecretToASecret(secret, compressed=False, addrtype=0): + vchIn = chr((addrtype + 128) & 255) + secret + if compressed: + vchIn += '\01' + return b58encode_with_checksum(vchIn) + + +def ASecretToSecret(key, addrtype=0): + vch = b58decode_strip_checksum(key) + if vch and vch[0] == chr((addrtype + 128) & 255): + return vch[1:] + elif is_minikey(key): + return minikey_to_private_key(key) + else: + return False + + +def regenerate_key(sec): + b = ASecretToSecret(sec) + if not b: + return False + b = b[0:32] + return EC_KEY(b) + + +def GetPubKey(pubkey, compressed=False): + return i2o_ECPublicKey(pubkey, compressed) + + +def GetSecret(pkey): + return ('%064x' % pkey.secret).decode('hex') + + +def is_compressed(sec): + b = ASecretToSecret(sec) + return len(b) == 33 + + +def public_key_from_private_key(sec): + # rebuild public key from private key, compressed or uncompressed + pkey = regenerate_key(sec) + assert pkey + compressed = is_compressed(sec) + public_key = GetPubKey(pkey.pubkey, compressed) + return public_key.encode('hex') + + +def address_from_private_key(sec): + public_key = public_key_from_private_key(sec) + address = public_key_to_address(public_key.decode('hex')) + return address + + +def is_private_key(key): + try: + k = ASecretToSecret(key) + return k is not False + except: + return False + +# end pywallet functions + + +def is_minikey(text): + # Minikeys are typically 22 or 30 characters, but this routine + # permits any length of 20 or more provided the minikey is valid. + # A valid minikey must begin with an 'S', be in base58, and when + # suffixed with '?' have its SHA256 hash begin with a zero byte. + # They are widely used in Casascius physical bitoins. + return (len(text) >= 20 and text[0] == 'S' + and all(c in B58_CHARS for c in text) + and ord(sha256(text + '?')[0]) == 0) + + +def minikey_to_private_key(text): + return sha256(text) + + +def msg_magic(message): + varint = var_int(len(message)) + encoded_varint = "".join([chr(int(varint[i:i + 2], 16)) for i in xrange(0, len(varint), 2)]) + return "\x18Bitcoin Signed Message:\n" + encoded_varint + message + + +def verify_message(address, signature, message): + try: + EC_KEY.verify_message(address, signature, message) + return True + except Exception as e: + return False + + +def encrypt_message(message, pubkey): + return EC_KEY.encrypt_message(message, pubkey.decode('hex')) + + +def chunks(l, n): + return [l[i:i + n] for i in xrange(0, len(l), n)] + + +def ECC_YfromX(x, curved=curve_secp256k1, odd=True): + _p = curved.p() + _a = curved.a() + _b = curved.b() + for offset in range(128): + Mx = x + offset + My2 = pow(Mx, 3, _p) + _a * pow(Mx, 2, _p) + _b % _p + My = pow(My2, (_p + 1) / 4, _p) + + if curved.contains_point(Mx, My): + if odd == bool(My & 1): + return [My, offset] + return [_p - My, offset] + raise Exception('ECC_YfromX: No Y found') + + +def negative_point(P): + return Point(P.curve(), P.x(), -P.y(), P.order()) + + +def point_to_ser(P, comp=True): + if comp: + return (('%02x' % (2 + (P.y() & 1))) + ('%064x' % P.x())).decode('hex') + return ('04' + ('%064x' % P.x()) + ('%064x' % P.y())).decode('hex') + + +def ser_to_point(Aser): + curve = curve_secp256k1 + generator = generator_secp256k1 + _r = generator.order() + assert Aser[0] in ['\x02', '\x03', '\x04'] + if Aser[0] == '\x04': + return Point(curve, string_to_number(Aser[1:33]), string_to_number(Aser[33:]), _r) + Mx = string_to_number(Aser[1:]) + return Point(curve, Mx, ECC_YfromX(Mx, curve, Aser[0] == '\x03')[0], _r) + + +class MyVerifyingKey(ecdsa.VerifyingKey): + @classmethod + def from_signature(cls, sig, recid, h, curve): + """ See http://www.secg.org/download/aid-780/sec1-v2.pdf, chapter 4.1.6 """ + curveFp = curve.curve + G = curve.generator + order = G.order() + # extract r,s from signature + r, s = util.sigdecode_string(sig, order) + # 1.1 + x = r + (recid / 2) * order + # 1.3 + alpha = (x * x * x + curveFp.a() * x + curveFp.b()) % curveFp.p() + beta = msqr.modular_sqrt(alpha, curveFp.p()) + y = beta if (beta - recid) % 2 == 0 else curveFp.p() - beta + # 1.4 the constructor checks that nR is at infinity + R = Point(curveFp, x, y, order) + # 1.5 compute e from message: + e = string_to_number(h) + minus_e = -e % order + # 1.6 compute Q = r^-1 (sR - eG) + inv_r = numbertheory.inverse_mod(r, order) + Q = inv_r * (s * R + minus_e * G) + return cls.from_public_point(Q, curve) + + +class MySigningKey(ecdsa.SigningKey): + """Enforce low S values in signatures""" + + def sign_number(self, number, entropy=None, k=None): + curve = SECP256k1 + G = curve.generator + order = G.order() + r, s = ecdsa.SigningKey.sign_number(self, number, entropy, k) + if s > order / 2: + s = order - s + return r, s + + +class EC_KEY(object): + def __init__(self, k): + secret = string_to_number(k) + self.pubkey = ecdsa.ecdsa.Public_key(generator_secp256k1, generator_secp256k1 * secret) + self.privkey = ecdsa.ecdsa.Private_key(self.pubkey, secret) + self.secret = secret + + def get_public_key(self, compressed=True): + return point_to_ser(self.pubkey.point, compressed).encode('hex') + + def sign(self, msg_hash): + private_key = MySigningKey.from_secret_exponent(self.secret, curve=SECP256k1) + public_key = private_key.get_verifying_key() + signature = private_key.sign_digest_deterministic(msg_hash, hashfunc=hashlib.sha256, + sigencode=ecdsa.util.sigencode_string) + assert public_key.verify_digest(signature, msg_hash, sigdecode=ecdsa.util.sigdecode_string) + return signature + + def sign_message(self, message, compressed, address): + signature = self.sign(Hash(msg_magic(message))) + for i in range(4): + sig = chr(27 + i + (4 if compressed else 0)) + signature + try: + self.verify_message(address, sig, message) + return sig + except Exception: + log.exception("error: cannot sign message") + continue + raise Exception("error: cannot sign message") + + @classmethod + def verify_message(cls, address, sig, message): + if len(sig) != 65: + raise Exception("Wrong encoding") + nV = ord(sig[0]) + if nV < 27 or nV >= 35: + raise Exception("Bad encoding") + if nV >= 31: + compressed = True + nV -= 4 + else: + compressed = False + recid = nV - 27 + + h = Hash(msg_magic(message)) + public_key = MyVerifyingKey.from_signature(sig[1:], recid, h, curve=SECP256k1) + # check public key + public_key.verify_digest(sig[1:], h, sigdecode=ecdsa.util.sigdecode_string) + pubkey = point_to_ser(public_key.pubkey.point, compressed) + # check that we get the original signing address + addr = public_key_to_address(pubkey) + if address != addr: + raise Exception("Bad signature") + + # ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; + # hmac-sha256 is used as the mac + + @classmethod + def encrypt_message(cls, message, pubkey): + + pk = ser_to_point(pubkey) + if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, pk.x(), pk.y()): + raise Exception('invalid pubkey') + + ephemeral_exponent = number_to_string(ecdsa.util.randrange(pow(2, 256)), + generator_secp256k1.order()) + ephemeral = EC_KEY(ephemeral_exponent) + ecdh_key = point_to_ser(pk * ephemeral.privkey.secret_multiplier) + key = hashlib.sha512(ecdh_key).digest() + iv, key_e, key_m = key[0:16], key[16:32], key[32:] + ciphertext = aes_encrypt_with_iv(key_e, iv, message) + ephemeral_pubkey = ephemeral.get_public_key(compressed=True).decode('hex') + encrypted = 'BIE1' + ephemeral_pubkey + ciphertext + mac = hmac.new(key_m, encrypted, hashlib.sha256).digest() + + return base64.b64encode(encrypted + mac) + + def decrypt_message(self, encrypted): + + encrypted = base64.b64decode(encrypted) + + if len(encrypted) < 85: + raise Exception('invalid ciphertext: length') + + magic = encrypted[:4] + ephemeral_pubkey = encrypted[4:37] + ciphertext = encrypted[37:-32] + mac = encrypted[-32:] + + if magic != 'BIE1': + raise Exception('invalid ciphertext: invalid magic bytes') + + try: + ephemeral_pubkey = ser_to_point(ephemeral_pubkey) + except AssertionError, e: + raise Exception('invalid ciphertext: invalid ephemeral pubkey') + + if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ephemeral_pubkey.x(), + ephemeral_pubkey.y()): + raise Exception('invalid ciphertext: invalid ephemeral pubkey') + + ecdh_key = point_to_ser(ephemeral_pubkey * self.privkey.secret_multiplier) + key = hashlib.sha512(ecdh_key).digest() + iv, key_e, key_m = key[0:16], key[16:32], key[32:] + if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest(): + raise Exception('invalid ciphertext: invalid mac') + + return aes_decrypt_with_iv(key_e, iv, ciphertext) + + +# BIP32 + +def random_seed(n): + return "%032x" % ecdsa.util.randrange(pow(2, n)) + + +BIP32_PRIME = 0x80000000 + + +def get_pubkeys_from_secret(secret): + # public key + private_key = ecdsa.SigningKey.from_string(secret, curve=SECP256k1) + public_key = private_key.get_verifying_key() + K = public_key.to_string() + K_compressed = GetPubKey(public_key.pubkey, True) + return K, K_compressed + + +# Child private key derivation function (from master private key) +# k = master private key (32 bytes) +# c = master chain code (extra entropy for key derivation) (32 bytes) +# n = the index of the key we want to derive. (only 32 bits will be used) +# If n is negative (i.e. the 32nd bit is set), the resulting private key's +# corresponding public key can NOT be determined without the master private key. +# However, if n is positive, the resulting private key's corresponding +# public key can be determined without the master private key. +def CKD_priv(k, c, n): + is_prime = n & BIP32_PRIME + return _CKD_priv(k, c, rev_hex(int_to_hex(n, 4)).decode('hex'), is_prime) + + +def _CKD_priv(k, c, s, is_prime): + order = generator_secp256k1.order() + keypair = EC_KEY(k) + cK = GetPubKey(keypair.pubkey, True) + data = chr(0) + k + s if is_prime else cK + s + I = hmac.new(c, data, hashlib.sha512).digest() + k_n = number_to_string((string_to_number(I[0:32]) + string_to_number(k)) % order, order) + c_n = I[32:] + return k_n, c_n + + +# Child public key derivation function (from public key only) +# K = master public key +# c = master chain code +# n = index of key we want to derive +# This function allows us to find the nth public key, as long as n is +# non-negative. If n is negative, we need the master private key to find it. +def CKD_pub(cK, c, n): + if n & BIP32_PRIME: + raise Exception("CKD pub error") + return _CKD_pub(cK, c, rev_hex(int_to_hex(n, 4)).decode('hex')) + + +# helper function, callable with arbitrary string +def _CKD_pub(cK, c, s): + order = generator_secp256k1.order() + I = hmac.new(c, cK + s, hashlib.sha512).digest() + curve = SECP256k1 + pubkey_point = string_to_number(I[0:32]) * curve.generator + ser_to_point(cK) + public_key = ecdsa.VerifyingKey.from_public_point(pubkey_point, curve=SECP256k1) + c_n = I[32:] + cK_n = GetPubKey(public_key.pubkey, True) + return cK_n, c_n + + +BITCOIN_HEADER_PRIV = "0488ade4" +BITCOIN_HEADER_PUB = "0488b21e" + +TESTNET_HEADER_PRIV = "04358394" +TESTNET_HEADER_PUB = "043587cf" + +BITCOIN_HEADERS = (BITCOIN_HEADER_PUB, BITCOIN_HEADER_PRIV) +TESTNET_HEADERS = (TESTNET_HEADER_PUB, TESTNET_HEADER_PRIV) + + +def _get_headers(testnet): + """Returns the correct headers for either testnet or bitcoin, in the form + of a 2-tuple, like (public, private).""" + if testnet: + return TESTNET_HEADERS + else: + return BITCOIN_HEADERS + + +def deserialize_xkey(xkey): + xkey = b58decode_strip_checksum(xkey) + assert len(xkey) == 78 + + xkey_header = xkey[0:4].encode('hex') + # Determine if the key is a bitcoin key or a testnet key. + if xkey_header in TESTNET_HEADERS: + head = TESTNET_HEADER_PRIV + elif xkey_header in BITCOIN_HEADERS: + head = BITCOIN_HEADER_PRIV + else: + raise Exception("Unknown xkey header: '%s'" % xkey_header) + + depth = ord(xkey[4]) + fingerprint = xkey[5:9] + child_number = xkey[9:13] + c = xkey[13:13 + 32] + if xkey[0:4].encode('hex') == head: + K_or_k = xkey[13 + 33:] + else: + K_or_k = xkey[13 + 32:] + return depth, fingerprint, child_number, c, K_or_k + + +def get_xkey_name(xkey, testnet=False): + depth, fingerprint, child_number, c, K = deserialize_xkey(xkey) + n = int(child_number.encode('hex'), 16) + if n & BIP32_PRIME: + child_id = "%d'" % (n - BIP32_PRIME) + else: + child_id = "%d" % n + if depth == 0: + return '' + elif depth == 1: + return child_id + else: + raise BaseException("xpub depth error") + + +def xpub_from_xprv(xprv, testnet=False): + depth, fingerprint, child_number, c, k = deserialize_xkey(xprv) + K, cK = get_pubkeys_from_secret(k) + header_pub, _ = _get_headers(testnet) + xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK + return b58encode_with_checksum(xpub) + + +def bip32_root(seed, testnet=False): + header_pub, header_priv = _get_headers(testnet) + I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest() + master_k = I[0:32] + master_c = I[32:] + K, cK = get_pubkeys_from_secret(master_k) + xprv = (header_priv + "00" + "00000000" + "00000000").decode("hex") + master_c + chr( + 0) + master_k + xpub = (header_pub + "00" + "00000000" + "00000000").decode("hex") + master_c + cK + return b58encode_with_checksum(xprv), b58encode_with_checksum(xpub) + + +def xpub_from_pubkey(cK, testnet=False): + header_pub, header_priv = _get_headers(testnet) + assert cK[0] in ['\x02', '\x03'] + master_c = chr(0) * 32 + xpub = (header_pub + "00" + "00000000" + "00000000").decode("hex") + master_c + cK + return b58encode_with_checksum(xpub) + + +def bip32_private_derivation(xprv, branch, sequence, testnet=False): + assert sequence.startswith(branch) + if branch == sequence: + return xprv, xpub_from_xprv(xprv, testnet) + header_pub, header_priv = _get_headers(testnet) + depth, fingerprint, child_number, c, k = deserialize_xkey(xprv) + sequence = sequence[len(branch):] + for n in sequence.split('/'): + if n == '': + continue + i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n) + parent_k = k + k, c = CKD_priv(k, c, i) + depth += 1 + + _, parent_cK = get_pubkeys_from_secret(parent_k) + fingerprint = hash_160(parent_cK)[0:4] + child_number = ("%08X" % i).decode('hex') + K, cK = get_pubkeys_from_secret(k) + xprv = header_priv.decode('hex') + chr(depth) + fingerprint + child_number + c + chr(0) + k + xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK + return b58encode_with_checksum(xprv), b58encode_with_checksum(xpub) + + +def bip32_public_derivation(xpub, branch, sequence, testnet=False): + header_pub, _ = _get_headers(testnet) + depth, fingerprint, child_number, c, cK = deserialize_xkey(xpub) + assert sequence.startswith(branch) + sequence = sequence[len(branch):] + for n in sequence.split('/'): + if n == '': + continue + i = int(n) + parent_cK = cK + cK, c = CKD_pub(cK, c, i) + depth += 1 + + fingerprint = hash_160(parent_cK)[0:4] + child_number = ("%08X" % i).decode('hex') + xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK + return b58encode_with_checksum(xpub) + + +def bip32_private_key(sequence, k, chain): + for i in sequence: + k, chain = CKD_priv(k, chain, i) + return SecretToASecret(k, True) diff --git a/lbrynet/wallet/manager.py b/lbrynet/wallet/manager.py new file mode 100644 index 000000000..66682b3b7 --- /dev/null +++ b/lbrynet/wallet/manager.py @@ -0,0 +1,88 @@ +import os +import logging + +from twisted.internet import defer + +import lbryschema + +from .protocol import Network +from .blockchain import BlockchainHeaders +from .wallet import Wallet + +log = logging.getLogger(__name__) + + +def chunks(l, n): + for i in range(0, len(l), n): + yield l[i:i+n] + + +class WalletManager: + + def __init__(self, storage, config): + self.storage = storage + self.config = config + lbryschema.BLOCKCHAIN_NAME = config['chain'] + self.headers = BlockchainHeaders(self.headers_path, config['chain']) + self.wallet = Wallet(self.wallet_path) + self.network = Network(config) + self.network.on_header.listen(self.process_header) + self.network.on_transaction.listen(self.process_transaction) + self._downloading_headers = False + + @property + def headers_path(self): + filename = 'blockchain_headers' + if self.config['chain'] != 'lbrycrd_main': + filename = '{}_headers'.format(self.config['chain'].split("_")[1]) + return os.path.join(self.config['wallet_path'], filename) + + @property + def wallet_path(self): + return os.path.join(self.config['wallet_path'], 'wallets', 'default_wallet') + + @defer.inlineCallbacks + def start(self): + self.wallet.load() + self.network.start() + yield self.network.on_connected.first + yield self.download_headers() + yield self.network.headers_subscribe() + yield self.download_transactions() + + def stop(self): + return self.network.stop() + + @defer.inlineCallbacks + def download_headers(self): + self._downloading_headers = True + while True: + sought_height = len(self.headers) + headers = yield self.network.block_headers(sought_height) + log.info("received {} headers starting at {} height".format(headers['count'], sought_height)) + if headers['count'] <= 0: + break + yield self.headers.connect(sought_height, headers['hex'].decode('hex')) + self._downloading_headers = False + + @defer.inlineCallbacks + def process_header(self, header): + if self._downloading_headers: + return + if header['block_height'] == len(self.headers): + # New header from network directly connects after the last local header. + yield self.headers.connect(len(self.headers), header['hex'].decode('hex')) + elif header['block_height'] > len(self.headers): + # New header is several heights ahead of local, do download instead. + yield self.download_headers() + + @defer.inlineCallbacks + def download_transactions(self): + for addresses in chunks(self.wallet.addresses, 500): + self.network.rpc([ + ('blockchain.address.subscribe', [address]) + for address in addresses + ]) + + def process_transaction(self, tx): + pass diff --git a/lbrynet/wallet/mnemonic.py b/lbrynet/wallet/mnemonic.py new file mode 100644 index 000000000..711b8ce23 --- /dev/null +++ b/lbrynet/wallet/mnemonic.py @@ -0,0 +1,157 @@ +import hashlib +import hmac +import math +import os +import pkgutil +import string +import unicodedata +import logging +import ecdsa +import pbkdf2 + +from . import constants +from .hashing import hmac_sha_512 + +log = logging.getLogger(__name__) + +# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html +CJK_INTERVALS = [ + (0x4E00, 0x9FFF, 'CJK Unified Ideographs'), + (0x3400, 0x4DBF, 'CJK Unified Ideographs Extension A'), + (0x20000, 0x2A6DF, 'CJK Unified Ideographs Extension B'), + (0x2A700, 0x2B73F, 'CJK Unified Ideographs Extension C'), + (0x2B740, 0x2B81F, 'CJK Unified Ideographs Extension D'), + (0xF900, 0xFAFF, 'CJK Compatibility Ideographs'), + (0x2F800, 0x2FA1D, 'CJK Compatibility Ideographs Supplement'), + (0x3190, 0x319F, 'Kanbun'), + (0x2E80, 0x2EFF, 'CJK Radicals Supplement'), + (0x2F00, 0x2FDF, 'CJK Radicals'), + (0x31C0, 0x31EF, 'CJK Strokes'), + (0x2FF0, 0x2FFF, 'Ideographic Description Characters'), + (0xE0100, 0xE01EF, 'Variation Selectors Supplement'), + (0x3100, 0x312F, 'Bopomofo'), + (0x31A0, 0x31BF, 'Bopomofo Extended'), + (0xFF00, 0xFFEF, 'Halfwidth and Fullwidth Forms'), + (0x3040, 0x309F, 'Hiragana'), + (0x30A0, 0x30FF, 'Katakana'), + (0x31F0, 0x31FF, 'Katakana Phonetic Extensions'), + (0x1B000, 0x1B0FF, 'Kana Supplement'), + (0xAC00, 0xD7AF, 'Hangul Syllables'), + (0x1100, 0x11FF, 'Hangul Jamo'), + (0xA960, 0xA97F, 'Hangul Jamo Extended A'), + (0xD7B0, 0xD7FF, 'Hangul Jamo Extended B'), + (0x3130, 0x318F, 'Hangul Compatibility Jamo'), + (0xA4D0, 0xA4FF, 'Lisu'), + (0x16F00, 0x16F9F, 'Miao'), + (0xA000, 0xA48F, 'Yi Syllables'), + (0xA490, 0xA4CF, 'Yi Radicals'), +] + + +def is_CJK(c): + n = ord(c) + for imin, imax, name in CJK_INTERVALS: + if imin <= n <= imax: + return True + return False + + +def prepare_seed(seed): + # normalize + seed = unicodedata.normalize('NFKD', unicode(seed)) + # lower + seed = seed.lower() + # remove accents + seed = u''.join([c for c in seed if not unicodedata.combining(c)]) + # normalize whitespaces + seed = u' '.join(seed.split()) + # remove whitespaces between CJK + seed = u''.join([seed[i] for i in range(len(seed)) if not (seed[i] in string.whitespace + and is_CJK(seed[i - 1]) + and is_CJK(seed[i + 1]))]) + return seed + + +filenames = { + 'en': 'english.txt', + 'es': 'spanish.txt', + 'ja': 'japanese.txt', + 'pt': 'portuguese.txt', + 'zh': 'chinese_simplified.txt' +} + + +class Mnemonic: + # Seed derivation no longer follows BIP39 + # Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum + + def __init__(self, lang=None): + lang = lang or "en" + filename = filenames.get(lang[0:2], 'english.txt') + s = pkgutil.get_data('lbrynet', os.path.join('wallet', 'wordlist', filename)) + s = unicodedata.normalize('NFKD', s.decode('utf8')) + lines = s.split('\n') + self.wordlist = [] + for line in lines: + line = line.split('#')[0] + line = line.strip(' \r') + assert ' ' not in line + if line: + self.wordlist.append(line) + log.info("wordlist has %d words", len(self.wordlist)) + + @classmethod + def mnemonic_to_seed(cls, mnemonic, passphrase): + PBKDF2_ROUNDS = 2048 + mnemonic = prepare_seed(mnemonic) + return pbkdf2.PBKDF2(mnemonic, 'lbryum' + passphrase, iterations=PBKDF2_ROUNDS, + macmodule=hmac, digestmodule=hashlib.sha512).read(64) + + def mnemonic_encode(self, i): + n = len(self.wordlist) + words = [] + while i: + x = i % n + i = i / n + words.append(self.wordlist[x]) + return ' '.join(words) + + def mnemonic_decode(self, seed): + n = len(self.wordlist) + words = seed.split() + i = 0 + while words: + w = words.pop() + k = self.wordlist.index(w) + i = i * n + k + return i + + def check_seed(self, seed, custom_entropy): + assert is_new_seed(seed) + i = self.mnemonic_decode(seed) + return i % custom_entropy == 0 + + def make_seed(self, num_bits=128, prefix=constants.SEED_PREFIX, custom_entropy=1): + n = int(math.ceil(math.log(custom_entropy, 2))) + # bits of entropy used by the prefix + k = len(prefix) * 4 + # we add at least 16 bits + n_added = max(16, k + num_bits - n) + log.info("make_seed %s adding %d bits", prefix, n_added) + my_entropy = ecdsa.util.randrange(pow(2, n_added)) + nonce = 0 + while True: + nonce += 1 + i = custom_entropy * (my_entropy + nonce) + seed = self.mnemonic_encode(i) + assert i == self.mnemonic_decode(seed) + if is_new_seed(seed, prefix): + break + log.info('%d words', len(seed.split())) + return seed + + +def is_new_seed(x, prefix=constants.SEED_PREFIX): + x = prepare_seed(x) + s = hmac_sha_512("Seed version", x.encode('utf8')).encode('hex') + return s.startswith(prefix) diff --git a/lbrynet/wallet/msqr.py b/lbrynet/wallet/msqr.py new file mode 100644 index 000000000..beb5bed7f --- /dev/null +++ b/lbrynet/wallet/msqr.py @@ -0,0 +1,96 @@ +# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/ + + +def modular_sqrt(a, p): + """ Find a quadratic residue (mod p) of 'a'. p + must be an odd prime. + + Solve the congruence of the form: + x^2 = a (mod p) + And returns x. Note that p - x is also a root. + + 0 is returned is no square root exists for + these a and p. + + The Tonelli-Shanks algorithm is used (except + for some simple cases in which the solution + is known from an identity). This algorithm + runs in polynomial time (unless the + generalized Riemann hypothesis is false). + """ + # Simple cases + # + if legendre_symbol(a, p) != 1: + return 0 + elif a == 0: + return 0 + elif p == 2: + return p + elif p % 4 == 3: + return pow(a, (p + 1) / 4, p) + + # Partition p-1 to s * 2^e for an odd s (i.e. + # reduce all the powers of 2 from p-1) + # + s = p - 1 + e = 0 + while s % 2 == 0: + s /= 2 + e += 1 + + # Find some 'n' with a legendre symbol n|p = -1. + # Shouldn't take long. + # + n = 2 + while legendre_symbol(n, p) != -1: + n += 1 + + # Here be dragons! + # Read the paper "Square roots from 1; 24, 51, + # 10 to Dan Shanks" by Ezra Brown for more + # information + # + + # x is a guess of the square root that gets better + # with each iteration. + # b is the "fudge factor" - by how much we're off + # with the guess. The invariant x^2 = ab (mod p) + # is maintained throughout the loop. + # g is used for successive powers of n to update + # both a and b + # r is the exponent - decreases with each update + # + x = pow(a, (s + 1) / 2, p) + b = pow(a, s, p) + g = pow(n, s, p) + r = e + + while True: + t = b + m = 0 + for m in xrange(r): + if t == 1: + break + t = pow(t, 2, p) + + if m == 0: + return x + + gs = pow(g, 2 ** (r - m - 1), p) + g = (gs * gs) % p + x = (x * gs) % p + b = (b * g) % p + r = m + + +def legendre_symbol(a, p): + """ Compute the Legendre symbol a|p using + Euler's criterion. p is a prime, a is + relatively prime to p (if p divides + a, then a|p = 0) + + Returns 1 if a has a square root modulo + p, -1 otherwise. + """ + ls = pow(a, (p - 1) / 2, p) + return -1 if ls == p - 1 else ls diff --git a/lbrynet/wallet/opcodes.py b/lbrynet/wallet/opcodes.py new file mode 100644 index 000000000..7527bc643 --- /dev/null +++ b/lbrynet/wallet/opcodes.py @@ -0,0 +1,76 @@ +import struct +from .enumeration import Enumeration + +opcodes = Enumeration("Opcodes", [ + ("OP_0", 0), ("OP_PUSHDATA1", 76), "OP_PUSHDATA2", "OP_PUSHDATA4", "OP_1NEGATE", "OP_RESERVED", + "OP_1", "OP_2", "OP_3", "OP_4", "OP_5", "OP_6", "OP_7", + "OP_8", "OP_9", "OP_10", "OP_11", "OP_12", "OP_13", "OP_14", "OP_15", "OP_16", + "OP_NOP", "OP_VER", "OP_IF", "OP_NOTIF", "OP_VERIF", "OP_VERNOTIF", "OP_ELSE", "OP_ENDIF", + "OP_VERIFY", + "OP_RETURN", "OP_TOALTSTACK", "OP_FROMALTSTACK", "OP_2DROP", "OP_2DUP", "OP_3DUP", "OP_2OVER", + "OP_2ROT", "OP_2SWAP", + "OP_IFDUP", "OP_DEPTH", "OP_DROP", "OP_DUP", "OP_NIP", "OP_OVER", "OP_PICK", "OP_ROLL", + "OP_ROT", + "OP_SWAP", "OP_TUCK", "OP_CAT", "OP_SUBSTR", "OP_LEFT", "OP_RIGHT", "OP_SIZE", "OP_INVERT", + "OP_AND", + "OP_OR", "OP_XOR", "OP_EQUAL", "OP_EQUALVERIFY", "OP_RESERVED1", "OP_RESERVED2", "OP_1ADD", + "OP_1SUB", "OP_2MUL", + "OP_2DIV", "OP_NEGATE", "OP_ABS", "OP_NOT", "OP_0NOTEQUAL", "OP_ADD", "OP_SUB", "OP_MUL", + "OP_DIV", + "OP_MOD", "OP_LSHIFT", "OP_RSHIFT", "OP_BOOLAND", "OP_BOOLOR", + "OP_NUMEQUAL", "OP_NUMEQUALVERIFY", "OP_NUMNOTEQUAL", "OP_LESSTHAN", + "OP_GREATERTHAN", "OP_LESSTHANOREQUAL", "OP_GREATERTHANOREQUAL", "OP_MIN", "OP_MAX", + "OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160", + "OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG", + "OP_CHECKMULTISIGVERIFY", "OP_NOP1", "OP_NOP2", "OP_NOP3", "OP_NOP4", "OP_NOP5", + "OP_CLAIM_NAME", + "OP_SUPPORT_CLAIM", "OP_UPDATE_CLAIM", + ("OP_SINGLEBYTE_END", 0xF0), + ("OP_DOUBLEBYTE_BEGIN", 0xF000), + "OP_PUBKEY", "OP_PUBKEYHASH", + ("OP_INVALIDOPCODE", 0xFFFF), +]) + + +def script_GetOp(bytes): + i = 0 + while i < len(bytes): + vch = None + opcode = ord(bytes[i]) + i += 1 + if opcode >= opcodes.OP_SINGLEBYTE_END: + opcode <<= 8 + opcode |= ord(bytes[i]) + i += 1 + + if opcode <= opcodes.OP_PUSHDATA4: + nSize = opcode + if opcode == opcodes.OP_PUSHDATA1: + nSize = ord(bytes[i]) + i += 1 + elif opcode == opcodes.OP_PUSHDATA2: + (nSize,) = struct.unpack_from('= d[0] > 0: + # Opcodes below OP_PUSHDATA4 all just push data onto stack, # and are equivalent. + continue + if to_match[i] != decoded[i][0]: + return False + return True diff --git a/lbrynet/wallet/protocol.py b/lbrynet/wallet/protocol.py new file mode 100644 index 000000000..a59c76ad0 --- /dev/null +++ b/lbrynet/wallet/protocol.py @@ -0,0 +1,282 @@ +import sys +import time +import json +import socket +import logging +from itertools import cycle +from twisted.internet import defer, reactor, protocol, threads +from twisted.application.internet import ClientService, CancelledError +from twisted.internet.endpoints import clientFromString +from twisted.protocols.basic import LineOnlyReceiver +from errors import RemoteServiceException, ProtocolException +from errors import TransportException + +from .stream import StreamController + +log = logging.getLogger() + + +class StratumClientProtocol(LineOnlyReceiver): + delimiter = '\n' + + def __init__(self): + self.request_id = 0 + self.lookup_table = {} + self.session = {} + + self.on_disconnected_controller = StreamController() + self.on_disconnected = self.on_disconnected_controller.stream + + def _get_id(self): + self.request_id += 1 + return self.request_id + + @property + def _ip(self): + return self.transport.getPeer().host + + def get_session(self): + return self.session + + def connectionMade(self): + try: + self.transport.setTcpNoDelay(True) + self.transport.setTcpKeepAlive(True) + self.transport.socket.setsockopt( + socket.SOL_TCP, socket.TCP_KEEPIDLE, 120 + # Seconds before sending keepalive probes + ) + self.transport.socket.setsockopt( + socket.SOL_TCP, socket.TCP_KEEPINTVL, 1 + # Interval in seconds between keepalive probes + ) + self.transport.socket.setsockopt( + socket.SOL_TCP, socket.TCP_KEEPCNT, 5 + # Failed keepalive probles before declaring other end dead + ) + except Exception as err: + # Supported only by the socket transport, + # but there's really no better place in code to trigger this. + log.warning("Error setting up socket: %s", err) + + def connectionLost(self, reason=None): + self.on_disconnected_controller.add(True) + + def lineReceived(self, line): + try: + message = json.loads(line) + except (ValueError, TypeError): + raise ProtocolException("Cannot decode message '%s'" % line.strip()) + msg_id = message.get('id', 0) + msg_result = message.get('result') + msg_error = message.get('error') + msg_method = message.get('method') + msg_params = message.get('params') + if msg_id: + # It's a RPC response + # Perform lookup to the table of waiting requests. + try: + meta = self.lookup_table[msg_id] + del self.lookup_table[msg_id] + except KeyError: + # When deferred object for given message ID isn't found, it's an error + raise ProtocolException( + "Lookup for deferred object for message ID '%s' failed." % msg_id) + # If there's an error, handle it as errback + # If both result and error are null, handle it as a success with blank result + if msg_error != None: + meta['defer'].errback( + RemoteServiceException(msg_error[0], msg_error[1], msg_error[2]) + ) + else: + meta['defer'].callback(msg_result) + elif msg_method: + if msg_method == 'blockchain.headers.subscribe': + self.network._on_header_controller.add(msg_params[0]) + elif msg_method == 'blockchain.address.subscribe': + self.network._on_address_controller.add(msg_params) + else: + log.warning("Cannot handle message '%s'" % line) + + def write_request(self, method, params, is_notification=False): + request_id = None if is_notification else self._get_id() + serialized = json.dumps({'id': request_id, 'method': method, 'params': params}) + self.sendLine(serialized) + return request_id + + def rpc(self, method, params, is_notification=False): + request_id = self.write_request(method, params, is_notification) + if is_notification: + return + d = defer.Deferred() + self.lookup_table[request_id] = { + 'method': method, + 'params': params, + 'defer': d, + } + return d + + +class StratumClientFactory(protocol.ClientFactory): + + protocol = StratumClientProtocol + + def __init__(self, network): + self.network = network + self.client = None + + def buildProtocol(self, addr): + client = self.protocol() + client.factory = self + client.network = self.network + self.client = client + return client + + +class Network: + + def __init__(self, config): + self.config = config + self.client = None + self.service = None + self.running = False + + self._on_connected_controller = StreamController() + self.on_connected = self._on_connected_controller.stream + + self._on_header_controller = StreamController() + self.on_header = self._on_header_controller.stream + + self._on_transaction_controller = StreamController() + self.on_transaction = self._on_transaction_controller.stream + + @defer.inlineCallbacks + def start(self): + for server in cycle(self.config.get('default_servers')): + endpoint = clientFromString(reactor, 'tcp:{}:{}'.format(*server)) + self.service = ClientService(endpoint, StratumClientFactory(self)) + self.service.startService() + try: + self.client = yield self.service.whenConnected(failAfterFailures=2) + self._on_connected_controller.add(True) + yield self.client.on_disconnected.first + except CancelledError: + return + except Exception as e: + pass + finally: + self.client = None + if not self.running: + return + + def stop(self): + self.running = False + if self.service is not None: + self.service.stopService() + if self.is_connected: + return self.client.on_disconnected.first + else: + return defer.succeed(True) + + @property + def is_connected(self): + return self.client is not None and self.client.connected + + def rpc(self, method, params, *args, **kwargs): + if self.is_connected: + return self.client.rpc(method, params, *args, **kwargs) + else: + raise TransportException("Attempting to send rpc request when connection is not available.") + + def claimtrie_getvaluesforuris(self, block_hash, *uris): + return self.rpc( + 'blockchain.claimtrie.getvaluesforuris', [block_hash] + list(uris) + ) + + def claimtrie_getvaluesforuri(self, block_hash, uri): + return self.rpc('blockchain.claimtrie.getvaluesforuri', [block_hash, uri]) + + def claimtrie_getclaimssignedbynthtoname(self, name, n): + return self.rpc('blockchain.claimtrie.getclaimssignedbynthtoname', [name, n]) + + def claimtrie_getclaimssignedbyid(self, certificate_id): + return self.rpc('blockchain.claimtrie.getclaimssignedbyid', [certificate_id]) + + def claimtrie_getclaimssignedby(self, name): + return self.rpc('blockchain.claimtrie.getclaimssignedby', [name]) + + def claimtrie_getnthclaimforname(self, name, n): + return self.rpc('blockchain.claimtrie.getnthclaimforname', [name, n]) + + def claimtrie_getclaimsbyids(self, *claim_ids): + return self.rpc('blockchain.claimtrie.getclaimsbyids', list(claim_ids)) + + def claimtrie_getclaimbyid(self, claim_id): + return self.rpc('blockchain.claimtrie.getclaimbyid', [claim_id]) + + def claimtrie_get(self): + return self.rpc('blockchain.claimtrie.get', []) + + def block_get_block(self, block_hash): + return self.rpc('blockchain.block.get_block', [block_hash]) + + def claimtrie_getclaimsforname(self, name): + return self.rpc('blockchain.claimtrie.getclaimsforname', [name]) + + def claimtrie_getclaimsintx(self, txid): + return self.rpc('blockchain.claimtrie.getclaimsintx', [txid]) + + def claimtrie_getvalue(self, name, block_hash=None): + return self.rpc('blockchain.claimtrie.getvalue', [name, block_hash]) + + def relayfee(self): + return self.rpc('blockchain.relayfee', []) + + def estimatefee(self): + return self.rpc('blockchain.estimatefee', []) + + def transaction_get(self, txid): + return self.rpc('blockchain.transaction.get', [txid]) + + def transaction_get_merkle(self, tx_hash, height, cache_only=False): + return self.rpc('blockchain.transaction.get_merkle', [tx_hash, height, cache_only]) + + def transaction_broadcast(self, raw_transaction): + return self.rpc('blockchain.transaction.broadcast', [raw_transaction]) + + def block_get_chunk(self, index, cache_only=False): + return self.rpc('blockchain.block.get_chunk', [index, cache_only]) + + def block_get_header(self, height, cache_only=False): + return self.rpc('blockchain.block.get_header', [height, cache_only]) + + def block_headers(self, height, count=10000): + return self.rpc('blockchain.block.headers', [height, count]) + + def utxo_get_address(self, txid, pos): + return self.rpc('blockchain.utxo.get_address', [txid, pos]) + + def address_listunspent(self, address): + return self.rpc('blockchain.address.listunspent', [address]) + + def address_get_proof(self, address): + return self.rpc('blockchain.address.get_proof', [address]) + + def address_get_balance(self, address): + return self.rpc('blockchain.address.get_balance', [address]) + + def address_get_mempool(self, address): + return self.rpc('blockchain.address.get_mempool', [address]) + + def address_get_history(self, address): + return self.rpc('blockchain.address.get_history', [address]) + + def address_subscribe(self, addresses): + if isinstance(addresses, str): + return self.rpc('blockchain.address.subscribe', [addresses]) + else: + msgs = map(lambda addr: ('blockchain.address.subscribe', [addr]), addresses) + self.network.send(msgs, self.addr_subscription_response) + + def headers_subscribe(self): + return self.rpc('blockchain.headers.subscribe', [], True) diff --git a/lbrynet/wallet/store.py b/lbrynet/wallet/store.py new file mode 100644 index 000000000..268a25f43 --- /dev/null +++ b/lbrynet/wallet/store.py @@ -0,0 +1,31 @@ +import os +import json + + +class JSONStore(dict): + + def __init__(self, config, name): + self.config = config + self.path = os.path.join(self.config.path, name) + self.load() + + def load(self): + try: + with open(self.path, 'r') as f: + self.update(json.loads(f.read())) + except: + pass + + def save(self): + with open(self.path, 'w') as f: + s = json.dumps(self, indent=4, sort_keys=True) + r = f.write(s) + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + self.save() + + def pop(self, key): + if key in self.keys(): + dict.pop(self, key) + self.save() diff --git a/lbrynet/wallet/stream.py b/lbrynet/wallet/stream.py new file mode 100644 index 000000000..fcc86d2df --- /dev/null +++ b/lbrynet/wallet/stream.py @@ -0,0 +1,127 @@ +from twisted.internet.defer import Deferred +from twisted.python.failure import Failure + + +class BroadcastSubscription: + + def __init__(self, controller, on_data, on_error, on_done): + self._controller = controller + self._previous = self._next = None + self._on_data = on_data + self._on_error = on_error + self._on_done = on_done + self.is_paused = False + self.is_canceled = False + self.is_closed = False + + def pause(self): + self.is_paused = True + + def resume(self): + self.is_paused = False + + def cancel(self): + self._controller._cancel(self) + self.is_canceled = True + + @property + def can_fire(self): + return not any((self.is_paused, self.is_canceled, self.is_closed)) + + def _add(self, data): + if self.can_fire and self._on_data is not None: + self._on_data(data) + + def _add_error(self, error, traceback): + if self.can_fire and self._on_error is not None: + self._on_error(error, traceback) + + def _close(self): + if self.can_fire and self._on_done is not None: + self._on_done() + self.is_closed = True + + +class StreamController: + + def __init__(self): + self.stream = Stream(self) + self._first_subscription = None + self._last_subscription = None + + @property + def has_listener(self): + return self._first_subscription is not None + + @property + def _iterate_subscriptions(self): + next = self._first_subscription + while next is not None: + subscription = next + next = next._next + yield subscription + + def add(self, event): + for subscription in self._iterate_subscriptions: + subscription._add(event) + + def add_error(self, error, traceback): + for subscription in self._iterate_subscriptions: + subscription._add_error(error, traceback) + + def close(self): + for subscription in self._iterate_subscriptions: + subscription._close() + + def _cancel(self, subscription): + previous = subscription._previous + next = subscription._next + if previous is None: + self._first_subscription = next + else: + previous._next = next + if next is None: + self._last_subscription = previous + else: + next._previous = previous + subscription._next = subscription._previous = subscription + + def _listen(self, on_data, on_error, on_done): + subscription = BroadcastSubscription(self, on_data, on_error, on_done) + old_last = self._last_subscription + self._last_subscription = subscription + subscription._previous = old_last + subscription._next = None + if old_last is None: + self._first_subscription = subscription + else: + old_last._next = subscription + return subscription + + +class Stream: + + def __init__(self, controller): + self._controller = controller + + def listen(self, on_data, on_error=None, on_done=None): + return self._controller._listen(on_data, on_error, on_done) + + @property + def first(self): + deferred = Deferred() + subscription = self.listen( + lambda value: self._cancel_and_callback(subscription, deferred, value), + lambda error, traceback: self._cancel_and_error(subscription, deferred, error, traceback) + ) + return deferred + + @staticmethod + def _cancel_and_callback(subscription, deferred, value): + subscription.cancel() + deferred.callback(value) + + @staticmethod + def _cancel_and_error(subscription, deferred, error, traceback): + subscription.cancel() + deferred.errback(Failure(error, exc_tb=traceback)) diff --git a/lbrynet/wallet/transaction.py b/lbrynet/wallet/transaction.py new file mode 100644 index 000000000..3c15e7839 --- /dev/null +++ b/lbrynet/wallet/transaction.py @@ -0,0 +1,702 @@ +import sys +import hashlib +import logging +import ecdsa +from ecdsa.curves import SECP256k1 + +from lbryschema.address import hash_160_bytes_to_address, public_key_to_address +from lbryschema.address import address_to_hash_160 + +from .constants import TYPE_SCRIPT, TYPE_PUBKEY, TYPE_UPDATE, TYPE_SUPPORT, TYPE_CLAIM +from .constants import TYPE_ADDRESS, NO_SIGNATURE +from .opcodes import opcodes, match_decoded, script_GetOp +from .bcd_data_stream import BCDataStream +from .hashing import Hash, hash_160, hash_encode +from .lbrycrd import op_push +from .lbrycrd import point_to_ser, MyVerifyingKey, MySigningKey +from .lbrycrd import regenerate_key, public_key_from_private_key +from .lbrycrd import encode_claim_id_hex, claim_id_hash +from .util import profiler, var_int, int_to_hex, parse_sig, rev_hex + +log = logging.getLogger() + + +def parse_xpub(x_pubkey): + if x_pubkey[0:2] in ['02', '03', '04']: + pubkey = x_pubkey + elif x_pubkey[0:2] == 'ff': + from lbryum.bip32 import BIP32_Account + xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) + pubkey = BIP32_Account.derive_pubkey_from_xpub(xpub, s[0], s[1]) + elif x_pubkey[0:2] == 'fd': + addrtype = ord(x_pubkey[2:4].decode('hex')) + hash160 = x_pubkey[4:].decode('hex') + pubkey = None + address = hash_160_bytes_to_address(hash160, addrtype) + else: + raise BaseException("Cannnot parse pubkey") + if pubkey: + address = public_key_to_address(pubkey.decode('hex')) + return pubkey, address + + +def parse_scriptSig(d, bytes): + try: + decoded = [x for x in script_GetOp(bytes)] + except Exception: + # coinbase transactions raise an exception + log.error("cannot find address in input script: {}".format(bytes.encode('hex'))) + return + + # payto_pubkey + match = [opcodes.OP_PUSHDATA4] + if match_decoded(decoded, match): + sig = decoded[0][1].encode('hex') + d['address'] = "(pubkey)" + d['signatures'] = [sig] + d['num_sig'] = 1 + d['x_pubkeys'] = ["(pubkey)"] + d['pubkeys'] = ["(pubkey)"] + return + + # non-generated TxIn transactions push a signature + # (seventy-something bytes) and then their public key + # (65 bytes) onto the stack: + match = [opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4] + if match_decoded(decoded, match): + sig = decoded[0][1].encode('hex') + x_pubkey = decoded[1][1].encode('hex') + try: + signatures = parse_sig([sig]) + pubkey, address = parse_xpub(x_pubkey) + except: + import traceback + traceback.print_exc(file=sys.stdout) + log.error("cannot find address in input script: {}".format(bytes.encode('hex'))) + return + d['signatures'] = signatures + d['x_pubkeys'] = [x_pubkey] + d['num_sig'] = 1 + d['pubkeys'] = [pubkey] + d['address'] = address + return + + # p2sh transaction, m of n + match = [opcodes.OP_0] + [opcodes.OP_PUSHDATA4] * (len(decoded) - 1) + if not match_decoded(decoded, match): + log.error("cannot find address in input script: {}".format(bytes.encode('hex'))) + return + x_sig = [x[1].encode('hex') for x in decoded[1:-1]] + dec2 = [x for x in script_GetOp(decoded[-1][1])] + m = dec2[0][0] - opcodes.OP_1 + 1 + n = dec2[-2][0] - opcodes.OP_1 + 1 + op_m = opcodes.OP_1 + m - 1 + op_n = opcodes.OP_1 + n - 1 + match_multisig = [op_m] + [opcodes.OP_PUSHDATA4] * n + [op_n, opcodes.OP_CHECKMULTISIG] + if not match_decoded(dec2, match_multisig): + log.error("cannot find address in input script: {}".format(bytes.encode('hex'))) + return + x_pubkeys = map(lambda x: x[1].encode('hex'), dec2[1:-2]) + pubkeys = [parse_xpub(x)[0] for x in x_pubkeys] # xpub, addr = parse_xpub() + redeemScript = Transaction.multisig_script(pubkeys, m) + # write result in d + d['num_sig'] = m + d['signatures'] = parse_sig(x_sig) + d['x_pubkeys'] = x_pubkeys + d['pubkeys'] = pubkeys + d['redeemScript'] = redeemScript + d['address'] = hash_160_bytes_to_address(hash_160(redeemScript.decode('hex')), 5) + + +class NameClaim(object): + def __init__(self, name, value): + self.name = name + self.value = value + + +class ClaimUpdate(object): + def __init__(self, name, claim_id, value): + self.name = name + self.claim_id = claim_id + self.value = value + + +class ClaimSupport(object): + def __init__(self, name, claim_id): + self.name = name + self.claim_id = claim_id + + +def decode_claim_script(decoded_script): + if len(decoded_script) <= 6: + return False + op = 0 + claim_type = decoded_script[op][0] + if claim_type == opcodes.OP_UPDATE_CLAIM: + if len(decoded_script) <= 7: + return False + if claim_type not in [ + opcodes.OP_CLAIM_NAME, + opcodes.OP_SUPPORT_CLAIM, + opcodes.OP_UPDATE_CLAIM + ]: + return False + op += 1 + value = None + claim_id = None + claim = None + if not 0 <= decoded_script[op][0] <= opcodes.OP_PUSHDATA4: + return False + name = decoded_script[op][1] + op += 1 + if not 0 <= decoded_script[op][0] <= opcodes.OP_PUSHDATA4: + return False + if decoded_script[0][0] in [ + opcodes.OP_SUPPORT_CLAIM, + opcodes.OP_UPDATE_CLAIM + ]: + claim_id = decoded_script[op][1] + if len(claim_id) != 20: + return False + else: + value = decoded_script[op][1] + op += 1 + if decoded_script[0][0] == opcodes.OP_UPDATE_CLAIM: + value = decoded_script[op][1] + op += 1 + if decoded_script[op][0] != opcodes.OP_2DROP: + return False + op += 1 + if decoded_script[op][0] != opcodes.OP_DROP and decoded_script[0][0] == opcodes.OP_CLAIM_NAME: + return False + elif decoded_script[op][0] != opcodes.OP_2DROP and decoded_script[0][0] == \ + opcodes.OP_UPDATE_CLAIM: + return False + op += 1 + if decoded_script[0][0] == opcodes.OP_CLAIM_NAME: + if name is None or value is None: + return False + claim = NameClaim(name, value) + elif decoded_script[0][0] == opcodes.OP_UPDATE_CLAIM: + if name is None or value is None or claim_id is None: + return False + claim = ClaimUpdate(name, claim_id, value) + elif decoded_script[0][0] == opcodes.OP_SUPPORT_CLAIM: + if name is None or claim_id is None: + return False + claim = ClaimSupport(name, claim_id) + return claim, decoded_script[op:] + + +def get_address_from_output_script(script_bytes): + output_type = 0 + decoded = [x for x in script_GetOp(script_bytes)] + r = decode_claim_script(decoded) + claim_args = None + if r is not False: + claim_info, decoded = r + if isinstance(claim_info, NameClaim): + claim_args = (claim_info.name, claim_info.value) + output_type |= TYPE_CLAIM + elif isinstance(claim_info, ClaimSupport): + claim_args = (claim_info.name, claim_info.claim_id) + output_type |= TYPE_SUPPORT + elif isinstance(claim_info, ClaimUpdate): + claim_args = (claim_info.name, claim_info.claim_id, claim_info.value) + output_type |= TYPE_UPDATE + + # The Genesis Block, self-payments, and pay-by-IP-address payments look like: + # 65 BYTES:... CHECKSIG + match_pubkey = [opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG] + + # Pay-by-Bitcoin-address TxOuts look like: + # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG + match_p2pkh = [opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, + opcodes.OP_CHECKSIG] + + # p2sh + match_p2sh = [opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL] + + if match_decoded(decoded, match_pubkey): + output_val = decoded[0][1].encode('hex') + output_type |= TYPE_PUBKEY + elif match_decoded(decoded, match_p2pkh): + output_val = hash_160_bytes_to_address(decoded[2][1]) + output_type |= TYPE_ADDRESS + elif match_decoded(decoded, match_p2sh): + output_val = hash_160_bytes_to_address(decoded[1][1], 5) + output_type |= TYPE_ADDRESS + else: + output_val = bytes + output_type |= TYPE_SCRIPT + + if output_type & (TYPE_CLAIM | TYPE_SUPPORT | TYPE_UPDATE): + output_val = (claim_args, output_val) + + return output_type, output_val + + +def parse_input(vds): + d = {} + prevout_hash = hash_encode(vds.read_bytes(32)) + prevout_n = vds.read_uint32() + scriptSig = vds.read_bytes(vds.read_compact_size()) + d['scriptSig'] = scriptSig.encode('hex') + sequence = vds.read_uint32() + if prevout_hash == '00' * 32: + d['is_coinbase'] = True + else: + d['is_coinbase'] = False + d['prevout_hash'] = prevout_hash + d['prevout_n'] = prevout_n + d['sequence'] = sequence + d['pubkeys'] = [] + d['signatures'] = {} + d['address'] = None + if scriptSig: + parse_scriptSig(d, scriptSig) + return d + + +def parse_output(vds, i): + d = {} + d['value'] = vds.read_int64() + scriptPubKey = vds.read_bytes(vds.read_compact_size()) + d['type'], d['address'] = get_address_from_output_script(scriptPubKey) + d['scriptPubKey'] = scriptPubKey.encode('hex') + d['prevout_n'] = i + return d + + +def deserialize(raw): + vds = BCDataStream() + vds.write(raw.decode('hex')) + d = {} + start = vds.read_cursor + d['version'] = vds.read_int32() + n_vin = vds.read_compact_size() + d['inputs'] = list(parse_input(vds) for i in xrange(n_vin)) + n_vout = vds.read_compact_size() + d['outputs'] = list(parse_output(vds, i) for i in xrange(n_vout)) + d['lockTime'] = vds.read_uint32() + return d + + +def push_script(x): + return op_push(len(x) / 2) + x + + +class Transaction(object): + def __str__(self): + if self.raw is None: + self.raw = self.serialize() + return self.raw + + def __init__(self, raw): + if raw is None: + self.raw = None + elif type(raw) in [str, unicode]: + self.raw = raw.strip() if raw else None + elif type(raw) is dict: + self.raw = raw['hex'] + else: + raise BaseException("cannot initialize transaction", raw) + self._inputs = None + self._outputs = None + + def update(self, raw): + self.raw = raw + self._inputs = None + self.deserialize() + + def inputs(self): + if self._inputs is None: + self.deserialize() + return self._inputs + + def outputs(self): + if self._outputs is None: + self.deserialize() + return self._outputs + + def update_signatures(self, raw): + """Add new signatures to a transaction""" + d = deserialize(raw) + for i, txin in enumerate(self.inputs()): + sigs1 = txin.get('signatures') + sigs2 = d['inputs'][i].get('signatures') + for sig in sigs2: + if sig in sigs1: + continue + for_sig = Hash(self.tx_for_sig(i).decode('hex')) + # der to string + order = ecdsa.ecdsa.generator_secp256k1.order() + r, s = ecdsa.util.sigdecode_der(sig.decode('hex'), order) + sig_string = ecdsa.util.sigencode_string(r, s, order) + pubkeys = txin.get('pubkeys') + compressed = True + for recid in range(4): + public_key = MyVerifyingKey.from_signature(sig_string, recid, for_sig, + curve=SECP256k1) + pubkey = point_to_ser(public_key.pubkey.point, compressed).encode('hex') + if pubkey in pubkeys: + public_key.verify_digest(sig_string, for_sig, + sigdecode=ecdsa.util.sigdecode_string) + j = pubkeys.index(pubkey) + log.error("adding sig {} {} {} {}".format(i, j, pubkey, sig)) + self._inputs[i]['signatures'][j] = sig + self._inputs[i]['x_pubkeys'][j] = pubkey + break + # redo raw + self.raw = self.serialize() + + def deserialize(self): + if self.raw is None: + self.raw = self.serialize() + if self._inputs is not None: + return + d = deserialize(self.raw) + self._inputs = d['inputs'] + self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']] + self.locktime = d['lockTime'] + return d + + @classmethod + def from_io(cls, inputs, outputs, locktime=0): + self = cls(None) + self._inputs = inputs + self._outputs = outputs + self.locktime = locktime + return self + + @classmethod + def multisig_script(cls, public_keys, m): + n = len(public_keys) + assert n <= 15 + assert m <= n + op_m = format(opcodes.OP_1 + m - 1, 'x') + op_n = format(opcodes.OP_1 + n - 1, 'x') + keylist = [op_push(len(k) / 2) + k for k in public_keys] + return op_m + ''.join(keylist) + op_n + 'ae' + + @classmethod + def pay_script(cls, output_type, addr): + script = '' + if output_type & TYPE_CLAIM: + claim, addr = addr + claim_name, claim_value = claim + script += 'b5' # op_claim_name + script += push_script(claim_name.encode('hex')) + script += push_script(claim_value.encode('hex')) + script += '6d75' # op_2drop, op_drop + elif output_type & TYPE_SUPPORT: + claim, addr = addr + claim_name, claim_id = claim + script += 'b6' + script += push_script(claim_name.encode('hex')) + script += push_script(claim_id.encode('hex')) + script += '6d75' + elif output_type & TYPE_UPDATE: + claim, addr = addr + claim_name, claim_id, claim_value = claim + script += 'b7' + script += push_script(claim_name.encode('hex')) + script += push_script(claim_id.encode('hex')) + script += push_script(claim_value.encode('hex')) + script += '6d6d' + + if output_type & TYPE_SCRIPT: + script += addr.encode('hex') + elif output_type & TYPE_ADDRESS: # op_2drop, op_drop + addrtype, hash_160 = address_to_hash_160(addr) + if addrtype == 0: + script += '76a9' # op_dup, op_hash_160 + script += push_script(hash_160.encode('hex')) + script += '88ac' # op_equalverify, op_checksig + elif addrtype == 5: + script += 'a9' # op_hash_160 + script += push_script(hash_160.encode('hex')) + script += '87' # op_equal + else: + raise Exception("Unknown address type: %s" % addrtype) + else: + raise Exception("Unknown output type: %s" % output_type) + return script + + @classmethod + def input_script(cls, txin, i, for_sig): + # for_sig: + # -1 : do not sign, estimate length + # i>=0 : serialized tx for signing input i + # None : add all known signatures + + p2sh = txin.get('redeemScript') is not None + num_sig = txin['num_sig'] if p2sh else 1 + address = txin['address'] + + x_signatures = txin['signatures'] + signatures = filter(None, x_signatures) + is_complete = len(signatures) == num_sig + + if for_sig in [-1, None]: + # if we have enough signatures, we use the actual pubkeys + # use extended pubkeys (with bip32 derivation) + if for_sig == -1: + # we assume that signature will be 0x48 bytes long + pubkeys = txin['pubkeys'] + sig_list = ["00" * 0x48] * num_sig + elif is_complete: + pubkeys = txin['pubkeys'] + sig_list = ((sig + '01') for sig in signatures) + else: + pubkeys = txin['x_pubkeys'] + sig_list = ((sig + '01') if sig else NO_SIGNATURE for sig in x_signatures) + script = ''.join(push_script(x) for x in sig_list) + if not p2sh: + x_pubkey = pubkeys[0] + if x_pubkey is None: + addrtype, h160 = address_to_hash_160(txin['address']) + x_pubkey = 'fd' + (chr(addrtype) + h160).encode('hex') + script += push_script(x_pubkey) + else: + script = '00' + script # put op_0 in front of script + redeem_script = cls.multisig_script(pubkeys, num_sig) + script += push_script(redeem_script) + + elif for_sig == i: + script_type = TYPE_ADDRESS + if 'is_claim' in txin and txin['is_claim']: + script_type |= TYPE_CLAIM + address = ((txin['claim_name'], txin['claim_value']), address) + elif 'is_support' in txin and txin['is_support']: + script_type |= TYPE_SUPPORT + address = ((txin['claim_name'], txin['claim_id']), address) + elif 'is_update' in txin and txin['is_update']: + script_type |= TYPE_UPDATE + address = ((txin['claim_name'], txin['claim_id'], txin['claim_value']), address) + script = txin['redeemScript'] if p2sh else cls.pay_script(script_type, address) + else: + script = '' + + return script + + @classmethod + def serialize_input(cls, txin, i, for_sig): + # Prev hash and index + s = txin['prevout_hash'].decode('hex')[::-1].encode('hex') + s += int_to_hex(txin['prevout_n'], 4) + # Script length, script, sequence + script = cls.input_script(txin, i, for_sig) + s += var_int(len(script) / 2) + s += script + s += "ffffffff" + return s + + def BIP_LI01_sort(self): + # See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki + self._inputs.sort(key=lambda i: (i['prevout_hash'], i['prevout_n'])) + self._outputs.sort(key=lambda o: (o[2], self.pay_script(o[0], o[1]))) + + def serialize(self, for_sig=None): + inputs = self.inputs() + outputs = self.outputs() + s = int_to_hex(1, 4) # version + s += var_int(len(inputs)) # number of inputs + for i, txin in enumerate(inputs): + s += self.serialize_input(txin, i, for_sig) + s += var_int(len(outputs)) # number of outputs + for output in outputs: + output_type, addr, amount = output + s += int_to_hex(amount, 8) # amount + script = self.pay_script(output_type, addr) + s += var_int(len(script) / 2) # script length + s += script # script + s += int_to_hex(0, 4) # lock time + if for_sig is not None and for_sig != -1: + s += int_to_hex(1, 4) # hash type + return s + + def tx_for_sig(self, i): + return self.serialize(for_sig=i) + + def hash(self): + return Hash(self.raw.decode('hex'))[::-1].encode('hex') + + def get_claim_id(self, nout): + if nout < 0: + raise IndexError + if not self._outputs[nout][0] & TYPE_CLAIM: + raise ValueError + tx_hash = rev_hex(self.hash()).decode('hex') + return encode_claim_id_hex(claim_id_hash(tx_hash, nout)) + + def add_inputs(self, inputs): + self._inputs.extend(inputs) + self.raw = None + + def add_outputs(self, outputs): + self._outputs.extend(outputs) + self.raw = None + + def input_value(self): + return sum(x['value'] for x in self.inputs()) + + def output_value(self): + return sum(val for tp, addr, val in self.outputs()) + + def get_fee(self): + return self.input_value() - self.output_value() + + def is_final(self): + return not any([x.get('sequence') < 0xffffffff - 1 for x in self.inputs()]) + + @classmethod + def fee_for_size(cls, relay_fee, fee_per_kb, size): + '''Given a fee per kB in satoshis, and a tx size in bytes, + returns the transaction fee.''' + fee = int(fee_per_kb * size / 1000.) + if fee < relay_fee: + fee = relay_fee + return fee + + @profiler + def estimated_size(self): + '''Return an estimated tx size in bytes.''' + return len(self.serialize(-1)) / 2 # ASCII hex string + + @classmethod + def estimated_input_size(cls, txin): + '''Return an estimated of serialized input size in bytes.''' + return len(cls.serialize_input(txin, -1, -1)) / 2 + + def estimated_fee(self, relay_fee, fee_per_kb): + '''Return an estimated fee given a fee per kB in satoshis.''' + return self.fee_for_size(relay_fee, fee_per_kb, self.estimated_size()) + + def signature_count(self): + r = 0 + s = 0 + for txin in self.inputs(): + if txin.get('is_coinbase'): + continue + signatures = filter(None, txin.get('signatures', [])) + s += len(signatures) + r += txin.get('num_sig', -1) + return s, r + + def is_complete(self): + s, r = self.signature_count() + return r == s + + def inputs_without_script(self): + out = set() + for i, txin in enumerate(self.inputs()): + if txin.get('scriptSig') == '': + out.add(i) + return out + + def inputs_to_sign(self): + out = set() + for txin in self.inputs(): + num_sig = txin.get('num_sig') + if num_sig is None: + continue + x_signatures = txin['signatures'] + signatures = filter(None, x_signatures) + if len(signatures) == num_sig: + # input is complete + continue + for k, x_pubkey in enumerate(txin['x_pubkeys']): + if x_signatures[k] is not None: + # this pubkey already signed + continue + out.add(x_pubkey) + return out + + def sign(self, keypairs): + for i, txin in enumerate(self.inputs()): + num = txin['num_sig'] + for x_pubkey in txin['x_pubkeys']: + signatures = filter(None, txin['signatures']) + if len(signatures) == num: + # txin is complete + break + if x_pubkey in keypairs.keys(): + log.debug("adding signature for %s", x_pubkey) + # add pubkey to txin + txin = self._inputs[i] + x_pubkeys = txin['x_pubkeys'] + ii = x_pubkeys.index(x_pubkey) + sec = keypairs[x_pubkey] + pubkey = public_key_from_private_key(sec) + txin['x_pubkeys'][ii] = pubkey + txin['pubkeys'][ii] = pubkey + self._inputs[i] = txin + # add signature + for_sig = Hash(self.tx_for_sig(i).decode('hex')) + pkey = regenerate_key(sec) + secexp = pkey.secret + private_key = MySigningKey.from_secret_exponent(secexp, curve=SECP256k1) + public_key = private_key.get_verifying_key() + sig = private_key.sign_digest_deterministic(for_sig, hashfunc=hashlib.sha256, + sigencode=ecdsa.util.sigencode_der) + assert public_key.verify_digest(sig, for_sig, + sigdecode=ecdsa.util.sigdecode_der) + txin['signatures'][ii] = sig.encode('hex') + self._inputs[i] = txin + log.debug("is_complete: %s", self.is_complete()) + self.raw = self.serialize() + + def get_outputs(self): + """convert pubkeys to addresses""" + o = [] + for type, x, v in self.outputs(): + if type & (TYPE_CLAIM | TYPE_UPDATE | TYPE_SUPPORT): + x = x[1] + if type & TYPE_ADDRESS: + addr = x + elif type & TYPE_PUBKEY: + addr = public_key_to_address(x.decode('hex')) + else: + addr = 'SCRIPT ' + x.encode('hex') + o.append((addr, v)) # consider using yield (addr, v) + return o + + def get_output_addresses(self): + return [addr for addr, val in self.get_outputs()] + + def has_address(self, addr): + return (addr in self.get_output_addresses()) or ( + addr in (tx.get("address") for tx in self.inputs())) + + def as_dict(self): + if self.raw is None: + self.raw = self.serialize() + self.deserialize() + out = { + 'hex': self.raw, + 'complete': self.is_complete() + } + return out + + def requires_fee(self, wallet): + # see https://en.bitcoin.it/wiki/Transaction_fees + # + # size must be smaller than 1 kbyte for free tx + size = len(self.serialize(-1)) / 2 + if size >= 10000: + return True + # all outputs must be 0.01 BTC or larger for free tx + for addr, value in self.get_outputs(): + if value < 1000000: + return True + # priority must be large enough for free tx + threshold = 57600000 + weight = 0 + for txin in self.inputs(): + age = wallet.get_confirmations(txin["prevout_hash"])[0] + weight += txin["value"] * age + priority = weight / size + log.error("{} {}".format(priority, threshold)) + + return priority < threshold diff --git a/lbrynet/wallet/util.py b/lbrynet/wallet/util.py new file mode 100644 index 000000000..1a22c42f6 --- /dev/null +++ b/lbrynet/wallet/util.py @@ -0,0 +1,117 @@ +import logging +import os +import re +from decimal import Decimal +import json +from .constants import NO_SIGNATURE + +log = logging.getLogger(__name__) + + +def normalize_version(v): + return [int(x) for x in re.sub(r'(\.0+)*$', '', v).split(".")] + + +def json_decode(x): + try: + return json.loads(x, parse_float=Decimal) + except: + return x + + +def user_dir(): + if "HOME" in os.environ: + return os.path.join(os.environ["HOME"], ".lbryum") + elif "APPDATA" in os.environ: + return os.path.join(os.environ["APPDATA"], "LBRYum") + elif "LOCALAPPDATA" in os.environ: + return os.path.join(os.environ["LOCALAPPDATA"], "LBRYum") + elif 'ANDROID_DATA' in os.environ: + try: + import jnius + env = jnius.autoclass('android.os.Environment') + _dir = env.getExternalStorageDirectory().getPath() + return _dir + '/lbryum/' + except ImportError: + pass + return "/sdcard/lbryum/" + else: + # raise Exception("No home directory found in environment variables.") + return + + +def format_satoshis(x, is_diff=False, num_zeros=0, decimal_point=8, whitespaces=False): + from locale import localeconv + if x is None: + return 'unknown' + x = int(x) # Some callers pass Decimal + scale_factor = pow(10, decimal_point) + integer_part = "{:n}".format(int(abs(x) / scale_factor)) + if x < 0: + integer_part = '-' + integer_part + elif is_diff: + integer_part = '+' + integer_part + dp = localeconv()['decimal_point'] + fract_part = ("{:0" + str(decimal_point) + "}").format(abs(x) % scale_factor) + fract_part = fract_part.rstrip('0') + if len(fract_part) < num_zeros: + fract_part += "0" * (num_zeros - len(fract_part)) + result = integer_part + dp + fract_part + if whitespaces: + result += " " * (decimal_point - len(fract_part)) + result = " " * (15 - len(result)) + result + return result.decode('utf8') + + +def rev_hex(s): + return s.decode('hex')[::-1].encode('hex') + + +def int_to_hex(i, length=1): + s = hex(i)[2:].rstrip('L') + s = "0" * (2 * length - len(s)) + s + return rev_hex(s) + + +def hex_to_int(s): + return int('0x' + s[::-1].encode('hex'), 16) + + +def var_int(i): + # https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer + if i < 0xfd: + return int_to_hex(i) + elif i <= 0xffff: + return "fd" + int_to_hex(i, 2) + elif i <= 0xffffffff: + return "fe" + int_to_hex(i, 4) + else: + return "ff" + int_to_hex(i, 8) + + +# This function comes from bitcointools, bct-LICENSE.txt. +def long_hex(bytes): + return bytes.encode('hex_codec') + + +# This function comes from bitcointools, bct-LICENSE.txt. +def short_hex(bytes): + t = bytes.encode('hex_codec') + if len(t) < 11: + return t + return t[0:4] + "..." + t[-4:] + + +def parse_sig(x_sig): + s = [] + for sig in x_sig: + if sig[-2:] == '01': + s.append(sig[:-2]) + else: + assert sig == NO_SIGNATURE + s.append(None) + return s + + +def is_extended_pubkey(x_pubkey): + return x_pubkey[0:2] in ['fe', 'ff'] diff --git a/lbrynet/wallet/wallet.py b/lbrynet/wallet/wallet.py new file mode 100644 index 000000000..699af6a46 --- /dev/null +++ b/lbrynet/wallet/wallet.py @@ -0,0 +1,1499 @@ +import ast +import copy +import stat +import json +import os +import random +import threading +import time +import hashlib +import logging +from decimal import Decimal +from functools import partial + +from lbryschema.address import hash_160_bytes_to_address, public_key_to_address, is_address + +from .account import Account +from .constants import TYPE_ADDRESS, TYPE_CLAIM, TYPE_SUPPORT, TYPE_UPDATE, TYPE_PUBKEY +from .constants import EXPIRATION_BLOCKS, COINBASE_MATURITY, RECOMMENDED_FEE +from .coinchooser import COIN_CHOOSERS +from .transaction import Transaction +from .mnemonic import Mnemonic +from .util import rev_hex +from .errors import NotEnoughFunds, InvalidPassword +from .constants import NEW_SEED_VERSION +from .lbrycrd import regenerate_key, is_compressed, pw_encode, pw_decode +from .lbrycrd import bip32_private_key +from .lbrycrd import encode_claim_id_hex, deserialize_xkey, claim_id_hash +from .lbrycrd import bip32_private_derivation, bip32_root + +log = logging.getLogger(__name__) + + +class WalletStorage: + def __init__(self, path): + self.lock = threading.RLock() + self.data = {} + self.path = path + self.file_exists = False + self.modified = False + log.info("wallet path: %s", self.path) + if self.path: + self.read(self.path) + + def read(self, path): + """Read the contents of the wallet file.""" + try: + with open(self.path, "r") as f: + data = f.read() + except IOError: + return + try: + self.data = json.loads(data) + except: + try: + d = ast.literal_eval(data) # parse raw data from reading wallet file + labels = d.get('labels', {}) + except Exception as e: + raise IOError("Cannot read wallet file '%s'" % self.path) + self.data = {} + # In old versions of Electrum labels were latin1 encoded, this fixes breakage. + for i, label in labels.items(): + try: + unicode(label) + except UnicodeDecodeError: + d['labels'][i] = unicode(label.decode('latin1')) + for key, value in d.items(): + try: + json.dumps(key) + json.dumps(value) + except: + log.error('Failed to convert label to json format: {}'.format(key)) + continue + self.data[key] = value + self.file_exists = True + + def get(self, key, default=None): + with self.lock: + v = self.data.get(key) + if v is None: + v = default + else: + v = copy.deepcopy(v) + return v + + def put(self, key, value): + try: + json.dumps(key) + json.dumps(value) + except: + self.print_error("json error: cannot save", key) + return + with self.lock: + if value is not None: + if self.data.get(key) != value: + self.modified = True + self.data[key] = copy.deepcopy(value) + elif key in self.data: + self.modified = True + self.data.pop(key) + + def write(self): + with self.lock: + self._write() + + def _write(self): + if threading.currentThread().isDaemon(): + log.warning('daemon thread cannot write wallet') + return + if not self.modified: + return + s = json.dumps(self.data, indent=4, sort_keys=True) + temp_path = "%s.tmp.%s" % (self.path, os.getpid()) + with open(temp_path, "w") as f: + f.write(s) + f.flush() + os.fsync(f.fileno()) + + if os.path.exists(self.path): + mode = os.stat(self.path).st_mode + else: + mode = stat.S_IREAD | stat.S_IWRITE + # perform atomic write on POSIX systems + try: + os.rename(temp_path, self.path) + except: + os.remove(self.path) + os.rename(temp_path, self.path) + os.chmod(self.path, mode) + self.modified = False + + +class Wallet: + + root_name = 'x/' + root_derivation = "m/" + wallet_type = 'standard' + max_change_outputs = 3 + + def __init__(self, path): + self.storage = storage = WalletStorage(path) + + self.gap_limit = storage.get('gap_limit', 20) + self.gap_limit_for_change = 6 + + self.accounts = {} + self.seed_version = storage.get('seed_version', NEW_SEED_VERSION) + self.use_change = storage.get('use_change', True) + self.multiple_change = storage.get('multiple_change', False) + + self.use_encryption = storage.get('use_encryption', False) + self.seed = storage.get('seed', '') # encrypted + self.labels = storage.get('labels', {}) + self.frozen_addresses = set(storage.get('frozen_addresses', [])) + self.stored_height = storage.get('stored_height', 0) # last known height (for offline mode) + self.history = storage.get('addr_history', {}) # address -> list(txid, height) + + # Transactions pending verification. A map from tx hash to transaction + # height. Access is not contended so no lock is needed. + self.unverified_tx = {} + # Verified transactions. Each value is a (height, timestamp, block_pos) tuple. + # Access with self.lock. + self.verified_tx = storage.get('verified_tx3', {}) + + # there is a difference between wallet.up_to_date and interface.is_up_to_date() + # interface.is_up_to_date() returns true when all requests have been answered and processed + # wallet.up_to_date is true when the wallet is synchronized (stronger requirement) + self.up_to_date = False + + self.claim_certificates = storage.get('claim_certificates', {}) + self.default_certificate_claim = storage.get('default_certificate_claim', None) + + # save wallet type the first time + if self.storage.get('wallet_type') is None: + self.storage.put('wallet_type', self.wallet_type) + + self.master_public_keys = storage.get('master_public_keys', {}) + self.master_private_keys = storage.get('master_private_keys', {}) + self.mnemonic = Mnemonic(storage.get('lang', 'eng')) + + @property + def addresses(self): + for account in self.accounts.values(): + for sequence in account.sequences: + for address in sequence.addresses: + yield address + + def create(self): + seed = self.mnemonic.make_seed() + self.add_seed(seed, None) + self.add_xprv_from_seed(seed, self.root_name, None) + self.add_account('0', Account({ + 'xpub': self.master_public_keys.get("x/") + }, + self.gap_limit, + self.gap_limit_for_change, + self.address_is_old + )) + self.ensure_enough_addresses() + + def ensure_enough_addresses(self): + for account in self.accounts.values(): + account.ensure_enough_addresses() + + def load(self): + self.load_accounts() + self.load_transactions() + + def load_accounts(self): + for index, details in self.storage.get('accounts', {}).items(): + if 'xpub' in details: + self.accounts[index] = Account( + details, self.gap_limit, self.gap_limit_for_change, self.address_is_old + ) + else: + log.error("cannot load account: {}".format(details)) + + def load_transactions(self): + self.txi = self.storage.get('txi', {}) + self.txo = self.storage.get('txo', {}) + self.pruned_txo = self.storage.get('pruned_txo', {}) + tx_list = self.storage.get('transactions', {}) + self.claimtrie_transactions = self.storage.get('claimtrie_transactions', {}) + self.transactions = {} + for tx_hash, raw in tx_list.items(): + tx = Transaction(raw) + self.transactions[tx_hash] = tx + if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None and \ + (tx_hash not in self.pruned_txo.values()): + log.info("removing unreferenced tx: %s", tx_hash) + self.transactions.pop(tx_hash) + + # add to claimtrie transactions if its a claimtrie transaction + tx.deserialize() + for n, txout in enumerate(tx.outputs()): + if txout[0] & (TYPE_CLAIM | TYPE_UPDATE | TYPE_SUPPORT): + self.claimtrie_transactions[tx_hash + ':' + str(n)] = txout[0] + + def set_use_encryption(self, use_encryption): + self.use_encryption = use_encryption + self.storage.put('use_encryption', use_encryption) + + def save_transactions(self, write=False): + tx = {} + for k, v in self.transactions.items(): + tx[k] = str(v) + self.storage.put('transactions', tx) + self.storage.put('txi', self.txi) + self.storage.put('txo', self.txo) + self.storage.put('pruned_txo', self.pruned_txo) + self.storage.put('addr_history', self.history) + self.storage.put('claimtrie_transactions', self.claimtrie_transactions) + if write: + self.storage.write() + + def save_certificate(self, claim_id, private_key, write=False): + certificate_keys = self.storage.get('claim_certificates') or {} + certificate_keys[claim_id] = private_key + self.storage.put('claim_certificates', certificate_keys) + if write: + self.storage.write() + + def set_default_certificate(self, claim_id, overwrite_existing=True, write=False): + if self.default_certificate_claim is not None and overwrite_existing or not \ + self.default_certificate_claim: + self.storage.put('default_certificate_claim', claim_id) + if write: + self.storage.write() + self.default_certificate_claim = claim_id + + def get_certificate_signing_key(self, claim_id): + certificates = self.storage.get('claim_certificates', {}) + return certificates.get(claim_id, None) + + def get_certificate_claim_ids_for_signing(self): + certificates = self.storage.get('claim_certificates', {}) + return certificates.keys() + + def clear_history(self): + with self.transaction_lock: + self.txi = {} + self.txo = {} + self.pruned_txo = {} + self.save_transactions() + with self.lock: + self.history = {} + self.tx_addr_hist = {} + + def build_reverse_history(self): + self.tx_addr_hist = {} + for addr, hist in self.history.items(): + for tx_hash, h in hist: + s = self.tx_addr_hist.get(tx_hash, set()) + s.add(addr) + self.tx_addr_hist[tx_hash] = s + + def check_history(self): + save = False + for addr, hist in self.history.items(): + if not self.is_mine(addr): + self.history.pop(addr) + save = True + continue + + for tx_hash, tx_height in hist: + if tx_hash in self.pruned_txo.values() or self.txi.get(tx_hash) or self.txo.get( + tx_hash): + continue + tx = self.transactions.get(tx_hash) + if tx is not None: + self.add_transaction(tx_hash, tx) + save = True + if save: + self.save_transactions() + + def set_up_to_date(self, up_to_date): + with self.lock: + self.up_to_date = up_to_date + if up_to_date: + self.save_transactions(write=True) + + def is_up_to_date(self): + with self.lock: + return self.up_to_date + + def set_label(self, name, text=None): + changed = False + old_text = self.labels.get(name) + if text: + if old_text != text: + self.labels[name] = text + changed = True + else: + if old_text: + self.labels.pop(name) + changed = True + + if changed: + self.storage.put('labels', self.labels) + + return changed + + def is_mine(self, address): + return address in self.addresses + + def is_change(self, address): + if not self.is_mine(address): + return False + acct, s = self.get_address_index(address) + if s is None: + return False + return s[0] == 1 + + def get_address_index(self, address): + for acc_id in self.accounts: + for for_change in [0, 1]: + addresses = self.accounts[acc_id].get_addresses(for_change) + if address in addresses: + return acc_id, (for_change, addresses.index(address)) + raise Exception("Address not found", address) + + def get_private_key(self, address, password): + if self.is_watching_only(): + return [] + account_id, sequence = self.get_address_index(address) + return self.accounts[account_id].get_private_key(sequence, self, password) + + def get_public_keys(self, address): + account_id, sequence = self.get_address_index(address) + return self.accounts[account_id].get_pubkeys(*sequence) + + def sign_message(self, address, message, password): + keys = self.get_private_key(address, password) + assert len(keys) == 1 + sec = keys[0] + key = regenerate_key(sec) + compressed = is_compressed(sec) + return key.sign_message(message, compressed, address) + + def decrypt_message(self, pubkey, message, password): + address = public_key_to_address(pubkey.decode('hex')) + keys = self.get_private_key(address, password) + secret = keys[0] + ec = regenerate_key(secret) + decrypted = ec.decrypt_message(message) + return decrypted + + def add_unverified_tx(self, tx_hash, tx_height): + # Only add if confirmed and not verified + if tx_height > 0 and tx_hash not in self.verified_tx: + self.unverified_tx[tx_hash] = tx_height + + def add_verified_tx(self, tx_hash, info): + # Remove from the unverified map and add to the verified map and + self.unverified_tx.pop(tx_hash, None) + with self.lock: + self.verified_tx[tx_hash] = info # (tx_height, timestamp, pos) + self.storage.put('verified_tx3', self.verified_tx) + + conf, timestamp = self.get_confirmations(tx_hash) + self.network.trigger_callback('verified', tx_hash, conf, timestamp) + + def get_unverified_txs(self): + """Returns a map from tx hash to transaction height""" + return self.unverified_tx + + def undo_verifications(self, height): + """Used by the verifier when a reorg has happened""" + txs = [] + with self.lock: + for tx_hash, item in self.verified_tx: + tx_height, timestamp, pos = item + if tx_height >= height: + self.verified_tx.pop(tx_hash, None) + txs.append(tx_hash) + return txs + + def get_local_height(self): + """ return last known height if we are offline """ + return self.network.get_local_height() if self.network else self.stored_height + + def get_confirmations(self, tx): + """ return the number of confirmations of a monitored transaction. """ + with self.lock: + if tx in self.verified_tx: + height, timestamp, pos = self.verified_tx[tx] + conf = (self.get_local_height() - height + 1) + if conf <= 0: + timestamp = None + elif tx in self.unverified_tx: + conf = -1 + timestamp = None + else: + conf = 0 + timestamp = None + + return conf, timestamp + + def get_txpos(self, tx_hash): + "return position, even if the tx is unverified" + with self.lock: + x = self.verified_tx.get(tx_hash) + y = self.unverified_tx.get(tx_hash) + if x: + height, timestamp, pos = x + return height, pos + elif y: + return y, 0 + else: + return 1e12, 0 + + def is_found(self): + return self.history.values() != [[]] * len(self.history) + + def get_num_tx(self, address): + """ return number of transactions where address is involved """ + return len(self.history.get(address, [])) + + def get_tx_delta(self, tx_hash, address): + "effect of tx on address" + # pruned + if tx_hash in self.pruned_txo.values(): + return None + delta = 0 + # substract the value of coins sent from address + d = self.txi.get(tx_hash, {}).get(address, []) + for n, v in d: + delta -= v + # add the value of the coins received at address + d = self.txo.get(tx_hash, {}).get(address, []) + for n, v, cb in d: + delta += v + return delta + + def get_wallet_delta(self, tx): + """ effect of tx on wallet """ + addresses = self.addresses + is_relevant = False + is_send = False + is_pruned = False + is_partial = False + v_in = v_out = v_out_mine = 0 + for item in tx.inputs(): + addr = item.get('address') + if addr in addresses: + is_send = True + is_relevant = True + d = self.txo.get(item['prevout_hash'], {}).get(addr, []) + for n, v, cb in d: + if n == item['prevout_n']: + value = v + break + else: + value = None + if value is None: + is_pruned = True + else: + v_in += value + else: + is_partial = True + if not is_send: + is_partial = False + for addr, value in tx.get_outputs(): + v_out += value + if addr in addresses: + v_out_mine += value + is_relevant = True + if is_pruned: + # some inputs are mine: + fee = None + if is_send: + v = v_out_mine - v_out + else: + # no input is mine + v = v_out_mine + else: + v = v_out_mine - v_in + if is_partial: + # some inputs are mine, but not all + fee = None + is_send = v < 0 + else: + # all inputs are mine + fee = v_out - v_in + return is_relevant, is_send, v, fee + + def get_addr_io(self, address): + h = self.history.get(address, []) + received = {} + sent = {} + for tx_hash, height in h: + l = self.txo.get(tx_hash, {}).get(address, []) + for n, v, is_cb in l: + received[tx_hash + ':%d' % n] = (height, v, is_cb) + for tx_hash, height in h: + l = self.txi.get(tx_hash, {}).get(address, []) + for txi, v in l: + sent[txi] = height + return received, sent + + def get_addr_utxo(self, address): + coins, spent = self.get_addr_io(address) + for txi in spent: + coins.pop(txi) + return coins + + # return the total amount ever received by an address + def get_addr_received(self, address): + received, sent = self.get_addr_io(address) + return sum([v for height, v, is_cb in received.values()]) + + # return the balance of a bitcoin address: confirmed and matured, unconfirmed, unmatured + def get_addr_balance(self, address, exclude_claimtrietx=False): + received, sent = self.get_addr_io(address) + c = u = x = 0 + for txo, (tx_height, v, is_cb) in received.items(): + exclude_tx = False + # check if received transaction is a claimtrie tx to ourself + if exclude_claimtrietx: + prevout_hash, prevout_n = txo.split(':') + tx_type = self.claimtrie_transactions.get(txo) + if tx_type is not None: + exclude_tx = True + + if not exclude_tx: + if is_cb and tx_height + COINBASE_MATURITY > self.get_local_height(): + x += v + elif tx_height > 0: + c += v + else: + u += v + if txo in sent: + if sent[txo] > 0: + c -= v + else: + u -= v + return c, u, x + + # get coin object in order to abandon calimtrie transactions + # equivalent of get_spendable_coins but for claimtrie utxos + def get_spendable_claimtrietx_coin(self, txid, nOut): + tx = self.transactions.get(txid) + if tx is None: + raise BaseException('txid was not found in wallet') + tx.deserialize() + txouts = tx.outputs() + if len(txouts) < nOut + 1: + raise BaseException('nOut is too large') + txout = txouts[nOut] + txout_type, txout_dest, txout_value = txout + if not txout_type & (TYPE_CLAIM | TYPE_UPDATE | TYPE_SUPPORT): + raise BaseException('txid and nOut does not refer to a claimtrie transaction') + + address = txout_dest[1] + utxos = self.get_addr_utxo(address) + try: + utxo = utxos[txid + ':' + str(nOut)] + except KeyError: + raise BaseException('this claimtrie transaction has already been spent') + + # create inputs + is_update = txout_type & TYPE_UPDATE + is_claim = txout_type & TYPE_CLAIM + is_support = txout_type & TYPE_SUPPORT + + i = {'prevout_hash': txid, 'prevout_n': nOut, 'address': address, 'value': txout_value, + 'is_update': is_update, 'is_claim': is_claim, 'is_support': is_support, 'height': utxo[0]} + if is_claim: + i['claim_name'] = txout_dest[0][0] + i['claim_value'] = txout_dest[0][1] + elif is_support: + i['claim_name'] = txout_dest[0][0] + i['claim_id'] = txout_dest[0][1] + elif is_update: + i['claim_name'] = txout_dest[0][0] + i['claim_id'] = txout_dest[0][1] + i['claim_value'] = txout_dest[0][2] + else: + # should not reach here + raise ZeroDivisionError() + + self.add_input_info(i) + return i + + def get_spendable_coins(self, domain=None, exclude_frozen=True, abandon_txid=None): + coins = [] + found_abandon_txid = False + if domain is None: + domain = list(self.addresses) + if exclude_frozen: + domain = set(domain) - self.frozen_addresses + for addr in domain: + c = self.get_addr_utxo(addr) + for txo, v in c.items(): + tx_height, value, is_cb = v + if is_cb and tx_height + COINBASE_MATURITY > self.get_local_height(): + continue + prevout_hash, prevout_n = txo.split(':') + tx = self.transactions.get(prevout_hash) + tx.deserialize() + txout = tx.outputs()[int(prevout_n)] + if txout[0] & (TYPE_CLAIM | TYPE_SUPPORT | TYPE_UPDATE) == 0 or ( + abandon_txid is not None and prevout_hash == abandon_txid): + output = { + 'address': addr, + 'value': value, + 'prevout_n': int(prevout_n), + 'prevout_hash': prevout_hash, + 'height': tx_height, + 'coinbase': is_cb, + 'is_claim': bool(txout[0] & TYPE_CLAIM), + 'is_support': bool(txout[0] & TYPE_SUPPORT), + 'is_update': bool(txout[0] & TYPE_UPDATE), + } + if txout[0] & TYPE_CLAIM: + output['claim_name'] = txout[1][0][0] + output['claim_value'] = txout[1][0][1] + elif txout[0] & TYPE_SUPPORT: + output['claim_name'] = txout[1][0][0] + output['claim_id'] = txout[1][0][1] + elif txout[0] & TYPE_UPDATE: + output['claim_name'] = txout[1][0][0] + output['claim_id'] = txout[1][0][1] + output['claim_value'] = txout[1][0][2] + coins.append(output) + if abandon_txid is not None and prevout_hash == abandon_txid: + found_abandon_txid = True + continue + if abandon_txid is not None and not found_abandon_txid: + raise ValueError("Can't spend from the given txid") + return coins + + def get_account_addresses(self, acc_id, include_change=True): + '''acc_id of None means all user-visible accounts''' + addr_list = [] + acc_ids = self.accounts_to_show() if acc_id is None else [acc_id] + for _acc_id in acc_ids: + if _acc_id in self.accounts: + acc = self.accounts[_acc_id] + addr_list += acc.get_addresses(0) + if include_change: + addr_list += acc.get_addresses(1) + return addr_list + + def get_account_from_address(self, addr): + """Returns the account that contains this address, or None""" + for acc_id in self.accounts: # similar to get_address_index but simpler + if addr in self.get_account_addresses(acc_id): + return acc_id + return None + + def get_account_balance(self, account, exclude_claimtrietx=False): + return self.get_balance(self.get_account_addresses(account, exclude_claimtrietx)) + + def get_frozen_balance(self): + return self.get_balance(self.frozen_addresses) + + def get_balance(self, domain=None, exclude_claimtrietx=False): + if domain is None: + domain = self.addresses(True) + cc = uu = xx = 0 + for addr in domain: + c, u, x = self.get_addr_balance(addr, exclude_claimtrietx) + cc += c + uu += u + xx += x + return cc, uu, xx + + def get_address_history(self, address): + with self.lock: + return self.history.get(address, []) + + def get_status(self, h): + if not h: + return None + status = '' + for tx_hash, height in h: + status += tx_hash + ':%d:' % height + return hashlib.sha256(status).digest().encode('hex') + + def find_pay_to_pubkey_address(self, prevout_hash, prevout_n): + dd = self.txo.get(prevout_hash, {}) + for addr, l in dd.items(): + for n, v, is_cb in l: + if n == prevout_n: + self.print_error("found pay-to-pubkey address:", addr) + return addr + + def add_transaction(self, tx_hash, tx): + log.info("Adding tx: %s", tx_hash) + is_coinbase = True if tx.inputs()[0].get('is_coinbase') else False + with self.transaction_lock: + # add inputs + self.txi[tx_hash] = d = {} + for txi in tx.inputs(): + addr = txi.get('address') + if not txi.get('is_coinbase'): + prevout_hash = txi['prevout_hash'] + prevout_n = txi['prevout_n'] + ser = prevout_hash + ':%d' % prevout_n + if addr == "(pubkey)": + addr = self.find_pay_to_pubkey_address(prevout_hash, prevout_n) + # find value from prev output + if addr and self.is_mine(addr): + dd = self.txo.get(prevout_hash, {}) + for n, v, is_cb in dd.get(addr, []): + if n == prevout_n: + if d.get(addr) is None: + d[addr] = [] + d[addr].append((ser, v)) + break + else: + self.pruned_txo[ser] = tx_hash + + # add outputs + self.txo[tx_hash] = d = {} + for n, txo in enumerate(tx.outputs()): + ser = tx_hash + ':%d' % n + _type, x, v = txo + if _type & (TYPE_CLAIM | TYPE_UPDATE | TYPE_SUPPORT): + x = x[1] + self.claimtrie_transactions[ser] = _type + if _type & TYPE_ADDRESS: + addr = x + elif _type & TYPE_PUBKEY: + addr = public_key_to_address(x.decode('hex')) + else: + addr = None + if addr and self.is_mine(addr): + if d.get(addr) is None: + d[addr] = [] + d[addr].append((n, v, is_coinbase)) + # give v to txi that spends me + next_tx = self.pruned_txo.get(ser) + if next_tx is not None: + self.pruned_txo.pop(ser) + dd = self.txi.get(next_tx, {}) + if dd.get(addr) is None: + dd[addr] = [] + dd[addr].append((ser, v)) + # save + self.transactions[tx_hash] = tx + log.info("Saved") + + def remove_transaction(self, tx_hash): + with self.transaction_lock: + self.print_error("removing tx from history", tx_hash) + # tx = self.transactions.pop(tx_hash) + for ser, hh in self.pruned_txo.items(): + if hh == tx_hash: + self.pruned_txo.pop(ser) + # add tx to pruned_txo, and undo the txi addition + for next_tx, dd in self.txi.items(): + for addr, l in dd.items(): + ll = l[:] + for item in ll: + ser, v = item + prev_hash, prev_n = ser.split(':') + if prev_hash == tx_hash: + l.remove(item) + self.pruned_txo[ser] = next_tx + if not l: + dd.pop(addr) + else: + dd[addr] = l + try: + self.txi.pop(tx_hash) + self.txo.pop(tx_hash) + except KeyError: + self.print_error("tx was not in history", tx_hash) + + def receive_tx_callback(self, tx_hash, tx, tx_height): + self.add_transaction(tx_hash, tx) + self.save_transactions() + self.add_unverified_tx(tx_hash, tx_height) + + def receive_history_callback(self, addr, hist): + with self.lock: + old_hist = self.history.get(addr, []) + for tx_hash, height in old_hist: + if (tx_hash, height) not in hist: + # remove tx if it's not referenced in histories + self.tx_addr_hist[tx_hash].remove(addr) + if not self.tx_addr_hist[tx_hash]: + self.remove_transaction(tx_hash) + + self.history[addr] = hist + + for tx_hash, tx_height in hist: + # add it in case it was previously unconfirmed + self.add_unverified_tx(tx_hash, tx_height) + # add reference in tx_addr_hist + s = self.tx_addr_hist.get(tx_hash, set()) + s.add(addr) + self.tx_addr_hist[tx_hash] = s + # if addr is new, we have to recompute txi and txo + tx = self.transactions.get(tx_hash) + if tx is not None and self.txi.get(tx_hash, {}).get(addr) is None and self.txo.get( + tx_hash, {}).get(addr) is None: + self.add_transaction(tx_hash, tx) + + # Write updated TXI, TXO etc. + self.save_transactions() + + def get_history(self, domain=None): + from collections import defaultdict + # get domain + if domain is None: + domain = self.get_account_addresses(None) + + # 1. Get the history of each address in the domain, maintain the + # delta of a tx as the sum of its deltas on domain addresses + tx_deltas = defaultdict(int) + for addr in domain: + h = self.get_address_history(addr) + for tx_hash, height in h: + delta = self.get_tx_delta(tx_hash, addr) + if delta is None or tx_deltas[tx_hash] is None: + tx_deltas[tx_hash] = None + else: + tx_deltas[tx_hash] += delta + + # 2. create sorted history + history = [] + for tx_hash, delta in tx_deltas.items(): + conf, timestamp = self.get_confirmations(tx_hash) + history.append((tx_hash, conf, delta, timestamp)) + history.sort(key=lambda x: self.get_txpos(x[0])) + history.reverse() + + # 3. add balance + c, u, x = self.get_balance(domain) + balance = c + u + x + h2 = [] + for item in history: + tx_hash, conf, delta, timestamp = item + h2.append((tx_hash, conf, delta, timestamp, balance)) + if balance is None or delta is None: + balance = None + else: + balance -= delta + h2.reverse() + + # fixme: this may happen if history is incomplete + if balance not in [None, 0]: + self.print_error("Error: history not synchronized") + return [] + + return h2 + + def get_name_claims(self, domain=None, include_abandoned=True, include_supports=True, + exclude_expired=True): + claims = [] + if domain is None: + domain = self.get_account_addresses(None) + + for addr in domain: + txos, txis = self.get_addr_io(addr) + for txo, v in txos.items(): + tx_height, value, is_cb = v + prevout_hash, prevout_n = txo.split(':') + + tx = self.transactions.get(prevout_hash) + tx.deserialize() + txout = tx.outputs()[int(prevout_n)] + if not include_abandoned and txo in txis: + continue + if not include_supports and txout[0] & TYPE_SUPPORT: + continue + if txout[0] & (TYPE_CLAIM | TYPE_UPDATE | TYPE_SUPPORT): + local_height = self.get_local_height() + expired = tx_height + EXPIRATION_BLOCKS <= local_height + if expired and exclude_expired: + continue + output = { + 'txid': prevout_hash, + 'nout': int(prevout_n), + 'address': addr, + 'amount': Decimal(value), + 'height': tx_height, + 'expiration_height': tx_height + EXPIRATION_BLOCKS, + 'expired': expired, + 'confirmations': local_height - tx_height, + 'is_spent': txo in txis, + } + if tx_height: + output['height'] = tx_height + output['expiration_height'] = tx_height + EXPIRATION_BLOCKS + output['expired'] = expired + output['confirmations'] = local_height - tx_height + output['is_pending'] = False + else: + output['height'] = None + output['expiration_height'] = None + output['expired'] = expired + output['confirmations'] = None + output['is_pending'] = True + + if txout[0] & TYPE_CLAIM: + output['category'] = 'claim' + claim_name, claim_value = txout[1][0] + output['name'] = claim_name + output['value'] = claim_value.encode('hex') + claim_id = claim_id_hash(rev_hex(output['txid']).decode('hex'), + output['nout']) + claim_id = encode_claim_id_hex(claim_id) + output['claim_id'] = claim_id + elif txout[0] & TYPE_SUPPORT: + output['category'] = 'support' + claim_name, claim_id = txout[1][0] + output['name'] = claim_name + output['claim_id'] = encode_claim_id_hex(claim_id) + elif txout[0] & TYPE_UPDATE: + output['category'] = 'update' + claim_name, claim_id, claim_value = txout[1][0] + output['name'] = claim_name + output['value'] = claim_value.encode('hex') + output['claim_id'] = encode_claim_id_hex(claim_id) + if not expired: + output[ + 'blocks_to_expiration'] = tx_height + EXPIRATION_BLOCKS - local_height + claims.append(output) + return claims + + def get_label(self, tx_hash): + label = self.labels.get(tx_hash, '') + if label == '': + label = self.get_default_label(tx_hash) + return label + + def get_default_label(self, tx_hash): + if self.txi.get(tx_hash) == {}: + d = self.txo.get(tx_hash, {}) + labels = [] + for addr in d.keys(): + label = self.labels.get(addr) + if label: + labels.append(label) + return ', '.join(labels) + return '' + + def fee_per_kb(self, config): + b = config.get('dynamic_fees') + f = config.get('fee_factor', 50) + F = config.get('fee_per_kb', RECOMMENDED_FEE) + if b and self.network and self.network.fee: + result = min(RECOMMENDED_FEE, self.network.fee * (50 + f) / 100) + else: + result = F + return result + + def relayfee(self): + RELAY_FEE = 5000 + MAX_RELAY_FEE = 50000 + f = self.network.relay_fee if self.network and self.network.relay_fee else RELAY_FEE + return min(f, MAX_RELAY_FEE) + + def get_tx_fee(self, tx): + # this method can be overloaded + return tx.get_fee() + + def coin_chooser_name(self, config): + kind = config.get('coin_chooser') + if kind not in COIN_CHOOSERS: + kind = 'Priority' + return kind + + def coin_chooser(self, config): + klass = COIN_CHOOSERS[self.coin_chooser_name(config)] + return klass() + + def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None, change_addr=None, + abandon_txid=None): + # check outputs + for type, data, value in outputs: + if type & (TYPE_CLAIM | TYPE_UPDATE | TYPE_SUPPORT): + data = data[1] + if type & TYPE_ADDRESS: + assert is_address(data), "Address " + data + " is invalid!" + + # Avoid index-out-of-range with coins[0] below + if not coins: + raise NotEnoughFunds() + + for item in coins: + self.add_input_info(item) + + # change address + if change_addr: + change_addrs = [change_addr] + else: + # send change to one of the accounts involved in the tx + address = coins[0].get('address') + account, _ = self.get_address_index(address) + if self.use_change and self.accounts[account].has_change(): + # New change addresses are created only after a few + # confirmations. Select the unused addresses within the + # gap limit; if none take one at random + addrs = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change:] + change_addrs = [addr for addr in addrs if + self.get_num_tx(addr) == 0] + if not change_addrs: + change_addrs = [random.choice(addrs)] + else: + change_addrs = [address] + + # Fee estimator + if fixed_fee is None: + fee_estimator = partial(Transaction.fee_for_size, + self.relayfee(), + self.fee_per_kb(config)) + else: + fee_estimator = lambda size: fixed_fee + + # Change <= dust threshold is added to the tx fee + dust_threshold = 182 * 3 * self.relayfee() / 1000 + + # Let the coin chooser select the coins to spend + max_change = self.max_change_outputs if self.multiple_change else 1 + coin_chooser = self.coin_chooser(config) + tx = coin_chooser.make_tx(coins, outputs, change_addrs[:max_change], + fee_estimator, dust_threshold, abandon_txid=abandon_txid) + + # Sort the inputs and outputs deterministically + tx.BIP_LI01_sort() + + return tx + + def mktx(self, outputs, password, config, fee=None, change_addr=None, domain=None): + coins = self.get_spendable_coins(domain) + tx = self.make_unsigned_transaction(coins, outputs, config, fee, change_addr) + self.sign_transaction(tx, password) + return tx + + def add_input_info(self, txin): + address = txin['address'] + account_id, sequence = self.get_address_index(address) + account = self.accounts[account_id] + redeemScript = account.redeem_script(*sequence) + pubkeys = account.get_pubkeys(*sequence) + x_pubkeys = account.get_xpubkeys(*sequence) + # sort pubkeys and x_pubkeys, using the order of pubkeys + pubkeys, x_pubkeys = zip(*sorted(zip(pubkeys, x_pubkeys))) + txin['pubkeys'] = list(pubkeys) + txin['x_pubkeys'] = list(x_pubkeys) + txin['signatures'] = [None] * len(pubkeys) + + if redeemScript: + txin['redeemScript'] = redeemScript + txin['num_sig'] = account.m + else: + txin['redeemPubkey'] = account.get_pubkey(*sequence) + txin['num_sig'] = 1 + + def sign_transaction(self, tx, password): + if self.is_watching_only(): + return + # Raise if password is not correct. + self.check_password(password) + # Add derivation for utxo in wallets + for i, addr in self.utxo_can_sign(tx): + txin = tx.inputs()[i] + txin['address'] = addr + self.add_input_info(txin) + # Add private keys + keypairs = {} + for x in self.xkeys_can_sign(tx): + sec = self.get_private_key_from_xpubkey(x, password) + if sec: + keypairs[x] = sec + # Sign + if keypairs: + tx.sign(keypairs) + + def send_tx(self, tx, timeout=300): + # fixme: this does not handle the case where server does not answer + if not self.network.interface: + raise Exception("Not connected.") + + txid = tx.hash() + + with self.send_tx_lock: + self.network.send([('blockchain.transaction.broadcast', [str(tx)])], self.on_broadcast) + self.tx_event.wait() + success, result = self.receive_tx(txid, tx) + self.tx_event.clear() + + if not success: + log.error("send tx failed: %s", result) + return success, result + + log.debug("waiting for %s to be added to the wallet", txid) + now = time.time() + while txid not in self.transactions and time.time() < now + timeout: + time.sleep(0.2) + + if txid not in self.transactions: + #TODO: detect if the txid is not known because it changed + log.error("timed out while waiting to receive back a broadcast transaction, " + "expected txid: %s", txid) + return False, "timed out while waiting to receive back a broadcast transaction, " \ + "expected txid: %s" % txid + + log.info("successfully sent %s", txid) + return success, result + + def on_broadcast(self, r): + self.tx_result = r.get('result') + self.tx_event.set() + + def receive_tx(self, tx_hash, tx): + out = self.tx_result + if out != tx_hash: + return False, "error: " + out + return True, out + + def update_password(self, old_password, new_password): + if new_password == '': + new_password = None + + if self.has_seed(): + decoded = self.get_seed(old_password) + self.seed = pw_encode(decoded, new_password) + self.storage.put('seed', self.seed) + + if hasattr(self, 'master_private_keys'): + for k, v in self.master_private_keys.items(): + b = pw_decode(v, old_password) + c = pw_encode(b, new_password) + self.master_private_keys[k] = c + self.storage.put('master_private_keys', self.master_private_keys) + + self.set_use_encryption(new_password is not None) + + def is_frozen(self, addr): + return addr in self.frozen_addresses + + def set_frozen_state(self, addrs, freeze): + '''Set frozen state of the addresses to FREEZE, True or False''' + if all(self.is_mine(addr) for addr in addrs): + if freeze: + self.frozen_addresses |= set(addrs) + else: + self.frozen_addresses -= set(addrs) + self.storage.put('frozen_addresses', list(self.frozen_addresses)) + return True + return False + + def prepare_for_verifier(self): + # review transactions that are in the history + for addr, hist in self.history.items(): + for tx_hash, tx_height in hist: + # add it in case it was previously unconfirmed + self.add_unverified_tx(tx_hash, tx_height) + + # if we are on a pruning server, remove unverified transactions + vr = self.verified_tx.keys() + self.unverified_tx.keys() + for tx_hash in self.transactions.keys(): + if tx_hash not in vr: + log.info("removing transaction %s", tx_hash) + self.transactions.pop(tx_hash) + + def accounts_to_show(self): + return self.accounts.keys() + + def get_accounts(self): + return {a_id: a for a_id, a in self.accounts.items() + if a_id in self.accounts_to_show()} + + def get_account_name(self, k): + default_name = "Main account" if k == '0' else "Account " + k + return self.labels.get(k, default_name) + + def get_account_names(self): + ids = self.accounts_to_show() + return dict(zip(ids, map(self.get_account_name, ids))) + + def add_account(self, account_id, account): + self.accounts[account_id] = account + self.save_accounts() + + def save_accounts(self): + d = {} + for k, v in self.accounts.items(): + d[k] = v.dump() + self.storage.put('accounts', d) + + def is_used(self, address): + h = self.history.get(address, []) + c, u, x = self.get_addr_balance(address) + return len(h) > 0 and c + u + x == 0 + + def is_empty(self, address): + c, u, x = self.get_addr_balance(address) + return c + u + x == 0 + + def address_is_old(self, address, age_limit=2): + age = -1 + h = self.history.get(address, []) + for tx_hash, tx_height in h: + if tx_height == 0: + tx_age = 0 + else: + tx_age = self.get_local_height() - tx_height + 1 + if tx_age > age: + age = tx_age + return age > age_limit + + def can_sign(self, tx): + if self.is_watching_only(): + return False + if tx.is_complete(): + return False + if self.xkeys_can_sign(tx): + return True + if self.utxo_can_sign(tx): + return True + return False + + def utxo_can_sign(self, tx): + out = set() + coins = self.get_spendable_coins() + for i in tx.inputs_without_script(): + txin = tx.inputs[i] + for item in coins: + if txin.get('prevout_hash') == item.get('prevout_hash') and txin.get( + 'prevout_n') == item.get('prevout_n'): + out.add((i, item.get('address'))) + return out + + def xkeys_can_sign(self, tx): + out = set() + for x in tx.inputs_to_sign(): + if self.can_sign_xpubkey(x): + out.add(x) + return out + + def get_private_key_from_xpubkey(self, x_pubkey, password): + if x_pubkey[0:2] in ['02', '03', '04']: + addr = public_key_to_address(x_pubkey.decode('hex')) + if self.is_mine(addr): + return self.get_private_key(addr, password)[0] + elif x_pubkey[0:2] == 'ff': + xpub, sequence = Account.parse_xpubkey(x_pubkey) + for k, v in self.master_public_keys.items(): + if v == xpub: + xprv = self.get_master_private_key(k, password) + if xprv: + _, _, _, c, k = deserialize_xkey(xprv) + return bip32_private_key(sequence, k, c) + elif x_pubkey[0:2] == 'fd': + addrtype = ord(x_pubkey[2:4].decode('hex')) + addr = hash_160_bytes_to_address(x_pubkey[4:].decode('hex'), addrtype) + if self.is_mine(addr): + return self.get_private_key(addr, password)[0] + else: + raise BaseException("z") + + def can_sign_xpubkey(self, x_pubkey): + if x_pubkey[0:2] in ['02', '03', '04']: + addr = public_key_to_address(x_pubkey.decode('hex')) + return self.is_mine(addr) + elif x_pubkey[0:2] == 'ff': + if not isinstance(self, Wallet): + return False + xpub, sequence = Account.parse_xpubkey(x_pubkey) + return xpub in [self.master_public_keys[k] for k in self.master_private_keys.keys()] + elif x_pubkey[0:2] == 'fd': + addrtype = ord(x_pubkey[2:4].decode('hex')) + addr = hash_160_bytes_to_address(x_pubkey[4:].decode('hex'), addrtype) + return self.is_mine(addr) + else: + raise BaseException("z") + + def can_change_password(self): + return not self.is_watching_only() + + def get_unused_addresses(self, account): + # fixme: use slots from expired requests + domain = self.get_account_addresses(account, include_change=False) + return [addr for addr in domain if not self.history.get(addr)] + + def get_unused_address(self, account): + domain = self.get_account_addresses(account, include_change=False) + for addr in domain: + if not self.history.get(addr): + return addr + + def is_watching_only(self): + return not bool(self.master_private_keys) + + def get_master_public_key(self): + return self.master_public_keys.get(self.root_name) + + def get_master_private_key(self, account, password): + k = self.master_private_keys.get(account) + if not k: + return + xprv = pw_decode(k, password) + try: + deserialize_xkey(xprv) + except: + raise InvalidPassword() + return xprv + + def check_password(self, password): + xpriv = self.get_master_private_key(self.root_name, password) + xpub = self.master_public_keys[self.root_name] + if deserialize_xkey(xpriv)[3] != deserialize_xkey(xpub)[3]: + raise InvalidPassword() + + def add_master_public_key(self, name, xpub): + if xpub in self.master_public_keys.values(): + raise BaseException('Duplicate master public key') + self.master_public_keys[name] = xpub + self.storage.put('master_public_keys', self.master_public_keys) + + def add_master_private_key(self, name, xpriv, password): + self.master_private_keys[name] = pw_encode(xpriv, password) + self.storage.put('master_private_keys', self.master_private_keys) + + def derive_xkeys(self, root, derivation, password): + x = self.master_private_keys[root] + root_xprv = pw_decode(x, password) + xprv, xpub = bip32_private_derivation(root_xprv, root, derivation) + return xpub, xprv + + def mnemonic_to_seed(self, seed, password): + return Mnemonic.mnemonic_to_seed(seed, password) + + def format_seed(self, seed): + return NEW_SEED_VERSION, ' '.join(seed.split()) + + @classmethod + def account_derivation(cls, account_id): + return cls.root_derivation + account_id + + @classmethod + def address_derivation(cls, account_id, change, address_index): + account_derivation = cls.account_derivation(account_id) + return "%s/%d/%d" % (account_derivation, change, address_index) + + def address_id(self, address): + acc_id, (change, address_index) = self.get_address_index(address) + return self.address_derivation(acc_id, change, address_index) + + def add_xprv_from_seed(self, seed, name, password, passphrase=''): + # we don't store the seed, only the master xpriv + xprv, _ = bip32_root(self.mnemonic_to_seed(seed, passphrase)) + xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation) + self.add_master_public_key(name, xpub) + self.add_master_private_key(name, xprv, password) + + def add_xpub_from_seed(self, seed, name): + # store only master xpub + xprv, _ = bip32_root(self.mnemonic_to_seed(seed, '')) + _, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation) + self.add_master_public_key(name, xpub) + + def has_seed(self): + return self.seed != '' + + def add_seed(self, seed, password): + if self.seed: + raise Exception("a seed exists") + self.seed_version, self.seed = self.format_seed(seed) + if password: + self.seed = pw_encode(self.seed, password) + self.storage.put('seed', self.seed) + self.storage.put('seed_version', self.seed_version) + self.set_use_encryption(password is not None) + + def get_seed(self, password): + return pw_decode(self.seed, password) + + def get_mnemonic(self, password): + return self.get_seed(password) + + def num_unused_trailing_addresses(self, addresses): + k = 0 + for a in addresses[::-1]: + if self.history.get(a): + break + k = k + 1 + return k + + def min_acceptable_gap(self): + # fixme: this assumes wallet is synchronized + n = 0 + nmax = 0 + + for account in self.accounts.values(): + addresses = account.get_addresses(0) + k = self.num_unused_trailing_addresses(addresses) + for a in addresses[0:-k]: + if self.history.get(a): + n = 0 + else: + n += 1 + if n > nmax: + nmax = n + return nmax + 1 + + def default_account(self): + return self.accounts['0'] + + def create_new_address(self, account=None, for_change=0): + with self.lock: + if account is None: + account = self.default_account() + address = account.create_new_address(for_change) + self.add_address(address) + log.info("created address %s", address) + return address + + def add_address(self, address): + if address not in self.history: + self.history[address] = [] + if self.synchronizer: + self.synchronizer.add(address) + self.save_accounts() + + def get_least_used_address(self, account=None, for_change=False, max_count=100): + domain = self.get_account_addresses(account, include_change=for_change) + hist = {} + for addr in domain: + if for_change != self.is_change(addr): + continue + else: + h = self.history.get(addr) + if h and len(h) >= max_count: + continue + elif h: + hist[addr] = h + else: + hist[addr] = [] + if hist: + return sorted(hist.keys(), key=lambda x: len(hist[x]))[0] + return self.create_new_address(account, for_change=for_change) + + def is_beyond_limit(self, address, account, is_change): + addr_list = account.get_addresses(is_change) + i = addr_list.index(address) + prev_addresses = addr_list[:max(0, i)] + limit = self.gap_limit_for_change if is_change else self.gap_limit + if len(prev_addresses) < limit: + return False + prev_addresses = prev_addresses[max(0, i - limit):] + for addr in prev_addresses: + if self.history.get(addr): + return False + return True + + def get_master_public_keys(self): + out = {} + for k, account in self.accounts.items(): + name = self.get_account_name(k) + mpk_text = '\n\n'.join(account.get_master_pubkeys()) + out[name] = mpk_text + return out diff --git a/lbrynet/wallet/wordlist/chinese_simplified.txt b/lbrynet/wallet/wordlist/chinese_simplified.txt new file mode 100644 index 000000000..b90f1ed85 --- /dev/null +++ b/lbrynet/wallet/wordlist/chinese_simplified.txt @@ -0,0 +1,2048 @@ +的 +一 +是 +在 +不 +了 +有 +和 +人 +这 +中 +大 +为 +上 +个 +国 +我 +以 +要 +他 +时 +来 +用 +们 +生 +到 +作 +地 +于 +出 +就 +分 +对 +成 +会 +可 +主 +发 +年 +动 +同 +工 +也 +能 +下 +过 +子 +说 +产 +种 +面 +而 +方 +后 +多 +定 +行 +学 +法 +所 +民 +得 +经 +十 +三 +之 +进 +着 +等 +部 +度 +家 +电 +力 +里 +如 +水 +化 +高 +自 +二 +理 +起 +小 +物 +现 +实 +加 +量 +都 +两 +体 +制 +机 +当 +使 +点 +从 +业 +本 +去 +把 +性 +好 +应 +开 +它 +合 +还 +因 +由 +其 +些 +然 +前 +外 +天 +政 +四 +日 +那 +社 +义 +事 +平 +形 +相 +全 +表 +间 +样 +与 +关 +各 +重 +新 +线 +内 +数 +正 +心 +反 +你 +明 +看 +原 +又 +么 +利 +比 +或 +但 +质 +气 +第 +向 +道 +命 +此 +变 +条 +只 +没 +结 +解 +问 +意 +建 +月 +公 +无 +系 +军 +很 +情 +者 +最 +立 +代 +想 +已 +通 +并 +提 +直 +题 +党 +程 +展 +五 +果 +料 +象 +员 +革 +位 +入 +常 +文 +总 +次 +品 +式 +活 +设 +及 +管 +特 +件 +长 +求 +老 +头 +基 +资 +边 +流 +路 +级 +少 +图 +山 +统 +接 +知 +较 +将 +组 +见 +计 +别 +她 +手 +角 +期 +根 +论 +运 +农 +指 +几 +九 +区 +强 +放 +决 +西 +被 +干 +做 +必 +战 +先 +回 +则 +任 +取 +据 +处 +队 +南 +给 +色 +光 +门 +即 +保 +治 +北 +造 +百 +规 +热 +领 +七 +海 +口 +东 +导 +器 +压 +志 +世 +金 +增 +争 +济 +阶 +油 +思 +术 +极 +交 +受 +联 +什 +认 +六 +共 +权 +收 +证 +改 +清 +美 +再 +采 +转 +更 +单 +风 +切 +打 +白 +教 +速 +花 +带 +安 +场 +身 +车 +例 +真 +务 +具 +万 +每 +目 +至 +达 +走 +积 +示 +议 +声 +报 +斗 +完 +类 +八 +离 +华 +名 +确 +才 +科 +张 +信 +马 +节 +话 +米 +整 +空 +元 +况 +今 +集 +温 +传 +土 +许 +步 +群 +广 +石 +记 +需 +段 +研 +界 +拉 +林 +律 +叫 +且 +究 +观 +越 +织 +装 +影 +算 +低 +持 +音 +众 +书 +布 +复 +容 +儿 +须 +际 +商 +非 +验 +连 +断 +深 +难 +近 +矿 +千 +周 +委 +素 +技 +备 +半 +办 +青 +省 +列 +习 +响 +约 +支 +般 +史 +感 +劳 +便 +团 +往 +酸 +历 +市 +克 +何 +除 +消 +构 +府 +称 +太 +准 +精 +值 +号 +率 +族 +维 +划 +选 +标 +写 +存 +候 +毛 +亲 +快 +效 +斯 +院 +查 +江 +型 +眼 +王 +按 +格 +养 +易 +置 +派 +层 +片 +始 +却 +专 +状 +育 +厂 +京 +识 +适 +属 +圆 +包 +火 +住 +调 +满 +县 +局 +照 +参 +红 +细 +引 +听 +该 +铁 +价 +严 +首 +底 +液 +官 +德 +随 +病 +苏 +失 +尔 +死 +讲 +配 +女 +黄 +推 +显 +谈 +罪 +神 +艺 +呢 +席 +含 +企 +望 +密 +批 +营 +项 +防 +举 +球 +英 +氧 +势 +告 +李 +台 +落 +木 +帮 +轮 +破 +亚 +师 +围 +注 +远 +字 +材 +排 +供 +河 +态 +封 +另 +施 +减 +树 +溶 +怎 +止 +案 +言 +士 +均 +武 +固 +叶 +鱼 +波 +视 +仅 +费 +紧 +爱 +左 +章 +早 +朝 +害 +续 +轻 +服 +试 +食 +充 +兵 +源 +判 +护 +司 +足 +某 +练 +差 +致 +板 +田 +降 +黑 +犯 +负 +击 +范 +继 +兴 +似 +余 +坚 +曲 +输 +修 +故 +城 +夫 +够 +送 +笔 +船 +占 +右 +财 +吃 +富 +春 +职 +觉 +汉 +画 +功 +巴 +跟 +虽 +杂 +飞 +检 +吸 +助 +升 +阳 +互 +初 +创 +抗 +考 +投 +坏 +策 +古 +径 +换 +未 +跑 +留 +钢 +曾 +端 +责 +站 +简 +述 +钱 +副 +尽 +帝 +射 +草 +冲 +承 +独 +令 +限 +阿 +宣 +环 +双 +请 +超 +微 +让 +控 +州 +良 +轴 +找 +否 +纪 +益 +依 +优 +顶 +础 +载 +倒 +房 +突 +坐 +粉 +敌 +略 +客 +袁 +冷 +胜 +绝 +析 +块 +剂 +测 +丝 +协 +诉 +念 +陈 +仍 +罗 +盐 +友 +洋 +错 +苦 +夜 +刑 +移 +频 +逐 +靠 +混 +母 +短 +皮 +终 +聚 +汽 +村 +云 +哪 +既 +距 +卫 +停 +烈 +央 +察 +烧 +迅 +境 +若 +印 +洲 +刻 +括 +激 +孔 +搞 +甚 +室 +待 +核 +校 +散 +侵 +吧 +甲 +游 +久 +菜 +味 +旧 +模 +湖 +货 +损 +预 +阻 +毫 +普 +稳 +乙 +妈 +植 +息 +扩 +银 +语 +挥 +酒 +守 +拿 +序 +纸 +医 +缺 +雨 +吗 +针 +刘 +啊 +急 +唱 +误 +训 +愿 +审 +附 +获 +茶 +鲜 +粮 +斤 +孩 +脱 +硫 +肥 +善 +龙 +演 +父 +渐 +血 +欢 +械 +掌 +歌 +沙 +刚 +攻 +谓 +盾 +讨 +晚 +粒 +乱 +燃 +矛 +乎 +杀 +药 +宁 +鲁 +贵 +钟 +煤 +读 +班 +伯 +香 +介 +迫 +句 +丰 +培 +握 +兰 +担 +弦 +蛋 +沉 +假 +穿 +执 +答 +乐 +谁 +顺 +烟 +缩 +征 +脸 +喜 +松 +脚 +困 +异 +免 +背 +星 +福 +买 +染 +井 +概 +慢 +怕 +磁 +倍 +祖 +皇 +促 +静 +补 +评 +翻 +肉 +践 +尼 +衣 +宽 +扬 +棉 +希 +伤 +操 +垂 +秋 +宜 +氢 +套 +督 +振 +架 +亮 +末 +宪 +庆 +编 +牛 +触 +映 +雷 +销 +诗 +座 +居 +抓 +裂 +胞 +呼 +娘 +景 +威 +绿 +晶 +厚 +盟 +衡 +鸡 +孙 +延 +危 +胶 +屋 +乡 +临 +陆 +顾 +掉 +呀 +灯 +岁 +措 +束 +耐 +剧 +玉 +赵 +跳 +哥 +季 +课 +凯 +胡 +额 +款 +绍 +卷 +齐 +伟 +蒸 +殖 +永 +宗 +苗 +川 +炉 +岩 +弱 +零 +杨 +奏 +沿 +露 +杆 +探 +滑 +镇 +饭 +浓 +航 +怀 +赶 +库 +夺 +伊 +灵 +税 +途 +灭 +赛 +归 +召 +鼓 +播 +盘 +裁 +险 +康 +唯 +录 +菌 +纯 +借 +糖 +盖 +横 +符 +私 +努 +堂 +域 +枪 +润 +幅 +哈 +竟 +熟 +虫 +泽 +脑 +壤 +碳 +欧 +遍 +侧 +寨 +敢 +彻 +虑 +斜 +薄 +庭 +纳 +弹 +饲 +伸 +折 +麦 +湿 +暗 +荷 +瓦 +塞 +床 +筑 +恶 +户 +访 +塔 +奇 +透 +梁 +刀 +旋 +迹 +卡 +氯 +遇 +份 +毒 +泥 +退 +洗 +摆 +灰 +彩 +卖 +耗 +夏 +择 +忙 +铜 +献 +硬 +予 +繁 +圈 +雪 +函 +亦 +抽 +篇 +阵 +阴 +丁 +尺 +追 +堆 +雄 +迎 +泛 +爸 +楼 +避 +谋 +吨 +野 +猪 +旗 +累 +偏 +典 +馆 +索 +秦 +脂 +潮 +爷 +豆 +忽 +托 +惊 +塑 +遗 +愈 +朱 +替 +纤 +粗 +倾 +尚 +痛 +楚 +谢 +奋 +购 +磨 +君 +池 +旁 +碎 +骨 +监 +捕 +弟 +暴 +割 +贯 +殊 +释 +词 +亡 +壁 +顿 +宝 +午 +尘 +闻 +揭 +炮 +残 +冬 +桥 +妇 +警 +综 +招 +吴 +付 +浮 +遭 +徐 +您 +摇 +谷 +赞 +箱 +隔 +订 +男 +吹 +园 +纷 +唐 +败 +宋 +玻 +巨 +耕 +坦 +荣 +闭 +湾 +键 +凡 +驻 +锅 +救 +恩 +剥 +凝 +碱 +齿 +截 +炼 +麻 +纺 +禁 +废 +盛 +版 +缓 +净 +睛 +昌 +婚 +涉 +筒 +嘴 +插 +岸 +朗 +庄 +街 +藏 +姑 +贸 +腐 +奴 +啦 +惯 +乘 +伙 +恢 +匀 +纱 +扎 +辩 +耳 +彪 +臣 +亿 +璃 +抵 +脉 +秀 +萨 +俄 +网 +舞 +店 +喷 +纵 +寸 +汗 +挂 +洪 +贺 +闪 +柬 +爆 +烯 +津 +稻 +墙 +软 +勇 +像 +滚 +厘 +蒙 +芳 +肯 +坡 +柱 +荡 +腿 +仪 +旅 +尾 +轧 +冰 +贡 +登 +黎 +削 +钻 +勒 +逃 +障 +氨 +郭 +峰 +币 +港 +伏 +轨 +亩 +毕 +擦 +莫 +刺 +浪 +秘 +援 +株 +健 +售 +股 +岛 +甘 +泡 +睡 +童 +铸 +汤 +阀 +休 +汇 +舍 +牧 +绕 +炸 +哲 +磷 +绩 +朋 +淡 +尖 +启 +陷 +柴 +呈 +徒 +颜 +泪 +稍 +忘 +泵 +蓝 +拖 +洞 +授 +镜 +辛 +壮 +锋 +贫 +虚 +弯 +摩 +泰 +幼 +廷 +尊 +窗 +纲 +弄 +隶 +疑 +氏 +宫 +姐 +震 +瑞 +怪 +尤 +琴 +循 +描 +膜 +违 +夹 +腰 +缘 +珠 +穷 +森 +枝 +竹 +沟 +催 +绳 +忆 +邦 +剩 +幸 +浆 +栏 +拥 +牙 +贮 +礼 +滤 +钠 +纹 +罢 +拍 +咱 +喊 +袖 +埃 +勤 +罚 +焦 +潜 +伍 +墨 +欲 +缝 +姓 +刊 +饱 +仿 +奖 +铝 +鬼 +丽 +跨 +默 +挖 +链 +扫 +喝 +袋 +炭 +污 +幕 +诸 +弧 +励 +梅 +奶 +洁 +灾 +舟 +鉴 +苯 +讼 +抱 +毁 +懂 +寒 +智 +埔 +寄 +届 +跃 +渡 +挑 +丹 +艰 +贝 +碰 +拔 +爹 +戴 +码 +梦 +芽 +熔 +赤 +渔 +哭 +敬 +颗 +奔 +铅 +仲 +虎 +稀 +妹 +乏 +珍 +申 +桌 +遵 +允 +隆 +螺 +仓 +魏 +锐 +晓 +氮 +兼 +隐 +碍 +赫 +拨 +忠 +肃 +缸 +牵 +抢 +博 +巧 +壳 +兄 +杜 +讯 +诚 +碧 +祥 +柯 +页 +巡 +矩 +悲 +灌 +龄 +伦 +票 +寻 +桂 +铺 +圣 +恐 +恰 +郑 +趣 +抬 +荒 +腾 +贴 +柔 +滴 +猛 +阔 +辆 +妻 +填 +撤 +储 +签 +闹 +扰 +紫 +砂 +递 +戏 +吊 +陶 +伐 +喂 +疗 +瓶 +婆 +抚 +臂 +摸 +忍 +虾 +蜡 +邻 +胸 +巩 +挤 +偶 +弃 +槽 +劲 +乳 +邓 +吉 +仁 +烂 +砖 +租 +乌 +舰 +伴 +瓜 +浅 +丙 +暂 +燥 +橡 +柳 +迷 +暖 +牌 +秧 +胆 +详 +簧 +踏 +瓷 +谱 +呆 +宾 +糊 +洛 +辉 +愤 +竞 +隙 +怒 +粘 +乃 +绪 +肩 +籍 +敏 +涂 +熙 +皆 +侦 +悬 +掘 +享 +纠 +醒 +狂 +锁 +淀 +恨 +牲 +霸 +爬 +赏 +逆 +玩 +陵 +祝 +秒 +浙 +貌 +役 +彼 +悉 +鸭 +趋 +凤 +晨 +畜 +辈 +秩 +卵 +署 +梯 +炎 +滩 +棋 +驱 +筛 +峡 +冒 +啥 +寿 +译 +浸 +泉 +帽 +迟 +硅 +疆 +贷 +漏 +稿 +冠 +嫩 +胁 +芯 +牢 +叛 +蚀 +奥 +鸣 +岭 +羊 +凭 +串 +塘 +绘 +酵 +融 +盆 +锡 +庙 +筹 +冻 +辅 +摄 +袭 +筋 +拒 +僚 +旱 +钾 +鸟 +漆 +沈 +眉 +疏 +添 +棒 +穗 +硝 +韩 +逼 +扭 +侨 +凉 +挺 +碗 +栽 +炒 +杯 +患 +馏 +劝 +豪 +辽 +勃 +鸿 +旦 +吏 +拜 +狗 +埋 +辊 +掩 +饮 +搬 +骂 +辞 +勾 +扣 +估 +蒋 +绒 +雾 +丈 +朵 +姆 +拟 +宇 +辑 +陕 +雕 +偿 +蓄 +崇 +剪 +倡 +厅 +咬 +驶 +薯 +刷 +斥 +番 +赋 +奉 +佛 +浇 +漫 +曼 +扇 +钙 +桃 +扶 +仔 +返 +俗 +亏 +腔 +鞋 +棱 +覆 +框 +悄 +叔 +撞 +骗 +勘 +旺 +沸 +孤 +吐 +孟 +渠 +屈 +疾 +妙 +惜 +仰 +狠 +胀 +谐 +抛 +霉 +桑 +岗 +嘛 +衰 +盗 +渗 +脏 +赖 +涌 +甜 +曹 +阅 +肌 +哩 +厉 +烃 +纬 +毅 +昨 +伪 +症 +煮 +叹 +钉 +搭 +茎 +笼 +酷 +偷 +弓 +锥 +恒 +杰 +坑 +鼻 +翼 +纶 +叙 +狱 +逮 +罐 +络 +棚 +抑 +膨 +蔬 +寺 +骤 +穆 +冶 +枯 +册 +尸 +凸 +绅 +坯 +牺 +焰 +轰 +欣 +晋 +瘦 +御 +锭 +锦 +丧 +旬 +锻 +垄 +搜 +扑 +邀 +亭 +酯 +迈 +舒 +脆 +酶 +闲 +忧 +酚 +顽 +羽 +涨 +卸 +仗 +陪 +辟 +惩 +杭 +姚 +肚 +捉 +飘 +漂 +昆 +欺 +吾 +郎 +烷 +汁 +呵 +饰 +萧 +雅 +邮 +迁 +燕 +撒 +姻 +赴 +宴 +烦 +债 +帐 +斑 +铃 +旨 +醇 +董 +饼 +雏 +姿 +拌 +傅 +腹 +妥 +揉 +贤 +拆 +歪 +葡 +胺 +丢 +浩 +徽 +昂 +垫 +挡 +览 +贪 +慰 +缴 +汪 +慌 +冯 +诺 +姜 +谊 +凶 +劣 +诬 +耀 +昏 +躺 +盈 +骑 +乔 +溪 +丛 +卢 +抹 +闷 +咨 +刮 +驾 +缆 +悟 +摘 +铒 +掷 +颇 +幻 +柄 +惠 +惨 +佳 +仇 +腊 +窝 +涤 +剑 +瞧 +堡 +泼 +葱 +罩 +霍 +捞 +胎 +苍 +滨 +俩 +捅 +湘 +砍 +霞 +邵 +萄 +疯 +淮 +遂 +熊 +粪 +烘 +宿 +档 +戈 +驳 +嫂 +裕 +徙 +箭 +捐 +肠 +撑 +晒 +辨 +殿 +莲 +摊 +搅 +酱 +屏 +疫 +哀 +蔡 +堵 +沫 +皱 +畅 +叠 +阁 +莱 +敲 +辖 +钩 +痕 +坝 +巷 +饿 +祸 +丘 +玄 +溜 +曰 +逻 +彭 +尝 +卿 +妨 +艇 +吞 +韦 +怨 +矮 +歇 diff --git a/lbrynet/wallet/wordlist/english.txt b/lbrynet/wallet/wordlist/english.txt new file mode 100644 index 000000000..942040ed5 --- /dev/null +++ b/lbrynet/wallet/wordlist/english.txt @@ -0,0 +1,2048 @@ +abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo diff --git a/lbrynet/wallet/wordlist/japanese.txt b/lbrynet/wallet/wordlist/japanese.txt new file mode 100644 index 000000000..c4c9dca4e --- /dev/null +++ b/lbrynet/wallet/wordlist/japanese.txt @@ -0,0 +1,2048 @@ +あいこくしん +あいさつ +あいだ +あおぞら +あかちゃん +あきる +あけがた +あける +あこがれる +あさい +あさひ +あしあと +あじわう +あずかる +あずき +あそぶ +あたえる +あたためる +あたりまえ +あたる +あつい +あつかう +あっしゅく +あつまり +あつめる +あてな +あてはまる +あひる +あぶら +あぶる +あふれる +あまい +あまど +あまやかす +あまり +あみもの +あめりか +あやまる +あゆむ +あらいぐま +あらし +あらすじ +あらためる +あらゆる +あらわす +ありがとう +あわせる +あわてる +あんい +あんがい +あんこ +あんぜん +あんてい +あんない +あんまり +いいだす +いおん +いがい +いがく +いきおい +いきなり +いきもの +いきる +いくじ +いくぶん +いけばな +いけん +いこう +いこく +いこつ +いさましい +いさん +いしき +いじゅう +いじょう +いじわる +いずみ +いずれ +いせい +いせえび +いせかい +いせき +いぜん +いそうろう +いそがしい +いだい +いだく +いたずら +いたみ +いたりあ +いちおう +いちじ +いちど +いちば +いちぶ +いちりゅう +いつか +いっしゅん +いっせい +いっそう +いったん +いっち +いってい +いっぽう +いてざ +いてん +いどう +いとこ +いない +いなか +いねむり +いのち +いのる +いはつ +いばる +いはん +いびき +いひん +いふく +いへん +いほう +いみん +いもうと +いもたれ +いもり +いやがる +いやす +いよかん +いよく +いらい +いらすと +いりぐち +いりょう +いれい +いれもの +いれる +いろえんぴつ +いわい +いわう +いわかん +いわば +いわゆる +いんげんまめ +いんさつ +いんしょう +いんよう +うえき +うえる +うおざ +うがい +うかぶ +うかべる +うきわ +うくらいな +うくれれ +うけたまわる +うけつけ +うけとる +うけもつ +うける +うごかす +うごく +うこん +うさぎ +うしなう +うしろがみ +うすい +うすぎ +うすぐらい +うすめる +うせつ +うちあわせ +うちがわ +うちき +うちゅう +うっかり +うつくしい +うったえる +うつる +うどん +うなぎ +うなじ +うなずく +うなる +うねる +うのう +うぶげ +うぶごえ +うまれる +うめる +うもう +うやまう +うよく +うらがえす +うらぐち +うらない +うりあげ +うりきれ +うるさい +うれしい +うれゆき +うれる +うろこ +うわき +うわさ +うんこう +うんちん +うんてん +うんどう +えいえん +えいが +えいきょう +えいご +えいせい +えいぶん +えいよう +えいわ +えおり +えがお +えがく +えきたい +えくせる +えしゃく +えすて +えつらん +えのぐ +えほうまき +えほん +えまき +えもじ +えもの +えらい +えらぶ +えりあ +えんえん +えんかい +えんぎ +えんげき +えんしゅう +えんぜつ +えんそく +えんちょう +えんとつ +おいかける +おいこす +おいしい +おいつく +おうえん +おうさま +おうじ +おうせつ +おうたい +おうふく +おうべい +おうよう +おえる +おおい +おおう +おおどおり +おおや +おおよそ +おかえり +おかず +おがむ +おかわり +おぎなう +おきる +おくさま +おくじょう +おくりがな +おくる +おくれる +おこす +おこなう +おこる +おさえる +おさない +おさめる +おしいれ +おしえる +おじぎ +おじさん +おしゃれ +おそらく +おそわる +おたがい +おたく +おだやか +おちつく +おっと +おつり +おでかけ +おとしもの +おとなしい +おどり +おどろかす +おばさん +おまいり +おめでとう +おもいで +おもう +おもたい +おもちゃ +おやつ +おやゆび +およぼす +おらんだ +おろす +おんがく +おんけい +おんしゃ +おんせん +おんだん +おんちゅう +おんどけい +かあつ +かいが +がいき +がいけん +がいこう +かいさつ +かいしゃ +かいすいよく +かいぜん +かいぞうど +かいつう +かいてん +かいとう +かいふく +がいへき +かいほう +かいよう +がいらい +かいわ +かえる +かおり +かかえる +かがく +かがし +かがみ +かくご +かくとく +かざる +がぞう +かたい +かたち +がちょう +がっきゅう +がっこう +がっさん +がっしょう +かなざわし +かのう +がはく +かぶか +かほう +かほご +かまう +かまぼこ +かめれおん +かゆい +かようび +からい +かるい +かろう +かわく +かわら +がんか +かんけい +かんこう +かんしゃ +かんそう +かんたん +かんち +がんばる +きあい +きあつ +きいろ +ぎいん +きうい +きうん +きえる +きおう +きおく +きおち +きおん +きかい +きかく +きかんしゃ +ききて +きくばり +きくらげ +きけんせい +きこう +きこえる +きこく +きさい +きさく +きさま +きさらぎ +ぎじかがく +ぎしき +ぎじたいけん +ぎじにってい +ぎじゅつしゃ +きすう +きせい +きせき +きせつ +きそう +きぞく +きぞん +きたえる +きちょう +きつえん +ぎっちり +きつつき +きつね +きてい +きどう +きどく +きない +きなが +きなこ +きぬごし +きねん +きのう +きのした +きはく +きびしい +きひん +きふく +きぶん +きぼう +きほん +きまる +きみつ +きむずかしい +きめる +きもだめし +きもち +きもの +きゃく +きやく +ぎゅうにく +きよう +きょうりゅう +きらい +きらく +きりん +きれい +きれつ +きろく +ぎろん +きわめる +ぎんいろ +きんかくじ +きんじょ +きんようび +ぐあい +くいず +くうかん +くうき +くうぐん +くうこう +ぐうせい +くうそう +ぐうたら +くうふく +くうぼ +くかん +くきょう +くげん +ぐこう +くさい +くさき +くさばな +くさる +くしゃみ +くしょう +くすのき +くすりゆび +くせげ +くせん +ぐたいてき +くださる +くたびれる +くちこみ +くちさき +くつした +ぐっすり +くつろぐ +くとうてん +くどく +くなん +くねくね +くのう +くふう +くみあわせ +くみたてる +くめる +くやくしょ +くらす +くらべる +くるま +くれる +くろう +くわしい +ぐんかん +ぐんしょく +ぐんたい +ぐんて +けあな +けいかく +けいけん +けいこ +けいさつ +げいじゅつ +けいたい +げいのうじん +けいれき +けいろ +けおとす +けおりもの +げきか +げきげん +げきだん +げきちん +げきとつ +げきは +げきやく +げこう +げこくじょう +げざい +けさき +げざん +けしき +けしごむ +けしょう +げすと +けたば +けちゃっぷ +けちらす +けつあつ +けつい +けつえき +けっこん +けつじょ +けっせき +けってい +けつまつ +げつようび +げつれい +けつろん +げどく +けとばす +けとる +けなげ +けなす +けなみ +けぬき +げねつ +けねん +けはい +げひん +けぶかい +げぼく +けまり +けみかる +けむし +けむり +けもの +けらい +けろけろ +けわしい +けんい +けんえつ +けんお +けんか +げんき +けんげん +けんこう +けんさく +けんしゅう +けんすう +げんそう +けんちく +けんてい +けんとう +けんない +けんにん +げんぶつ +けんま +けんみん +けんめい +けんらん +けんり +こあくま +こいぬ +こいびと +ごうい +こうえん +こうおん +こうかん +ごうきゅう +ごうけい +こうこう +こうさい +こうじ +こうすい +ごうせい +こうそく +こうたい +こうちゃ +こうつう +こうてい +こうどう +こうない +こうはい +ごうほう +ごうまん +こうもく +こうりつ +こえる +こおり +ごかい +ごがつ +ごかん +こくご +こくさい +こくとう +こくない +こくはく +こぐま +こけい +こける +ここのか +こころ +こさめ +こしつ +こすう +こせい +こせき +こぜん +こそだて +こたい +こたえる +こたつ +こちょう +こっか +こつこつ +こつばん +こつぶ +こてい +こてん +ことがら +ことし +ことば +ことり +こなごな +こねこね +このまま +このみ +このよ +ごはん +こひつじ +こふう +こふん +こぼれる +ごまあぶら +こまかい +ごますり +こまつな +こまる +こむぎこ +こもじ +こもち +こもの +こもん +こやく +こやま +こゆう +こゆび +こよい +こよう +こりる +これくしょん +ころっけ +こわもて +こわれる +こんいん +こんかい +こんき +こんしゅう +こんすい +こんだて +こんとん +こんなん +こんびに +こんぽん +こんまけ +こんや +こんれい +こんわく +ざいえき +さいかい +さいきん +ざいげん +ざいこ +さいしょ +さいせい +ざいたく +ざいちゅう +さいてき +ざいりょう +さうな +さかいし +さがす +さかな +さかみち +さがる +さぎょう +さくし +さくひん +さくら +さこく +さこつ +さずかる +ざせき +さたん +さつえい +ざつおん +ざっか +ざつがく +さっきょく +ざっし +さつじん +ざっそう +さつたば +さつまいも +さてい +さといも +さとう +さとおや +さとし +さとる +さのう +さばく +さびしい +さべつ +さほう +さほど +さます +さみしい +さみだれ +さむけ +さめる +さやえんどう +さゆう +さよう +さよく +さらだ +ざるそば +さわやか +さわる +さんいん +さんか +さんきゃく +さんこう +さんさい +ざんしょ +さんすう +さんせい +さんそ +さんち +さんま +さんみ +さんらん +しあい +しあげ +しあさって +しあわせ +しいく +しいん +しうち +しえい +しおけ +しかい +しかく +じかん +しごと +しすう +じだい +したうけ +したぎ +したて +したみ +しちょう +しちりん +しっかり +しつじ +しつもん +してい +してき +してつ +じてん +じどう +しなぎれ +しなもの +しなん +しねま +しねん +しのぐ +しのぶ +しはい +しばかり +しはつ +しはらい +しはん +しひょう +しふく +じぶん +しへい +しほう +しほん +しまう +しまる +しみん +しむける +じむしょ +しめい +しめる +しもん +しゃいん +しゃうん +しゃおん +じゃがいも +しやくしょ +しゃくほう +しゃけん +しゃこ +しゃざい +しゃしん +しゃせん +しゃそう +しゃたい +しゃちょう +しゃっきん +じゃま +しゃりん +しゃれい +じゆう +じゅうしょ +しゅくはく +じゅしん +しゅっせき +しゅみ +しゅらば +じゅんばん +しょうかい +しょくたく +しょっけん +しょどう +しょもつ +しらせる +しらべる +しんか +しんこう +じんじゃ +しんせいじ +しんちく +しんりん +すあげ +すあし +すあな +ずあん +すいえい +すいか +すいとう +ずいぶん +すいようび +すうがく +すうじつ +すうせん +すおどり +すきま +すくう +すくない +すける +すごい +すこし +ずさん +すずしい +すすむ +すすめる +すっかり +ずっしり +ずっと +すてき +すてる +すねる +すのこ +すはだ +すばらしい +ずひょう +ずぶぬれ +すぶり +すふれ +すべて +すべる +ずほう +すぼん +すまい +すめし +すもう +すやき +すらすら +するめ +すれちがう +すろっと +すわる +すんぜん +すんぽう +せあぶら +せいかつ +せいげん +せいじ +せいよう +せおう +せかいかん +せきにん +せきむ +せきゆ +せきらんうん +せけん +せこう +せすじ +せたい +せたけ +せっかく +せっきゃく +ぜっく +せっけん +せっこつ +せっさたくま +せつぞく +せつだん +せつでん +せっぱん +せつび +せつぶん +せつめい +せつりつ +せなか +せのび +せはば +せびろ +せぼね +せまい +せまる +せめる +せもたれ +せりふ +ぜんあく +せんい +せんえい +せんか +せんきょ +せんく +せんげん +ぜんご +せんさい +せんしゅ +せんすい +せんせい +せんぞ +せんたく +せんちょう +せんてい +せんとう +せんぬき +せんねん +せんぱい +ぜんぶ +ぜんぽう +せんむ +せんめんじょ +せんもん +せんやく +せんゆう +せんよう +ぜんら +ぜんりゃく +せんれい +せんろ +そあく +そいとげる +そいね +そうがんきょう +そうき +そうご +そうしん +そうだん +そうなん +そうび +そうめん +そうり +そえもの +そえん +そがい +そげき +そこう +そこそこ +そざい +そしな +そせい +そせん +そそぐ +そだてる +そつう +そつえん +そっかん +そつぎょう +そっけつ +そっこう +そっせん +そっと +そとがわ +そとづら +そなえる +そなた +そふぼ +そぼく +そぼろ +そまつ +そまる +そむく +そむりえ +そめる +そもそも +そよかぜ +そらまめ +そろう +そんかい +そんけい +そんざい +そんしつ +そんぞく +そんちょう +ぞんび +ぞんぶん +そんみん +たあい +たいいん +たいうん +たいえき +たいおう +だいがく +たいき +たいぐう +たいけん +たいこ +たいざい +だいじょうぶ +だいすき +たいせつ +たいそう +だいたい +たいちょう +たいてい +だいどころ +たいない +たいねつ +たいのう +たいはん +だいひょう +たいふう +たいへん +たいほ +たいまつばな +たいみんぐ +たいむ +たいめん +たいやき +たいよう +たいら +たいりょく +たいる +たいわん +たうえ +たえる +たおす +たおる +たおれる +たかい +たかね +たきび +たくさん +たこく +たこやき +たさい +たしざん +だじゃれ +たすける +たずさわる +たそがれ +たたかう +たたく +ただしい +たたみ +たちばな +だっかい +だっきゃく +だっこ +だっしゅつ +だったい +たてる +たとえる +たなばた +たにん +たぬき +たのしみ +たはつ +たぶん +たべる +たぼう +たまご +たまる +だむる +ためいき +ためす +ためる +たもつ +たやすい +たよる +たらす +たりきほんがん +たりょう +たりる +たると +たれる +たれんと +たろっと +たわむれる +だんあつ +たんい +たんおん +たんか +たんき +たんけん +たんご +たんさん +たんじょうび +だんせい +たんそく +たんたい +だんち +たんてい +たんとう +だんな +たんにん +だんねつ +たんのう +たんぴん +だんぼう +たんまつ +たんめい +だんれつ +だんろ +だんわ +ちあい +ちあん +ちいき +ちいさい +ちえん +ちかい +ちから +ちきゅう +ちきん +ちけいず +ちけん +ちこく +ちさい +ちしき +ちしりょう +ちせい +ちそう +ちたい +ちたん +ちちおや +ちつじょ +ちてき +ちてん +ちぬき +ちぬり +ちのう +ちひょう +ちへいせん +ちほう +ちまた +ちみつ +ちみどろ +ちめいど +ちゃんこなべ +ちゅうい +ちゆりょく +ちょうし +ちょさくけん +ちらし +ちらみ +ちりがみ +ちりょう +ちるど +ちわわ +ちんたい +ちんもく +ついか +ついたち +つうか +つうじょう +つうはん +つうわ +つかう +つかれる +つくね +つくる +つけね +つける +つごう +つたえる +つづく +つつじ +つつむ +つとめる +つながる +つなみ +つねづね +つのる +つぶす +つまらない +つまる +つみき +つめたい +つもり +つもる +つよい +つるぼ +つるみく +つわもの +つわり +てあし +てあて +てあみ +ていおん +ていか +ていき +ていけい +ていこく +ていさつ +ていし +ていせい +ていたい +ていど +ていねい +ていひょう +ていへん +ていぼう +てうち +ておくれ +てきとう +てくび +でこぼこ +てさぎょう +てさげ +てすり +てそう +てちがい +てちょう +てつがく +てつづき +でっぱ +てつぼう +てつや +でぬかえ +てぬき +てぬぐい +てのひら +てはい +てぶくろ +てふだ +てほどき +てほん +てまえ +てまきずし +てみじか +てみやげ +てらす +てれび +てわけ +てわたし +でんあつ +てんいん +てんかい +てんき +てんぐ +てんけん +てんごく +てんさい +てんし +てんすう +でんち +てんてき +てんとう +てんない +てんぷら +てんぼうだい +てんめつ +てんらんかい +でんりょく +でんわ +どあい +といれ +どうかん +とうきゅう +どうぐ +とうし +とうむぎ +とおい +とおか +とおく +とおす +とおる +とかい +とかす +ときおり +ときどき +とくい +とくしゅう +とくてん +とくに +とくべつ +とけい +とける +とこや +とさか +としょかん +とそう +とたん +とちゅう +とっきゅう +とっくん +とつぜん +とつにゅう +とどける +ととのえる +とない +となえる +となり +とのさま +とばす +どぶがわ +とほう +とまる +とめる +ともだち +ともる +どようび +とらえる +とんかつ +どんぶり +ないかく +ないこう +ないしょ +ないす +ないせん +ないそう +なおす +ながい +なくす +なげる +なこうど +なさけ +なたでここ +なっとう +なつやすみ +ななおし +なにごと +なにもの +なにわ +なのか +なふだ +なまいき +なまえ +なまみ +なみだ +なめらか +なめる +なやむ +ならう +ならび +ならぶ +なれる +なわとび +なわばり +にあう +にいがた +にうけ +におい +にかい +にがて +にきび +にくしみ +にくまん +にげる +にさんかたんそ +にしき +にせもの +にちじょう +にちようび +にっか +にっき +にっけい +にっこう +にっさん +にっしょく +にっすう +にっせき +にってい +になう +にほん +にまめ +にもつ +にやり +にゅういん +にりんしゃ +にわとり +にんい +にんか +にんき +にんげん +にんしき +にんずう +にんそう +にんたい +にんち +にんてい +にんにく +にんぷ +にんまり +にんむ +にんめい +にんよう +ぬいくぎ +ぬかす +ぬぐいとる +ぬぐう +ぬくもり +ぬすむ +ぬまえび +ぬめり +ぬらす +ぬんちゃく +ねあげ +ねいき +ねいる +ねいろ +ねぐせ +ねくたい +ねくら +ねこぜ +ねこむ +ねさげ +ねすごす +ねそべる +ねだん +ねつい +ねっしん +ねつぞう +ねったいぎょ +ねぶそく +ねふだ +ねぼう +ねほりはほり +ねまき +ねまわし +ねみみ +ねむい +ねむたい +ねもと +ねらう +ねわざ +ねんいり +ねんおし +ねんかん +ねんきん +ねんぐ +ねんざ +ねんし +ねんちゃく +ねんど +ねんぴ +ねんぶつ +ねんまつ +ねんりょう +ねんれい +のいず +のおづま +のがす +のきなみ +のこぎり +のこす +のこる +のせる +のぞく +のぞむ +のたまう +のちほど +のっく +のばす +のはら +のべる +のぼる +のみもの +のやま +のらいぬ +のらねこ +のりもの +のりゆき +のれん +のんき +ばあい +はあく +ばあさん +ばいか +ばいく +はいけん +はいご +はいしん +はいすい +はいせん +はいそう +はいち +ばいばい +はいれつ +はえる +はおる +はかい +ばかり +はかる +はくしゅ +はけん +はこぶ +はさみ +はさん +はしご +ばしょ +はしる +はせる +ぱそこん +はそん +はたん +はちみつ +はつおん +はっかく +はづき +はっきり +はっくつ +はっけん +はっこう +はっさん +はっしん +はったつ +はっちゅう +はってん +はっぴょう +はっぽう +はなす +はなび +はにかむ +はぶらし +はみがき +はむかう +はめつ +はやい +はやし +はらう +はろうぃん +はわい +はんい +はんえい +はんおん +はんかく +はんきょう +ばんぐみ +はんこ +はんしゃ +はんすう +はんだん +ぱんち +ぱんつ +はんてい +はんとし +はんのう +はんぱ +はんぶん +はんぺん +はんぼうき +はんめい +はんらん +はんろん +ひいき +ひうん +ひえる +ひかく +ひかり +ひかる +ひかん +ひくい +ひけつ +ひこうき +ひこく +ひさい +ひさしぶり +ひさん +びじゅつかん +ひしょ +ひそか +ひそむ +ひたむき +ひだり +ひたる +ひつぎ +ひっこし +ひっし +ひつじゅひん +ひっす +ひつぜん +ぴったり +ぴっちり +ひつよう +ひてい +ひとごみ +ひなまつり +ひなん +ひねる +ひはん +ひびく +ひひょう +ひほう +ひまわり +ひまん +ひみつ +ひめい +ひめじし +ひやけ +ひやす +ひよう +びょうき +ひらがな +ひらく +ひりつ +ひりょう +ひるま +ひるやすみ +ひれい +ひろい +ひろう +ひろき +ひろゆき +ひんかく +ひんけつ +ひんこん +ひんしゅ +ひんそう +ぴんち +ひんぱん +びんぼう +ふあん +ふいうち +ふうけい +ふうせん +ぷうたろう +ふうとう +ふうふ +ふえる +ふおん +ふかい +ふきん +ふくざつ +ふくぶくろ +ふこう +ふさい +ふしぎ +ふじみ +ふすま +ふせい +ふせぐ +ふそく +ぶたにく +ふたん +ふちょう +ふつう +ふつか +ふっかつ +ふっき +ふっこく +ぶどう +ふとる +ふとん +ふのう +ふはい +ふひょう +ふへん +ふまん +ふみん +ふめつ +ふめん +ふよう +ふりこ +ふりる +ふるい +ふんいき +ぶんがく +ぶんぐ +ふんしつ +ぶんせき +ふんそう +ぶんぽう +へいあん +へいおん +へいがい +へいき +へいげん +へいこう +へいさ +へいしゃ +へいせつ +へいそ +へいたく +へいてん +へいねつ +へいわ +へきが +へこむ +べにいろ +べにしょうが +へらす +へんかん +べんきょう +べんごし +へんさい +へんたい +べんり +ほあん +ほいく +ぼうぎょ +ほうこく +ほうそう +ほうほう +ほうもん +ほうりつ +ほえる +ほおん +ほかん +ほきょう +ぼきん +ほくろ +ほけつ +ほけん +ほこう +ほこる +ほしい +ほしつ +ほしゅ +ほしょう +ほせい +ほそい +ほそく +ほたて +ほたる +ぽちぶくろ +ほっきょく +ほっさ +ほったん +ほとんど +ほめる +ほんい +ほんき +ほんけ +ほんしつ +ほんやく +まいにち +まかい +まかせる +まがる +まける +まこと +まさつ +まじめ +ますく +まぜる +まつり +まとめ +まなぶ +まぬけ +まねく +まほう +まもる +まゆげ +まよう +まろやか +まわす +まわり +まわる +まんが +まんきつ +まんぞく +まんなか +みいら +みうち +みえる +みがく +みかた +みかん +みけん +みこん +みじかい +みすい +みすえる +みせる +みっか +みつかる +みつける +みてい +みとめる +みなと +みなみかさい +みねらる +みのう +みのがす +みほん +みもと +みやげ +みらい +みりょく +みわく +みんか +みんぞく +むいか +むえき +むえん +むかい +むかう +むかえ +むかし +むぎちゃ +むける +むげん +むさぼる +むしあつい +むしば +むじゅん +むしろ +むすう +むすこ +むすぶ +むすめ +むせる +むせん +むちゅう +むなしい +むのう +むやみ +むよう +むらさき +むりょう +むろん +めいあん +めいうん +めいえん +めいかく +めいきょく +めいさい +めいし +めいそう +めいぶつ +めいれい +めいわく +めぐまれる +めざす +めした +めずらしい +めだつ +めまい +めやす +めんきょ +めんせき +めんどう +もうしあげる +もうどうけん +もえる +もくし +もくてき +もくようび +もちろん +もどる +もらう +もんく +もんだい +やおや +やける +やさい +やさしい +やすい +やすたろう +やすみ +やせる +やそう +やたい +やちん +やっと +やっぱり +やぶる +やめる +ややこしい +やよい +やわらかい +ゆうき +ゆうびんきょく +ゆうべ +ゆうめい +ゆけつ +ゆしゅつ +ゆせん +ゆそう +ゆたか +ゆちゃく +ゆでる +ゆにゅう +ゆびわ +ゆらい +ゆれる +ようい +ようか +ようきゅう +ようじ +ようす +ようちえん +よかぜ +よかん +よきん +よくせい +よくぼう +よけい +よごれる +よさん +よしゅう +よそう +よそく +よっか +よてい +よどがわく +よねつ +よやく +よゆう +よろこぶ +よろしい +らいう +らくがき +らくご +らくさつ +らくだ +らしんばん +らせん +らぞく +らたい +らっか +られつ +りえき +りかい +りきさく +りきせつ +りくぐん +りくつ +りけん +りこう +りせい +りそう +りそく +りてん +りねん +りゆう +りゅうがく +りよう +りょうり +りょかん +りょくちゃ +りょこう +りりく +りれき +りろん +りんご +るいけい +るいさい +るいじ +るいせき +るすばん +るりがわら +れいかん +れいぎ +れいせい +れいぞうこ +れいとう +れいぼう +れきし +れきだい +れんあい +れんけい +れんこん +れんさい +れんしゅう +れんぞく +れんらく +ろうか +ろうご +ろうじん +ろうそく +ろくが +ろこつ +ろじうら +ろしゅつ +ろせん +ろてん +ろめん +ろれつ +ろんぎ +ろんぱ +ろんぶん +ろんり +わかす +わかめ +わかやま +わかれる +わしつ +わじまし +わすれもの +わらう +われる diff --git a/lbrynet/wallet/wordlist/portuguese.txt b/lbrynet/wallet/wordlist/portuguese.txt new file mode 100644 index 000000000..394c88da2 --- /dev/null +++ b/lbrynet/wallet/wordlist/portuguese.txt @@ -0,0 +1,1654 @@ +# Copyright (c) 2014, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +abaular +abdominal +abeto +abissinio +abjeto +ablucao +abnegar +abotoar +abrutalhar +absurdo +abutre +acautelar +accessorios +acetona +achocolatado +acirrar +acne +acovardar +acrostico +actinomicete +acustico +adaptavel +adeus +adivinho +adjunto +admoestar +adnominal +adotivo +adquirir +adriatico +adsorcao +adutora +advogar +aerossol +afazeres +afetuoso +afixo +afluir +afortunar +afrouxar +aftosa +afunilar +agentes +agito +aglutinar +aiatola +aimore +aino +aipo +airoso +ajeitar +ajoelhar +ajudante +ajuste +alazao +albumina +alcunha +alegria +alexandre +alforriar +alguns +alhures +alivio +almoxarife +alotropico +alpiste +alquimista +alsaciano +altura +aluviao +alvura +amazonico +ambulatorio +ametodico +amizades +amniotico +amovivel +amurada +anatomico +ancorar +anexo +anfora +aniversario +anjo +anotar +ansioso +anturio +anuviar +anverso +anzol +aonde +apaziguar +apito +aplicavel +apoteotico +aprimorar +aprumo +apto +apuros +aquoso +arauto +arbusto +arduo +aresta +arfar +arguto +aritmetico +arlequim +armisticio +aromatizar +arpoar +arquivo +arrumar +arsenio +arturiano +aruaque +arvores +asbesto +ascorbico +aspirina +asqueroso +assustar +astuto +atazanar +ativo +atletismo +atmosferico +atormentar +atroz +aturdir +audivel +auferir +augusto +aula +aumento +aurora +autuar +avatar +avexar +avizinhar +avolumar +avulso +axiomatico +azerbaijano +azimute +azoto +azulejo +bacteriologista +badulaque +baforada +baixote +bajular +balzaquiana +bambuzal +banzo +baoba +baqueta +barulho +bastonete +batuta +bauxita +bavaro +bazuca +bcrepuscular +beato +beduino +begonia +behaviorista +beisebol +belzebu +bemol +benzido +beocio +bequer +berro +besuntar +betume +bexiga +bezerro +biatlon +biboca +bicuspide +bidirecional +bienio +bifurcar +bigorna +bijuteria +bimotor +binormal +bioxido +bipolarizacao +biquini +birutice +bisturi +bituca +biunivoco +bivalve +bizarro +blasfemo +blenorreia +blindar +bloqueio +blusao +boazuda +bofete +bojudo +bolso +bombordo +bonzo +botina +boquiaberto +bostoniano +botulismo +bourbon +bovino +boximane +bravura +brevidade +britar +broxar +bruno +bruxuleio +bubonico +bucolico +buda +budista +bueiro +buffer +bugre +bujao +bumerangue +burundines +busto +butique +buzios +caatinga +cabuqui +cacunda +cafuzo +cajueiro +camurca +canudo +caquizeiro +carvoeiro +casulo +catuaba +cauterizar +cebolinha +cedula +ceifeiro +celulose +cerzir +cesto +cetro +ceus +cevar +chavena +cheroqui +chita +chovido +chuvoso +ciatico +cibernetico +cicuta +cidreira +cientistas +cifrar +cigarro +cilio +cimo +cinzento +cioso +cipriota +cirurgico +cisto +citrico +ciumento +civismo +clavicula +clero +clitoris +cluster +coaxial +cobrir +cocota +codorniz +coexistir +cogumelo +coito +colusao +compaixao +comutativo +contentamento +convulsivo +coordenativa +coquetel +correto +corvo +costureiro +cotovia +covil +cozinheiro +cretino +cristo +crivo +crotalo +cruzes +cubo +cucuia +cueiro +cuidar +cujo +cultural +cunilingua +cupula +curvo +custoso +cutucar +czarismo +dablio +dacota +dados +daguerreotipo +daiquiri +daltonismo +damista +dantesco +daquilo +darwinista +dasein +dativo +deao +debutantes +decurso +deduzir +defunto +degustar +dejeto +deltoide +demover +denunciar +deputado +deque +dervixe +desvirtuar +deturpar +deuteronomio +devoto +dextrose +dezoito +diatribe +dicotomico +didatico +dietista +difuso +digressao +diluvio +diminuto +dinheiro +dinossauro +dioxido +diplomatico +dique +dirimivel +disturbio +diurno +divulgar +dizivel +doar +dobro +docura +dodoi +doer +dogue +doloso +domo +donzela +doping +dorsal +dossie +dote +doutro +doze +dravidico +dreno +driver +dropes +druso +dubnio +ducto +dueto +dulija +dundum +duodeno +duquesa +durou +duvidoso +duzia +ebano +ebrio +eburneo +echarpe +eclusa +ecossistema +ectoplasma +ecumenismo +eczema +eden +editorial +edredom +edulcorar +efetuar +efigie +efluvio +egiptologo +egresso +egua +einsteiniano +eira +eivar +eixos +ejetar +elastomero +eldorado +elixir +elmo +eloquente +elucidativo +emaranhar +embutir +emerito +emfa +emitir +emotivo +empuxo +emulsao +enamorar +encurvar +enduro +enevoar +enfurnar +enguico +enho +enigmista +enlutar +enormidade +enpreendimento +enquanto +enriquecer +enrugar +entusiastico +enunciar +envolvimento +enxuto +enzimatico +eolico +epiteto +epoxi +epura +equivoco +erario +erbio +ereto +erguido +erisipela +ermo +erotizar +erros +erupcao +ervilha +esburacar +escutar +esfuziante +esguio +esloveno +esmurrar +esoterismo +esperanca +espirito +espurio +essencialmente +esturricar +esvoacar +etario +eterno +etiquetar +etnologo +etos +etrusco +euclidiano +euforico +eugenico +eunuco +europio +eustaquio +eutanasia +evasivo +eventualidade +evitavel +evoluir +exaustor +excursionista +exercito +exfoliado +exito +exotico +expurgo +exsudar +extrusora +exumar +fabuloso +facultativo +fado +fagulha +faixas +fajuto +faltoso +famoso +fanzine +fapesp +faquir +fartura +fastio +faturista +fausto +favorito +faxineira +fazer +fealdade +febril +fecundo +fedorento +feerico +feixe +felicidade +felipe +feltro +femur +fenotipo +fervura +festivo +feto +feudo +fevereiro +fezinha +fiasco +fibra +ficticio +fiduciario +fiesp +fifa +figurino +fijiano +filtro +finura +fiorde +fiquei +firula +fissurar +fitoteca +fivela +fixo +flavio +flexor +flibusteiro +flotilha +fluxograma +fobos +foco +fofura +foguista +foie +foliculo +fominha +fonte +forum +fosso +fotossintese +foxtrote +fraudulento +frevo +frivolo +frouxo +frutose +fuba +fucsia +fugitivo +fuinha +fujao +fulustreco +fumo +funileiro +furunculo +fustigar +futurologo +fuxico +fuzue +gabriel +gado +gaelico +gafieira +gaguejo +gaivota +gajo +galvanoplastico +gamo +ganso +garrucha +gastronomo +gatuno +gaussiano +gaviao +gaxeta +gazeteiro +gear +geiser +geminiano +generoso +genuino +geossinclinal +gerundio +gestual +getulista +gibi +gigolo +gilete +ginseng +giroscopio +glaucio +glacial +gleba +glifo +glote +glutonia +gnostico +goela +gogo +goitaca +golpista +gomo +gonzo +gorro +gostou +goticula +gourmet +governo +gozo +graxo +grevista +grito +grotesco +gruta +guaxinim +gude +gueto +guizo +guloso +gume +guru +gustativo +gustavo +gutural +habitue +haitiano +halterofilista +hamburguer +hanseniase +happening +harpista +hastear +haveres +hebreu +hectometro +hedonista +hegira +helena +helminto +hemorroidas +henrique +heptassilabo +hertziano +hesitar +heterossexual +heuristico +hexagono +hiato +hibrido +hidrostatico +hieroglifo +hifenizar +higienizar +hilario +himen +hino +hippie +hirsuto +historiografia +hitlerista +hodometro +hoje +holograma +homus +honroso +hoquei +horto +hostilizar +hotentote +huguenote +humilde +huno +hurra +hutu +iaia +ialorixa +iambico +iansa +iaque +iara +iatista +iberico +ibis +icar +iceberg +icosagono +idade +ideologo +idiotice +idoso +iemenita +iene +igarape +iglu +ignorar +igreja +iguaria +iidiche +ilativo +iletrado +ilharga +ilimitado +ilogismo +ilustrissimo +imaturo +imbuzeiro +imerso +imitavel +imovel +imputar +imutavel +inaveriguavel +incutir +induzir +inextricavel +infusao +ingua +inhame +iniquo +injusto +inning +inoxidavel +inquisitorial +insustentavel +intumescimento +inutilizavel +invulneravel +inzoneiro +iodo +iogurte +ioio +ionosfera +ioruba +iota +ipsilon +irascivel +iris +irlandes +irmaos +iroques +irrupcao +isca +isento +islandes +isotopo +isqueiro +israelita +isso +isto +iterbio +itinerario +itrio +iuane +iugoslavo +jabuticabeira +jacutinga +jade +jagunco +jainista +jaleco +jambo +jantarada +japones +jaqueta +jarro +jasmim +jato +jaula +javel +jazz +jegue +jeitoso +jejum +jenipapo +jeova +jequitiba +jersei +jesus +jetom +jiboia +jihad +jilo +jingle +jipe +jocoso +joelho +joguete +joio +jojoba +jorro +jota +joule +joviano +jubiloso +judoca +jugular +juizo +jujuba +juliano +jumento +junto +jururu +justo +juta +juventude +labutar +laguna +laico +lajota +lanterninha +lapso +laquear +lastro +lauto +lavrar +laxativo +lazer +leasing +lebre +lecionar +ledo +leguminoso +leitura +lele +lemure +lento +leonardo +leopardo +lepton +leque +leste +letreiro +leucocito +levitico +lexicologo +lhama +lhufas +liame +licoroso +lidocaina +liliputiano +limusine +linotipo +lipoproteina +liquidos +lirismo +lisura +liturgico +livros +lixo +lobulo +locutor +lodo +logro +lojista +lombriga +lontra +loop +loquaz +lorota +losango +lotus +louvor +luar +lubrificavel +lucros +lugubre +luis +luminoso +luneta +lustroso +luto +luvas +luxuriante +luzeiro +maduro +maestro +mafioso +magro +maiuscula +majoritario +malvisto +mamute +manutencao +mapoteca +maquinista +marzipa +masturbar +matuto +mausoleu +mavioso +maxixe +mazurca +meandro +mecha +medusa +mefistofelico +megera +meirinho +melro +memorizar +menu +mequetrefe +mertiolate +mestria +metroviario +mexilhao +mezanino +miau +microssegundo +midia +migratorio +mimosa +minuto +miosotis +mirtilo +misturar +mitzvah +miudos +mixuruca +mnemonico +moagem +mobilizar +modulo +moer +mofo +mogno +moita +molusco +monumento +moqueca +morubixaba +mostruario +motriz +mouse +movivel +mozarela +muarra +muculmano +mudo +mugir +muitos +mumunha +munir +muon +muquira +murros +musselina +nacoes +nado +naftalina +nago +naipe +naja +nalgum +namoro +nanquim +napolitano +naquilo +nascimento +nautilo +navios +nazista +nebuloso +nectarina +nefrologo +negus +nelore +nenufar +nepotismo +nervura +neste +netuno +neutron +nevoeiro +newtoniano +nexo +nhenhenhem +nhoque +nigeriano +niilista +ninho +niobio +niponico +niquelar +nirvana +nisto +nitroglicerina +nivoso +nobreza +nocivo +noel +nogueira +noivo +nojo +nominativo +nonuplo +noruegues +nostalgico +noturno +nouveau +nuanca +nublar +nucleotideo +nudista +nulo +numismatico +nunquinha +nupcias +nutritivo +nuvens +oasis +obcecar +obeso +obituario +objetos +oblongo +obnoxio +obrigatorio +obstruir +obtuso +obus +obvio +ocaso +occipital +oceanografo +ocioso +oclusivo +ocorrer +ocre +octogono +odalisca +odisseia +odorifico +oersted +oeste +ofertar +ofidio +oftalmologo +ogiva +ogum +oigale +oitavo +oitocentos +ojeriza +olaria +oleoso +olfato +olhos +oliveira +olmo +olor +olvidavel +ombudsman +omeleteira +omitir +omoplata +onanismo +ondular +oneroso +onomatopeico +ontologico +onus +onze +opalescente +opcional +operistico +opio +oposto +oprobrio +optometrista +opusculo +oratorio +orbital +orcar +orfao +orixa +orla +ornitologo +orquidea +ortorrombico +orvalho +osculo +osmotico +ossudo +ostrogodo +otario +otite +ouro +ousar +outubro +ouvir +ovario +overnight +oviparo +ovni +ovoviviparo +ovulo +oxala +oxente +oxiuro +oxossi +ozonizar +paciente +pactuar +padronizar +paete +pagodeiro +paixao +pajem +paludismo +pampas +panturrilha +papudo +paquistanes +pastoso +patua +paulo +pauzinhos +pavoroso +paxa +pazes +peao +pecuniario +pedunculo +pegaso +peixinho +pejorativo +pelvis +penuria +pequno +petunia +pezada +piauiense +pictorico +pierro +pigmeu +pijama +pilulas +pimpolho +pintura +piorar +pipocar +piqueteiro +pirulito +pistoleiro +pituitaria +pivotar +pixote +pizzaria +plistoceno +plotar +pluviometrico +pneumonico +poco +podridao +poetisa +pogrom +pois +polvorosa +pomposo +ponderado +pontudo +populoso +poquer +porvir +posudo +potro +pouso +povoar +prazo +prezar +privilegios +proximo +prussiano +pseudopode +psoriase +pterossauros +ptialina +ptolemaico +pudor +pueril +pufe +pugilista +puir +pujante +pulverizar +pumba +punk +purulento +pustula +putsch +puxe +quatrocentos +quetzal +quixotesco +quotizavel +rabujice +racista +radonio +rafia +ragu +rajado +ralo +rampeiro +ranzinza +raptor +raquitismo +raro +rasurar +ratoeira +ravioli +razoavel +reavivar +rebuscar +recusavel +reduzivel +reexposicao +refutavel +regurgitar +reivindicavel +rejuvenescimento +relva +remuneravel +renunciar +reorientar +repuxo +requisito +resumo +returno +reutilizar +revolvido +rezonear +riacho +ribossomo +ricota +ridiculo +rifle +rigoroso +rijo +rimel +rins +rios +riqueza +riquixa +rissole +ritualistico +rivalizar +rixa +robusto +rococo +rodoviario +roer +rogo +rojao +rolo +rompimento +ronronar +roqueiro +rorqual +rosto +rotundo +rouxinol +roxo +royal +ruas +rucula +rudimentos +ruela +rufo +rugoso +ruivo +rule +rumoroso +runico +ruptura +rural +rustico +rutilar +saariano +sabujo +sacudir +sadomasoquista +safra +sagui +sais +samurai +santuario +sapo +saquear +sartriano +saturno +saude +sauva +saveiro +saxofonista +sazonal +scherzo +script +seara +seborreia +secura +seduzir +sefardim +seguro +seja +selvas +sempre +senzala +sepultura +sequoia +sestercio +setuplo +seus +seviciar +sezonismo +shalom +siames +sibilante +sicrano +sidra +sifilitico +signos +silvo +simultaneo +sinusite +sionista +sirio +sisudo +situar +sivan +slide +slogan +soar +sobrio +socratico +sodomizar +soerguer +software +sogro +soja +solver +somente +sonso +sopro +soquete +sorveteiro +sossego +soturno +sousafone +sovinice +sozinho +suavizar +subverter +sucursal +sudoriparo +sufragio +sugestoes +suite +sujo +sultao +sumula +suntuoso +suor +supurar +suruba +susto +suturar +suvenir +tabuleta +taco +tadjique +tafeta +tagarelice +taitiano +talvez +tampouco +tanzaniano +taoista +tapume +taquion +tarugo +tascar +tatuar +tautologico +tavola +taxionomista +tchecoslovaco +teatrologo +tectonismo +tedioso +teflon +tegumento +teixo +telurio +temporas +tenue +teosofico +tepido +tequila +terrorista +testosterona +tetrico +teutonico +teve +texugo +tiara +tibia +tiete +tifoide +tigresa +tijolo +tilintar +timpano +tintureiro +tiquete +tiroteio +tisico +titulos +tive +toar +toboga +tofu +togoles +toicinho +tolueno +tomografo +tontura +toponimo +toquio +torvelinho +tostar +toto +touro +toxina +trazer +trezentos +trivialidade +trovoar +truta +tuaregue +tubular +tucano +tudo +tufo +tuiste +tulipa +tumultuoso +tunisino +tupiniquim +turvo +tutu +ucraniano +udenista +ufanista +ufologo +ugaritico +uiste +uivo +ulceroso +ulema +ultravioleta +umbilical +umero +umido +umlaut +unanimidade +unesco +ungulado +unheiro +univoco +untuoso +urano +urbano +urdir +uretra +urgente +urinol +urna +urologo +urro +ursulina +urtiga +urupe +usavel +usbeque +usei +usineiro +usurpar +utero +utilizar +utopico +uvular +uxoricidio +vacuo +vadio +vaguear +vaivem +valvula +vampiro +vantajoso +vaporoso +vaquinha +varziano +vasto +vaticinio +vaudeville +vazio +veado +vedico +veemente +vegetativo +veio +veja +veludo +venusiano +verdade +verve +vestuario +vetusto +vexatorio +vezes +viavel +vibratorio +victor +vicunha +vidros +vietnamita +vigoroso +vilipendiar +vime +vintem +violoncelo +viquingue +virus +visualizar +vituperio +viuvo +vivo +vizir +voar +vociferar +vodu +vogar +voile +volver +vomito +vontade +vortice +vosso +voto +vovozinha +voyeuse +vozes +vulva +vupt +western +xadrez +xale +xampu +xango +xarope +xaual +xavante +xaxim +xenonio +xepa +xerox +xicara +xifopago +xiita +xilogravura +xinxim +xistoso +xixi +xodo +xogum +xucro +zabumba +zagueiro +zambiano +zanzar +zarpar +zebu +zefiro +zeloso +zenite +zumbi diff --git a/lbrynet/wallet/wordlist/spanish.txt b/lbrynet/wallet/wordlist/spanish.txt new file mode 100644 index 000000000..d0900c2c7 --- /dev/null +++ b/lbrynet/wallet/wordlist/spanish.txt @@ -0,0 +1,2048 @@ +ábaco +abdomen +abeja +abierto +abogado +abono +aborto +abrazo +abrir +abuelo +abuso +acabar +academia +acceso +acción +aceite +acelga +acento +aceptar +ácido +aclarar +acné +acoger +acoso +activo +acto +actriz +actuar +acudir +acuerdo +acusar +adicto +admitir +adoptar +adorno +aduana +adulto +aéreo +afectar +afición +afinar +afirmar +ágil +agitar +agonía +agosto +agotar +agregar +agrio +agua +agudo +águila +aguja +ahogo +ahorro +aire +aislar +ajedrez +ajeno +ajuste +alacrán +alambre +alarma +alba +álbum +alcalde +aldea +alegre +alejar +alerta +aleta +alfiler +alga +algodón +aliado +aliento +alivio +alma +almeja +almíbar +altar +alteza +altivo +alto +altura +alumno +alzar +amable +amante +amapola +amargo +amasar +ámbar +ámbito +ameno +amigo +amistad +amor +amparo +amplio +ancho +anciano +ancla +andar +andén +anemia +ángulo +anillo +ánimo +anís +anotar +antena +antiguo +antojo +anual +anular +anuncio +añadir +añejo +año +apagar +aparato +apetito +apio +aplicar +apodo +aporte +apoyo +aprender +aprobar +apuesta +apuro +arado +araña +arar +árbitro +árbol +arbusto +archivo +arco +arder +ardilla +arduo +área +árido +aries +armonía +arnés +aroma +arpa +arpón +arreglo +arroz +arruga +arte +artista +asa +asado +asalto +ascenso +asegurar +aseo +asesor +asiento +asilo +asistir +asno +asombro +áspero +astilla +astro +astuto +asumir +asunto +atajo +ataque +atar +atento +ateo +ático +atleta +átomo +atraer +atroz +atún +audaz +audio +auge +aula +aumento +ausente +autor +aval +avance +avaro +ave +avellana +avena +avestruz +avión +aviso +ayer +ayuda +ayuno +azafrán +azar +azote +azúcar +azufre +azul +baba +babor +bache +bahía +baile +bajar +balanza +balcón +balde +bambú +banco +banda +baño +barba +barco +barniz +barro +báscula +bastón +basura +batalla +batería +batir +batuta +baúl +bazar +bebé +bebida +bello +besar +beso +bestia +bicho +bien +bingo +blanco +bloque +blusa +boa +bobina +bobo +boca +bocina +boda +bodega +boina +bola +bolero +bolsa +bomba +bondad +bonito +bono +bonsái +borde +borrar +bosque +bote +botín +bóveda +bozal +bravo +brazo +brecha +breve +brillo +brinco +brisa +broca +broma +bronce +brote +bruja +brusco +bruto +buceo +bucle +bueno +buey +bufanda +bufón +búho +buitre +bulto +burbuja +burla +burro +buscar +butaca +buzón +caballo +cabeza +cabina +cabra +cacao +cadáver +cadena +caer +café +caída +caimán +caja +cajón +cal +calamar +calcio +caldo +calidad +calle +calma +calor +calvo +cama +cambio +camello +camino +campo +cáncer +candil +canela +canguro +canica +canto +caña +cañón +caoba +caos +capaz +capitán +capote +captar +capucha +cara +carbón +cárcel +careta +carga +cariño +carne +carpeta +carro +carta +casa +casco +casero +caspa +castor +catorce +catre +caudal +causa +cazo +cebolla +ceder +cedro +celda +célebre +celoso +célula +cemento +ceniza +centro +cerca +cerdo +cereza +cero +cerrar +certeza +césped +cetro +chacal +chaleco +champú +chancla +chapa +charla +chico +chiste +chivo +choque +choza +chuleta +chupar +ciclón +ciego +cielo +cien +cierto +cifra +cigarro +cima +cinco +cine +cinta +ciprés +circo +ciruela +cisne +cita +ciudad +clamor +clan +claro +clase +clave +cliente +clima +clínica +cobre +cocción +cochino +cocina +coco +código +codo +cofre +coger +cohete +cojín +cojo +cola +colcha +colegio +colgar +colina +collar +colmo +columna +combate +comer +comida +cómodo +compra +conde +conejo +conga +conocer +consejo +contar +copa +copia +corazón +corbata +corcho +cordón +corona +correr +coser +cosmos +costa +cráneo +cráter +crear +crecer +creído +crema +cría +crimen +cripta +crisis +cromo +crónica +croqueta +crudo +cruz +cuadro +cuarto +cuatro +cubo +cubrir +cuchara +cuello +cuento +cuerda +cuesta +cueva +cuidar +culebra +culpa +culto +cumbre +cumplir +cuna +cuneta +cuota +cupón +cúpula +curar +curioso +curso +curva +cutis +dama +danza +dar +dardo +dátil +deber +débil +década +decir +dedo +defensa +definir +dejar +delfín +delgado +delito +demora +denso +dental +deporte +derecho +derrota +desayuno +deseo +desfile +desnudo +destino +desvío +detalle +detener +deuda +día +diablo +diadema +diamante +diana +diario +dibujo +dictar +diente +dieta +diez +difícil +digno +dilema +diluir +dinero +directo +dirigir +disco +diseño +disfraz +diva +divino +doble +doce +dolor +domingo +don +donar +dorado +dormir +dorso +dos +dosis +dragón +droga +ducha +duda +duelo +dueño +dulce +dúo +duque +durar +dureza +duro +ébano +ebrio +echar +eco +ecuador +edad +edición +edificio +editor +educar +efecto +eficaz +eje +ejemplo +elefante +elegir +elemento +elevar +elipse +élite +elixir +elogio +eludir +embudo +emitir +emoción +empate +empeño +empleo +empresa +enano +encargo +enchufe +encía +enemigo +enero +enfado +enfermo +engaño +enigma +enlace +enorme +enredo +ensayo +enseñar +entero +entrar +envase +envío +época +equipo +erizo +escala +escena +escolar +escribir +escudo +esencia +esfera +esfuerzo +espada +espejo +espía +esposa +espuma +esquí +estar +este +estilo +estufa +etapa +eterno +ética +etnia +evadir +evaluar +evento +evitar +exacto +examen +exceso +excusa +exento +exigir +exilio +existir +éxito +experto +explicar +exponer +extremo +fábrica +fábula +fachada +fácil +factor +faena +faja +falda +fallo +falso +faltar +fama +familia +famoso +faraón +farmacia +farol +farsa +fase +fatiga +fauna +favor +fax +febrero +fecha +feliz +feo +feria +feroz +fértil +fervor +festín +fiable +fianza +fiar +fibra +ficción +ficha +fideo +fiebre +fiel +fiera +fiesta +figura +fijar +fijo +fila +filete +filial +filtro +fin +finca +fingir +finito +firma +flaco +flauta +flecha +flor +flota +fluir +flujo +flúor +fobia +foca +fogata +fogón +folio +folleto +fondo +forma +forro +fortuna +forzar +fosa +foto +fracaso +frágil +franja +frase +fraude +freír +freno +fresa +frío +frito +fruta +fuego +fuente +fuerza +fuga +fumar +función +funda +furgón +furia +fusil +fútbol +futuro +gacela +gafas +gaita +gajo +gala +galería +gallo +gamba +ganar +gancho +ganga +ganso +garaje +garza +gasolina +gastar +gato +gavilán +gemelo +gemir +gen +género +genio +gente +geranio +gerente +germen +gesto +gigante +gimnasio +girar +giro +glaciar +globo +gloria +gol +golfo +goloso +golpe +goma +gordo +gorila +gorra +gota +goteo +gozar +grada +gráfico +grano +grasa +gratis +grave +grieta +grillo +gripe +gris +grito +grosor +grúa +grueso +grumo +grupo +guante +guapo +guardia +guerra +guía +guiño +guion +guiso +guitarra +gusano +gustar +haber +hábil +hablar +hacer +hacha +hada +hallar +hamaca +harina +haz +hazaña +hebilla +hebra +hecho +helado +helio +hembra +herir +hermano +héroe +hervir +hielo +hierro +hígado +higiene +hijo +himno +historia +hocico +hogar +hoguera +hoja +hombre +hongo +honor +honra +hora +hormiga +horno +hostil +hoyo +hueco +huelga +huerta +hueso +huevo +huida +huir +humano +húmedo +humilde +humo +hundir +huracán +hurto +icono +ideal +idioma +ídolo +iglesia +iglú +igual +ilegal +ilusión +imagen +imán +imitar +impar +imperio +imponer +impulso +incapaz +índice +inerte +infiel +informe +ingenio +inicio +inmenso +inmune +innato +insecto +instante +interés +íntimo +intuir +inútil +invierno +ira +iris +ironía +isla +islote +jabalí +jabón +jamón +jarabe +jardín +jarra +jaula +jazmín +jefe +jeringa +jinete +jornada +joroba +joven +joya +juerga +jueves +juez +jugador +jugo +juguete +juicio +junco +jungla +junio +juntar +júpiter +jurar +justo +juvenil +juzgar +kilo +koala +labio +lacio +lacra +lado +ladrón +lagarto +lágrima +laguna +laico +lamer +lámina +lámpara +lana +lancha +langosta +lanza +lápiz +largo +larva +lástima +lata +látex +latir +laurel +lavar +lazo +leal +lección +leche +lector +leer +legión +legumbre +lejano +lengua +lento +leña +león +leopardo +lesión +letal +letra +leve +leyenda +libertad +libro +licor +líder +lidiar +lienzo +liga +ligero +lima +límite +limón +limpio +lince +lindo +línea +lingote +lino +linterna +líquido +liso +lista +litera +litio +litro +llaga +llama +llanto +llave +llegar +llenar +llevar +llorar +llover +lluvia +lobo +loción +loco +locura +lógica +logro +lombriz +lomo +lonja +lote +lucha +lucir +lugar +lujo +luna +lunes +lupa +lustro +luto +luz +maceta +macho +madera +madre +maduro +maestro +mafia +magia +mago +maíz +maldad +maleta +malla +malo +mamá +mambo +mamut +manco +mando +manejar +manga +maniquí +manjar +mano +manso +manta +mañana +mapa +máquina +mar +marco +marea +marfil +margen +marido +mármol +marrón +martes +marzo +masa +máscara +masivo +matar +materia +matiz +matriz +máximo +mayor +mazorca +mecha +medalla +medio +médula +mejilla +mejor +melena +melón +memoria +menor +mensaje +mente +menú +mercado +merengue +mérito +mes +mesón +meta +meter +método +metro +mezcla +miedo +miel +miembro +miga +mil +milagro +militar +millón +mimo +mina +minero +mínimo +minuto +miope +mirar +misa +miseria +misil +mismo +mitad +mito +mochila +moción +moda +modelo +moho +mojar +molde +moler +molino +momento +momia +monarca +moneda +monja +monto +moño +morada +morder +moreno +morir +morro +morsa +mortal +mosca +mostrar +motivo +mover +móvil +mozo +mucho +mudar +mueble +muela +muerte +muestra +mugre +mujer +mula +muleta +multa +mundo +muñeca +mural +muro +músculo +museo +musgo +música +muslo +nácar +nación +nadar +naipe +naranja +nariz +narrar +nasal +natal +nativo +natural +náusea +naval +nave +navidad +necio +néctar +negar +negocio +negro +neón +nervio +neto +neutro +nevar +nevera +nicho +nido +niebla +nieto +niñez +niño +nítido +nivel +nobleza +noche +nómina +noria +norma +norte +nota +noticia +novato +novela +novio +nube +nuca +núcleo +nudillo +nudo +nuera +nueve +nuez +nulo +número +nutria +oasis +obeso +obispo +objeto +obra +obrero +observar +obtener +obvio +oca +ocaso +océano +ochenta +ocho +ocio +ocre +octavo +octubre +oculto +ocupar +ocurrir +odiar +odio +odisea +oeste +ofensa +oferta +oficio +ofrecer +ogro +oído +oír +ojo +ola +oleada +olfato +olivo +olla +olmo +olor +olvido +ombligo +onda +onza +opaco +opción +ópera +opinar +oponer +optar +óptica +opuesto +oración +orador +oral +órbita +orca +orden +oreja +órgano +orgía +orgullo +oriente +origen +orilla +oro +orquesta +oruga +osadía +oscuro +osezno +oso +ostra +otoño +otro +oveja +óvulo +óxido +oxígeno +oyente +ozono +pacto +padre +paella +página +pago +país +pájaro +palabra +palco +paleta +pálido +palma +paloma +palpar +pan +panal +pánico +pantera +pañuelo +papá +papel +papilla +paquete +parar +parcela +pared +parir +paro +párpado +parque +párrafo +parte +pasar +paseo +pasión +paso +pasta +pata +patio +patria +pausa +pauta +pavo +payaso +peatón +pecado +pecera +pecho +pedal +pedir +pegar +peine +pelar +peldaño +pelea +peligro +pellejo +pelo +peluca +pena +pensar +peñón +peón +peor +pepino +pequeño +pera +percha +perder +pereza +perfil +perico +perla +permiso +perro +persona +pesa +pesca +pésimo +pestaña +pétalo +petróleo +pez +pezuña +picar +pichón +pie +piedra +pierna +pieza +pijama +pilar +piloto +pimienta +pino +pintor +pinza +piña +piojo +pipa +pirata +pisar +piscina +piso +pista +pitón +pizca +placa +plan +plata +playa +plaza +pleito +pleno +plomo +pluma +plural +pobre +poco +poder +podio +poema +poesía +poeta +polen +policía +pollo +polvo +pomada +pomelo +pomo +pompa +poner +porción +portal +posada +poseer +posible +poste +potencia +potro +pozo +prado +precoz +pregunta +premio +prensa +preso +previo +primo +príncipe +prisión +privar +proa +probar +proceso +producto +proeza +profesor +programa +prole +promesa +pronto +propio +próximo +prueba +público +puchero +pudor +pueblo +puerta +puesto +pulga +pulir +pulmón +pulpo +pulso +puma +punto +puñal +puño +pupa +pupila +puré +quedar +queja +quemar +querer +queso +quieto +química +quince +quitar +rábano +rabia +rabo +ración +radical +raíz +rama +rampa +rancho +rango +rapaz +rápido +rapto +rasgo +raspa +rato +rayo +raza +razón +reacción +realidad +rebaño +rebote +recaer +receta +rechazo +recoger +recreo +recto +recurso +red +redondo +reducir +reflejo +reforma +refrán +refugio +regalo +regir +regla +regreso +rehén +reino +reír +reja +relato +relevo +relieve +relleno +reloj +remar +remedio +remo +rencor +rendir +renta +reparto +repetir +reposo +reptil +res +rescate +resina +respeto +resto +resumen +retiro +retorno +retrato +reunir +revés +revista +rey +rezar +rico +riego +rienda +riesgo +rifa +rígido +rigor +rincón +riñón +río +riqueza +risa +ritmo +rito +rizo +roble +roce +rociar +rodar +rodeo +rodilla +roer +rojizo +rojo +romero +romper +ron +ronco +ronda +ropa +ropero +rosa +rosca +rostro +rotar +rubí +rubor +rudo +rueda +rugir +ruido +ruina +ruleta +rulo +rumbo +rumor +ruptura +ruta +rutina +sábado +saber +sabio +sable +sacar +sagaz +sagrado +sala +saldo +salero +salir +salmón +salón +salsa +salto +salud +salvar +samba +sanción +sandía +sanear +sangre +sanidad +sano +santo +sapo +saque +sardina +sartén +sastre +satán +sauna +saxofón +sección +seco +secreto +secta +sed +seguir +seis +sello +selva +semana +semilla +senda +sensor +señal +señor +separar +sepia +sequía +ser +serie +sermón +servir +sesenta +sesión +seta +setenta +severo +sexo +sexto +sidra +siesta +siete +siglo +signo +sílaba +silbar +silencio +silla +símbolo +simio +sirena +sistema +sitio +situar +sobre +socio +sodio +sol +solapa +soldado +soledad +sólido +soltar +solución +sombra +sondeo +sonido +sonoro +sonrisa +sopa +soplar +soporte +sordo +sorpresa +sorteo +sostén +sótano +suave +subir +suceso +sudor +suegra +suelo +sueño +suerte +sufrir +sujeto +sultán +sumar +superar +suplir +suponer +supremo +sur +surco +sureño +surgir +susto +sutil +tabaco +tabique +tabla +tabú +taco +tacto +tajo +talar +talco +talento +talla +talón +tamaño +tambor +tango +tanque +tapa +tapete +tapia +tapón +taquilla +tarde +tarea +tarifa +tarjeta +tarot +tarro +tarta +tatuaje +tauro +taza +tazón +teatro +techo +tecla +técnica +tejado +tejer +tejido +tela +teléfono +tema +temor +templo +tenaz +tender +tener +tenis +tenso +teoría +terapia +terco +término +ternura +terror +tesis +tesoro +testigo +tetera +texto +tez +tibio +tiburón +tiempo +tienda +tierra +tieso +tigre +tijera +tilde +timbre +tímido +timo +tinta +tío +típico +tipo +tira +tirón +titán +títere +título +tiza +toalla +tobillo +tocar +tocino +todo +toga +toldo +tomar +tono +tonto +topar +tope +toque +tórax +torero +tormenta +torneo +toro +torpedo +torre +torso +tortuga +tos +tosco +toser +tóxico +trabajo +tractor +traer +tráfico +trago +traje +tramo +trance +trato +trauma +trazar +trébol +tregua +treinta +tren +trepar +tres +tribu +trigo +tripa +triste +triunfo +trofeo +trompa +tronco +tropa +trote +trozo +truco +trueno +trufa +tubería +tubo +tuerto +tumba +tumor +túnel +túnica +turbina +turismo +turno +tutor +ubicar +úlcera +umbral +unidad +unir +universo +uno +untar +uña +urbano +urbe +urgente +urna +usar +usuario +útil +utopía +uva +vaca +vacío +vacuna +vagar +vago +vaina +vajilla +vale +válido +valle +valor +válvula +vampiro +vara +variar +varón +vaso +vecino +vector +vehículo +veinte +vejez +vela +velero +veloz +vena +vencer +venda +veneno +vengar +venir +venta +venus +ver +verano +verbo +verde +vereda +verja +verso +verter +vía +viaje +vibrar +vicio +víctima +vida +vídeo +vidrio +viejo +viernes +vigor +vil +villa +vinagre +vino +viñedo +violín +viral +virgo +virtud +visor +víspera +vista +vitamina +viudo +vivaz +vivero +vivir +vivo +volcán +volumen +volver +voraz +votar +voto +voz +vuelo +vulgar +yacer +yate +yegua +yema +yerno +yeso +yodo +yoga +yogur +zafiro +zanja +zapato +zarza +zona +zorro +zumo +zurdo