fiki (Python)
The Python implementation of fiki. Requires Python 3.11 or newer, and depends on cryptography and http-sfv and on nothing else.
From a fresh clone to passing tests
cd py
uv sync
uv run pytest
The suite enforces 100% branch coverage; a gap needs an approved deviation: node in the repo's this.i.
Signing a request
from fiki import Key, sign_request
key = Key.generate()
print(key.aid) # register this once with whoever you call
print(key.seed.hex()) # 32 bytes; persist them somewhere the cron job can read
url = "https://api.example.com/things?limit=1"
body = b'{"hello": "world"}'
headers = sign_request(key=key, method="POST", url=url, body=body)
By default the signature binds the method, the host, the path, the query string, and a digest of the body. Pass the body wherever you pass the URL: fiki covers a body it is given, or refuses to sign — but it cannot cover one it never sees.
Verifying a request
from fiki import verify_request
verdict = verify_request(
method=request.method,
url=request.url, # a full URL, or a path plus a Host header
headers=request.headers,
body=request.body,
max_age=300, # seconds, or None to decline the check
)
verdict.aid # who signed it
max_age has no default and must be given. Both defaults would be wrong: a number guesses at somebody else's clock skew and replay window, and skipping the check silently is the thing the argument exists to prevent. An expires the signer declared is enforced either way.
Responses, resolved keyids, and a minimum covered set
These exist for the KERI profile of RFC 9421 (this.i @7f28p7xk, @6g9zjsv9) and are, for now, in this port only.
from fiki import (REQUEST_MINIMUM, RESPONSE_MINIMUM, Request, sign_request, sign_response,
verify_request, verify_response)
# A keyid that is not the key itself, such as a KERI AID, needs a resolver on the verify side.
# The resolver returns the 32 raw bytes of the key, or None; fiki never decodes the keyid itself.
headers = sign_request(key=key, method="POST", url=url, body=body, keyid=aid)
verdict = verify_request(method="POST", url=url, headers=headers, body=body, max_age=300,
resolve=current_key_for, minimum=REQUEST_MINIMUM)
# A response binds the request it answers with RFC 9421's req parameter.
asked = Request(method="POST", url=url, headers=request_headers, body=body)
signed = sign_response(key=key, status=200, request=asked, body=reply)
verify_response(status=200, headers=signed, body=reply, request=asked, max_age=300,
minimum=RESPONSE_MINIMUM)
minimum refuses a signature that covers less than the named components even when it is valid, and refuses a body — Content-Length above zero, any Transfer-Encoding, or one that simply arrived — whose content-digest is not covered. The method is signed exactly as given, with no case change, so pass it as it will go on the wire.
minimum=None, the default, enforces nothing: a body handed to verify_request with no covered content-digest is then accepted, and only the verdict's covered shows it. Pass a minimum whenever you hand over a body. Signers take the same minimum and refuse to cover less. A client checking a response should pass expected_keyid, the AID it is talking to; a server should pass authorities, the set it serves, which also makes @authority required: a signature that does not cover it is InsufficientCoverage. An unsigned 401 is Unauthenticated, and a resolver raises UnsupportedSigner for a key state with no single effective signer.
Conformance
tests/test_rfc9421_conformance.py signs RFC 9421's own Appendix B.2.6 request with the RFC's own published Ed25519 key and asserts the RFC's own signature, byte for byte. tests/test_vectors.py runs the shared vectors/ at the repository root, which every port runs. tests/test_keri_vectors.py runs vectors/keri/, the KERI profile's set, which only this port runs so far.
Metadata
Release files for fiki 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fiki-0.7.0.tar.gz | 85.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fiki-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 113.3 kB
Release files / fiki-0.7.0.tar.gz
| Download URL | fiki-0.7.0.tar.gz |
|---|---|
| Size | 85.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
347af1504e159a487fcf9e23aec274fcbb220f5d3b85dd46f5d8b36c7b5fa09b
|
|
BLAKE2b-256 checksum How to use checksums |
5e04d8172ac8dd8de5555099ad584e65b575f849dba991d27f3dbd6d8a8313d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logRelease files / fiki-0.7.0-py3-none-any.whl
| Download URL | fiki-0.7.0-py3-none-any.whl |
|---|---|
| Size | 27.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
94af4fddabb34988d35fc99a875fabd2d3f7517a7340fe0bbef6dd8abf353d3f
|
|
BLAKE2b-256 checksum How to use checksums |
95686782c9a3571eee2185a9bc76fe18e8be5e3f0a09bfe64ab2553a43a58609
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency log