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.")