def validate_compressed_public_key(cls, public_key: bytes) -> None: try: PublicKey.from_compressed_bytes(public_key) except (EthKeysValidationError, ValueError) as error: raise ValidationError( f"Public key {encode_hex(public_key)} is invalid compressed public key: {error}" ) from error
def ecdh_agree(private_key: bytes, public_key: bytes) -> bytes: """Perform the ECDH key agreement. The public key is expected in uncompressed format and the resulting secret point will be formatted as a 0x02 or 0x03 prefix (depending on the sign of the secret's y component) followed by 32 bytes of the x component. """ # We cannot use `cryptography.hazmat.primitives.asymmetric.ec.ECDH only gives us the x # component of the shared secret point, but we need both x and y. public_key_eth_keys = PublicKey(public_key) public_key_compressed = public_key_eth_keys.to_compressed_bytes() public_key_coincurve = coincurve.keys.PublicKey(public_key_compressed) secret_coincurve = public_key_coincurve.multiply(private_key) return secret_coincurve.format()
def create_JWK(): """Create a private key and return it formatted as JWK """ # Generate the private key using Ethereum methods acc = Account.create( extra_entropy= "Alastria is the first Public-Permissioned Blockchain Network") # Get the public key publicKey = PublicKey.from_private(acc._key_obj) # The public key is 64 bytes composed of the x and y curve coordinates # x and y are each 32 bytes long # We convert x and y to hex, so the dictionary can be converted to JSON x = publicKey[:32] y = publicKey[32:] # Create the Json Web Key (JWK) representation, as specified by W3C DID Document format key_JWK = JWK(kty="EC", crv="secp256k1", d=base64url_encode(acc.privateKey), x=base64url_encode(x), y=base64url_encode(y)) return key_JWK
def validate_uncompressed_public_key(cls, public_key: bytes) -> None: try: PublicKey(public_key) except EthKeysValidationError as error: raise ValidationError( f"Public key {encode_hex(public_key)} is invalid uncompressed public key: {error}" ) from error
def __message(self, public_key: PublicKey) -> str: body = { "name": f'{self.__class__.__module__}:{self.__class__.__name__}', "from": public_key.to_checksum_address(), "data": self.data } return json.dumps(body)
def __validate(self, public_key: PublicKey): transaction_address = json.loads(self.raw_transaction)['from'] recovered_address = public_key.to_checksum_address() if transaction_address != recovered_address: raise exceptions.WrongSignatureError( f'{recovered_address} did not match expected {transaction_address}' )
def complete_handshake(self, response_packet: Packet) -> HandshakeResult: if not self.is_response_packet(response_packet): raise ValueError( f"Packet {response_packet} is not the expected response packet" ) if not isinstance(response_packet, WhoAreYouPacket): raise TypeError("Invariant: Only WhoAreYou packets are valid responses") who_are_you_packet = response_packet # compute session keys ( ephemeral_private_key, ephemeral_public_key, ) = self.handshake_scheme.create_handshake_key_pair() remote_public_key_object = PublicKey.from_compressed_bytes( self.remote_enr.public_key ) remote_public_key_uncompressed = remote_public_key_object.to_bytes() session_keys = self.handshake_scheme.compute_session_keys( local_private_key=ephemeral_private_key, remote_public_key=remote_public_key_uncompressed, local_node_id=self.local_enr.node_id, remote_node_id=self.remote_node_id, id_nonce=who_are_you_packet.id_nonce, is_locally_initiated=True, ) # prepare response packet id_nonce_signature = self.handshake_scheme.create_id_nonce_signature( id_nonce=who_are_you_packet.id_nonce, ephemeral_public_key=ephemeral_public_key, private_key=self.local_private_key, ) enr: Optional[ENRAPI] if who_are_you_packet.enr_sequence_number < self.local_enr.sequence_number: enr = self.local_enr else: enr = None auth_header_packet = AuthHeaderPacket.prepare( tag=self.tag, auth_tag=get_random_auth_tag(), id_nonce=who_are_you_packet.id_nonce, message=self.initial_message, initiator_key=session_keys.encryption_key, id_nonce_signature=id_nonce_signature, auth_response_key=session_keys.auth_response_key, enr=enr, ephemeral_public_key=ephemeral_public_key, ) return HandshakeResult( session_keys=session_keys, enr=None, message=None, auth_header_packet=auth_header_packet, )
def create_account(account_name, password, overwrite=False): """Creates a wallet account. This is essentially a private/public key pair. This is NOT an identity yet in Red T. """ db = get_wallet_db() # If not overwrite and account already exists, just return that account if overwrite == False: acc = get_account(account_name, password) if acc is not None: return acc # Generate the private key using Ethereum methods acc = Account.create( extra_entropy= "Alastria is the first Public-Permissioned Blockchain Network") address = acc.address publicKey = PublicKey.from_private(acc._key_obj).to_hex() # Encrypt the private key and prepare for saving it key_encrypted = acc.encrypt(password) key_encrypted = json.dumps(key_encrypted) db.execute( 'REPLACE INTO testaccount (name, address, publickey, privatekey) VALUES (?, ?, ?, ?)', (account_name, address, publicKey, key_encrypted)) # Commit database db.commit() # Return account to caller return acc
def verify(msghash: bytes, signature, public_key): """Verify that data has been signed with Etheruem private key. :param signature: :return: """ key_api = KeyAPI('eth_keys.backends.NativeECCBackend') return key_api.ecdsa_verify(msghash, Signature(signature), PublicKey(public_key))
def verify_message(self, address: str, pubkey: bytes, message: bytes, signature_bytes: bytes): """ Verifies that the message was signed by the keypair. """ # Check that address and pubkey match eth_pubkey = PublicKey(pubkey) signature = EthSignature(signature_bytes=signature_bytes) if not eth_pubkey.to_checksum_address() == address: raise ValueError("Pubkey address ({}) doesn't match the provided address ({})".format(eth_pubkey.to_checksum_address, address)) hashed_message = keccak(message) if not self.blockchain.interface.call_backend_verify( eth_pubkey, signature, hashed_message): raise PowerUpError("Signature is not valid for this message or pubkey.") else: return True
def ecdh_agree(privkey: datatypes.PrivateKey, pubkey: datatypes.PublicKey) -> bytes: """Performs a key exchange operation using the ECDH algorithm.""" privkey_as_int = int(cast(int, privkey)) ec_privkey = ec.derive_private_key(privkey_as_int, CURVE, default_backend()) pubkey_bytes = b'\x04' + pubkey.to_bytes() pubkey_nums = ec.EllipticCurvePublicNumbers.from_encoded_point(CURVE, pubkey_bytes) ec_pubkey = pubkey_nums.public_key(default_backend()) return ec_privkey.exchange(ec.ECDH(), ec_pubkey)
def create_stub_enr(pubkey: datatypes.PublicKey, address: AddressAPI) -> ENR: return ENR( 0, { IDENTITY_SCHEME_ENR_KEY: V4CompatIdentityScheme.id, V4CompatIdentityScheme.public_key_enr_key: pubkey.to_compressed_bytes(), IP_V4_ADDRESS_ENR_KEY: address.ip_packed, UDP_PORT_ENR_KEY: address.udp_port, TCP_PORT_ENR_KEY: address.tcp_port, }, signature=b'')
def ecdsa_verify(self, msg_hash: bytes, signature: BaseSignature, public_key: PublicKey) -> bool: der_encoded_signature = der.two_int_sequence_encoder( signature.r, signature.s) coincurve_public_key = self.keys.PublicKey(b"\x04" + public_key.to_bytes()) return coincurve_public_key.verify( der_encoded_signature, msg_hash, hasher=None, )
def validate(transaction, signature, public_key): try: s = Signature(signature_bytes=signature) txID = hashlib.sha256( bytes.fromhex(transaction) if type(transaction) is str else transaction).digest() keys = KeyAPI('eth_keys.backends.NativeECCBackend') publicKey = PublicKey(bytes.fromhex(public_key)) return keys.ecdsa_verify(txID, s, publicKey), txID.hex() except: return False, ""
def ecdsa_verify(self, msg_hash: bytes, signature: BaseSignature, public_key: PublicKey) -> bool: # coincurve rejects signatures with a high s, so convert to the equivalent low s form low_s = coerce_low_s(signature.s) der_encoded_signature = der.two_int_sequence_encoder( signature.r, low_s) coincurve_public_key = self.keys.PublicKey(b"\x04" + public_key.to_bytes()) return coincurve_public_key.verify( der_encoded_signature, msg_hash, hasher=None, )
def validate_signature(cls, enr: "ENR") -> None: public_key = PublicKey.from_compressed_bytes( enr[cls.public_key_enr_key]) message = enr.get_signing_message() try: signature = NonRecoverableSignature(enr.signature) except BadSignature: is_valid = False else: is_valid = signature.verify_msg(message, public_key) if not is_valid: raise ValidationError("Invalid signature")
def ecdsa_recover(self, msg_hash: bytes, signature: Signature) -> PublicKey: signature_bytes = signature.to_bytes() try: public_key_bytes = self.keys.PublicKey.from_signature_and_message( signature_bytes, msg_hash, hasher=None, ).format(compressed=False)[1:] except (ValueError, Exception) as err: # `coincurve` can raise `ValueError` or `Exception` dependending on # how the signature is invalid. raise BadSignature(str(err)) public_key = PublicKey(public_key_bytes, backend=self) return public_key
def ecdh_agree(privkey: datatypes.PrivateKey, pubkey: datatypes.PublicKey) -> bytes: """Performs a key exchange operation using the ECDH algorithm.""" privkey_as_int = int(cast(int, privkey)) ec_privkey = ec.derive_private_key(privkey_as_int, CURVE, default_backend()) pubkey_bytes = b'\x04' + pubkey.to_bytes() try: # either of these can raise a ValueError: pubkey_nums = ec.EllipticCurvePublicKey.from_encoded_point(CURVE, pubkey_bytes) ec_pubkey = pubkey_nums.public_numbers().public_key(default_backend()) except ValueError as exc: # Not all bytes can be made into valid public keys, see the warning at # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/ # under EllipticCurvePublicNumbers(x, y) raise _InvalidPublicKey(str(exc)) from exc return ec_privkey.exchange(ec.ECDH(), ec_pubkey)
def validate_signature(cls, *, message: bytes, signature: bytes, public_key: bytes) -> None: public_key_object = PublicKey.from_compressed_bytes(public_key) try: signature_object = NonRecoverableSignature(signature) except BadSignature: is_valid = False else: is_valid = signature_object.verify_msg(message, public_key_object) if not is_valid: raise ValidationError( f"Signature {encode_hex(signature)} is not valid for message {encode_hex(message)} " f"and public key {encode_hex(public_key)}")
def generate_private_key( entropy: str = "Alastria is the first Public-Permissioned Blockchain Network" ) -> AccountData: # Generate the private key using Ethereum methods eth_acc = Account.create(extra_entropy=entropy) # Get the address, public key and private key into the AccountData structure, # which is independent from eth_account.Account account = AccountData(address=eth_acc.address, publicKey=PublicKey.from_private( eth_acc._key_obj).to_hex(), privateKey=eth_acc.privateKey.hex()) return account
def compute_session_keys(cls, *, local_private_key: bytes, remote_public_key: bytes, local_node_id: NodeID, remote_node_id: NodeID, id_nonce: IDNonce, is_locally_initiated: bool) -> SessionKeys: local_private_key_object = PrivateKey(local_private_key) remote_public_key_object = PublicKey.from_compressed_bytes( remote_public_key) secret = ecdh_agree(local_private_key_object, remote_public_key_object) if is_locally_initiated: initiator_node_id, recipient_node_id = local_node_id, remote_node_id else: initiator_node_id, recipient_node_id = remote_node_id, local_node_id info = b"".join(( HKDF_INFO, initiator_node_id, recipient_node_id, )) hkdf = HKDF( algorithm=SHA256(), length=3 * AES128_KEY_SIZE, salt=id_nonce, info=info, backend=cls.cryptography_backend, ) expanded_key = hkdf.derive(secret) if len(expanded_key) != 3 * AES128_KEY_SIZE: raise Exception( "Invariant: Secret is expanded to three AES128 keys") initiator_key = expanded_key[:AES128_KEY_SIZE] recipient_key = expanded_key[AES128_KEY_SIZE:2 * AES128_KEY_SIZE] auth_response_key = expanded_key[2 * AES128_KEY_SIZE:3 * AES128_KEY_SIZE] if is_locally_initiated: encryption_key, decryption_key = initiator_key, recipient_key else: encryption_key, decryption_key = recipient_key, initiator_key return SessionKeys( encryption_key=AES128Key(encryption_key), decryption_key=AES128Key(decryption_key), auth_response_key=AES128Key(auth_response_key), )
def from_pubkey_and_addr(cls: Type[TNode], pubkey: datatypes.PublicKey, address: AddressAPI) -> TNode: enr = ENR(0, { IDENTITY_SCHEME_ENR_KEY: V4CompatIdentityScheme.id, V4CompatIdentityScheme.public_key_enr_key: pubkey.to_compressed_bytes(), IP_V4_ADDRESS_ENR_KEY: address.ip_packed, UDP_PORT_ENR_KEY: address.udp_port, TCP_PORT_ENR_KEY: address.tcp_port, }, signature=b'') return cls(enr)
def key_JWK(account_name, password): """Gets the Private key in JWK format. --- Definitions --- {"name": "account_name", "prompt": "Account name", "default": "did:elsi:VATES-A87471264"} {"name": "password", "prompt": "Password to decrypt private key", "default": "Mypassword"} """ # The password is required, to be able to get the private key from the database if password is None: return None db = get_wallet_db() account = db.execute('SELECT * FROM testaccount WHERE name = ?', (account_name, )).fetchone() # Check if account_name was in the database if account is None: return None # Attempt to decrypt the private key with the password and derive the public key private_key = Account.decrypt(account["privatekey"], password) acc = Account.from_key(private_key) publicKey = PublicKey.from_private(acc._key_obj) # The public key is 64 bytes composed of the x and y curve coordinates # x and y are each 32 bytes long # We convert x and y to hex, so the dictionary can be converted to JSON x = publicKey[:32] y = publicKey[32:] # Create the Json Web Key (JWK) representation, as specified by W3C DID Document format key_JWK = JWK(kty="EC", crv="secp256k1", d=base64url_encode(acc.privateKey), x=base64url_encode(x), y=base64url_encode(y)) return key_JWK
def create_and_save_account(account_name, password): db = get_wallet_db() # Generate the private key using Ethereum methods acc = Account.create( extra_entropy= "Alastria is the first Public-Permissioned Blockchain Network") address = acc.address publicKey = PublicKey.from_private(acc._key_obj).to_hex() # Encrypt the private key and prepare for saving it key_encrypted = acc.encrypt(password) key_encrypted = json.dumps(key_encrypted) print(f"Saving {address} and its private key in database)") db.execute( 'REPLACE INTO testaccount (name, address, publickey, privatekey) VALUES (?, ?, ?, ?)', (account_name, address, publicKey, key_encrypted)) # Commit database, just in case db.commit() # Return account to caller return acc
def private_key_to_checksum_address(key): if key.startswith('0x'): key = key[2:] return PublicKey.from_private(PrivateKey( bytes.fromhex(key))).to_checksum_address()
def __init__(self, pubkey: datatypes.PublicKey, address: Address) -> None: self.pubkey = pubkey self.address = address self.id = big_endian_to_int(keccak(pubkey.to_bytes()))
def peer_id_from_pubkey(pubkey: datatypes.PublicKey) -> ID: algo = multihash.Func.sha2_256 mh_digest = multihash.digest(pubkey.to_bytes(), algo) return ID(mh_digest.encode())
def validateHASH(txID, signature, public_key): s = Signature(signature_bytes=signature) keys = KeyAPI('eth_keys.backends.NativeECCBackend') publicKey = PublicKey(bytes.fromhex(public_key)) return keys.ecdsa_verify(txID, s, publicKey)
def private_key_to_public_key(self, private_key: PrivateKey) -> PublicKey: public_key_bytes = private_key_to_public_key(private_key.to_bytes()) public_key = PublicKey(public_key_bytes, backend=self) return public_key
def ecdsa_recover(self, msg_hash: bytes, signature: Signature) -> PublicKey: public_key_bytes = ecdsa_raw_recover(msg_hash, signature.vrs) public_key = PublicKey(public_key_bytes, backend=self) return public_key