import base58 import ecdsa import hashlib from struct import pack, unpack from Crypto.Hash import RIPEMD160 COMPRESSED_PUBLIC_KEY_LENGTH = 33 NEO_ADDRESS_VERSION = 0x35 UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65 DECODE_ADDRESS_LENGTH = 21 PS_IN_HASH160 = 0x0C CHECK_SIG_DESCRIPTOR = int.from_bytes( hashlib.sha256("System.Crypto.CheckSig".encode('ascii')).digest()[:4], byteorder='little' ) class KeyExtension: def get_private_key_from_wif(self, wif: str) -> bytes: """ Converts a WIF private key to a byte array. :param wif: WIF private key in string format. :return: Private key in byte format (32 bytes). :raises ValueError: If the WIF key is incorrect. """ assert not self.is_empty(wif) decoded = base58.b58decode_check(wif) if len(decoded) != 34 or decoded[0] != 0x80 or decoded[-1] != 0x01: raise ValueError("Incorrect WIF private key") private_key = decoded[1:-1] return private_key def get_public_key(self, private_key: bytes) -> bytes: """ Extract public key from Private key :param private_key: Private key in byte format (32 bytes). :return: compressed public key in byte format (33 bytes). :raises ValueError: If the private_key key is empty or null. """ assert not self.is_empty(private_key) if len(private_key) != 32: raise ValueError(f"Incorrect len of private key, Expected: 32, Actual: {len(private_key)}") public_key = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.NIST256p).get_verifying_key() compressed_public_key = public_key.to_string("compressed") return compressed_public_key def get_owner_id_by_public_key(self, public_key: bytes) -> str: if len(public_key) != COMPRESSED_PUBLIC_KEY_LENGTH: raise ValueError(f"Encoded compressed public key has wrong length. Expected {COMPRESSED_PUBLIC_KEY_LENGTH}, got {len(public_key)}") script_hash = self.get_script_hash(public_key) data = bytearray(DECODE_ADDRESS_LENGTH) data[0] = NEO_ADDRESS_VERSION data[1:] = script_hash return base58.b58encode_check(data).decode('utf-8') def get_script_hash(self, public_key: bytes): script = self.create_signature_redeem_script(public_key) sha256_hash = hashlib.sha256(script).digest() return self.get_ripemd160(sha256_hash) @staticmethod def create_signature_redeem_script(public_key: bytes): if len(public_key) != COMPRESSED_PUBLIC_KEY_LENGTH: raise ValueError(f"Encoded compressed public key has wrong length. Expected {COMPRESSED_PUBLIC_KEY_LENGTH}, got {len(public_key)}") script = bytearray([PS_IN_HASH160, COMPRESSED_PUBLIC_KEY_LENGTH]) script.extend(public_key) script.append(UNCOMPRESSED_PUBLIC_KEY_LENGTH) script.extend(pack("