initial commit
Some checks failed
CI / lint (push) Successful in 1m37s
CI / test-python (push) Successful in 1m49s
CI / test-zig (push) Successful in 1m39s
CI / test-wasm (push) Successful in 1m54s
CI / test (push) Successful in 14m44s
CI / miri (push) Successful in 14m18s
CI / build (push) Successful in 1m9s
CI / fuzz-regression (push) Successful in 9m9s
CI / publish (push) Failing after 1m10s
CI / publish-python (push) Failing after 1m46s
CI / publish-wasm (push) Has been cancelled

Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
This commit is contained in:
Kamal Tufekcic 2026-04-02 23:48:10 +03:00
commit 1d99048c95
No known key found for this signature in database
165830 changed files with 79062 additions and 0 deletions

112
soliton_py/src/identity.rs Normal file
View file

@ -0,0 +1,112 @@
//! Identity key management: keygen, sign, verify, fingerprint.
use crate::errors::to_py_err;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
/// An identity keypair (X-Wing + Ed25519 + ML-DSA-65).
///
/// Secret key material is zeroized when this object is garbage collected.
/// For deterministic cleanup, call `close()` explicitly or use as a context
/// manager: `with Identity.generate() as id: ...`
#[pyclass]
pub struct Identity {
pk: soliton::identity::IdentityPublicKey,
sk: Option<soliton::identity::IdentitySecretKey>,
}
#[pymethods]
impl Identity {
/// Generate a fresh identity keypair.
#[staticmethod]
fn generate() -> PyResult<Self> {
let id = soliton::identity::generate_identity().map_err(to_py_err)?;
Ok(Self {
pk: id.public_key,
sk: Some(id.secret_key),
})
}
/// Reconstruct from serialized public + secret key bytes.
#[staticmethod]
fn from_bytes(pk_bytes: &[u8], sk_bytes: &[u8]) -> PyResult<Self> {
let pk = soliton::identity::IdentityPublicKey::from_bytes(pk_bytes.to_vec())
.map_err(to_py_err)?;
let sk = soliton::identity::IdentitySecretKey::from_bytes(sk_bytes.to_vec())
.map_err(to_py_err)?;
Ok(Self { pk, sk: Some(sk) })
}
/// Reconstruct a public-key-only identity (cannot sign).
#[staticmethod]
fn from_public_bytes(pk_bytes: &[u8]) -> PyResult<Self> {
let pk = soliton::identity::IdentityPublicKey::from_bytes(pk_bytes.to_vec())
.map_err(to_py_err)?;
Ok(Self { pk, sk: None })
}
/// Public key bytes.
fn public_key<'py>(&self, py: Python<'py>) -> Py<PyBytes> {
PyBytes::new(py, self.pk.as_bytes()).into()
}
/// Secret key bytes. Raises if this is a public-key-only identity.
fn secret_key<'py>(&self, py: Python<'py>) -> PyResult<Py<PyBytes>> {
let sk = self
.sk
.as_ref()
.ok_or_else(|| crate::errors::InvalidDataError::new_err("no secret key"))?;
Ok(PyBytes::new(py, sk.as_bytes()).into())
}
/// SHA3-256 fingerprint of the public key (32 bytes).
fn fingerprint<'py>(&self, py: Python<'py>) -> Py<PyBytes> {
let fp = soliton::primitives::sha3_256::hash(self.pk.as_bytes());
PyBytes::new(py, &fp).into()
}
/// Hex-encoded fingerprint.
fn fingerprint_hex(&self) -> String {
soliton::primitives::sha3_256::fingerprint_hex(self.pk.as_bytes())
}
/// Hybrid sign (Ed25519 + ML-DSA-65).
fn sign<'py>(&self, py: Python<'py>, message: &[u8]) -> PyResult<Py<PyBytes>> {
let sk = self
.sk
.as_ref()
.ok_or_else(|| crate::errors::InvalidDataError::new_err("no secret key"))?;
let sig = soliton::identity::hybrid_sign(sk, message).map_err(to_py_err)?;
Ok(PyBytes::new(py, sig.as_bytes()).into())
}
/// Verify a hybrid signature against this identity's public key.
fn verify(&self, message: &[u8], signature: &[u8]) -> PyResult<()> {
let sig = soliton::identity::HybridSignature::from_bytes(signature.to_vec())
.map_err(to_py_err)?;
soliton::identity::hybrid_verify(&self.pk, message, &sig).map_err(to_py_err)
}
/// Zeroize the secret key immediately.
fn close(&mut self) {
self.sk = None;
}
fn __enter__(slf: Py<Self>) -> Py<Self> {
slf
}
fn __exit__(
&mut self,
_exc_type: Option<&Bound<'_, PyAny>>,
_exc_val: Option<&Bound<'_, PyAny>>,
_exc_tb: Option<&Bound<'_, PyAny>>,
) {
self.close();
}
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Identity>()?;
Ok(())
}