|
All checks were successful
CI / lint (push) Successful in 1m35s
CI / test-python (push) Successful in 1m46s
CI / test-zig (push) Successful in 1m37s
CI / test-wasm (push) Successful in 1m52s
CI / test (push) Successful in 14m22s
CI / miri (push) Successful in 13m57s
CI / build (push) Successful in 1m6s
CI / fuzz-regression (push) Successful in 9m4s
CI / publish-python (push) Successful in 1m46s
CI / publish (push) Successful in 1m52s
CI / publish-wasm (push) Successful in 1m55s
Signed-off-by: Kamal Tufekcic <kamal@lo.sh> |
||
|---|---|---|
| .. | ||
| python/soliton | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| LICENSE.md | ||
| pyproject.toml | ||
| README.md | ||
soliton
Python bindings for libsoliton — a pure-Rust post-quantum cryptographic library providing composite identity keys (X-Wing + ML-DSA-65), hybrid signatures, KEM-based authentication, asynchronous key exchange, double-ratchet message encryption, streaming AEAD, and encrypted storage.
Install
pip install soliton-py
Builds from source via maturin — requires a Rust toolchain.
Quick Start
Identity Keys
import soliton
# Generate a post-quantum identity keypair.
with soliton.Identity.generate() as alice:
# Sign a message (Ed25519 + ML-DSA-65 hybrid).
sig = alice.sign(b"hello")
alice.verify(b"hello", sig)
# Fingerprint (SHA3-256 of public key).
print(alice.fingerprint_hex())
# Persist keys.
pk = alice.public_key() # 3200 bytes
sk = alice.secret_key() # 2496 bytes
# Secret key is zeroized on context exit.
Key Exchange (KEX)
# Bob generates a signed pre-key.
spk_pub, spk_sk = soliton.xwing_keygen()
spk_sig = soliton.kex_sign_prekey(bob_sk, spk_pub)
# Alice initiates a session.
initiated = soliton.kex_initiate(
alice_pk, alice_sk, bob_pk,
spk_pub, spk_id=1, spk_sig=spk_sig,
crypto_version="lo-crypto-v1",
)
# Bob receives the session.
received = soliton.kex_receive(
bob_pk, bob_sk, alice_pk,
initiated.session_init_encoded(),
initiated.sender_sig(), spk_sk,
)
Ratchet (Ongoing Messaging)
# First message (pre-ratchet).
aad = soliton.kex_build_first_message_aad(
initiated.sender_fingerprint(),
initiated.recipient_fingerprint(),
initiated.session_init_encoded(),
)
ct, rik_a = soliton.Ratchet.encrypt_first_message(
initiated.take_initial_chain_key(), b"hello bob", aad,
)
pt, rik_b = soliton.Ratchet.decrypt_first_message(
received.take_initial_chain_key(), ct, aad,
)
# Initialize ratchets.
with soliton.Ratchet.init_alice(
initiated.take_root_key(), rik_a,
alice_fp, bob_fp, initiated.ek_pk(), initiated.ek_sk(),
) as alice_r:
header, ciphertext = alice_r.encrypt(b"message 1")
# Serialize for persistence.
blob, epoch = alice_r.to_bytes() # consumes the ratchet
Encrypted Storage
with soliton.StorageKeyRing(version=1, key=key_bytes) as ring:
blob = ring.encrypt_blob("channel-1", "segment-0", plaintext)
data = ring.decrypt_blob("channel-1", "segment-0", blob)
# Key rotation.
ring.add_key(version=2, key=new_key, make_active=True)
Streaming AEAD (File Encryption)
with soliton.StreamEncryptor(key) as enc:
header = enc.header() # 26 bytes — send first
ct1 = enc.encrypt_chunk(chunk1) # non-final: must be 1 MiB
ct2 = enc.encrypt_chunk(chunk2, is_last=True) # final: any size
with soliton.StreamDecryptor(key, header) as dec:
pt1, is_last = dec.decrypt_chunk(ct1)
pt2, is_last = dec.decrypt_chunk(ct2)
Authentication (Zero-Knowledge)
# Server generates challenge.
ct, token = soliton.auth_challenge(client_pk)
# Client responds.
proof = soliton.auth_respond(client_sk, ct)
# Server verifies (constant-time).
assert soliton.auth_verify(token, proof)
Primitives
digest = soliton.sha3_256(b"data") # 32 bytes
tag = soliton.hmac_sha3_256(key, data) # 32 bytes
okm = soliton.hkdf_sha3_256(salt, ikm, info, length=64) # variable
ok = soliton.hmac_sha3_256_verify(tag1, tag2) # constant-time
phrase = soliton.verification_phrase(pk_a, pk_b) # 6 EFF words
Error Handling
All errors are subclasses of soliton.SolitonError:
| Exception | Meaning |
|---|---|
AeadError |
AEAD decryption failed (wrong key, tampered ciphertext) |
VerificationError |
Signature verification failed |
BundleVerificationError |
Pre-key bundle invalid |
DuplicateMessageError |
Replayed message counter |
ChainExhaustedError |
Counter-space exhausted — re-establish session |
InvalidLengthError |
Wrong-size parameter |
InvalidDataError |
Malformed input |
Context Managers
All types holding secret material support with statements for automatic zeroization:
with soliton.Identity.generate() as id:
... # secret key zeroized on exit
with soliton.Ratchet.init_alice(...) as r:
... # ratchet state reset on exit
with soliton.StorageKeyRing(1, key) as ring:
... # key material zeroized on exit
Documentation
- Specification.md — full cryptographic specification
- CHEATSHEET.md — API quick reference
- Abstract.md — formal security model