Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 0.12.0 #100

Merged
merged 18 commits into from
Jan 15, 2025
16 changes: 16 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# Version 0.12.0 - January 15, 2025

- Fixes https://github.com/open-quantum-safe/liboqs-python/issues/98. The API
that NIST has introduced in
[FIPS 204](https://csrc.nist.gov/pubs/fips/204/final)
for ML-DSA includes a context string of length >= 0. Added new API for
signing with a context string
- `Signature.sign_with_ctx_str(self, message, context)`
- `Signature.verify_with_ctx_str(self, message, signature, context,
public_key)`
- When operations fail (i.e., `OQS_SUCCESS != 0`) in functions returning
non-boolean objects, a `RuntimeError` is now raised, instead of returning 0
- Bugfix on Linux, `c_int` -> `c_size_t` for buffer sizes
- Pyright type checking fixes
- Updated examples to use `ML-KEM` and `ML-DSA` as the defaults

# Version 0.10.0 - April 1, 2024

- Replaced CHANGES by
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018-2024 Open Quantum Safe
Copyright (c) 2018-2025 Open Quantum Safe

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ you have access to a Python 3 interpreter. liboqs-python has been extensively
tested on Linux, macOS and Windows platforms. Continuous integration is
provided via GitHub actions.

The project contains the following files and directories:
The project contains the following files and directories

- **`oqs/oqs.py`: a Python 3 module wrapper for the liboqs C library.**
- `oqs/rand.py`: a Python 3 module supporting RNGs from `<oqs/rand.h>`
Expand Down Expand Up @@ -84,7 +84,8 @@ an alternative path, e.g., `C:\liboqs`, by passing the
cmake -S liboqs -B liboqs/build -DCMAKE_INSTALL_PREFIX="C:\liboqs" -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE -DBUILD_SHARED_LIBS=ON
```

Alternatively you can set the `OQS_INSTALL_PATH` environment variable to point to the installation directory, e.g., on a UNIX-like system execute:
Alternatively, you can set the `OQS_INSTALL_PATH` environment variable to point
to the installation directory, e.g., on a UNIX-like system, execute

```shell
export OQS_INSTALL_PATH=/path/to/liboqs
Expand Down Expand Up @@ -148,7 +149,7 @@ python3 liboqs-python/examples/rand.py
Execute

```shell
nose2 --verbose liboqs-python
nose2 --verbose
```

---
Expand Down Expand Up @@ -205,7 +206,7 @@ docker run -it oqs-python sh -c ". venv/bin/activate && python liboqs-python/exa
Or, run the unit tests with

```shell
docker run -it oqs-python sh -c ". venv/bin/activate && nose2 --verbose liboqs-python"
docker run -it oqs-python sh -c ". venv/bin/activate && nose2 --verbose"
```

In case you want to use the Docker container as a development environment,
Expand Down Expand Up @@ -265,7 +266,7 @@ Waterloo.

### Contributors

Contributors to the liboqs-python wrapper include:
Contributors to the liboqs-python wrapper include

- Ben Davies (University of Waterloo)
- Vlad Gheorghiu ([softwareQ Inc.](https://www.softwareq.ca) and the University
Expand Down
8 changes: 4 additions & 4 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# liboqs-python version 0.10.0
# liboqs-python version 0.12.0

---

Expand All @@ -24,13 +24,13 @@ See in particular limitations on intended use.

## Release notes

This release of liboqs-python was released on April 1, 2024. Its release
This release of liboqs-python was released on January 15, 2025. Its release
page on GitHub is
https://github.com/open-quantum-safe/liboqs-python/releases/tag/0.10.0.
https://github.com/open-quantum-safe/liboqs-python/releases/tag/0.12.0.

---

## What's New

This is the 10th release of liboqs-python. For a list of changes see
This is the 11th release of liboqs-python. For a list of changes see
[CHANGES.md](https://github.com/open-quantum-safe/liboqs-python/blob/main/CHANGES.md).
2 changes: 1 addition & 1 deletion examples/kem.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
pprint(kems, compact=True)

# Create client and server with sample KEM mechanisms
kemalg = "Kyber512"
kemalg = "ML-KEM-512"
with oqs.KeyEncapsulation(kemalg) as client:
with oqs.KeyEncapsulation(kemalg) as server:
print("\nKey encapsulation details:")
Expand Down
2 changes: 1 addition & 1 deletion examples/sig.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
message = "This is the message to sign".encode()

# Create signer and verifier with sample signature mechanisms
sigalg = "Dilithium2"
sigalg = "ML-DSA-44"
with oqs.Signature(sigalg) as signer:
with oqs.Signature(sigalg) as verifier:
print("\nSignature details:")
Expand Down
150 changes: 117 additions & 33 deletions oqs/oqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def _countdown(seconds):
def _load_shared_obj(name, additional_searching_paths=None):
"""Attempts to load shared library."""
paths = []
dll = ct.windll if platform.system() == "Windows" else ct.cdll
dll = ct.windll if platform.system() == "Windows" else ct.cdll # type: ignore

# Search additional path, if any
if additional_searching_paths:
Expand Down Expand Up @@ -138,7 +138,6 @@ def _load_liboqs():
assert _liboqs
except RuntimeError:
sys.exit("Could not load liboqs shared library")

return _liboqs


Expand All @@ -162,7 +161,7 @@ def native():
def oqs_version():
"""liboqs version string."""
native().OQS_version.restype = ct.c_char_p
return ct.c_char_p(native().OQS_version()).value.decode("UTF-8")
return ct.c_char_p(native().OQS_version()).value.decode() # type: ignore


# Warn the user if the liboqs version differs from liboqs-python version
Expand Down Expand Up @@ -262,7 +261,7 @@ def __init__(self, alg_name, secret_key=None):
def __enter__(self):
return self

def __exit__(self, ctx_type, ctx_value, ctx_traceback):
def __exit__(self, _ctx_type, _ctx_value, _ctx_traceback):
self.free()

def generate_keypair(self):
Expand All @@ -276,7 +275,10 @@ def generate_keypair(self):
rv = native().OQS_KEM_keypair(
self._kem, ct.byref(public_key), ct.byref(self.secret_key)
)
return bytes(public_key) if rv == OQS_SUCCESS else 0
if rv == OQS_SUCCESS:
return bytes(public_key)
else:
raise RuntimeError("Can not generate keypair")

def export_secret_key(self):
"""Exports the secret key."""
Expand All @@ -288,30 +290,36 @@ def encap_secret(self, public_key):

:param public_key: the peer's public key.
"""
my_public_key = ct.create_string_buffer(
c_public_key = ct.create_string_buffer(
public_key, self._kem.contents.length_public_key
)
ciphertext = ct.create_string_buffer(self._kem.contents.length_ciphertext)
shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret)
rv = native().OQS_KEM_encaps(
self._kem, ct.byref(ciphertext), ct.byref(shared_secret), my_public_key
self._kem, ct.byref(ciphertext), ct.byref(shared_secret), c_public_key
)
return bytes(ciphertext), bytes(shared_secret) if rv == OQS_SUCCESS else 0
if rv == OQS_SUCCESS:
return bytes(ciphertext), bytes(shared_secret)
else:
raise RuntimeError("Can not encapsulate secret")

def decap_secret(self, ciphertext):
"""
Decapsulates the ciphertext and returns the secret.

:param ciphertext: the ciphertext received from the peer.
"""
my_ciphertext = ct.create_string_buffer(
c_ciphertext = ct.create_string_buffer(
ciphertext, self._kem.contents.length_ciphertext
)
shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret)
rv = native().OQS_KEM_decaps(
self._kem, ct.byref(shared_secret), my_ciphertext, self.secret_key
self._kem, ct.byref(shared_secret), c_ciphertext, self.secret_key
)
return bytes(shared_secret) if rv == OQS_SUCCESS else 0
if rv == OQS_SUCCESS:
return bytes(shared_secret)
else:
raise RuntimeError("Can not decapsulate secret")

def free(self):
"""Releases the native resources."""
Expand Down Expand Up @@ -374,6 +382,7 @@ class Signature(ct.Structure):
("alg_version", ct.c_char_p),
("claimed_nist_level", ct.c_ubyte),
("euf_cma", ct.c_ubyte),
("sig_with_ctx_support", ct.c_ubyte),
("length_public_key", ct.c_size_t),
("length_secret_key", ct.c_size_t),
("length_signature", ct.c_size_t),
Expand Down Expand Up @@ -404,6 +413,7 @@ def __init__(self, alg_name, secret_key=None):
"version": self._sig.contents.alg_version.decode(),
"claimed_nist_level": int(self._sig.contents.claimed_nist_level),
"is_euf_cma": bool(self._sig.contents.euf_cma),
"sig_with_ctx_support": bool(self._sig.contents.sig_with_ctx_support),
"length_public_key": int(self._sig.contents.length_public_key),
"length_secret_key": int(self._sig.contents.length_secret_key),
"length_signature": int(self._sig.contents.length_signature),
Expand All @@ -417,7 +427,7 @@ def __init__(self, alg_name, secret_key=None):
def __enter__(self):
return self

def __exit__(self, ctx_type, ctx_value, ctx_traceback):
def __exit__(self, _ctx_type, _ctx_value, _ctx_traceback):
self.free()

def generate_keypair(self):
Expand All @@ -431,7 +441,10 @@ def generate_keypair(self):
rv = native().OQS_SIG_keypair(
self._sig, ct.byref(public_key), ct.byref(self.secret_key)
)
return bytes(public_key) if rv == OQS_SUCCESS else 0
if rv == OQS_SUCCESS:
return bytes(public_key)
else:
raise RuntimeError("Can not generate keypair")

def export_secret_key(self):
"""Exports the secret key."""
Expand All @@ -444,22 +457,25 @@ def sign(self, message):
:param message: the message to sign.
"""
# Provide length to avoid extra null char
my_message = ct.create_string_buffer(message, len(message))
message_len = ct.c_int(len(my_message))
signature = ct.create_string_buffer(self._sig.contents.length_signature)
sig_len = ct.c_int(
self._sig.contents.length_signature
) # initialize to maximum signature size
c_message = ct.create_string_buffer(message, len(message))
c_message_len = ct.c_size_t(len(c_message))
c_signature = ct.create_string_buffer(self._sig.contents.length_signature)

# Initialize to maximum signature size
signature_len = ct.c_size_t(self._sig.contents.length_signature)

rv = native().OQS_SIG_sign(
self._sig,
ct.byref(signature),
ct.byref(sig_len),
my_message,
message_len,
ct.byref(c_signature),
ct.byref(signature_len),
c_message,
c_message_len,
self.secret_key,
)

return bytes(signature[: sig_len.value]) if rv == OQS_SUCCESS else 0
if rv == OQS_SUCCESS:
return bytes(c_signature[: signature_len.value])
else:
raise RuntimeError("Can not sign message")

def verify(self, message, signature, public_key):
"""
Expand All @@ -470,17 +486,85 @@ def verify(self, message, signature, public_key):
:param public_key: the signer's public key.
"""
# Provide length to avoid extra null char
my_message = ct.create_string_buffer(message, len(message))
message_len = ct.c_int(len(my_message))

# Provide length to avoid extra null char in sig
my_signature = ct.create_string_buffer(signature, len(signature))
sig_len = ct.c_int(len(my_signature))
my_public_key = ct.create_string_buffer(
c_message = ct.create_string_buffer(message, len(message))
c_message_len = ct.c_size_t(len(c_message))
c_signature = ct.create_string_buffer(signature, len(signature))
signature_len = ct.c_size_t(len(c_signature))
c_public_key = ct.create_string_buffer(
public_key, self._sig.contents.length_public_key
)

rv = native().OQS_SIG_verify(
self._sig, my_message, message_len, my_signature, sig_len, my_public_key
self._sig,
c_message,
c_message_len,
c_signature,
signature_len,
c_public_key,
)
return True if rv == OQS_SUCCESS else False

def sign_with_ctx_str(self, message, context):
"""
Signs the provided message with context string and returns the signature.

:param context: the context string.
:param message: the message to sign.
"""
# Provide length to avoid extra null char
c_message = ct.create_string_buffer(message, len(message))
c_message_len = ct.c_size_t(len(c_message))
c_context = ct.create_string_buffer(context, len(context))
context_len = ct.c_size_t(len(c_context))
c_signature = ct.create_string_buffer(self._sig.contents.length_signature)

# Initialize to maximum signature size
c_signature_len = ct.c_size_t(self._sig.contents.length_signature)

rv = native().OQS_SIG_sign_with_ctx_str(
self._sig,
ct.byref(c_signature),
ct.byref(c_signature_len),
c_message,
c_message_len,
c_context,
context_len,
self.secret_key,
)
if rv == OQS_SUCCESS:
return bytes(c_signature[: c_signature_len.value])
else:
raise RuntimeError("Can not sign message with context string")

def verify_with_ctx_str(self, message, signature, context, public_key):
"""
Verifies the provided signature on the message with context string; returns True if valid.

:param message: the signed message.
:param signature: the signature on the message.
:param context: the context string.
:param public_key: the signer's public key.
"""
# Provide length to avoid extra null char
c_message = ct.create_string_buffer(message, len(message))
c_message_len = ct.c_size_t(len(c_message))
c_signature = ct.create_string_buffer(signature, len(signature))
c_signature_len = ct.c_size_t(len(c_signature))
c_context = ct.create_string_buffer(context, len(context))
c_context_len = ct.c_size_t(len(c_context))
c_public_key = ct.create_string_buffer(
public_key, self._sig.contents.length_public_key
)

rv = native().OQS_SIG_verify_with_ctx_str(
self._sig,
c_message,
c_message_len,
c_signature,
c_signature_len,
c_context,
c_context_len,
c_public_key,
)
return True if rv == OQS_SUCCESS else False

Expand Down
2 changes: 1 addition & 1 deletion oqs/rand.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def randombytes(bytes_to_read):
:return: random bytes.
"""
result = oqs.ct.create_string_buffer(bytes_to_read)
oqs.native().OQS_randombytes(result, oqs.ct.c_int(bytes_to_read))
oqs.native().OQS_randombytes(result, oqs.ct.c_size_t(bytes_to_read))
return bytes(result)


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "liboqs-python"
requires-python = ">=3.8"
version = "0.10.0"
version = "0.12.0"
description = "Python bindings for liboqs, providing post-quantum public key cryptography algorithms"
authors = [
{ name = "Open Quantum Safe project", email = "[email protected]" },
Expand Down
Loading
Loading