def crypto_box_afternm(message, nonce, k): """ Encrypts and returns the message ``message`` using the shared key ``k`` and the nonce ``nonce``. :param message: bytes :param nonce: bytes :param k: bytes :rtype: bytes """ if len(nonce) != crypto_box_NONCEBYTES: raise exc.ValueError("Invalid nonce") if len(k) != crypto_box_BEFORENMBYTES: raise exc.ValueError("Invalid shared key") padded = b"\x00" * crypto_box_ZEROBYTES + message ciphertext = ffi.new("unsigned char[]", len(padded)) rc = lib.crypto_box_afternm(ciphertext, padded, len(padded), nonce, k) ensure(rc == 0, 'Unexpected library error', raising=exc.RuntimeError) return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
def crypto_secretbox_open(ciphertext, nonce, key): """ Decrypt and returns the encrypted message ``ciphertext`` with the secret ``key`` and the nonce ``nonce``. :param ciphertext: bytes :param nonce: bytes :param key: bytes :rtype: bytes """ if len(key) != crypto_secretbox_KEYBYTES: raise exc.ValueError("Invalid key") if len(nonce) != crypto_secretbox_NONCEBYTES: raise exc.ValueError("Invalid nonce") padded = b"\x00" * crypto_secretbox_BOXZEROBYTES + ciphertext plaintext = ffi.new("unsigned char[]", len(padded)) res = lib.crypto_secretbox_open(plaintext, padded, len(padded), nonce, key) ensure(res == 0, "Decryption failed. Ciphertext failed verification", raising=exc.CryptoError) plaintext = ffi.buffer(plaintext, len(padded)) return plaintext[crypto_secretbox_ZEROBYTES:]
def crypto_box_open_afternm(ciphertext, nonce, k): """ Decrypts and returns the encrypted message ``ciphertext``, using the shared key ``k`` and the nonce ``nonce``. :param ciphertext: bytes :param nonce: bytes :param k: bytes :rtype: bytes """ if len(nonce) != crypto_box_NONCEBYTES: raise exc.ValueError("Invalid nonce") if len(k) != crypto_box_BEFORENMBYTES: raise exc.ValueError("Invalid shared key") padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext plaintext = ffi.new("unsigned char[]", len(padded)) res = lib.crypto_box_open_afternm(plaintext, padded, len(padded), nonce, k) ensure(res == 0, "An error occurred trying to decrypt the message", raising=exc.CryptoError) return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
def crypto_box(message, nonce, pk, sk): """ Encrypts and returns a message ``message`` using the secret key ``sk``, public key ``pk``, and the nonce ``nonce``. :param message: bytes :param nonce: bytes :param pk: bytes :param sk: bytes :rtype: bytes """ if len(nonce) != crypto_box_NONCEBYTES: raise exc.ValueError("Invalid nonce size") if len(pk) != crypto_box_PUBLICKEYBYTES: raise exc.ValueError("Invalid public key") if len(sk) != crypto_box_SECRETKEYBYTES: raise exc.ValueError("Invalid secret key") padded = (b"\x00" * crypto_box_ZEROBYTES) + message ciphertext = ffi.new("unsigned char[]", len(padded)) rc = lib.crypto_box(ciphertext, padded, len(padded), nonce, pk, sk) ensure(rc == 0, 'Unexpected library error', raising=exc.RuntimeError) return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
def crypto_box_open(ciphertext, nonce, pk, sk): """ Decrypts and returns an encrypted message ``ciphertext``, using the secret key ``sk``, public key ``pk``, and the nonce ``nonce``. :param ciphertext: bytes :param nonce: bytes :param pk: bytes :param sk: bytes :rtype: bytes """ if len(nonce) != crypto_box_NONCEBYTES: raise exc.ValueError("Invalid nonce size") if len(pk) != crypto_box_PUBLICKEYBYTES: raise exc.ValueError("Invalid public key") if len(sk) != crypto_box_SECRETKEYBYTES: raise exc.ValueError("Invalid secret key") padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext plaintext = ffi.new("unsigned char[]", len(padded)) res = lib.crypto_box_open(plaintext, padded, len(padded), nonce, pk, sk) ensure(res == 0, "An error occurred trying to decrypt the message", raising=exc.CryptoError) return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
def crypto_box_seal_open(ciphertext, pk, sk): """ Decrypts and returns an encrypted message ``ciphertext``, using the recipent's secret key ``sk`` and the sender's ephemeral public key embedded in the sealed box. The box contruct nonce is derived from the recipient's public key ``pk`` and the sender's public key. :param ciphertext: bytes :param pk: bytes :param sk: bytes :rtype: bytes .. versionadded:: 1.2 """ ensure( isinstance(ciphertext, bytes), "input ciphertext must be bytes", raising=TypeError, ) ensure( isinstance(pk, bytes), "public key must be bytes", raising=TypeError ) ensure( isinstance(sk, bytes), "secret key must be bytes", raising=TypeError ) if len(pk) != crypto_box_PUBLICKEYBYTES: raise exc.ValueError("Invalid public key") if len(sk) != crypto_box_SECRETKEYBYTES: raise exc.ValueError("Invalid secret key") _clen = len(ciphertext) ensure( _clen >= crypto_box_SEALBYTES, ("Input cyphertext must be " "at least {} long").format( crypto_box_SEALBYTES ), raising=exc.TypeError, ) _mlen = _clen - crypto_box_SEALBYTES # zero-length malloc results are implementation.dependent plaintext = ffi.new("unsigned char[]", max(1, _mlen)) res = lib.crypto_box_seal_open(plaintext, ciphertext, _clen, pk, sk) ensure( res == 0, "An error occurred trying to decrypt the message", raising=exc.CryptoError, ) return ffi.buffer(plaintext, _mlen)[:]
def crypto_pwhash_alg(outlen, passwd, salt, opslimit, memlimit, alg): """ Derive a raw cryptographic key using the ``passwd`` and the ``salt`` given as input to the ``alg`` algorithm. :param outlen: the length of the derived key :type outlen: int :param passwd: The input password :type passwd: bytes :param opslimit: computational cost :type opslimit: int :param memlimit: memory cost :type memlimit: int :param alg: algorithm identifier :type alg: int :return: derived key :rtype: bytes """ ensure(isinstance(outlen, integer_types), raising=exc.TypeError) ensure(isinstance(opslimit, integer_types), raising=exc.TypeError) ensure(isinstance(memlimit, integer_types), raising=exc.TypeError) ensure(isinstance(alg, integer_types), raising=exc.TypeError) ensure(isinstance(passwd, bytes), raising=exc.TypeError) if len(salt) != crypto_pwhash_SALTBYTES: raise exc.ValueError("salt must be exactly {0} bytes long".format( crypto_pwhash_SALTBYTES)) if outlen < crypto_pwhash_BYTES_MIN: raise exc.ValueError( 'derived key must be at least {0} bytes long'.format( crypto_pwhash_BYTES_MIN)) elif outlen > crypto_pwhash_BYTES_MAX: raise exc.ValueError( 'derived key must be at most {0} bytes long'.format( crypto_pwhash_BYTES_MAX)) _check_argon2_limits_alg(opslimit, memlimit, alg) outbuf = ffi.new("unsigned char[]", outlen) ret = lib.crypto_pwhash(outbuf, outlen, passwd, len(passwd), salt, opslimit, memlimit, alg) ensure(ret == 0, 'Unexpected failure in key derivation', raising=exc.RuntimeError) return ffi.buffer(outbuf, outlen)[:]
def _check_argon2_limits_alg(opslimit, memlimit, alg): if (alg == crypto_pwhash_ALG_ARGON2I13): if memlimit < crypto_pwhash_argon2i_MEMLIMIT_MIN: raise exc.ValueError('memlimit must be at least {0} bytes'.format( crypto_pwhash_argon2i_MEMLIMIT_MIN)) elif memlimit > crypto_pwhash_argon2i_MEMLIMIT_MAX: raise exc.ValueError('memlimit must be at most {0} bytes'.format( crypto_pwhash_argon2i_MEMLIMIT_MAX)) if opslimit < crypto_pwhash_argon2i_OPSLIMIT_MIN: raise exc.ValueError('opslimit must be at least {0}'.format( crypto_pwhash_argon2i_OPSLIMIT_MIN)) elif opslimit > crypto_pwhash_argon2i_OPSLIMIT_MAX: raise exc.ValueError('opslimit must be at most {0}'.format( crypto_pwhash_argon2i_OPSLIMIT_MAX)) elif (alg == crypto_pwhash_ALG_ARGON2ID13): if memlimit < crypto_pwhash_argon2id_MEMLIMIT_MIN: raise exc.ValueError('memlimit must be at least {0} bytes'.format( crypto_pwhash_argon2id_MEMLIMIT_MIN)) elif memlimit > crypto_pwhash_argon2id_MEMLIMIT_MAX: raise exc.ValueError('memlimit must be at most {0} bytes'.format( crypto_pwhash_argon2id_MEMLIMIT_MAX)) if opslimit < crypto_pwhash_argon2id_OPSLIMIT_MIN: raise exc.ValueError('opslimit must be at least {0}'.format( crypto_pwhash_argon2id_OPSLIMIT_MIN)) elif opslimit > crypto_pwhash_argon2id_OPSLIMIT_MAX: raise exc.ValueError('opslimit must be at most {0}'.format( crypto_pwhash_argon2id_OPSLIMIT_MAX)) else: raise exc.TypeError('Unsupported algorithm')
def decrypt(self, ciphertext, nonce=None, encoder=encoding.RawEncoder): """ Decrypts the ciphertext using the `nonce` (explicitly, when passed as a parameter or implicitly, when omitted, as part of the ciphertext) and returns the plaintext message. :param ciphertext: [:class:`bytes`] The encrypted message to decrypt :param nonce: [:class:`bytes`] The nonce used when encrypting the ciphertext :param encoder: The encoder used to decode the ciphertext. :rtype: [:class:`bytes`] """ # Decode our ciphertext ciphertext = encoder.decode(ciphertext) if nonce is None: # If we were given the nonce and ciphertext combined, split them. nonce = ciphertext[:self.NONCE_SIZE] ciphertext = ciphertext[self.NONCE_SIZE:] if len(nonce) != self.NONCE_SIZE: raise exc.ValueError("The nonce must be exactly %s bytes long" % self.NONCE_SIZE) plaintext = nacl.bindings.crypto_box_open_afternm( ciphertext, nonce, self._shared_key, ) return plaintext
def encrypt(self, plaintext, nonce=None, encoder=encoding.RawEncoder): """ Encrypts the plaintext message using the given `nonce` (or generates one randomly if omitted) and returns the ciphertext encoded with the encoder. .. warning:: It is **VITALLY** important that the nonce is a nonce, i.e. it is a number used only once for any given key. If you fail to do this, you compromise the privacy of the messages encrypted. Give your nonces a different prefix, or have one side use an odd counter and one an even counter. Just make sure they are different. :param plaintext: [:class:`bytes`] The plaintext message to encrypt :param nonce: [:class:`bytes`] The nonce to use in the encryption :param encoder: The encoder to use to encode the ciphertext :rtype: [:class:`nacl.utils.EncryptedMessage`] """ if nonce is None: nonce = random(self.NONCE_SIZE) if len(nonce) != self.NONCE_SIZE: raise exc.ValueError( "The nonce must be exactly %s bytes long" % self.NONCE_SIZE, ) ciphertext = nacl.bindings.crypto_secretbox(plaintext, nonce, self._key) encoded_nonce = encoder.encode(nonce) encoded_ciphertext = encoder.encode(ciphertext) return EncryptedMessage._from_parts( encoded_nonce, encoded_ciphertext, encoder.encode(nonce + ciphertext), )
def crypto_box_seal(message, pk): """ Encrypts and returns a message ``message`` using an ephemeral secret key and the public key ``pk``. The ephemeral public key, which is embedded in the sealed box, is also used, in combination with ``pk``, to derive the nonce needed for the underlying box construct. :param message: bytes :param pk: bytes :rtype: bytes .. versionadded:: 1.2 """ ensure(isinstance(message, bytes), "input message must be bytes", raising=TypeError) ensure(isinstance(pk, bytes), "public key must be bytes", raising=TypeError) if len(pk) != crypto_box_PUBLICKEYBYTES: raise exc.ValueError("Invalid public key") _mlen = len(message) _clen = crypto_box_SEALBYTES + _mlen ciphertext = ffi.new("unsigned char[]", _clen) rc = lib.crypto_box_seal(ciphertext, message, _mlen, pk) ensure(rc == 0, 'Unexpected library error', raising=exc.RuntimeError) return ffi.buffer(ciphertext, _clen)[:]
def crypto_box_seed_keypair(seed): """ Returns a (public, secret) keypair deterministically generated from an input ``seed``. .. warning:: The seed **must** be high-entropy; therefore, its generator **must** be a cryptographic quality random function like, for example, :func:`~nacl.utils.random`. .. warning:: The seed **must** be protected and remain secret. Anyone who knows the seed is really in possession of the corresponding PrivateKey. :param seed: bytes :rtype: (bytes(public_key), bytes(secret_key)) """ ensure(isinstance(seed, bytes), "seed must be bytes", raising=TypeError) if len(seed) != crypto_box_SEEDBYTES: raise exc.ValueError("Invalid seed") pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES) sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES) rc = lib.crypto_box_seed_keypair(pk, sk, seed) ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError) return ( ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:], ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:], )
def crypto_shorthash_siphashx24(data, key): """Compute a fast, cryptographic quality, keyed hash of the input data :param data: :type data: bytes :param key: len(key) must be equal to :py:data:`.XKEYBYTES` (16) :type key: bytes :raises nacl.exceptions.UnavailableError: If called when using a minimal build of libsodium. """ ensure( has_crypto_shorthash_siphashx24, "Not available in minimal build", raising=exc.UnavailableError, ) if len(key) != XKEYBYTES: raise exc.ValueError( "Key length must be exactly {0} bytes".format(XKEYBYTES)) digest = ffi.new("unsigned char[]", XBYTES) rc = lib.crypto_shorthash_siphashx24(digest, data, len(data), key) ensure(rc == 0, raising=exc.RuntimeError) return ffi.buffer(digest, XBYTES)[:]
def verify(self, smessage, signature=None, encoder=encoding.RawEncoder): """ Verifies the signature of a signed message, returning the message if it has not been tampered with else raising :class:`~nacl.signing.BadSignatureError`. :param smessage: [:class:`bytes`] Either the original messaged or a signature and message concated together. :param signature: [:class:`bytes`] If an unsigned message is given for smessage then the detached signature must be provided. :param encoder: A class that is able to decode the secret message and signature. :rtype: :class:`bytes` """ if signature is not None: # If we were given the message and signature separately, validate # signature size and combine them. if not isinstance(signature, bytes): raise exc.TypeError( "Verification signature must be created from %d bytes" % nacl.bindings.crypto_sign_BYTES, ) if len(signature) != nacl.bindings.crypto_sign_BYTES: raise exc.ValueError( "The signature must be exactly %d bytes long" % nacl.bindings.crypto_sign_BYTES, ) smessage = signature + encoder.decode(smessage) else: # Decode the signed message smessage = encoder.decode(smessage) return nacl.bindings.crypto_sign_open(smessage, self._key)
def __init__(self, public_key, encoder=encoding.RawEncoder): self._public_key = encoder.decode(public_key) if not isinstance(self._public_key, bytes): raise exc.TypeError("PublicKey must be created from 32 bytes") if len(self._public_key) != self.SIZE: raise exc.ValueError( "The public key must be exactly {0} bytes long".format( self.SIZE))
def __init__(self, key, encoder=encoding.RawEncoder): key = encoder.decode(key) if not isinstance(key, bytes): raise exc.TypeError("SecretBox must be created from 32 bytes") if len(key) != self.KEY_SIZE: raise exc.ValueError( "The key must be exactly %s bytes long" % self.KEY_SIZE, ) self._key = key
def __init__(self, key, encoder=encoding.RawEncoder): # Decode the key key = encoder.decode(key) if not isinstance(key, bytes): raise exc.TypeError("VerifyKey must be created from 32 bytes") if len(key) != nacl.bindings.crypto_sign_PUBLICKEYBYTES: raise exc.ValueError( "The key must be exactly %s bytes long" % nacl.bindings.crypto_sign_PUBLICKEYBYTES, ) self._key = key
def crypto_box_beforenm(pk, sk): """ Computes and returns the shared key for the public key ``pk`` and the secret key ``sk``. This can be used to speed up operations where the same set of keys is going to be used multiple times. :param pk: bytes :param sk: bytes :rtype: bytes """ if len(pk) != crypto_box_PUBLICKEYBYTES: raise exc.ValueError("Invalid public key") if len(sk) != crypto_box_SECRETKEYBYTES: raise exc.ValueError("Invalid secret key") k = ffi.new("unsigned char[]", crypto_box_BEFORENMBYTES) rc = lib.crypto_box_beforenm(k, pk, sk) ensure(rc == 0, 'Unexpected library error', raising=exc.RuntimeError) return ffi.buffer(k, crypto_box_BEFORENMBYTES)[:]
def crypto_secretbox(message, nonce, key): """ Encrypts and returns the message ``message`` with the secret ``key`` and the nonce ``nonce``. :param message: bytes :param nonce: bytes :param key: bytes :rtype: bytes """ if len(key) != crypto_secretbox_KEYBYTES: raise exc.ValueError("Invalid key") if len(nonce) != crypto_secretbox_NONCEBYTES: raise exc.ValueError("Invalid nonce") padded = b"\x00" * crypto_secretbox_ZEROBYTES + message ciphertext = ffi.new("unsigned char[]", len(padded)) res = lib.crypto_secretbox(ciphertext, padded, len(padded), nonce, key) ensure(res == 0, "Encryption failed", raising=exc.CryptoError) ciphertext = ffi.buffer(ciphertext, len(padded)) return ciphertext[crypto_secretbox_BOXZEROBYTES:]
def crypto_sign_ed25519_sk_to_seed(secret_key_bytes): """ Extract the seed from a secret Ed25519 key (encoded as bytes ``secret_key_bytes``). Raises a ValueError if ``secret_key_bytes``is not of length ``crypto_sign_SECRETKEYBYTES`` :param secret_key_bytes: bytes :rtype: bytes """ if len(secret_key_bytes) != crypto_sign_SECRETKEYBYTES: raise exc.ValueError("Invalid secret key") return secret_key_bytes[:crypto_sign_SEEDBYTES]
def __init__(self, private_key, encoder=encoding.RawEncoder): # Decode the secret_key private_key = encoder.decode(private_key) if not isinstance(private_key, bytes): raise exc.TypeError( "PrivateKey must be created from a 32 byte seed") # Verify that our seed is the proper size if len(private_key) != self.SIZE: raise exc.ValueError( "The secret key must be exactly %d bytes long" % self.SIZE) raw_public_key = nacl.bindings.crypto_scalarmult_base(private_key) self._private_key = private_key self.public_key = PublicKey(raw_public_key)
def crypto_shorthash_siphash24(data, key): """Compute a fast, cryptographic quality, keyed hash of the input data :param data: :type data: bytes :param key: len(key) must be equal to :py:data:`.KEYBYTES` (16) :type key: bytes """ if len(key) != KEYBYTES: raise exc.ValueError( "Key length must be exactly {0} bytes".format(KEYBYTES)) digest = ffi.new("unsigned char[]", BYTES) rc = lib.crypto_shorthash_siphash24(digest, data, len(data), key) ensure(rc == 0, raising=exc.RuntimeError) return ffi.buffer(digest, BYTES)[:]
def encrypt( self, plaintext, aad=b"", nonce=None, encoder=encoding.RawEncoder ): """ Encrypts the plaintext message using the given `nonce` (or generates one randomly if omitted) and returns the ciphertext encoded with the encoder. .. warning:: It is vitally important for :param nonce: to be unique. By default, it is generated randomly; [:class:`Aead`] uses XChacha20 for extended (192b) nonce size, so the risk of reusing random nonces is negligible. It is *strongly recommended* to keep this behaviour, as nonce reuse will compromise the privacy of encrypted messages. Should implicit nonces be inadequate for your application, the second best option is using split counters; e.g. if sending messages encrypted under a shared key between 2 users, each user can use the number of messages it sent so far, prefixed or suffixed with a 1bit user id. Note that the counter must **never** be rolled back (due to overflow, on-disk state being rolled back to an earlier backup, ...) :param plaintext: [:class:`bytes`] The plaintext message to encrypt :param nonce: [:class:`bytes`] The nonce to use in the encryption :param encoder: The encoder to use to encode the ciphertext :rtype: [:class:`nacl.utils.EncryptedMessage`] """ if nonce is None: nonce = random(self.NONCE_SIZE) if len(nonce) != self.NONCE_SIZE: raise exc.ValueError( "The nonce must be exactly %s bytes long" % self.NONCE_SIZE, ) ciphertext = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_encrypt( plaintext, aad, nonce, self._key ) encoded_nonce = encoder.encode(nonce) encoded_ciphertext = encoder.encode(ciphertext) return EncryptedMessage._from_parts( encoded_nonce, encoded_ciphertext, encoder.encode(nonce + ciphertext), )
def __init__(self, seed, encoder=encoding.RawEncoder): # Decode the seed seed = encoder.decode(seed) if not isinstance(seed, bytes): raise exc.TypeError( "SigningKey must be created from a 32 byte seed") # Verify that our seed is the proper size if len(seed) != nacl.bindings.crypto_sign_SEEDBYTES: raise exc.ValueError("The seed must be exactly %d bytes long" % nacl.bindings.crypto_sign_SEEDBYTES) public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(seed) self._seed = seed self._signing_key = secret_key self.verify_key = VerifyKey(public_key)
def crypto_sign_ed25519_blake2b_seed_keypair(seed): """ Computes and returns the public key and secret key using the seed ``seed``. :param seed: bytes :rtype: (bytes(public_key), bytes(secret_key)) """ if len(seed) != crypto_sign_SEEDBYTES: raise exc.ValueError("Invalid seed") pk = ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) sk = ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) rc = lib.crypto_sign_ed25519_blake2b_seed_keypair(pk, sk, seed) ensure(rc == 0, 'Unexpected library error', raising=exc.RuntimeError) return ( ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], )
def crypto_sign_ed25519_sk_to_curve25519(secret_key_bytes): """ Converts a secret Ed25519 key (encoded as bytes ``secret_key_bytes``) to a secret Curve25519 key as bytes. Raises a ValueError if ``secret_key_bytes``is not of length ``crypto_sign_SECRETKEYBYTES`` :param secret_key_bytes: bytes :rtype: bytes """ if len(secret_key_bytes) != crypto_sign_SECRETKEYBYTES: raise exc.ValueError("Invalid curve public key") curve_secret_key_len = crypto_sign_curve25519_BYTES curve_secret_key = ffi.new("unsigned char[]", curve_secret_key_len) rc = lib.crypto_sign_ed25519_sk_to_curve25519(curve_secret_key, secret_key_bytes) ensure(rc == 0, 'Unexpected library error', raising=exc.RuntimeError) return ffi.buffer(curve_secret_key, curve_secret_key_len)[:]
def crypto_sign_ed25519_pk_to_curve25519(public_key_bytes): """ Converts a public Ed25519 key (encoded as bytes ``public_key_bytes``) to a public Curve25519 key as bytes. Raises a ValueError if ``public_key_bytes`` is not of length ``crypto_sign_PUBLICKEYBYTES`` :param public_key_bytes: bytes :rtype: bytes """ if len(public_key_bytes) != crypto_sign_PUBLICKEYBYTES: raise exc.ValueError("Invalid curve public key") curve_public_key_len = crypto_sign_curve25519_BYTES curve_public_key = ffi.new("unsigned char[]", curve_public_key_len) rc = lib.crypto_sign_ed25519_pk_to_curve25519(curve_public_key, public_key_bytes) ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError) return ffi.buffer(curve_public_key, curve_public_key_len)[:]