feat: Sprint 3 — C FFI bindings, WASM compilation, Python example, SDK docs

- Create quicproquo-ffi crate with 7 extern "C" functions: connect,
  login, send, receive, disconnect, last_error, free_string
  (produces libquicproquo_ffi.so and .a)
- Feature-gate quicproquo-core for WASM: identity, hybrid_kem,
  safety_numbers, sealed_sender, app_message, padding, transcript
  all compile to wasm32-unknown-unknown
- Add Python ctypes example (examples/python/qpq_client.py) with
  QpqClient wrapper class and CLI
- Add SDK documentation: FFI reference, WASM guide, qpq-gen generators
- Update Dockerfile for quicproquo-ffi workspace member
This commit is contained in:
2026-03-03 23:47:40 +01:00
parent 9ab306d891
commit db46b72f58
16 changed files with 1402 additions and 80 deletions

View File

@@ -6,6 +6,7 @@ use thiserror::Error;
#[derive(Debug, Error)]
pub enum CoreError {
/// Cap'n Proto serialisation or deserialisation failed.
#[cfg(feature = "native")]
#[error("Cap'n Proto error: {0}")]
Capnp(#[from] capnp::Error),

View File

@@ -17,8 +17,6 @@
//! collision-resistant identifier for logging.
use ed25519_dalek::{Signer as DalekSigner, SigningKey, VerifyingKey};
use openmls_traits::signatures::Signer;
use openmls_traits::types::{Error as MlsError, SignatureScheme};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use zeroize::Zeroizing;
@@ -87,15 +85,16 @@ impl IdentityKeypair {
/// Implement the openmls `Signer` trait so `IdentityKeypair` can be passed
/// directly to `KeyPackage::builder().build(...)` without needing the external
/// `openmls_basic_credential` crate.
impl Signer for IdentityKeypair {
fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, MlsError> {
#[cfg(feature = "native")]
impl openmls_traits::signatures::Signer for IdentityKeypair {
fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, openmls_traits::types::Error> {
let sk = self.signing_key();
let sig: ed25519_dalek::Signature = sk.sign(payload);
Ok(sig.to_bytes().to_vec())
}
fn signature_scheme(&self) -> SignatureScheme {
SignatureScheme::ED25519
fn signature_scheme(&self) -> openmls_traits::types::SignatureScheme {
openmls_traits::types::SignatureScheme::ED25519
}
}

View File

@@ -1,6 +1,28 @@
//! Core cryptographic primitives, MLS group state machine, and hybrid
//! post-quantum KEM for quicproquo.
//!
//! # WASM support
//!
//! When compiled with `--no-default-features` (disabling the `native` feature),
//! the following modules are available for `wasm32-unknown-unknown`:
//!
//! - `identity` — Ed25519 identity keypair (generate, sign, verify)
//! - `hybrid_kem` — X25519 + ML-KEM-768 hybrid key encapsulation
//! - `safety_numbers` — Signal-style safety number computation
//! - `sealed_sender` — sender identity + Ed25519 signature envelope
//! - `app_message` — rich application message serialisation/parsing
//! - `padding` — message padding to hide plaintext lengths
//! - `transcript` — encrypted tamper-evident message transcript
//! - `error` — `CoreError` type
//!
//! The following modules require the `native` feature (MLS, OPAQUE, Cap'n Proto):
//!
//! - `group` — MLS group state machine (openmls)
//! - `keypackage` — MLS KeyPackage generation
//! - `hybrid_crypto` — hybrid HPKE provider for OpenMLS
//! - `keystore` — OpenMLS key store with optional disk persistence
//! - `opaque_auth` — OPAQUE cipher suite configuration
//!
//! # Module layout
//!
//! | Module | Responsibility |
@@ -15,36 +37,50 @@
mod app_message;
mod error;
mod group;
mod hybrid_crypto;
mod hybrid_kem;
mod identity;
mod keypackage;
mod keystore;
pub mod opaque_auth;
pub mod padding;
pub mod safety_numbers;
pub mod sealed_sender;
pub mod transcript;
// ── Public API ────────────────────────────────────────────────────────────────
// ── Native-only modules (MLS, OPAQUE, filesystem) ───────────────────────────
#[cfg(feature = "native")]
mod group;
#[cfg(feature = "native")]
mod hybrid_crypto;
#[cfg(feature = "native")]
mod keypackage;
#[cfg(feature = "native")]
mod keystore;
#[cfg(feature = "native")]
pub mod opaque_auth;
// ── Public API (always available) ───────────────────────────────────────────
pub use app_message::{
serialize, serialize_chat, serialize_reaction, serialize_read_receipt, serialize_reply,
serialize_typing, parse, generate_message_id, AppMessage, MessageType, VERSION as APP_MESSAGE_VERSION,
};
pub use error::CoreError;
pub use group::{GroupMember, ReceivedMessage, ReceivedMessageWithSender};
pub use hybrid_kem::{
hybrid_decrypt, hybrid_encrypt, HybridKemError, HybridKeypair, HybridKeypairBytes,
HybridPublicKey,
};
pub use hybrid_crypto::{HybridCrypto, HybridCryptoProvider};
pub use identity::{verify_delivery_proof, IdentityKeypair};
pub use keypackage::{generate_key_package, validate_keypackage_ciphersuite};
pub use keystore::DiskKeyStore;
pub use safety_numbers::compute_safety_number;
pub use transcript::{
read_transcript, verify_transcript_chain, ChainVerdict, DecodedRecord, TranscriptRecord,
TranscriptWriter,
};
// ── Public API (native only) ────────────────────────────────────────────────
#[cfg(feature = "native")]
pub use group::{GroupMember, ReceivedMessage, ReceivedMessageWithSender};
#[cfg(feature = "native")]
pub use hybrid_crypto::{HybridCrypto, HybridCryptoProvider};
#[cfg(feature = "native")]
pub use keypackage::{generate_key_package, validate_keypackage_ciphersuite};
#[cfg(feature = "native")]
pub use keystore::DiskKeyStore;