From 9607f2a3450d8227f7fa418d790ea878d9d86861 Mon Sep 17 00:00:00 2001 From: kamal Date: Thu, 2 Apr 2026 20:50:46 +0000 Subject: [PATCH] Add Python --- Python.md | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 Python.md diff --git a/Python.md b/Python.md new file mode 100644 index 0000000..aed0a7a --- /dev/null +++ b/Python.md @@ -0,0 +1,158 @@ +# 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 +```