def init_key(): cv = Curve.get_curve('secp256k1') pv_key = ECPrivateKey( int(hashlib.md5(settings.PRIVATE_KEY.encode()).hexdigest(), 16), cv) settings.PUBLIC_KEY = hex( int.from_bytes(cv.encode_point(pv_key.get_public_key().W), "big")) print(settings.PUBLIC_KEY)
def tweak_add(self, scalar): if USE_SECP: self.obj = self.obj.tweak_add(scalar) else: scalar = int.from_bytes(scalar, 'big') privKey = ECPrivateKey(scalar, CURVE_SECP256K1) self.obj = ECPublicKey(self.obj.W + privKey.get_public_key().W)
def verify(api_key, api_secret, params, signature, timestamp): payload = __composePayload(api_key, params, timestamp) pv_key = ECPrivateKey( int(binascii.hexlify(base64.b64decode(api_secret)), 16), cv) hashed_payload = hashlib.sha256(payload.encode("UTF-8")).hexdigest() return ECDSA().verify(bytearray.fromhex(hashed_payload), bytearray.fromhex(signature), pv_key.get_public_key())
def gen_random_tx(curve): n = curve.order P = curve.generator sA = random.randint(0, n) sk = ECPrivateKey(sA, curve) QA = sA * P pk = ECPublicKey(QA) payee_sA = random.randint(0, n) payee_sk = ECPrivateKey(payee_sA, curve) payee_QA = sA * P payee_pk = ECPublicKey(payee_QA) sum_string = "*** Bitcoin transaction ***\n" serial = random.getrandbits(128) sum_string += "Serial number: " + str(serial) + "\n" sum_string += "Payer Public key - x: " + str(QA.x) + "\n" sum_string += "Payer Public key - y: " + str(QA.y) + "\n" sum_string += "Payee Public key - x: " + str(payee_QA.x) + "\n" sum_string += "Payee Public key - y: " + str(payee_QA.y) + "\n" amount = random.randint(1, 1000000) sum_string += "Amount: " + str(amount) + " Satoshi" + "\n" signer = ECDSA() sig = signer.sign(sum_string.encode('UTF-8'), sk) (r, s) = decode_sig(sig) # k = random.randint(1, n - 1) # R = k * P # r = R.x % n # #r = str(r).encode('UTF-8') # h = hashlib.sha3_256() # h.update(sum_string.encode('UTF-8')) # # h.update(str(r).encode('UTF-8')) # #h.update(r) # m + r # s = (modinv(k, n) * ((int(h.hexdigest(), 16)) + (sA * r))) % n # #h = int(h.hexdigest(), 16) sum_string += "Signature - r: " + str(r) + "\n" sum_string += "Signature - s: " + str(s) + "\n" return sum_string
def derive_keypair( cls: Type[ED25519], decoded_seed: bytes, is_validator: bool ) -> Tuple[str, str]: """ Derives a keypair. Args: decoded_seed: an ED25519 seed from which to derive keypair. is_validator: if True indicates that caller wishes to derive a validator keypair from this seed, however, that is always invalid for this algorithm and will cause this function to raise. Returns: A (private key, public key) derived from seed Raises: XRPLKeypairsException: If the keypair is a validator keypair. """ if is_validator: raise XRPLKeypairsException("validator keypairs cannot use ED25519") raw_private = sha512_first_half(decoded_seed) private = ECPrivateKey(int.from_bytes(raw_private, "big"), _CURVE) public = EDDSA.get_public_key(private, sha512) return ( cls._format_key(cls._public_key_to_str(public)), cls._format_key(cls._private_key_to_str(private)), )
def get_public_key(priv_key, curve=curve.P256, hashfunc=sha256, fmt='RAW'): if fmt in ['RAW', '04']: priv_key = int(priv_key, 16) pub_key_obj = ECPrivateKey(priv_key, curve).get_public_key() return point_to_hex_str(pub_key_obj.W, fmt=fmt) else: raise UnknownPublicKeyFormatError("fmt: '%s'" % fmt)
def derive_keypair( cls: Type[ED25519], decoded_seed: bytes, is_validator: bool ) -> Tuple[str, str]: """ Derives a key pair in Ed25519 format for use with the XRP Ledger from a seed value. Args: decoded_seed: The Ed25519 seed to derive a key pair from, as bytes. is_validator: Whether to derive a validator keypair. However, validator signing keys cannot use Ed25519. (See `#3434 <https://github.com/ripple/rippled/issues/3434>`_ for more information.) Returns: A (public key, private key) pair derived from the given seed. Raises: XRPLKeypairsException: If the keypair is a validator keypair. """ if is_validator: raise XRPLKeypairsException("Validator key pairs cannot use Ed25519") raw_private = sha512_first_half(decoded_seed) private = ECPrivateKey(int.from_bytes(raw_private, "big"), _CURVE) public = EDDSA.get_public_key(private, sha512) return ( cls._format_key(cls._public_key_to_str(public)), cls._format_key(cls._private_key_to_str(private)), )
def __init__(self, privkey=None, raw=True): if USE_SECP: self.obj = secp256k1.PrivateKey(privkey, raw) self.pubkey = self.obj.pubkey else: if not raw: raise Exception("Non raw init unsupported") if privkey == None: privkey = ecpy.ecrand.rnd(CURVE_SECP256K1.order) else: privkey = int.from_bytes(privkey,'big') self.obj = ECPrivateKey(privkey, CURVE_SECP256K1) pubkey = self.obj.get_public_key().W out = b"\x04" out += pubkey.x.to_bytes(32, 'big') out += pubkey.y.to_bytes(32, 'big') self.pubkey = PublicKey(out, raw=True)
class PrivateKey(object): def __init__(self, privkey=None, raw=True, flags=None, ctx=None): if USE_SECP: if flags == None: flags = secp256k1.ALL_FLAGS self.obj = secp256k1.PrivateKey(privkey, raw, flags, ctx) self.pubkey = self.obj.pubkey else: if not raw: raise Exception("Non raw init unsupported") if privkey == None: privkey = ecpy.ecrand.rnd(CURVE_SECP256K1.order) else: privkey = int.from_bytes(privkey, 'big') self.obj = ECPrivateKey(privkey, CURVE_SECP256K1) pubkey = self.obj.get_public_key().W out = b"\x04" out += pubkey.x.to_bytes(32, 'big') out += pubkey.y.to_bytes(32, 'big') self.pubkey = PublicKey(out, raw=True) def serialize(self): if USE_SECP: return self.obj.serialize() else: return "%.64x" % self.obj.d def ecdsa_serialize(self, raw_sig): if USE_SECP: return self.obj.ecdsa_serialize(raw_sig) else: return raw_sig def ecdsa_sign(self, msg, raw=False, digest=hashlib.sha256, rfc6979=False): if USE_SECP: return self.obj.ecdsa_sign(msg, raw, digest) else: if not raw: h = digest() h.update(msg) msg = h.digest() if rfc6979: signature = SIGNER.sign_rfc6979(msg, self.obj, digest, True) else: signature = SIGNER.sign(msg, self.obj, True) return signature
def createPriKey(wifKey): wif_encoding_private_key = wifKey wifUNCompressed = bitcoin.decode_privkey(wif_encoding_private_key, 'wif_compressed') decimalToHex = bitcoin.encode(wifUNCompressed, 16) # return str cv = Curve.get_curve('secp256k1') pv_key = ECPrivateKey(int("0x" + decimalToHex, 16), cv) # 16进制str 转为 int return pv_key
def gen_keypair(curve=curve.P256, hashfunc=sha256, pub_key_fmt='RAW'): if pub_key_fmt in ['RAW', '04']: priv_key = _gen_private_key(curve) pub_key_obj = ECPrivateKey(priv_key, curve).get_public_key() return int_to_hex_str(priv_key), point_to_hex_str(pub_key_obj.W, fmt=pub_key_fmt) else: raise UnknownPublicKeyFormatError("fmt: '%s'" % fmt)
def _(): cv = Curve.get_curve('secp256k1'); pu_key = ECPublicKey( Point(0x65d5b8bf9ab1801c9f168d4815994ad35f1dcb6ae6c7a1a303966b677b813b00, 0xe6b865e529b8ecbf71cf966e900477d49ced5846d7662dd2dd11ccd55c0aff7f, cv)) pv_key = ECPrivateKey( 0xfb26a4e75eec75544c0f44e937dcf5ee6355c7176600b9688c667e5c283b43c5, cv) ; signer = ECDSA(fmt="ITUPLE") sig = signer.sign(b'01234567890123456789012345678912', pv_key) ;return sig
def __init__(self, params: ZKParameters): """ Initialize the ZKProof """ self._curve: Curve = Curve.get_curve(params.curve) if self._curve is None: raise NotImplementedError(f"Invalid Curve '{params.curve}'") self._ecc: ECPrivateKey = ECPrivateKey(params.d, self._curve) self._g0: int = convert.point_to_int(self._ecc.get_public_key().W)
def __init__(self) -> None: cv = Curve.get_curve('secp256k1') pu_key = ECPublicKey() pv_key = ECPrivateKey() print(pu_key) print(pv_key)
class PrivateKey(object): def __init__(self, privkey=None, raw=True, flags=None, ctx=None): if USE_SECP: if flags == None: flags = secp256k1.ALL_FLAGS self.obj = secp256k1.PrivateKey(privkey, raw, flags, ctx) self.pubkey = self.obj.pubkey else: if not raw: raise Exception("Non raw init unsupported") if privkey == None: privkey = ecpy.ecrand.rnd(CURVE_SECP256K1.order) else: privkey = int.from_bytes(privkey,'big') self.obj = ECPrivateKey(privkey, CURVE_SECP256K1) pubkey = self.obj.get_public_key().W out = b"\x04" out += pubkey.x.to_bytes(32, 'big') out += pubkey.y.to_bytes(32, 'big') self.pubkey = PublicKey(out, raw=True) def serialize(self): if USE_SECP: return self.obj.serialize() else: return "%.64x"%self.obj.d def ecdsa_serialize(self, raw_sig): if USE_SECP: return self.obj.ecdsa_serialize(raw_sig) else: return raw_sig def ecdsa_sign(self, msg, raw=False, digest=hashlib.sha256): if USE_SECP: return self.obj.ecdsa_sign(msg, raw, digest) else: if not raw: h = digest() h.update(msg) msg = h.digest() signature = SIGNER.sign(msg, self.obj) return bytearray(signature)
def gen_random_tx(curve): # get a random 128 BIT integer for serial number serial_num = Num.getRandomNBitInteger(128) # create the public key for sender n = curve.order P = curve.generator sA = Num.getRandomRange(0, n + 1) sK = ECPrivateKey(sA, curve) QA = sA * P pk = ECPublicKey(QA) signer = ECDSA() # create the public key for sendee sA_2 = Num.getRandomRange(0, n + 1) sK_2 = ECPrivateKey(sA_2, curve) P2 = curve.generator QA_2 = sA_2 * P2 pk_2 = ECPublicKey(QA_2) # header for the block temp = "*** Bitcoin transaction ***\n" # add the serial number to the block temp = temp + "Serial number: " + str(serial_num) + "\n" # write payers public keys temp = temp + "Payer public key - x: " + str(QA.x) + "\n" temp = temp + "Payer public key - y: " + str(QA.y) + "\n" # write payees public keys temp = temp + "Payee public key - x: " + str(QA_2.x) + "\n" temp = temp + "Payee public key - y: " + str(QA_2.y) + "\n" # get random transaction val amount = Num.getRandomRange(0, 1000001) temp = temp + "Amount: " + str(amount) + "\n" sig = signer.sign(temp.encode("utf-8"), sK) (r, s) = decode_sig(sig) temp = temp + "Signature (r): " + str(r) + "\n" temp = temp + "Signature (s): " + str(s) + "\n" return temp
def _do_derive_part( cls: Type[SECP256K1], bytes_input: bytes, phase: Literal["root", "mid"]) -> Tuple[ECPublicKey, ECPrivateKey]: """ Given bytes_input determine public/private keypair for a given phase of this algorithm. The difference between generating the root and intermediate keypairs is just what bytes are input by the caller and that the intermediate keypair needs to inject _INTERMEDIATE_KEYPAIR_PADDING into the value to hash to get the raw private key. """ def _candidate_merger(candidate: bytes) -> bytes: if phase == "root": return bytes_input + candidate return bytes_input + _INTERMEDIATE_KEYPAIR_PADDING + candidate raw_private = cls._get_secret(_candidate_merger) wrapped_private = ECPrivateKey(int.from_bytes(raw_private, "big"), _CURVE) return wrapped_private.get_public_key(), wrapped_private
def sign(api_key, api_secret, params, timestamp=None): if timestamp is None: timestamp = __currentTimestamp() payload = __composePayload(api_key, params, timestamp) hashed_payload = hashlib.sha256(payload.encode("UTF-8")).hexdigest() pv_key = ECPrivateKey( int(binascii.hexlify(base64.b64decode(api_secret)), 16), cv) signature_bytes = ECDSA().sign(bytearray.fromhex(hashed_payload), pv_key) return binascii.hexlify(signature_bytes).decode("UTF-8"), timestamp
def sign(priv_key, data, hashfunc=sha256, curve=curve.P256, sign_fmt='DER', sign_size=32): priv_key_int = int(priv_key, 16) priv_key_obj = ECPrivateKey(priv_key_int, curve) if sign_fmt in ['RAW', 'DER']: pass else: raise UnknownSignatureFormatError("fmt: '%s'" % sign_fmt) signer = ECDSAWithSize(fmt=sign_fmt, size=sign_size) msg = hashfunc(data.encode()).digest() signature = signer.sign(msg, priv_key_obj).hex() return signature
def _derive_final_pair( cls: Type[SECP256K1], root_public: ECPublicKey, root_private: ECPrivateKey, mid_public: ECPublicKey, mid_private: ECPrivateKey, ) -> Tuple[ECPublicKey, ECPrivateKey]: raw_private = (root_private.d + mid_private.d) % _GROUP_ORDER wrapped_private = ECPrivateKey(raw_private, _CURVE) wrapped_public = ECPublicKey( _CURVE.add_point(root_public.W, mid_public.W)) return wrapped_public, wrapped_private
def sign(cls: Type[ED25519], message: bytes, private_key: str) -> bytes: """ Signs a message using a given Ed25519 private key. Args: message: The message to sign, as bytes. private_key: The private key to use to sign the message. Returns: The signature of the message. """ raw_private = private_key[len(PREFIX) :] wrapped_private = ECPrivateKey(int(raw_private, 16), _CURVE) return cast(bytes, _SIGNER.sign(message, wrapped_private))
def sign(cls: Type[ED25519], message: bytes, private_key: str) -> bytes: """ Signs a message. Args: message: Message to sign private_key: Key with which to sign message Returns: The signature of message signed using private_key. """ raw_private = private_key[len(PREFIX) :] wrapped_private = ECPrivateKey(int(raw_private, 16), _CURVE) return cast(bytes, _SIGNER.sign(message, wrapped_private))
def gen_random_tx(curve): serial = random.randrange(pow(2, 127), pow(2, 128)) amount = random.randrange(1, 1000001) n = curve.order P = curve.generator sA = random.randint(0, n) sB = random.randint(0, n) skA = ECPrivateKey(sA, curve) skB = ECPrivateKey(sB, curve) QA = sA * P QB = sB * P pkA = ECPublicKey(QA) pkB = ECPublicKey(QB) signer = ECDSA() trans = "**** Bitcoin transaction ****" + \ "\nSerial number: " + str(serial) + \ "\nPayer public key - x: " + str(QA.x) + \ "\nPayer public key - y: " + str(QA.y) + \ "\nPayee public key - x: " + str(QB.x) + \ "\nPayee public key - y: " + str(QB.y) + \ "\nAmount: " + str(amount) + "\n" t = trans.encode("UTF-8") sig = signer.sign(t, skA) (r, s) = decode_sig(sig) trans += "Signature (r): " + str(r) + "\n" + "Signature (s): " + str( s) + "\n" return trans
def gen_random_tx(curve): serial = random.randint(0, 2**128 - 1) # creates 128 bit random serial number n = curve.order P = curve.generator sA = random.randint(0, n) sk = ECPrivateKey(sA, curve) QA = sA * P pk = ECPublicKey(QA) sB = random.randint(0, n) skB = ECPrivateKey(sB, curve) QB = sB * P pkB = ECPublicKey(QB) amount = random.randint(1, 1000000) # create a random int for amount transaction = "**** Bitcoin transaction ****\n" transaction += "Serial number: " + str(serial) + "\n" transaction += "Payer public key - x: " + str(QA.x) + "\n" transaction += "Payer public key - y: " + str(QA.y) + "\n" transaction += "Payee public key - x: " + str(QB.x) + "\n" transaction += "Payee public key - y: " + str(QB.y) + "\n" transaction += "Amount: " + str(amount) + "\n" signer = ECDSA() message = transaction message = message.encode('UTF-8') sig = signer.sign(message, sk) (r, s) = decode_sig(sig) transaction += "Signature (r): " + str(r) + "\n" transaction += "Signature (s): " + str(s) + "\n" return transaction
def __init__(self, privkey=None, raw=True, flags=None, ctx=None): if USE_SECP: if flags == None: flags = secp256k1.ALL_FLAGS self.obj = secp256k1.PrivateKey(privkey, raw, flags, ctx) self.pubkey = self.obj.pubkey else: if not raw: raise Exception("Non raw init unsupported") if privkey == None: privkey = ecpy.ecrand.rnd(CURVE_SECP256K1.order) else: privkey = int.from_bytes(privkey,'big') self.obj = ECPrivateKey(privkey, CURVE_SECP256K1) pubkey = self.obj.get_public_key().W out = b"\x04" out += pubkey.x.to_bytes(32, 'big') out += pubkey.y.to_bytes(32, 'big') self.pubkey = PublicKey(out, raw=True)
def sign(cls: Type[SECP256K1], message: bytes, private_key: str) -> bytes: """ Signs message in SECP256k1 using the given private key. Args: message: The message to sign in SECP256k1. private_key: The private key to use to sign the message. Returns: The signed message. """ wrapped_private = ECPrivateKey(int(private_key, 16), _CURVE) return cast( bytes, _SIGNER.sign_rfc6979( sha512_first_half(message), wrapped_private, sha256, canonical=True, ), )
if __name__ == "__main__": try: ### EDDSA cv = Curve.get_curve('Ed25519') # public key # x: 74ad28205b4f384bc0813e6585864e528085f91fb6a5096f244ae01e57de43ae # y: 0c66f42af155cdc08c96c42ecf2c989cbc7e1b4da70ab7925a8943e8c317403d pu_key = ECPublicKey(Point(0x74ad28205b4f384bc0813e6585864e528085f91fb6a5096f244ae01e57de43ae, 0x0c66f42af155cdc08c96c42ecf2c989cbc7e1b4da70ab7925a8943e8c317403d, cv)) # private key # s: 0x4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb pv_key = ECPrivateKey(0x4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb, cv) pu = EDDSA.get_public_key(pv_key) assert(pu.W == pu_key.W); # sig: # 0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da # 0x085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00 expected_sig = int(0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00) expected_sig = expected_sig.to_bytes(64,'big') #msg: # 72 msg = int(0x72) msg = msg.to_bytes(1,'big')
def from_bytes(cls, priv_key_bytes): if len(priv_key_bytes) != PRIV_KEY_LENGTH: raise InvalidPrivateKeyError() priv_key_int = int.from_bytes(priv_key_bytes, "big") priv_key = ECPrivateKey(priv_key_int, CURVE) return cls(priv_key)
def generate(cls): priv_key_bytes = ecpy.ecrand.rnd(CURVE.order) priv_key = ECPrivateKey(priv_key_bytes, CURVE) return cls(priv_key=priv_key)
### EDDSA cv = Curve.get_curve('Ed25519') # public key # x: 74ad28205b4f384bc0813e6585864e528085f91fb6a5096f244ae01e57de43ae # y: 0c66f42af155cdc08c96c42ecf2c989cbc7e1b4da70ab7925a8943e8c317403d pu_key = ECPublicKey( Point( 0x74ad28205b4f384bc0813e6585864e528085f91fb6a5096f244ae01e57de43ae, 0x0c66f42af155cdc08c96c42ecf2c989cbc7e1b4da70ab7925a8943e8c317403d, cv)) # private key # s: 0x4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb pv_key = ECPrivateKey( 0x4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb, cv) # sig: # 0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da # 0x085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00 expected_sig = int( 0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00 ) expected_sig = expected_sig.to_bytes(64, 'big') #msg: # 72 msg = int(0x72) msg = msg.to_bytes(1, 'big')
#pvkey: # f028458b39af92fea938486ecc49562d0e7731b53d9b25e2701183e4f2adc991 #Hash: # 8c7632afe967e2e16ae7f39dc32c252b3d751fa6e01daa0efc3c174e230f4617 #Signer's public: 04 # 81bc1f9486564d3d57a305e8f9067df2a7e1f007d4af4fed085aca139c6b9c7a # 8e3f35e4d7fb27a56a3f35d34c8c2b27cd1d266d5294df131bf3c1cbc39f5a91 #App signature: # 304402203a329589dbc6f3bb88bf90b45b5d4935a18e13e2cb8fcee0b94b3102ec19645702202f61af55df0e56e71d40a9f5f111faeb2f831c1fd314c55227ac44110fb33049 ### ECS # test key cv = Curve.get_curve('secp256k1') pv_key = ECPrivateKey(0xf028458b39af92fea938486ecc49562d0e7731b53d9b25e2701183e4f2adc991,cv) pu_key = ECPublicKey(Point(0x81bc1f9486564d3d57a305e8f9067df2a7e1f007d4af4fed085aca139c6b9c7a, 0x8e3f35e4d7fb27a56a3f35d34c8c2b27cd1d266d5294df131bf3c1cbc39f5a91, cv)) k = pv_key.get_public_key() assert(k.W.x == pu_key.W.x) assert(k.W.y == pu_key.W.y) print("Public key ok") msg = 0x8c7632afe967e2e16ae7f39dc32c252b3d751fa6e01daa0efc3c174e230f4617 msg = msg.to_bytes(32,'big') sig = 0x304402203a329589dbc6f3bb88bf90b45b5d4935a18e13e2cb8fcee0b94b3102ec19645702202f61af55df0e56e71d40a9f5f111faeb2f831c1fd314c55227ac44110fb33049
xPub = b'\x02'+pu_key.W.x.to_bytes(size,'big') hasher.update(xQ+xPub+msg) v = hasher.digest() v = int.from_bytes(v,'big') v = v%n return v == r if __name__ == "__main__": import sys,random try: cv = Curve.get_curve('NIST-P256') pu_key = ECPublicKey(Point(0x09b58b88323c52d1080aa525c89e8e12c6f40fcb014640fa88081ed9e9352de7, 0x5ccbbd189538516238b0b0b28acb5f0b5e27217c3a9872421219de0aeebf1080, cv)) pv_key = ECPrivateKey(0x5202a3d8acaf6909d12c9a774cd886f9fba61137ffd3e8e76aed363fb47ac492, cv) msg = int(0x616263) msg = msg.to_bytes(3,'big') k = int(0xde7e0e5e663f24183414b7c72f24546b81e9e5f410bebf26f3ca5fa82f5192c8) ## ISO R=0x5A79A0AA9B241E381A594B220554D096A5F09FA628AD9A33C3CE4393ADE1DEF7 S=0x5C0EB78B67A513C3E53B2619F96855E291D5141C7CD0915E1D04B347457C9601 signer = ECSchnorr(hashlib.sha256,"ISO","ITUPLE") sig = signer.sign_k(msg,pv_key,k) assert(R==sig[0]) assert(S==sig[1]) assert(signer.verify(msg,sig,pu_key))