Metadata-Version: 2.4
Name: broadkey
Version: 0.0.3
Summary: Research implementation of Boneh-Gentry-Waters broadcast key encapsulation (not for production use)
Keywords: broadcast-encryption,BGW,key-encapsulation,pairing
Author: wykwit
License-Expression: GPL-3.0-or-later
License-File: LICENSE
License-File: lib/pbc_bkem/COPYING
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: C
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Security :: Cryptography
Project-URL: Homepage, https://gitlab.com/wykwit/broadkey
Project-URL: Repository, https://gitlab.com/wykwit/broadkey
Project-URL: Issues, https://gitlab.com/wykwit/broadkey/-/issues
Requires-Python: >=3.9
Provides-Extra: test
Requires-Dist: cryptography; extra == "test"
Description-Content-Type: text/markdown

# broadkey

Research library for key encapsulation based on the Boneh-Gentry-Waters
broadcast encryption scheme, built on top of the
[PBC_BKEM](https://github.com/oliverguenther/PBC_BKEM) implementation.

> **For research and experimentation only.** broadkey is a teaching and
> research implementation. It is unaudited, uses parameters below modern
> security targets, and implements only the basic (non-CCA) construction.
> Do not use it to protect real data. See [security](#security) for the
> full list of known shortcomings.

## installation

broadkey is a CPython extension that links against
[PBC](https://crypto.stanford.edu/pbc/) and [GMP](https://gmplib.org/).
On Linux x86_64 and aarch64 (glibc 2.28+ or musl 1.2+),
`pip install broadkey` installs a binary wheel with both libraries
bundled and needs nothing else. Everywhere else it compiles from the
source distribution and needs, on the target machine:

- a C compiler, `pkg-config`, and GMP development headers
  (`libgmp-dev` on Debian/Ubuntu, `gmp` on Arch, `gmp` via Homebrew);
- PBC headers and shared library. Distributions rarely package it;
  `.ci/build-pbc.sh PREFIX` builds a pinned upstream commit into `PREFIX`
  (needs `git`, autotools, `autoconf-archive`, `flex`, `bison`).

If PBC lives outside the default search paths, point the compiler and
loader at it for both the install and every later import:

```
$ .ci/build-pbc.sh "$HOME/.local/pbc"
$ export CFLAGS="-I$HOME/.local/pbc/include" LDFLAGS="-L$HOME/.local/pbc/lib"
$ export LD_LIBRARY_PATH="$HOME/.local/pbc/lib"
$ pip install broadkey
```

A failing `pip install` with `library 'pbc' not found`, `library 'gmp' not
found`, or `gmp.h: No such file` means those libraries or their development
headers are missing. `ImportError: libpbc.so.1` means the library was found
at build time but not at run time.

Tested in CI on Linux x86_64 with CPython 3.9–3.15; wheels are built and
tested for CPython 3.9–3.15 on manylinux and musllinux, x86_64 and
aarch64. Other platforms are untested.

## build and test

Requires meson, ninja, a C compiler, PBC and GMP. The message test
additionally needs the `cryptography` package and is skipped without it.

```
$ meson setup build
$ meson compile -C build
$ meson test -C build
```

`meson test` runs the C test suite (`bkem`), the Python module tests
(`broadkey-python`), the HKDF/AES-GCM message round-trip
(`broadkey-messages`), the CLI protocol tests (`broadkey-cli`), CLI/Python
key interchange (`broadkey-interop`), and initialization allocation-failure
regressions (`broadkey-failures`). The fault-injection extension is built
only for testing and is never installed. CI additionally runs the suite
under AddressSanitizer/UndefinedBehaviorSanitizer and gates on the linters
below.

Wheels are built with [cibuildwheel](https://cibuildwheel.pypa.io/)
(configured in `pyproject.toml`; needs Docker):

```
$ cibuildwheel --platform linux --output-dir wheelhouse
```

## linting

```
$ clang-format -i lib/*/*.c lib/*/*.h bin/*.c tests/*.c
$ clang-tidy -p build lib/*/*.c bin/*.c tests/*.c
$ ruff check tests .ci && ruff format tests .ci
$ shellcheck .ci/*.sh
$ meson format -i meson.build
```

Tidy needs a configured build dir first (for `compile_commands.json`).
CI pins every tool version in `.ci/constraints.txt`.

## wire protocol

Both programs read stdin as a stream of whitespace-separated tokens: line
breaks carry no meaning on input, so a record may be split across lines or
several records may share one line. The diagrams below show the canonical
layout, which is also exactly what the programs write. Participant ids are
zero-based and must be distinct within a request. Every group element is a
hex string of exactly `2 * element_length_in_bytes` characters (256 hex
characters for the bundled type-A parameters; lowercase on output, either
case accepted on input). Both programs flush stdout after each response. A
negative recipient count `c` terminates the session; EOF before a record
starts also ends it cleanly with exit status 0. Malformed input —
including EOF in the middle of a record, integers outside the declared
ranges, and any public-key, private-key or header element (every header
slot, not only the ones a client uses) that is not a non-identity member of
the order-r subgroup — produces a diagnostic on stderr and exit status 1.

### server

Reads `n` (number of participants) from stdin, then request lines. Writes the
public key, one private key per participant, and one response per request.

```
<-- n
--> <g> <g_i[0]> ... <g_i[2B-2]> <v_i[0]> ... <v_i[A-1]>
--> 0 <d_0>
--> ...
--> n-1 <d_{n-1}>
<-- c id0 id1 ...
--> c id0 id1 ... <HDR[0]> ... <HDR[A]>
--> <K>
...
```

`c` is the number of recipients followed by their ids; `c < 0` terminates.

### client

Reads `n`, the public key line and its own private key line from stdin, then
request lines. Writes one key line per request.

```
<-- n
<-- <g> <g_i[0]> ... <g_i[2B-2]> <v_i[0]> ... <v_i[A-1]>
<-- index <i> <d_i>
<-- c id0 id1 ... <HDR[0]> ... <HDR[A]>
--> <K>
...
```

`c < 0` or EOF terminates.

## Python module

The `broadkey` module wraps the same protocol with in-memory strings. It
exports one type, `State`, with four positional-only methods.

- `State()` — takes no arguments. One protocol role per instance: the first
  *successful* `server_init` or `client_init` fixes the role; calling a key
  method on a fresh state raises `RuntimeError`. Calling `__init__()` again
  resets the object to a fresh state.
- `server_init(n) -> str` — set up for `n` participants (`1 <= n <=
  1_000_000`, int or decimal string). Returns the public-key line followed
  by `n` lines `"i <d_i>"`, one private key per participant, each line
  terminated by `\n` (including the last). Each participant must receive
  only its own line over an authenticated, confidential channel; the
  public-key line may be public. Re-initialising a server replaces its
  state only after the result string is ready; a failed `server_init`,
  including an allocation failure, leaves the previous server state intact.
- `client_init(data) -> None` — initialise from `"n\nPK\nindex i d_i"`,
  i.e. the participant count, the public-key line, and that participant's
  private-key line prefixed with `index`. Tokens are whitespace-separated
  (a trailing newline is fine) and hex is case-insensitive. A failed
  `client_init` on an initialised client leaves the previous client state
  intact.
- `server_key(recipients) -> str` — `recipients` is a list/tuple of distinct
  participant ids (or the wire-format string `"c id0 id1 ..."`). Returns
  `"header\nK"` (no trailing newline): the header line to broadcast and the
  encapsulated key `K` as hex.
- `client_key(header) -> str` — decapsulate a header line; returns `K` as
  hex. Every header element is validated, including blocks this client
  does not use.

**An excluded participant gets no error and no secret.** For an honest
header that excludes a client, the value that client computes is a fixed,
public group element: the same for every system and every broadcast, and
known to anyone. Anything derived from it is public too. Authorisation
must therefore come from the authenticated encryption layer (an excluded
party's AEAD decryption fails), never from `client_key` succeeding or from
comparing keys.

`K` is the hex encoding of a GT group element, not a symmetric key. Derive
working keys with a KDF (e.g. HKDF-SHA256 with a domain-separating `info`)
and encrypt with an AEAD; `tests/test_messages.py` is a complete example.
The KDF does not raise the ~80-bit security level of the parameters.

Errors: malformed input — wrong token counts, bad hex, ids out of range or
duplicated, embedded NUL characters, or group elements outside the order-r
subgroup — raises `ValueError`; wrong argument types or counts raise
`TypeError`; using a state in the wrong role or before initialisation, or a
failure of setup itself (including an unreadable `/dev/urandom`), raises
`RuntimeError`; allocation failure raises `MemoryError`.

Randomness comes from `/dev/urandom` only, never from PBC's deterministic
fallback. The source is opened once per process and read without userspace
buffering, so forks do not duplicate unread random bytes. If the initial
open fails, setup fails (`RuntimeError`; exit 1 for the CLIs). If a read
fails mid-operation, the process aborts rather than continue with
predictable keys.

```python
import broadkey

server = broadkey.State()
pk, *keys = server.server_init(3).rstrip("\n").split("\n")

clients = []
for line in keys:
    c = broadkey.State()
    c.client_init("3\n%s\nindex %s" % (pk, line))
    clients.append(c)

header, k = server.server_key([0, 2]).split("\n")
assert clients[0].client_key(header) == k
assert clients[2].client_key(header) == k
assert clients[1].client_key(header) != k   # excluded
```

`tests/benchmark.py` times these calls (minimum of 3 runs, except the
single-run full-message figure) and needs the `cryptography` package.

## compatibility

Pre-1.0: the Python API, wire format, and pairing parameters may change
between minor versions without a migration path. Keys and headers produced
by one version are not guaranteed to be readable by another.

## security

broadkey is a research library. It has not been audited, and nothing in
this repository is a claim of security for any deployment. Do not use it to
protect real data without independent expert review of the construction,
the parameters, and the code.

Known shortcomings:

- **Weak parameters.** The bundled type-A pairing parameters give roughly
  80-bit security (160-bit group order `r`), below the 128-bit modern
  target. They are hardcoded; there is no way to select stronger ones.
- **Basic construction only.** broadkey implements the BGW section 3.2
  construction, which the paper proves secure only against a static,
  non-adaptive adversary. The CCA-secure variant is NOT implemented.
  Nothing authenticates the header, the recipient list, or the sender.
- **No secret for excluded users, no error either.** An excluded
  participant computes a fixed public value instead of failing; only an
  authenticated encryption layer on top reveals exclusion.
- **Centralised key generation.** `server_init` generates every
  participant's private key in one process and returns them together.
  Key distribution and protection are entirely the caller's problem.
- **No side-channel or memory hygiene guarantees.** Nothing is constant-time
  or reviewed for side channels; private keys pass through ordinary Python
  strings and are not securely erased from memory.
- **Ageing dependency.** PBC is no longer actively developed. Moving to a
  maintained pairing library is a redesign, because BGW here relies on a
  symmetric (type-A) pairing.
- **Limited testing.** Tests are regression tests against this
  implementation, plus sanitizer runs in CI; there is no fuzzing and no
  independent check against the paper.
- **Unstable formats.** Keys, headers and the wire format carry no version
  or parameter identifier and may change before 1.0 (see
  [compatibility](#compatibility)).
- **Resource limits are not a DoS defence.** Up to 1,000,000 participants
  are accepted; large inputs cost significant CPU and memory.

## license

broadkey is licensed under the GNU General Public License, version 3 or
(at your option) any later version (SPDX: `GPL-3.0-or-later`). The full
text is in [`LICENSE`](LICENSE).

This covers the Python extension (`lib/module/`), the command-line programs
(`bin/`), the tests, and the local modifications to the BKEM core.
`lib/pbc_bkem/` is derived from Oliver Günther's
[PBC_BKEM](https://github.com/oliverguenther/PBC_BKEM) (GPLv3-or-later);
see `lib/pbc_bkem/README.md` for attribution and the dated list of changes.

broadkey links against [PBC](https://crypto.stanford.edu/pbc/) (LGPL-3.0 or
Apache-2.0) and [GMP](https://gmplib.org/) (LGPL-3.0-or-later or
GPL-2.0-or-later). Source distributions build against the system copies.
Binary wheels bundle both as shared libraries (under `broadkey.libs/`),
which may be replaced; their license texts and exact versions ship in the
wheel's `.dist-info/licenses/bundled/`.

```
broadkey
Copyright (C) 2024, 2026  wykwit

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
```

