diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3a7d7af3..ed08ff4c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,12 @@ Changelog ========= +0.23.13 +------- +* receiver parameter removed from beempy decrypt +* beempy encrypt / decrypt is able to encryp/derypt a binary file +* encrypt_binary, decrypt_binary and extract_decrypt_memo_data added to beem.memo +* extract_memo_data added to beembase.memo + 0.23.12 ------- * add participation_rate to Blockchain diff --git a/beem/cli.py b/beem/cli.py index 29ba0766..44ffcc14 100644 --- a/beem/cli.py +++ b/beem/cli.py @@ -2088,46 +2088,95 @@ def message(message_file, account, verify): @cli.command() -@click.argument('sender', nargs=1) @click.argument('memo', nargs=-1) @click.option('--account', '-a', help='Account which decrypts the memo with its memo key') +@click.option('--output', '-o', help='Output file name. Result is stored, when set instead of printed.') +@click.option('--info', '-i', help='Shows information about public keys and used nonce', is_flag=True, default=False) @click.option('--text', '-t', help='Reads the text file content', is_flag=True, default=False) -def decrypt(sender, memo, account, text): +@click.option('--binary', '-b', help='Reads the binary file content', is_flag=True, default=False) +def decrypt(memo, account, output, info, text, binary): """decrypt a (or more than one) decrypted memo/file with your memo key """ + if text and binary: + print("You cannot set text and binary!") + return stm = shared_blockchain_instance() if stm.rpc is not None: stm.rpc.rpcconnect() if not account: account = stm.config["default_account"] - m = Memo(from_account=sender, to_account=account, blockchain_instance=stm) + m = Memo(from_account=None, to_account=account, blockchain_instance=stm) + if not unlock_wallet(stm): - return + return for entry in memo: print("\n") + if not binary and info: + from_key, to_key, nonce = m.extract_decrypt_memo_data(entry) + try: + from_account = stm.wallet.getAccountFromPublicKey(str(from_key)) + to_account = stm.wallet.getAccountFromPublicKey(str(to_key)) + if from_account is not None: + print("from: %s" % str(from_account)) + else: + print("from: %s" % str(from_key)) + if to_account is not None: + print("to: %s" % str(to_account)) + else: + print("to: %s" % str(to_key)) + print("nonce: %s" % nonce) + except: + print("from: %s" % str(from_key)) + print("to: %s" % str(to_key)) + print("nonce: %s" % nonce) if text: with open(entry) as f: message = f.read() + elif binary: + if output is None: + output = entry + ".dec" + ret = m.decrypt_binary(entry, output, buffer_size=2048) + if info: + t = PrettyTable(["Key", "Value"]) + t.align = "l" + t.add_row(["file", entry]) + for key in ret: + t.add_row([key, ret[key]]) + print(t) else: message = entry - out = m.decrypt(message) if text: - with open(entry, "w", encoding="utf-8") as f: + out = m.decrypt(message) + if output is None: + output = entry + with open(output, "w", encoding="utf-8") as f: f.write(out) - else: - print(out) + elif not binary: + out = m.decrypt(message) + if info: + print("message: %s" % out) + if output: + with open(output, "w", encoding="utf-8") as f: + f.write(out) + elif not info: + print(out) @cli.command() @click.argument('receiver', nargs=1) @click.argument('memo', nargs=-1) @click.option('--account', '-a', help='Account which encrypts the memo with its memo key') +@click.option('--output', '-o', help='Output file name. Result is stored, when set instead of printed.') @click.option('--text', '-t', help='Reads the text file content', is_flag=True, default=False) -def encrypt(receiver, memo, account, text): +@click.option('--binary', '-b', help='Reads the binary file content', is_flag=True, default=False) +def encrypt(receiver, memo, account, output, text, binary): """encrypt a (or more than one) memo text/file with the your memo key """ + if text and binary: + print("You cannot set text and binary!") + return stm = shared_blockchain_instance() if stm.rpc is not None: stm.rpc.rpcconnect() @@ -2141,16 +2190,30 @@ def encrypt(receiver, memo, account, text): if text: with open(entry) as f: message = f.read() + if message[0] == "#": + message = message[1:] + elif binary: + if output is None: + output = entry + ".enc" + m.encrypt_binary(entry, output, buffer_size=2048) else: message = entry - if message[0] == "#": - message = message[1:] - out = m.encrypt(message)["message"] + if message[0] == "#": + message = message[1:] + if text: - with open(entry, "w", encoding="utf-8") as f: + out = m.encrypt(message)["message"] + if output is None: + output = entry + with open(output, "w", encoding="utf-8") as f: f.write(out) - else: - print(out) + elif not binary: + out = m.encrypt(message)["message"] + if output is None: + print(out) + else: + with open(output, "w", encoding="utf-8") as f: + f.write(out) @cli.command() diff --git a/beem/memo.py b/beem/memo.py index e5b69728..c78e25b3 100644 --- a/beem/memo.py +++ b/beem/memo.py @@ -7,6 +7,11 @@ from builtins import object from beem.instance import shared_blockchain_instance import random +import os +import struct +from binascii import hexlify, unhexlify +from beemgraphenebase.base58 import base58encode, base58decode +from beem.version import version as __version__ from beembase import memo as BtsMemo from beemgraphenebase.account import PrivateKey, PublicKey from .account import Account @@ -149,8 +154,12 @@ def __init__( if to_account: self.to_account = Account(to_account, blockchain_instance=self.blockchain) + else: + self.to_account = None if from_account: self.from_account = Account(from_account, blockchain_instance=self.blockchain) + else: + self.from_account = None def unlock_wallet(self, *args, **kwargs): """ Unlock the library internal wallet @@ -213,6 +222,63 @@ def encrypt(self, memo, bts_encrypt=False): "to": self.to_account["memo_key"] } + def encrypt_binary(self, infile, outfile, buffer_size=2048): + """ Encrypt a binary file + + :param str infile: input file name + :param str outfile: output file name + :param int buffer_size: write buffer size + """ + if not os.path.exists(infile): + raise ValueError("%s does not exists!" % infile) + + nonce = str(random.getrandbits(64)) + memo_wif = self.blockchain.wallet.getPrivateKeyForPublicKey( + self.from_account["memo_key"] + ) + if not memo_wif: + raise MissingKeyError("Memo key for %s missing!" % self.from_account["name"]) + + if not hasattr(self, 'chain_prefix'): + self.chain_prefix = self.blockchain.prefix + + file_size = os.path.getsize(infile) + priv = PrivateKey(memo_wif) + pub = PublicKey( + self.to_account["memo_key"], + prefix=self.chain_prefix + ) + enc = BtsMemo.encode_memo( + priv, + pub, + nonce, + "beem/%s" % __version__, + prefix=self.chain_prefix + ) + enc = unhexlify(base58decode(enc[1:])) + shared_secret = BtsMemo.get_shared_secret(priv, pub) + aes, check = BtsMemo.init_aes(shared_secret, nonce) + with open(outfile, 'wb') as fout: + fout.write(struct.pack(' n: + fout.write(decd) + else: + fout.write(decd[:file_size]) # <- remove padding on last block + file_size -= n + return { + "file_size": orig_file_size, + "from_key": str(from_key), + "to_key": str(to_key), + "nonce": nonce, + "beem_version": beem_version + } \ No newline at end of file diff --git a/beem/version.py b/beem/version.py index 07b16fba..37facaf7 100644 --- a/beem/version.py +++ b/beem/version.py @@ -1,2 +1,2 @@ """THIS FILE IS GENERATED FROM beem SETUP.PY.""" -version = '0.23.12' +version = '0.23.13' diff --git a/beemapi/version.py b/beemapi/version.py index 07b16fba..37facaf7 100644 --- a/beemapi/version.py +++ b/beemapi/version.py @@ -1,2 +1,2 @@ """THIS FILE IS GENERATED FROM beem SETUP.PY.""" -version = '0.23.12' +version = '0.23.13' diff --git a/beembase/memo.py b/beembase/memo.py index 691c2a6a..cc5adeb1 100644 --- a/beembase/memo.py +++ b/beembase/memo.py @@ -190,6 +190,21 @@ def encode_memo(priv, pub, nonce, message, **kwargs): return "#" + base58encode(hexlify(py23_bytes(tx)).decode("ascii")) +def extract_memo_data(message): + """ Returns the stored pubkey keys, nonce, checksum and encrypted message of a memo""" + raw = base58decode(message[1:]) + from_key = PublicKey(raw[:66]) + raw = raw[66:] + to_key = PublicKey(raw[:66]) + raw = raw[66:] + nonce = str(struct.unpack_from("= 2.0.0', 'pytest', 'pytest-mock', 'parameterized']