Skip to content

Commit 41f7bcb

Browse files
authored
Version 0.12.0 (#100)
* Version 0.12.0 Signed-off-by: Vlad Gheorghiu <[email protected]> * update * Update Signed-off-by: Vlad Gheorghiu <[email protected]> * Update Signed-off-by: Vlad Gheorghiu <[email protected]> * Update Signed-off-by: Vlad Gheorghiu <[email protected]> * update Signed-off-by: Vlad Gheorghiu <[email protected]> * Update Signed-off-by: Vlad Gheorghiu <[email protected]> * update Signed-off-by: Vlad Gheorghiu <[email protected]> * Update Signed-off-by: Vlad Gheorghiu <[email protected]> * Update Signed-off-by: Vlad Gheorghiu <[email protected]> * Updates Signed-off-by: Vlad Gheorghiu <[email protected]> * c_int->c_size_t Signed-off-by: Vlad Gheorghiu <[email protected]> * Fixes Signed-off-by: Vlad Gheorghiu <[email protected]> * Update Signed-off-by: Vlad Gheorghiu <[email protected]> * Version 0.12 Signed-off-by: Vlad Gheorghiu <[email protected]> * Update Signed-off-by: Vlad Gheorghiu <[email protected]> * Updated CHANGES Signed-off-by: Vlad Gheorghiu <[email protected]> --------- Signed-off-by: Vlad Gheorghiu <[email protected]>
1 parent 921e5ad commit 41f7bcb

File tree

11 files changed

+188
-66
lines changed

11 files changed

+188
-66
lines changed

CHANGES.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# Version 0.12.0 - January 15, 2025
2+
3+
- Fixes https://github.com/open-quantum-safe/liboqs-python/issues/98. The API
4+
that NIST has introduced in
5+
[FIPS 204](https://csrc.nist.gov/pubs/fips/204/final)
6+
for ML-DSA includes a context string of length >= 0. Added new API for
7+
signing with a context string
8+
- `Signature.sign_with_ctx_str(self, message, context)`
9+
- `Signature.verify_with_ctx_str(self, message, signature, context,
10+
public_key)`
11+
- When operations fail (i.e., `OQS_SUCCESS != 0`) in functions returning
12+
non-boolean objects, a `RuntimeError` is now raised, instead of returning 0
13+
- Bugfix on Linux, `c_int` -> `c_size_t` for buffer sizes
14+
- Pyright type checking fixes
15+
- Updated examples to use `ML-KEM` and `ML-DSA` as the defaults
16+
117
# Version 0.10.0 - April 1, 2024
218

319
- Replaced CHANGES by

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2018-2024 Open Quantum Safe
3+
Copyright (c) 2018-2025 Open Quantum Safe
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ you have access to a Python 3 interpreter. liboqs-python has been extensively
1919
tested on Linux, macOS and Windows platforms. Continuous integration is
2020
provided via GitHub actions.
2121

22-
The project contains the following files and directories:
22+
The project contains the following files and directories
2323

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

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

8990
```shell
9091
export OQS_INSTALL_PATH=/path/to/liboqs
@@ -148,7 +149,7 @@ python3 liboqs-python/examples/rand.py
148149
Execute
149150

150151
```shell
151-
nose2 --verbose liboqs-python
152+
nose2 --verbose
152153
```
153154

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

207208
```shell
208-
docker run -it oqs-python sh -c ". venv/bin/activate && nose2 --verbose liboqs-python"
209+
docker run -it oqs-python sh -c ". venv/bin/activate && nose2 --verbose"
209210
```
210211

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

266267
### Contributors
267268

268-
Contributors to the liboqs-python wrapper include:
269+
Contributors to the liboqs-python wrapper include
269270

270271
- Ben Davies (University of Waterloo)
271272
- Vlad Gheorghiu ([softwareQ Inc.](https://www.softwareq.ca) and the University

RELEASE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# liboqs-python version 0.10.0
1+
# liboqs-python version 0.12.0
22

33
---
44

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

2525
## Release notes
2626

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

3131
---
3232

3333
## What's New
3434

35-
This is the 10th release of liboqs-python. For a list of changes see
35+
This is the 11th release of liboqs-python. For a list of changes see
3636
[CHANGES.md](https://github.com/open-quantum-safe/liboqs-python/blob/main/CHANGES.md).

examples/kem.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
pprint(kems, compact=True)
1111

1212
# Create client and server with sample KEM mechanisms
13-
kemalg = "Kyber512"
13+
kemalg = "ML-KEM-512"
1414
with oqs.KeyEncapsulation(kemalg) as client:
1515
with oqs.KeyEncapsulation(kemalg) as server:
1616
print("\nKey encapsulation details:")

examples/sig.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
message = "This is the message to sign".encode()
1313

1414
# Create signer and verifier with sample signature mechanisms
15-
sigalg = "Dilithium2"
15+
sigalg = "ML-DSA-44"
1616
with oqs.Signature(sigalg) as signer:
1717
with oqs.Signature(sigalg) as verifier:
1818
print("\nSignature details:")

oqs/oqs.py

Lines changed: 117 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def _countdown(seconds):
4545
def _load_shared_obj(name, additional_searching_paths=None):
4646
"""Attempts to load shared library."""
4747
paths = []
48-
dll = ct.windll if platform.system() == "Windows" else ct.cdll
48+
dll = ct.windll if platform.system() == "Windows" else ct.cdll # type: ignore
4949

5050
# Search additional path, if any
5151
if additional_searching_paths:
@@ -138,7 +138,6 @@ def _load_liboqs():
138138
assert _liboqs
139139
except RuntimeError:
140140
sys.exit("Could not load liboqs shared library")
141-
142141
return _liboqs
143142

144143

@@ -162,7 +161,7 @@ def native():
162161
def oqs_version():
163162
"""liboqs version string."""
164163
native().OQS_version.restype = ct.c_char_p
165-
return ct.c_char_p(native().OQS_version()).value.decode("UTF-8")
164+
return ct.c_char_p(native().OQS_version()).value.decode() # type: ignore
166165

167166

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

265-
def __exit__(self, ctx_type, ctx_value, ctx_traceback):
264+
def __exit__(self, _ctx_type, _ctx_value, _ctx_traceback):
266265
self.free()
267266

268267
def generate_keypair(self):
@@ -276,7 +275,10 @@ def generate_keypair(self):
276275
rv = native().OQS_KEM_keypair(
277276
self._kem, ct.byref(public_key), ct.byref(self.secret_key)
278277
)
279-
return bytes(public_key) if rv == OQS_SUCCESS else 0
278+
if rv == OQS_SUCCESS:
279+
return bytes(public_key)
280+
else:
281+
raise RuntimeError("Can not generate keypair")
280282

281283
def export_secret_key(self):
282284
"""Exports the secret key."""
@@ -288,30 +290,36 @@ def encap_secret(self, public_key):
288290
289291
:param public_key: the peer's public key.
290292
"""
291-
my_public_key = ct.create_string_buffer(
293+
c_public_key = ct.create_string_buffer(
292294
public_key, self._kem.contents.length_public_key
293295
)
294296
ciphertext = ct.create_string_buffer(self._kem.contents.length_ciphertext)
295297
shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret)
296298
rv = native().OQS_KEM_encaps(
297-
self._kem, ct.byref(ciphertext), ct.byref(shared_secret), my_public_key
299+
self._kem, ct.byref(ciphertext), ct.byref(shared_secret), c_public_key
298300
)
299-
return bytes(ciphertext), bytes(shared_secret) if rv == OQS_SUCCESS else 0
301+
if rv == OQS_SUCCESS:
302+
return bytes(ciphertext), bytes(shared_secret)
303+
else:
304+
raise RuntimeError("Can not encapsulate secret")
300305

301306
def decap_secret(self, ciphertext):
302307
"""
303308
Decapsulates the ciphertext and returns the secret.
304309
305310
:param ciphertext: the ciphertext received from the peer.
306311
"""
307-
my_ciphertext = ct.create_string_buffer(
312+
c_ciphertext = ct.create_string_buffer(
308313
ciphertext, self._kem.contents.length_ciphertext
309314
)
310315
shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret)
311316
rv = native().OQS_KEM_decaps(
312-
self._kem, ct.byref(shared_secret), my_ciphertext, self.secret_key
317+
self._kem, ct.byref(shared_secret), c_ciphertext, self.secret_key
313318
)
314-
return bytes(shared_secret) if rv == OQS_SUCCESS else 0
319+
if rv == OQS_SUCCESS:
320+
return bytes(shared_secret)
321+
else:
322+
raise RuntimeError("Can not decapsulate secret")
315323

316324
def free(self):
317325
"""Releases the native resources."""
@@ -374,6 +382,7 @@ class Signature(ct.Structure):
374382
("alg_version", ct.c_char_p),
375383
("claimed_nist_level", ct.c_ubyte),
376384
("euf_cma", ct.c_ubyte),
385+
("sig_with_ctx_support", ct.c_ubyte),
377386
("length_public_key", ct.c_size_t),
378387
("length_secret_key", ct.c_size_t),
379388
("length_signature", ct.c_size_t),
@@ -404,6 +413,7 @@ def __init__(self, alg_name, secret_key=None):
404413
"version": self._sig.contents.alg_version.decode(),
405414
"claimed_nist_level": int(self._sig.contents.claimed_nist_level),
406415
"is_euf_cma": bool(self._sig.contents.euf_cma),
416+
"sig_with_ctx_support": bool(self._sig.contents.sig_with_ctx_support),
407417
"length_public_key": int(self._sig.contents.length_public_key),
408418
"length_secret_key": int(self._sig.contents.length_secret_key),
409419
"length_signature": int(self._sig.contents.length_signature),
@@ -417,7 +427,7 @@ def __init__(self, alg_name, secret_key=None):
417427
def __enter__(self):
418428
return self
419429

420-
def __exit__(self, ctx_type, ctx_value, ctx_traceback):
430+
def __exit__(self, _ctx_type, _ctx_value, _ctx_traceback):
421431
self.free()
422432

423433
def generate_keypair(self):
@@ -431,7 +441,10 @@ def generate_keypair(self):
431441
rv = native().OQS_SIG_keypair(
432442
self._sig, ct.byref(public_key), ct.byref(self.secret_key)
433443
)
434-
return bytes(public_key) if rv == OQS_SUCCESS else 0
444+
if rv == OQS_SUCCESS:
445+
return bytes(public_key)
446+
else:
447+
raise RuntimeError("Can not generate keypair")
435448

436449
def export_secret_key(self):
437450
"""Exports the secret key."""
@@ -444,22 +457,25 @@ def sign(self, message):
444457
:param message: the message to sign.
445458
"""
446459
# Provide length to avoid extra null char
447-
my_message = ct.create_string_buffer(message, len(message))
448-
message_len = ct.c_int(len(my_message))
449-
signature = ct.create_string_buffer(self._sig.contents.length_signature)
450-
sig_len = ct.c_int(
451-
self._sig.contents.length_signature
452-
) # initialize to maximum signature size
460+
c_message = ct.create_string_buffer(message, len(message))
461+
c_message_len = ct.c_size_t(len(c_message))
462+
c_signature = ct.create_string_buffer(self._sig.contents.length_signature)
463+
464+
# Initialize to maximum signature size
465+
signature_len = ct.c_size_t(self._sig.contents.length_signature)
466+
453467
rv = native().OQS_SIG_sign(
454468
self._sig,
455-
ct.byref(signature),
456-
ct.byref(sig_len),
457-
my_message,
458-
message_len,
469+
ct.byref(c_signature),
470+
ct.byref(signature_len),
471+
c_message,
472+
c_message_len,
459473
self.secret_key,
460474
)
461-
462-
return bytes(signature[: sig_len.value]) if rv == OQS_SUCCESS else 0
475+
if rv == OQS_SUCCESS:
476+
return bytes(c_signature[: signature_len.value])
477+
else:
478+
raise RuntimeError("Can not sign message")
463479

464480
def verify(self, message, signature, public_key):
465481
"""
@@ -470,17 +486,85 @@ def verify(self, message, signature, public_key):
470486
:param public_key: the signer's public key.
471487
"""
472488
# Provide length to avoid extra null char
473-
my_message = ct.create_string_buffer(message, len(message))
474-
message_len = ct.c_int(len(my_message))
475-
476-
# Provide length to avoid extra null char in sig
477-
my_signature = ct.create_string_buffer(signature, len(signature))
478-
sig_len = ct.c_int(len(my_signature))
479-
my_public_key = ct.create_string_buffer(
489+
c_message = ct.create_string_buffer(message, len(message))
490+
c_message_len = ct.c_size_t(len(c_message))
491+
c_signature = ct.create_string_buffer(signature, len(signature))
492+
signature_len = ct.c_size_t(len(c_signature))
493+
c_public_key = ct.create_string_buffer(
480494
public_key, self._sig.contents.length_public_key
481495
)
496+
482497
rv = native().OQS_SIG_verify(
483-
self._sig, my_message, message_len, my_signature, sig_len, my_public_key
498+
self._sig,
499+
c_message,
500+
c_message_len,
501+
c_signature,
502+
signature_len,
503+
c_public_key,
504+
)
505+
return True if rv == OQS_SUCCESS else False
506+
507+
def sign_with_ctx_str(self, message, context):
508+
"""
509+
Signs the provided message with context string and returns the signature.
510+
511+
:param context: the context string.
512+
:param message: the message to sign.
513+
"""
514+
# Provide length to avoid extra null char
515+
c_message = ct.create_string_buffer(message, len(message))
516+
c_message_len = ct.c_size_t(len(c_message))
517+
c_context = ct.create_string_buffer(context, len(context))
518+
context_len = ct.c_size_t(len(c_context))
519+
c_signature = ct.create_string_buffer(self._sig.contents.length_signature)
520+
521+
# Initialize to maximum signature size
522+
c_signature_len = ct.c_size_t(self._sig.contents.length_signature)
523+
524+
rv = native().OQS_SIG_sign_with_ctx_str(
525+
self._sig,
526+
ct.byref(c_signature),
527+
ct.byref(c_signature_len),
528+
c_message,
529+
c_message_len,
530+
c_context,
531+
context_len,
532+
self.secret_key,
533+
)
534+
if rv == OQS_SUCCESS:
535+
return bytes(c_signature[: c_signature_len.value])
536+
else:
537+
raise RuntimeError("Can not sign message with context string")
538+
539+
def verify_with_ctx_str(self, message, signature, context, public_key):
540+
"""
541+
Verifies the provided signature on the message with context string; returns True if valid.
542+
543+
:param message: the signed message.
544+
:param signature: the signature on the message.
545+
:param context: the context string.
546+
:param public_key: the signer's public key.
547+
"""
548+
# Provide length to avoid extra null char
549+
c_message = ct.create_string_buffer(message, len(message))
550+
c_message_len = ct.c_size_t(len(c_message))
551+
c_signature = ct.create_string_buffer(signature, len(signature))
552+
c_signature_len = ct.c_size_t(len(c_signature))
553+
c_context = ct.create_string_buffer(context, len(context))
554+
c_context_len = ct.c_size_t(len(c_context))
555+
c_public_key = ct.create_string_buffer(
556+
public_key, self._sig.contents.length_public_key
557+
)
558+
559+
rv = native().OQS_SIG_verify_with_ctx_str(
560+
self._sig,
561+
c_message,
562+
c_message_len,
563+
c_signature,
564+
c_signature_len,
565+
c_context,
566+
c_context_len,
567+
c_public_key,
484568
)
485569
return True if rv == OQS_SUCCESS else False
486570

oqs/rand.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def randombytes(bytes_to_read):
1919
:return: random bytes.
2020
"""
2121
result = oqs.ct.create_string_buffer(bytes_to_read)
22-
oqs.native().OQS_randombytes(result, oqs.ct.c_int(bytes_to_read))
22+
oqs.native().OQS_randombytes(result, oqs.ct.c_size_t(bytes_to_read))
2323
return bytes(result)
2424

2525

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
88
[project]
99
name = "liboqs-python"
1010
requires-python = ">=3.8"
11-
version = "0.10.0"
11+
version = "0.12.0"
1212
description = "Python bindings for liboqs, providing post-quantum public key cryptography algorithms"
1313
authors = [
1414
{ name = "Open Quantum Safe project", email = "[email protected]" },

0 commit comments

Comments
 (0)