def encode_memo(priv, pub, nonce, message, **kwargs): """ Encode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Alice) :param PublicKey pub: Public Key (of Bob) :param int nonce: Random nonce :param str message: Memo message :return: Encrypted message :rtype: hex """ shared_secret = get_shared_secret(priv, pub) aes, check = init_aes(shared_secret, nonce) raw = py23_bytes(message, 'utf8') # Padding BS = 16 if len(raw) % BS: raw = _pad(raw, BS) # Encryption cipher = hexlify(aes.encrypt(raw)).decode('ascii') prefix = kwargs.pop("prefix", default_prefix) s = { "from": format(priv.pubkey, prefix), "to": format(pub, prefix), "nonce": nonce, "check": check, "encrypted": cipher, "prefix": prefix } tx = Memo(**s) return "#" + base58encode(hexlify(py23_bytes(tx)).decode("ascii"))
def test_Bool(self): u = types.Bool(True) self.assertEqual(py23_bytes(u), b"\x01") self.assertEqual(str(u), 'true') u = types.Bool(False) self.assertEqual(py23_bytes(u), b"\x00") self.assertEqual(str(u), 'false')
def test_varint32(self): u = types.Varint32(2**32 - 1) self.assertEqual(py23_bytes(u), b"\xff\xff\xff\xff\x0f") self.assertEqual(str(u), str(4294967295)) u = types.Id(2**32 - 1) self.assertEqual(py23_bytes(u), b"\xff\xff\xff\xff\x0f") self.assertEqual(str(u), str(4294967295))
def test_bytes_int(self): """ In Py3, bytes(int) -> bytes object of size given by the parameter initialized with null """ self.assertEqual(py23_bytes(5), b'\x00\x00\x00\x00\x00') # Test using newint: self.assertEqual(py23_bytes(int(5)), b'\x00\x00\x00\x00\x00') self.assertTrue(isinstance(py23_bytes(int(5)), bytes_types))
def test_string(self): u = types.String("HelloFoobar") self.assertEqual(py23_bytes(u), b"\x0bHelloFoobar") self.assertEqual(str(u), "HelloFoobar") u = types.String("\x07\x08\x09\x0a\x0b\x0c\x0d\x0e") self.assertEqual(py23_bytes(u), b"\x14u0007b\t\nu000bf\ru000e") self.assertEqual(str(u), "\x07\x08\x09\x0a\x0b\x0c\x0d\x0e")
def test_Optional(self): u = types.Optional(types.Uint16(10)) self.assertEqual(py23_bytes(u), b"\x01\n\x00") self.assertEqual(str(u), '10') self.assertFalse(u.isempty()) u = types.Optional(None) self.assertEqual(py23_bytes(u), b"\x00") self.assertEqual(str(u), 'None') self.assertTrue(u.isempty())
def test_isinstance_bytes_subclass(self): """ Issue #89 """ value = py23_bytes(b'abc') class Magic(): def __bytes__(self): return py23_bytes(b'abc') self.assertEqual(value, py23_bytes(Magic()))
def test_array(self): u = types.Array([types.Uint8(10) for x in range(2)] + [11]) self.assertEqual(py23_bytes(u), b'\x03\n\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') self.assertEqual(str(u), "[10, 10, 11]") u = types.Set([types.Uint16(10) for x in range(10)]) self.assertEqual(py23_bytes(u), b"\n\n\x00\n\x00\n\x00\n\x00\n\x00\n\x00\n\x00\n\x00\n\x00\n\x00") self.assertEqual(str(u), "[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]") u = types.Array(["Foobar"]) # We do not support py23_bytes of Array containing String only! # self.assertEqual(py23_bytes(u), b'') self.assertEqual(str(u), '["Foobar"]')
def test_bytes_encoding_arg_non_kwarg(self): """ As above, but with a positional argument """ u = u'Unicode string: \u5b54\u5b50' b = py23_bytes(u, 'utf-8') self.assertEqual(b, u.encode('utf-8'))
def test_Static_variant(self): class Tmp(types.Uint16): def json(self): return "Foobar" u = types.Static_variant(Tmp(10), 10) self.assertEqual(py23_bytes(u), b"\n\n\x00") self.assertEqual(str(u), '[10, "Foobar"]')
def verify(self, pubkeys=[], chain=None, recover_parameter=False): """Returned pubkeys have to be checked if they are existing""" if not chain: raise chain_params = self.getChainParams(chain) self.deriveDigest(chain) signatures = self.data["signatures"].data pubKeysFound = [] for signature in signatures: if recover_parameter: p = verify_message( self.message, py23_bytes(signature) ) else: p = None if p is None: for i in range(4): try: p = verify_message( self.message, py23_bytes(signature), recover_parameter=i ) phex = hexlify(p).decode('ascii') pubKeysFound.append(phex) except Exception: p = None else: phex = hexlify(p).decode('ascii') pubKeysFound.append(phex) for pubkey in pubkeys: if not isinstance(pubkey, PublicKey): raise Exception("Pubkeys must be array of 'PublicKey'") k = pubkey.unCompressed()[2:] if k not in pubKeysFound and repr(pubkey) not in pubKeysFound: k = PublicKey(PublicKey(k).compressed()) f = format(k, chain_params["prefix"]) raise Exception("Signature for %s missing!" % f) return pubKeysFound
def doit(self, printWire=False, ops=None): if ops is None: ops = [Operation(self.op)] tx = Signed_Transaction(ref_block_num=self.ref_block_num, ref_block_prefix=self.ref_block_prefix, expiration=self.expiration, operations=ops) tx = tx.sign([self.wif], chain=self.prefix) tx.verify([PrivateKey(self.wif, prefix=u"STM").pubkey], self.prefix) txWire = hexlify(py23_bytes(tx)).decode("ascii")
def test_bytes_encoding_arg(self): """ The bytes class has changed in Python 3 to accept an additional argument in the constructor: encoding. It would be nice to support this without breaking the isinstance(..., bytes) test below. """ u = u'Unicode string: \u5b54\u5b50' b = py23_bytes(u, encoding='utf-8') self.assertEqual(b, u.encode('utf-8'))
def hash_op(event): """ This method generates a hash of blockchain operation. """ if isinstance(event, dict) and "type" in event and "value" in event: op_type = event["type"] if len(op_type) > 10 and op_type[len(op_type) - 10:] == "_operation": op_type = op_type[:-10] op = event["value"] event = [op_type, op] data = json.dumps(event, sort_keys=True) return hashlib.sha1(py23_bytes(data, 'utf-8')).hexdigest()
def prehash_message(self, timestamp, account, method, params, nonce): """ Prepare a hash for the Conveyor API request with SHA256 according to https://gitlab.syncad.com/hive-group//rpc-auth Hashing of `second` is then done inside `ecdsasig.sign_message()`. :param str timestamp: valid iso8601 datetime ending in "Z" :param str account: valid hive blockchain account name :param str method: Conveyor method name to be called :param bytes param: base64 encoded request parameters :param bytes nonce: random 8 bytes """ first = hashlib.sha256(py23_bytes(timestamp + account + method + params, self.ENCODING)) return self.K + first.digest() + nonce
def test_sign_message(self, module): if module == "cryptography": if not ecda.CRYPTOGRAPHY_AVAILABLE: return ecda.SECP256K1_MODULE = "cryptography" elif module == "secp256k1": if not ecda.SECP256K1_AVAILABLE: return ecda.SECP256K1_MODULE = "secp256k1" else: ecda.SECP256K1_MODULE = module pub_key = py23_bytes(repr(PrivateKey(wif).pubkey), "latin") signature = ecda.sign_message("Foobar", wif) pub_key_sig = ecda.verify_message("Foobar", signature) self.assertEqual(hexlify(pub_key_sig), pub_key)
def __init__(self, url="https://conveyor.hive.blog", hive_instance=None): """ Initialize a Conveyor instance :param str url: (optional) URL to the Conveyor API, defaults to https://conveyor.hive.blog :param bhive.hive.Hive hive_instance: Hive instance """ self.url = url self.hive = hive_instance or shared_hive_instance() self.id = 0 self.ENCODING = 'utf-8' self.TIMEFORMAT = '%Y-%m-%dT%H:%M:%S.%f' self.K = hashlib.sha256(py23_bytes('steem_jsonrpc_auth', self.ENCODING)).digest()
def id(self): """ The transaction id of this transaction """ # Store signatures temporarily since they are not part of # transaction id sigs = self.data["signatures"] self.data.pop("signatures", None) # Generage Hash of the seriliazed version h = hashlib.sha256(py23_bytes(self)).digest() # recover signatures self.data["signatures"] = sigs # Return properly truncated tx hash return hexlify(h[:20]).decode("ascii")
def get_tx_size(self, op): """Returns the tx size of an operation""" ops = [Operation(op)] prefix = u"HIVE" wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" ref_block_num = 34294 ref_block_prefix = 3707022213 expiration = "2016-04-06T08:29:27" tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) txWire = hexlify(py23_bytes(tx)).decode("ascii") tx_size = len(txWire) return tx_size
def deriveDigest(self, chain): chain_params = self.getChainParams(chain) # Chain ID self.chainid = chain_params["chain_id"] # Do not serialize signatures sigs = self.data["signatures"] self.data["signatures"] = [] # Get message to sign # bytes(self) will give the wire formated data according to # GrapheneObject and the data given in __init__() self.message = unhexlify(self.chainid) + py23_bytes(self) self.digest = hashlib.sha256(self.message).digest() # restore signatures self.data["signatures"] = sigs
def decode_memo(priv, message): """ Decode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Bob) :param base58encoded message: Encrypted Memo message :return: Decrypted message :rtype: str :raise ValueError: if message cannot be decoded as valid UTF-8 string """ # decode structure 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("<Q", unhexlify(raw[:16]))[0]) raw = raw[16:] check = struct.unpack_from("<I", unhexlify(raw[:8]))[0] raw = raw[8:] cipher = raw if repr(to_key) == repr(priv.pubkey): shared_secret = get_shared_secret(priv, from_key) elif repr(from_key) == repr(priv.pubkey): shared_secret = get_shared_secret(priv, to_key) else: raise ValueError("Incorrect PrivateKey") # Init encryption aes, checksum = init_aes(shared_secret, nonce) # Check if not check == checksum: raise AssertionError("Checksum failure") # Encryption # remove the varint prefix (FIXME, long messages!) message = cipher[2:] message = aes.decrypt(unhexlify(py23_bytes(message, 'ascii'))) try: return _unpad(message.decode('utf8'), 16) except: # noqa FIXME(sneak) raise ValueError(message)
def upload(self, image, account, image_name=None): """ Uploads an image :param image: path to the image or image in bytes representation which should be uploaded :type image: str, bytes :param str account: Account which is used to upload. A posting key must be provided. :param str image_name: optional .. code-block:: python from bhive import Hive from bhive.imageuploader import ImageUploader hv = Hive(keys=["5xxx"]) # private posting key iu = ImageUploader(hive_instance=hv) iu.upload("path/to/image.png", "account_name") # "private posting key belongs to account_name """ account = Account(account, hive_instance=self.hive) if "posting" not in account: account.refresh() if "posting" not in account: raise AssertionError("Could not access posting permission") for authority in account["posting"]["key_auths"]: posting_wif = self.hive.wallet.getPrivateKeyForPublicKey( authority[0]) if isinstance(image, string_types): image_data = open(image, 'rb').read() elif isinstance(image, io.BytesIO): image_data = image.read() else: image_data = image message = py23_bytes(self.challenge, "ascii") + image_data signature = sign_message(message, posting_wif) signature_in_hex = hexlify(signature).decode("ascii") files = {image_name or 'image': image_data} url = "%s/%s/%s" % (self.base_url, account["name"], signature_in_hex) r = requests.post(url, files=files) return r.json()
def init_aes_bts(shared_secret, nonce): """ Initialize AES instance :param hex shared_secret: Shared Secret to use as encryption key :param int nonce: Random nonce :return: AES instance :rtype: AES """ # Shared Secret ss = hashlib.sha512(unhexlify(shared_secret)).digest() # Seed seed = py23_bytes(str(nonce), 'ascii') + hexlify(ss) seed_digest = hexlify(hashlib.sha512(seed).digest()).decode('ascii') # Check'sum' check = hashlib.sha256(unhexlify(seed_digest)).digest() check = struct.unpack_from("<I", check[:4])[0] # AES key = unhexlify(seed_digest[0:64]) iv = unhexlify(seed_digest[64:96]) return AES.new(key, AES.MODE_CBC, iv)
def _request(self, account, method, params, key): """Assemble the request, hash it, sign it and send it to the Conveyor instance. Returns the server response as JSON. :param str account: account name :param str method: Conveyor method name to be called :param dict params: request parameters as `dict` :param str key: Hive posting key for signing """ params_bytes = py23_bytes(json.dumps(params), self.ENCODING) params_enc = base64.b64encode(params_bytes).decode(self.ENCODING) timestamp = datetime.utcnow().strftime(self.TIMEFORMAT)[:-3] + "Z" nonce_int = random.getrandbits(64) nonce_bytes = struct.pack('>Q', nonce_int) # 64bit ULL, big endian nonce_str = "%016x" % (nonce_int) message = self.prehash_message(timestamp, account, method, params_enc, nonce_bytes) signature = sign_message(message, key) signature_hex = hexlify(signature).decode(self.ENCODING) request = { "jsonrpc": "2.0", "id": self.id, "method": method, "params": { "__signed": { "account": account, "nonce": nonce_str, "params": params_enc, "signatures": [signature_hex], "timestamp": timestamp } } } r = requests.post(self.url, data=json.dumps(request)) self.id += 1 return r.json()
def encode_memo_bts(priv, pub, nonce, message): """ Encode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Alice) :param PublicKey pub: Public Key (of Bob) :param int nonce: Random nonce :param str message: Memo message :return: Encrypted message :rtype: hex """ shared_secret = get_shared_secret(priv, pub) aes = init_aes_bts(shared_secret, nonce) # Checksum raw = py23_bytes(message, 'utf8') checksum = hashlib.sha256(raw).digest() raw = (checksum[0:4] + raw) # Padding BS = 16 # FIXME: this adds 16 bytes even if not required if len(raw) % BS: raw = _pad(raw, BS) # Encryption return hexlify(aes.encrypt(raw)).decode('ascii')
def decode_memo_bts(priv, pub, nonce, message): """ Decode a message with a shared secret between Alice and Bob :param PrivateKey priv: Private Key (of Bob) :param PublicKey pub: Public Key (of Alice) :param int nonce: Nonce used for Encryption :param bytes message: Encrypted Memo message :return: Decrypted message :rtype: str :raise ValueError: if message cannot be decoded as valid UTF-8 string """ shared_secret = get_shared_secret(priv, pub) aes = init_aes_bts(shared_secret, nonce) # Encryption raw = py23_bytes(message, 'ascii') cleartext = aes.decrypt(unhexlify(raw)) # TODO, verify checksum message = cleartext[4:] try: return _unpad(message.decode('utf8'), 16) except Exception: raise ValueError(message)
def deriveChecksum(self, s): """ Derive the checksum """ checksum = hashlib.sha256(py23_bytes(s, "ascii")).hexdigest() return checksum[:4]
def test_int16(self): u = types.Int16(2**15 - 1) self.assertEqual(py23_bytes(u), b"\xff\x7f") self.assertEqual(str(u), str(2**15 - 1))
def test_int64(self): u = types.Int64(2**63 - 1) self.assertEqual(py23_bytes(u), b"\xff\xff\xff\xff\xff\xff\xff\x7f") self.assertEqual(str(u), str(9223372036854775807))
def test_uint64(self): u = types.Uint64(2**64 - 1) self.assertEqual(py23_bytes(u), b"\xff\xff\xff\xff\xff\xff\xff\xff") self.assertEqual(str(u), str(2**64 - 1))