# soliton Python bindings for [libsoliton](https://git.lo.sh/lo/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 ```bash pip install soliton ``` Builds from source via [maturin](https://www.maturin.rs/) — requires a Rust toolchain. ## Quick Start ### Identity Keys ```python 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) ```python # 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) ```python # 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 ```python 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) ```python 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) ```python # 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 ```python 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: ```python 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](https://git.lo.sh/lo/libsoliton/src/branch/main/Specification.md) — full cryptographic specification - [CHEATSHEET.md](https://git.lo.sh/lo/libsoliton/src/branch/main/CHEATSHEET.md) — API quick reference - [Abstract.md](https://git.lo.sh/lo/libsoliton/src/branch/main/Abstract.md) — formal security model ## License [AGPL-3.0-only](LICENSE.md)