Source code for xeddsa.bindings

from __future__ import annotations

import secrets
from typing import Optional

try:
    from _libxeddsa import ffi, lib
except ImportError:
    from .libxeddsa_emscripten import ffi, lib  # type: ignore[assignment]


__all__ = [
    "Priv", "PRIV_SIZE",
    "Seed", "SEED_SIZE",
    "Curve25519Pub", "CURVE_25519_PUB_SIZE",
    "Ed25519Pub", "ED_25519_PUB_SIZE",
    "Ed25519Signature", "ED_25519_SIGNATURE_SIZE",
    "Nonce", "NONCE_SIZE",
    "SharedSecret", "SHARED_SECRET_SIZE",

    "XEdDSAException",

    "ed25519_priv_sign",
    "ed25519_seed_sign",
    "ed25519_verify",

    "curve25519_pub_to_ed25519_pub",
    "ed25519_pub_to_curve25519_pub",

    "priv_to_curve25519_pub",
    "priv_to_ed25519_pub",
    "seed_to_ed25519_pub",

    "priv_force_sign",
    "seed_to_priv",

    "x25519"
]

Priv = bytes
PRIV_SIZE = 32
PRIV_FFI = f"uint8_t[{PRIV_SIZE}]"

Seed = bytes
SEED_SIZE = 32
SEED_FFI = f"uint8_t[{SEED_SIZE}]"

Curve25519Pub = bytes
CURVE_25519_PUB_SIZE = 32
CURVE_25519_PUB_FFI = f"uint8_t[{CURVE_25519_PUB_SIZE}]"

Ed25519Pub = bytes
ED_25519_PUB_SIZE = 32
ED_25519_PUB_FFI = f"uint8_t[{ED_25519_PUB_SIZE}]"

Ed25519Signature = bytes
ED_25519_SIGNATURE_SIZE = 64
ED_25519_SIGNATURE_FFI = f"uint8_t[{ED_25519_SIGNATURE_SIZE}]"

Nonce = bytes
NONCE_SIZE = 64
NONCE_FFI = f"uint8_t[{NONCE_SIZE}]"

SharedSecret = bytes
SHARED_SECRET_SIZE = 32
SHARED_SECRET_FFI = f"uint8_t[{SHARED_SECRET_SIZE}]"


[docs] class XEdDSAException(Exception): """ Exception raised in case of critical security errors. """
[docs] def ed25519_priv_sign(priv: Priv, msg: bytes, nonce: Optional[Nonce] = None) -> Ed25519Signature: """ Sign a message using a Curve25519/Ed25519 private key. Args: priv: The Curve25519/Ed25519 private key to sign with. msg: The message to sign. nonce: 64 bytes of secure random data. If `None` is passed, the nonce is generated for you. Returns: An Ed25519-compatible signature of `msg`. """ if len(priv) != PRIV_SIZE: raise ValueError(f"Expected size of the private key in bytes: {PRIV_SIZE} (PRIV_SIZE)") if nonce is None: nonce = secrets.token_bytes(NONCE_SIZE) if len(nonce) != NONCE_SIZE: raise ValueError(f"Expected size of the nonce in bytes: {NONCE_SIZE} (NONCE_SIZE") sig_ffi = ffi.new(ED_25519_SIGNATURE_FFI) priv_ffi = ffi.new(PRIV_FFI, priv) msg_ffi = ffi.new("uint8_t[]", msg) msg_size = len(msg) nonce_ffi = ffi.new(NONCE_FFI, nonce) lib.ed25519_priv_sign(sig_ffi, priv_ffi, msg_ffi, msg_size, nonce_ffi) return bytes(sig_ffi)
[docs] def ed25519_seed_sign(seed: Seed, msg: bytes) -> Ed25519Signature: """ Sign a message using a Curve25519/Ed25519 seed. Args: seed: The Curve25519/Ed25519 seed to sign with. msg: The message to sign. Returns: An Ed25519-compatible signature of `msg`. """ if len(seed) != SEED_SIZE: raise ValueError(f"Expected size of the seed in bytes: {SEED_SIZE} (SEED_SIZE)") sig_ffi = ffi.new(ED_25519_SIGNATURE_FFI) seed_ffi = ffi.new(SEED_FFI, seed) msg_ffi = ffi.new("uint8_t[]", msg) msg_size = len(msg) lib.ed25519_seed_sign(sig_ffi, seed_ffi, msg_ffi, msg_size) return bytes(sig_ffi)
[docs] def ed25519_verify(sig: Ed25519Signature, ed25519_pub: Ed25519Pub, msg: bytes) -> bool: """ Verify an Ed25519 signature. Args: sig: An Ed25519-compatible signature of `msg`. ed25519_pub: The Ed25519 public key to verify the signature with. msg: The message. Returns: Whether the signature verification was successful. """ if len(sig) != ED_25519_SIGNATURE_SIZE: raise ValueError( f"Expected size of the signature in bytes: {ED_25519_SIGNATURE_SIZE} (ED_25519_SIGNATURE_SIZE)" ) if len(ed25519_pub) != ED_25519_PUB_SIZE: raise ValueError( f"Expected size of the Ed25519 public key in bytes: {ED_25519_PUB_SIZE} (ED_25519_PUB_SIZE)" ) sig_ffi = ffi.new(ED_25519_SIGNATURE_FFI, sig) ed25519_pub_ffi = ffi.new(ED_25519_PUB_FFI, ed25519_pub) msg_ffi = ffi.new("uint8_t[]", msg) msg_size = len(msg) return lib.ed25519_verify(sig_ffi, ed25519_pub_ffi, msg_ffi, msg_size) == 0
[docs] def curve25519_pub_to_ed25519_pub(curve25519_pub: Curve25519Pub, set_sign_bit: bool) -> Ed25519Pub: """ Convert a Curve25519 public key into an Ed25519 public key. Args: curve25519_pub: The Curve25519 public key to convert into its Ed25519 equivalent. set_sign_bit: Whether to set the sign bit of the output Ed25519 public key. Returns: The Ed25519 public key corresponding to the Curve25519 public key. """ if len(curve25519_pub) != CURVE_25519_PUB_SIZE: raise ValueError( f"Expected size of the Curve25519 public key in bytes: {CURVE_25519_PUB_SIZE}" " (CURVE_25519_PUB_SIZE)" ) ed25519_pub_ffi = ffi.new(ED_25519_PUB_FFI) curve25519_pub_ffi = ffi.new(CURVE_25519_PUB_FFI, curve25519_pub) lib.curve25519_pub_to_ed25519_pub(ed25519_pub_ffi, curve25519_pub_ffi, set_sign_bit) return bytes(ed25519_pub_ffi)
[docs] def ed25519_pub_to_curve25519_pub(ed25519_pub: Ed25519Pub) -> Curve25519Pub: """ Convert an Ed25519 public key into a Curve25519 public key. Re-export of libsodiums/ref10s `crypto_sign_ed25519_pk_to_curve25519` function for convenience. Args: ed25519_pub: The Ed25519 public key to convert into its Curve25519 equivalent. Returns: The Curve25519 public key corresponding to the Ed25519 public key. Raises: XEdDSAException: if the public key was rejected due to suboptimal security propierties. """ if len(ed25519_pub) != ED_25519_PUB_SIZE: raise ValueError( f"Expected size of the Ed25519 public key in bytes: {ED_25519_PUB_SIZE} (ED_25519_PUB_SIZE)" ) curve25519_pub_ffi = ffi.new(CURVE_25519_PUB_FFI) ed25519_pub_ffi = ffi.new(ED_25519_PUB_FFI, ed25519_pub) if lib.ed25519_pub_to_curve25519_pub(curve25519_pub_ffi, ed25519_pub_ffi) != 0: raise XEdDSAException("Ed25519 public key rejected due to suboptimal security properties.") return bytes(curve25519_pub_ffi)
[docs] def priv_to_curve25519_pub(priv: Priv) -> Curve25519Pub: """ Derive the Curve25519 public key from a Curve25519/Ed25519 private key. Args: priv: The Curve25519/Ed25519 private key. Returns: The Curve25519 public key. """ if len(priv) != PRIV_SIZE: raise ValueError(f"Expected size of the private key in bytes: {PRIV_SIZE} (PRIV_SIZE)") curve25519_pub_ffi = ffi.new(CURVE_25519_PUB_FFI) priv_ffi = ffi.new(PRIV_FFI, priv) lib.priv_to_curve25519_pub(curve25519_pub_ffi, priv_ffi) return bytes(curve25519_pub_ffi)
[docs] def priv_to_ed25519_pub(priv: Priv) -> Ed25519Pub: """ Derive the Ed25519 public key from a Curve25519/Ed25519 private key. Args: priv: The Curve25519/Ed25519 private key. Returns: The Ed25519 public key. """ if len(priv) != PRIV_SIZE: raise ValueError(f"Expected size of the private key in bytes: {PRIV_SIZE} (PRIV_SIZE)") ed25519_pub_ffi = ffi.new(ED_25519_PUB_FFI) priv_ffi = ffi.new(PRIV_FFI, priv) lib.priv_to_ed25519_pub(ed25519_pub_ffi, priv_ffi) return bytes(ed25519_pub_ffi)
[docs] def seed_to_ed25519_pub(seed: Seed) -> Ed25519Pub: """ Derive the Ed25519 public key from a Curve25519/Ed25519 seed. Args: seed: The Curve25519/Ed25519 seed. Returns: The Ed25519 public key. """ if len(seed) != SEED_SIZE: raise ValueError(f"Expected size of the seed in bytes: {SEED_SIZE} (SEED_SIZE)") ed25519_pub_ffi = ffi.new(ED_25519_PUB_FFI) seed_ffi = ffi.new(SEED_FFI, seed) lib.seed_to_ed25519_pub(ed25519_pub_ffi, seed_ffi) return bytes(ed25519_pub_ffi)
[docs] def priv_force_sign(priv: Priv, set_sign_bit: bool) -> Priv: """ Negate a Curve25519/Ed25519 private key if necessary, such that the corresponding Ed25519 public key has the sign bit set (or not set) as requested. Args: priv: The original Curve25519/Ed25519 private key. set_sign_bit: Whether the goal is for the sign bit to be set on the Ed25519 public key corresponding to the adjusted Curve25519/Ed25519 private key. Returns: The adjusted Curve25519/Ed25519 private key. """ if len(priv) != PRIV_SIZE: raise ValueError(f"Expected size of the private key in bytes: {PRIV_SIZE} (PRIV_SIZE)") priv_out_ffi = ffi.new(PRIV_FFI) priv_in_ffi = ffi.new(PRIV_FFI, priv) lib.priv_force_sign(priv_out_ffi, priv_in_ffi, set_sign_bit) return bytes(priv_out_ffi)
[docs] def seed_to_priv(seed: Seed) -> Priv: """ Derive the Curve25519/Ed25519 private key from a Curve25519/Ed25519 seed. Re-export of libsodiums/ref10s `crypto_sign_ed25519_sk_to_curve25519` function for convenience. Args: seed: The Curve25519/Ed25519 seed. Returns: The Curve25519/Ed25519 private key derived from the seed. """ if len(seed) != SEED_SIZE: raise ValueError(f"Expected size of the seed in bytes: {SEED_SIZE} (SEED_SIZE)") priv_ffi = ffi.new(PRIV_FFI) seed_ffi = ffi.new(SEED_FFI, seed) lib.seed_to_priv(priv_ffi, seed_ffi) return bytes(priv_ffi)
[docs] def x25519(priv: Priv, curve25519_pub: Curve25519Pub) -> SharedSecret: """ Perform Diffie-Hellman key agreement on Curve25519, also known as X25519. Args: priv: The private key partaking in the key agreement. curve25519_pub: The public key partaking in the key agreement. Returns: The shared secret. Raises: XEdDSAException: if the public key was rejected due to suboptimal security propierties or if the shared secret consists of only zeros """ if len(priv) != PRIV_SIZE: raise ValueError(f"Expected size of the private key in bytes: {PRIV_SIZE} (PRIV_SIZE)") if len(curve25519_pub) != CURVE_25519_PUB_SIZE: raise ValueError( f"Expected size of the Curve25519 public key in bytes: {CURVE_25519_PUB_SIZE}" " (CURVE_25519_PUB_SIZE)" ) shared_secret_ffi = ffi.new(SHARED_SECRET_FFI) priv_ffi = ffi.new(PRIV_FFI, priv) curve25519_pub_ffi = ffi.new(CURVE_25519_PUB_FFI, curve25519_pub) if lib.x25519(shared_secret_ffi, priv_ffi, curve25519_pub_ffi) != 0: raise XEdDSAException( "Key agreement failed, either due to suboptimal security properties of the public key, or due to" " the shared secret consisting of only zeros." ) return bytes(shared_secret_ffi)
if not 2 <= lib.XEDDSA_VERSION_MAJOR < 3: raise ImportError( "Wrong version of libxeddsa bound. Expected >=2,<3, got" f" {lib.XEDDSA_VERSION_MAJOR}.{lib.XEDDSA_VERSION_MINOR}.{lib.XEDDSA_VERSION_REVISION}" ) if lib.xeddsa_init() < 0: raise ImportError("libxeddsa couldn't be initialized.")