#key-exchange #pqxdh

no-std pqxdh-zoa

Simple and generic implementation of Signal's PQXDH

2 releases

Uses new Rust 2024

new 0.1.1 Feb 18, 2026
0.1.0 Feb 17, 2026

#896 in Cryptography

AGPL-3.0-only

21KB
286 lines

PQXDH

Key exchange protocol protocol with forward secrecy that should resist "harvest now decrypt later" quantum attacks.

No-std, generic (KEM, AEAD, Hash can be changed) implementation.

https://signal.org/docs/specifications/pqxdh/

Status

Security disclaimer: This crate has not been audited. It is still experimental and not indended to be used in production at the moment.

We are also implementing other protocols from Signal. The final goal is to make a secure messaging system for the Duniter ecosystem using its web of trust as a robust PKI.

Issues/patches/questions welcome (use e-mail if you don't have an account on GitLab Duniter).

Use

Add the following dependencies: (these are the recommended ones but others can be used)

aes-gcm-siv = "0.12.0-rc.3"
ed25519-dalek = { version = "3.0.0-pre.6", features = ["rand_core"] }
ml-kem = "0.3.0-rc.0"
rand = "0.10.0"
sha2 = "0.11.0-rc.5"

Example: Bob publishes a key bundle so Alice can use it to send Bob a message.

use ml_kem::Kem;
use pqxdh_zoa::{PQXDH, ParamString, PrekeyBundle, traits::PQXDH as _};
use rand::{Rng, RngExt, rngs::ThreadRng};

type MyPQXDH = PQXDH<
	aes_gcm_siv::Aes256GcmSiv,
	ml_kem::MlKem1024,
	sha2::Sha512,
	MyPQXDHParamString,
	std::vec::Vec<u8>,
>;

struct MyPQXDHParamString;
impl ParamString for MyPQXDHParamString {
	fn param_string<'a>() -> &'a [u8] {
		b"TestPQXDH_CURVE25519_SHA-512_ML-KEM-1024"
	}
}

let mut rng = rand::rng();

// Alice
let alice_id = ed25519_dalek::SigningKey::generate(&mut rng);

// Bob
let bob_id = ed25519_dalek::SigningKey::generate(&mut rng);
let curve_prekey = ed25519_dalek::SigningKey::generate(&mut rng);
let curve_ot_prekey = ed25519_dalek::SigningKey::generate(&mut rng);
let (pqsk, pqpk) = ml_kem::MlKem1024::generate_keypair_from_rng(&mut rng);

let prekey_bundle = PrekeyBundle {
	curve_prekey: curve_prekey.verifying_key(),
	curve_ot_prekey: Some(curve_ot_prekey.verifying_key()),
	identity_key: bob_id.verifying_key(),
	pq_prekey: pqpk,
	pq_prekey_id: 42,
};

let mut cleartext = b"Regardez les canards attendre au quai de la gare.";
let (sk, ad, mut ciphertext) =
	MyPQXDH::send::<ThreadRng>(&alice_id, &prekey_bundle, cleartext.to_vec(), &mut rng)
		.unwrap();
let (rec_sk, rec_ad) = MyPQXDH::receive(
	&bob_id,
	&curve_prekey,
	Some(&curve_ot_prekey),
	&mut ciphertext,
	&pqsk,
)
.unwrap();
assert_eq!(cleartext.as_slice(), ciphertext.message.as_slice());
assert_eq!(sk, rec_sk);
assert_eq!(ad, rec_ad);

License

Support me via LiberaPay

GNU AGPL v3, CopyLeft 2025-2026 Pascal Engélibert (why copyleft?)

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.

Dependencies

~2.4–3.5MB
~71K SLoC