From 97251137afc3599ff2a91c8051108e2c02b29ccd Mon Sep 17 00:00:00 2001 From: Markus Maiwald Date: Fri, 30 Jan 2026 23:08:15 +0100 Subject: [PATCH] feat(l1): Integrate real LibOQS (ML-KEM-768) - Build System: Link against static liboqs.a (built without OpenSSL) - PQXDH: Replace stubs with OQS_KEM_ml_kem_768 functions - Tests: Verify full handshake with real post-quantum KEM - Disable Kyber (old) in liboqs build to fix symbol conflicts --- build.zig | 3 ++ l1-identity/pqxdh.zig | 10 +++--- l1-identity/test_pqxdh.zig | 62 +++++--------------------------------- 3 files changed, 15 insertions(+), 60 deletions(-) diff --git a/build.zig b/build.zig index 58dec86..8a5cf40 100644 --- a/build.zig +++ b/build.zig @@ -152,6 +152,9 @@ pub fn build(b: *std.Build) void { .root_module = l1_pqxdh_mod, }); l1_pqxdh_tests.linkLibC(); + l1_pqxdh_tests.addIncludePath(b.path("vendor/liboqs/install/include")); + l1_pqxdh_tests.addLibraryPath(b.path("vendor/liboqs/install/lib")); // For liboqs.a + l1_pqxdh_tests.linkSystemLibrary("oqs"); const run_l1_pqxdh_tests = b.addRunArtifact(l1_pqxdh_tests); // Link time module to l1_vector_mod diff --git a/l1-identity/pqxdh.zig b/l1-identity/pqxdh.zig index 1f41adf..f1eaafa 100644 --- a/l1-identity/pqxdh.zig +++ b/l1-identity/pqxdh.zig @@ -21,20 +21,20 @@ const crypto = std.crypto; // FIPS 203: ML-KEM-768 (post-standardization naming for Kyber-768) /// ML-KEM-768 key generation -extern "c" fn OQS_KEM_kyber768_keypair( +extern "c" fn OQS_KEM_ml_kem_768_keypair( public_key: ?*u8, secret_key: ?*u8, ) c_int; /// ML-KEM-768 encapsulation (creates shared secret + ciphertext) -extern "c" fn OQS_KEM_kyber768_encaps( +extern "c" fn OQS_KEM_ml_kem_768_encaps( ciphertext: ?*u8, shared_secret: ?*u8, public_key: ?*const u8, ) c_int; /// ML-KEM-768 decapsulation (recovers shared secret from ciphertext) -extern "c" fn OQS_KEM_kyber768_decaps( +extern "c" fn OQS_KEM_ml_kem_768_decaps( shared_secret: ?*u8, ciphertext: ?*const u8, secret_key: ?*const u8, @@ -246,7 +246,7 @@ pub fn initiator( var kem_ct: [ML_KEM_768.CIPHERTEXT_SIZE]u8 = undefined; // Call liboqs ML-KEM encapsulation - const kem_result = OQS_KEM_kyber768_encaps( + const kem_result = OQS_KEM_ml_kem_768_encaps( @ptrCast(&kem_ct), @ptrCast(&kem_ss), @ptrCast(&bob_prekey_bundle.signed_prekey_mlkem), @@ -332,7 +332,7 @@ pub fn responder( var kem_ss: [ML_KEM_768.SHARED_SECRET_SIZE]u8 = undefined; // Call liboqs ML-KEM decapsulation - const kem_result = OQS_KEM_kyber768_decaps( + const kem_result = OQS_KEM_ml_kem_768_decaps( @ptrCast(&kem_ss), @ptrCast(&alice_initial_message.mlkem_ciphertext), @ptrCast(&bob_mlkem_private), diff --git a/l1-identity/test_pqxdh.zig b/l1-identity/test_pqxdh.zig index a0c5311..dfb9528 100644 --- a/l1-identity/test_pqxdh.zig +++ b/l1-identity/test_pqxdh.zig @@ -9,60 +9,12 @@ const pqxdh = @import("pqxdh.zig"); const testing = std.testing; // ============================================================================ -// STUB: ML-KEM-768 Functions (for testing without liboqs) +// Real LibOQS Functions (via C Import) // ============================================================================ -// These will be replaced with real liboqs FFI once library is built -export fn OQS_KEM_kyber768_keypair( - public_key: ?*u8, - secret_key: ?*u8, -) c_int { - // Stub: Fill with deterministic test data - if (public_key) |pk| { - const pk_slice: [*]u8 = @ptrCast(pk); - @memset(pk_slice[0..pqxdh.ML_KEM_768.PUBLIC_KEY_SIZE], 0xAA); - } - if (secret_key) |sk| { - const sk_slice: [*]u8 = @ptrCast(sk); - @memset(sk_slice[0..pqxdh.ML_KEM_768.SECRET_KEY_SIZE], 0xBB); - } - return 0; // Success -} - -export fn OQS_KEM_kyber768_encaps( - ciphertext: ?*u8, - shared_secret: ?*u8, - public_key: ?*const u8, -) c_int { - _ = public_key; // Use in real impl - - // Stub: Generate deterministic shared secret + ciphertext - if (ciphertext) |ct| { - const ct_slice: [*]u8 = @ptrCast(ct); - @memset(ct_slice[0..pqxdh.ML_KEM_768.CIPHERTEXT_SIZE], 0xCC); - } - if (shared_secret) |ss| { - const ss_slice: [*]u8 = @ptrCast(ss); - @memset(ss_slice[0..pqxdh.ML_KEM_768.SHARED_SECRET_SIZE], 0xDD); - } - return 0; // Success -} - -export fn OQS_KEM_kyber768_decaps( - shared_secret: ?*u8, - ciphertext: ?*const u8, - secret_key: ?*const u8, -) c_int { - _ = ciphertext; // Use in real impl - _ = secret_key; // Use in real impl - - // Stub: Must return SAME shared secret as encaps for protocol to work - if (shared_secret) |ss| { - const ss_slice: [*]u8 = @ptrCast(ss); - @memset(ss_slice[0..pqxdh.ML_KEM_768.SHARED_SECRET_SIZE], 0xDD); - } - return 0; // Success -} +const c = @cImport({ + @cInclude("oqs/oqs.h"); +}); // ============================================================================ // Helper: Generate Test Keypairs @@ -132,7 +84,7 @@ test "PQXDHInitialMessage serialization roundtrip" { try testing.expectEqualSlices(u8, &msg.mlkem_ciphertext, &restored.mlkem_ciphertext); } -test "PQXDH full handshake roundtrip (stubbed ML-KEM)" { +test "PQXDH full handshake roundtrip (real ML-KEM)" { const allocator = testing.allocator; // === Bob's Setup === @@ -151,7 +103,7 @@ test "PQXDH full handshake roundtrip (stubbed ML-KEM)" { // Generate Bob's ML-KEM keypair (stubbed) var bob_mlkem_public: [pqxdh.ML_KEM_768.PUBLIC_KEY_SIZE]u8 = undefined; var bob_mlkem_private: [pqxdh.ML_KEM_768.SECRET_KEY_SIZE]u8 = undefined; - const kem_result = OQS_KEM_kyber768_keypair(&bob_mlkem_public[0], &bob_mlkem_private[0]); + const kem_result = c.OQS_KEM_ml_kem_768_keypair(&bob_mlkem_public[0], &bob_mlkem_private[0]); try testing.expectEqual(@as(c_int, 0), kem_result); // Create Bob's prekey bundle (signature stubbed for now) @@ -211,6 +163,6 @@ test "PQXDH error: invalid ML-KEM encapsulation" { var public_key: [pqxdh.ML_KEM_768.PUBLIC_KEY_SIZE]u8 = undefined; var secret_key: [pqxdh.ML_KEM_768.SECRET_KEY_SIZE]u8 = undefined; - const result = OQS_KEM_kyber768_keypair(&public_key[0], &secret_key[0]); + const result = c.OQS_KEM_ml_kem_768_keypair(&public_key[0], &secret_key[0]); try testing.expectEqual(@as(c_int, 0), result); }