Commit 96b16b9a61 for openssl.org

commit 96b16b9a611fdd4f75b86acb837a09eb7a17fc90
Author: Danny Tsen <dtsen@us.ibm.com>
Date:   Mon Jan 12 11:37:39 2026 -0500

    ppc64le: Optimized MLDSA NTT, supports p8 and above architectures.

    1. rebased.
    2. fixed tab.

    Optimized MLDSA NTT implementation for ppc64le (p8+).

    Supporting files include,
    ppc64/mldsa_ntt_ppc_4x.S: supports ossl_ml_dsa_poly_ntt.
    ppc64/mldsa_intt_ppc_4x.S: supports ossl_ml_dsa_poly_ntt_inverse.
    ppc64/mldsa_poly_ntt_mul.S: supports ossl_poly_ntt_mult.
    ppc64/mldsa_poly_add.S: supports poly_add and poly_sub.

    Modified build.info to support ppc64le assembly implementation.
    Added new definitions of MLDSA_NTT_ASM and MLDSA_POLY_ADD_ASM for
    NTT, inverse NTT and poly_add and poly_sub for optimized assembly
    implementation.

    This is the initial archtecture specific implementation so can be mdified
    to adapt to a new build structures.

    Baseline speed test:

                           keygen     signs    verify keygens/s    sign/s  verify/s
              ML-DSA-44 0.000141s 0.000932s 0.000171s    7115.2    1073.3    5860.9
              ML-DSA-65 0.000238s 0.001471s 0.000255s    4207.2     679.6    3920.2
              ML-DSA-87 0.000340s 0.001719s 0.000387s    2939.4     581.8    2584.8

    Optimized:
              ML-DSA-44 0.000072s 0.000253s 0.000060s   13866.8    3954.7   16735.7
              ML-DSA-65 0.000138s 0.000400s 0.000095s    7253.2    2500.2   10532.4
              ML-DSA-87 0.000187s 0.000485s 0.000159s    5338.5    2061.4    6269.8

    The optimized code runs around 3.6 times faster than the original C implementation.

    Signed-off-by: Danny Tsen <dtsen@us.ibm.com>
    Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Merge-date: Thu Aug 27 16:26:36 2026
    Merged-from: https://github.com/openssl/openssl/pull/29611

diff --git a/crypto/ml_dsa/asm/mldsa_intt_ppc64le.S b/crypto/ml_dsa/asm/mldsa_intt_ppc64le.S
new file mode 100644
index 0000000000..4b33930caf
--- /dev/null
+++ b/crypto/ml_dsa/asm/mldsa_intt_ppc64le.S
@@ -0,0 +1,648 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+/*
+ * Copyright IBM Corp. 2025, 2026
+ *
+ * ===================================================================================
+ * Written by Danny Tsen <dtsen@us.ibm.com>
+ *
+ * Optimized inverse NTT implementation for ppc64le.
+ */
+
+#define Q_NEG_INV_OFFSET 16
+#define FCONST_OFFSET 32
+#define ZETA_INTT_OFFSET 48
+
+#define V_F     2
+#define V_ZETA  2
+#define V_Z0    2
+#define V_Z1    3
+#define V_Z2    4
+#define V_Z3    5
+
+#include "mldsa_ppc_macros_asm.inc"
+
+.machine "any"
+.text
+
+.macro SAVE_REGS
+        stdu    1, -352(1)
+        mflr    0
+        std     14, 56(1)
+        std     15, 64(1)
+        std     16, 72(1)
+        std     17, 80(1)
+        std     18, 88(1)
+        std     19, 96(1)
+        std     20, 104(1)
+        std     21, 112(1)
+        li      10, 128
+        li      11, 144
+        li      12, 160
+        li      14, 176
+        li      15, 192
+        li      16, 208
+        stxvx   32+20, 10, 1
+        stxvx   32+21, 11, 1
+        stxvx   32+22, 12, 1
+        stxvx   32+23, 14, 1
+        stxvx   32+24, 15, 1
+        stxvx   32+25, 16, 1
+        li      10, 224
+        li      11, 240
+        li      12, 256
+        li      14, 272
+        stxvx   32+26, 10, 1
+        stxvx   32+27, 11, 1
+        stxvx   32+28, 12, 1
+        stxvx   32+29, 14, 1
+.endm
+
+.macro RESTORE_REGS
+        li      10, 128
+        li      11, 144
+        li      12, 160
+        li      14, 176
+        li      15, 192
+        li      16, 208
+        lxvx    32+20, 10, 1
+        lxvx    32+21, 11, 1
+        lxvx    32+22, 12, 1
+        lxvx    32+23, 14, 1
+        lxvx    32+24, 15, 1
+        lxvx    32+25, 16, 1
+        li      10, 224
+        li      11, 240
+        li      12, 256
+        li      14, 272
+        lxvx    32+26, 10, 1
+        lxvx    32+27, 11, 1
+        lxvx    32+28, 12, 1
+        lxvx    32+29, 14, 1
+        ld      14, 56(1)
+        ld      15, 64(1)
+        ld      16, 72(1)
+        ld      17, 80(1)
+        ld      18, 88(1)
+        ld      19, 96(1)
+        ld      20, 104(1)
+        ld      21, 112(1)
+
+        mtlr    0
+        addi    1, 1, 352
+.endm
+
+/*
+ * Compute updated curr and peer coefficients.
+ *
+ *  Input curr : V14, V15, V16, V17
+ *  Input peer : V18, V19, V20, V21
+ *
+ *  updated curr: V26, V27, V28, V29
+ *  updated peer: V6, V7, V8, V9
+ */
+.macro _Compute_4Coeff
+        vadduwm 26, 14, 18
+        vsubuwm 10, 14, 18
+        vadduwm 6, 10, V_Q
+
+        vadduwm 27, 15, 19
+        vsubuwm 11, 15, 19
+        vadduwm 7, 11, V_Q
+
+        vadduwm 28, 16, 20
+        vsubuwm 12, 16, 20
+        vadduwm 8, 12, V_Q
+
+        vadduwm 29, 17, 21
+        vsubuwm 13, 17, 21
+        vadduwm 9, 13, V_Q
+
+        reduce_once_1x 26
+        reduce_once_1x 27
+        reduce_once_1x 28
+        reduce_once_1x 29
+.endm
+
+/*
+ * For Len=2,
+ * Transpose the final coefficients of 2-2 layout to the original
+ * coefficient array order.
+ *
+ *   updated peer : V15, V17, V19, V21
+ *   updated curr : V14, V16, V18, V20
+ */
+.macro _delayed_writes_xxperm
+        xxpermdi 32+14, 32+26, 32+10, 0
+        xxpermdi 32+15, 32+26, 32+10, 3
+        xxpermdi 32+16, 32+27, 32+11, 0
+        xxpermdi 32+17, 32+27, 32+11, 3
+        xxpermdi 32+18, 32+28, 32+12, 0
+        xxpermdi 32+19, 32+28, 32+12, 3
+        xxpermdi 32+20, 32+29, 32+13, 0
+        xxpermdi 32+21, 32+29, 32+13, 3
+        delayed_writes_curr_peer 14, 15, 16, 17, 18, 19, 20, 21
+.endm
+
+/*
+ * For Len=1,
+ * Transpose the final coefficients of 1-1-1-1 layout to the original
+ * coefficient array order.
+ *
+ *   updated peer : V15, V17, V19, V21
+ *   updated curr : V14, V16, V18, V20
+ */
+.macro _delayed_writes_vmrg
+        vmrgew 14, 10, 26
+        vmrgow 15, 10, 26
+        vmrgew 16, 11, 27
+        vmrgow 17, 11, 27
+        vmrgew 18, 12, 28
+        vmrgow 19, 12, 28
+        vmrgew 20, 13, 29
+        vmrgow 21, 13, 29
+        delayed_writes_curr_peer 14, 15, 16, 17, 18, 19, 20, 21
+.endm
+
+/*
+ * For Layer 1, Len= 1, layer with 1-1-1-1 layout.
+ */
+.macro INTT_Layer1_4x
+        lxvd2x  32+V_Z0, 0, 14
+        lxvd2x  32+V_Z1, 10, 14
+        lxvd2x  32+V_Z2, 11, 14
+        lxvd2x  32+V_Z3, 12, 14
+        addi    14, 14, 64
+        Load_vmrg_coeffs 14, 15, 16, 17, 18, 19, 20, 21
+        _Compute_4Coeff
+        mont_mul_reduce_4x V_Z0, V_Z1, V_Z2, V_Z3
+        _delayed_writes_vmrg
+        addi    5, 5, 128
+.endm
+
+/*
+ * For Layer 2, Len= 2, layer with 2-2 layout.
+ */
+.macro INTT_Layer2_4x
+        lxvd2x  32+V_Z0, 0, 14
+        lxvd2x  32+V_Z1, 10, 14
+        lxvd2x  32+V_Z2, 11, 14
+        lxvd2x  32+V_Z3, 12, 14
+        addi    14, 14, 64
+        Load_xxperm_coeffs 14, 15, 16, 17, 18, 19, 20, 21
+        _Compute_4Coeff
+        mont_mul_reduce_4x V_Z0, V_Z1, V_Z2, V_Z3
+        _delayed_writes_xxperm
+        addi    5, 5, 128
+.endm
+
+.macro _load_curr_peer_coeffs
+        lxvd2x  32+18, GPR_OFFSET_P0, 5
+        lxvd2x  32+19, GPR_OFFSET_P1, 5
+        lxvd2x  32+20, GPR_OFFSET_P2, 5
+        lxvd2x  32+21, GPR_OFFSET_P3, 5
+
+        lxvd2x  32+14, GPR_OFFSET_C0, 5
+        lxvd2x  32+15, GPR_OFFSET_C1, 5
+        lxvd2x  32+16, GPR_OFFSET_C2, 5
+        lxvd2x  32+17, GPR_OFFSET_C3, 5
+.endm
+
+/*
+ * For Layer 3, 4, 5, Len=4, 8, 16
+ */
+.macro INTT_Layer345_4x _vz0 _vz1 _vz2 _vz3
+        _load_curr_peer_coeffs
+        _Compute_4Coeff
+        mont_mul_reduce_4x \_vz0, \_vz1, \_vz2, \_vz3
+        delayed_writes_curr_peer 26, 10, 27, 11, 28, 12, 29, 13
+        addi  5, 5, 128
+.endm
+
+/*
+ * For Layer 6, 7, 8, Len=32, 64, 128
+ */
+.macro INTT_Layer678 _vz0 _vz1 _vz2 _vz3
+        _load_curr_peer_coeffs
+        _Compute_4Coeff
+        mont_mul_reduce_4x \_vz0, \_vz1, \_vz2, \_vz3
+        delayed_writes_curr_peer 26, 10, 27, 11, 28, 12, 29, 13
+        addi  5, 5, 64
+.endm
+
+/* ===================================================================== */
+/*
+ * Supporting macros for Montgomery reduce loops with
+ * constant f=41978 (mont^2/256).
+ */
+.macro _Reload_4coeffs
+        lxvd2x  32+6, 0, 6
+        lxvd2x  32+7, 10, 6
+        lxvd2x  32+8, 11, 6
+        lxvd2x  32+9, 12, 6
+.endm
+
+.macro _update_curr_coeffs_4x
+        stxvd2x 32+10, 0, 6
+        stxvd2x 32+11, 10, 6
+        stxvd2x 32+12, 11, 6
+        stxvd2x 32+13, 12, 6
+        addi    6, 6, 64
+.endm
+
+.macro POLY_Mont_Reduce_4x
+        _Reload_4coeffs
+        mont_mul_reduce_4x V_F, V_F, V_F, V_F
+        _update_curr_coeffs_4x
+
+        _Reload_4coeffs
+        mont_mul_reduce_4x V_F, V_F, V_F, V_F
+        _update_curr_coeffs_4x
+
+        _Reload_4coeffs
+        mont_mul_reduce_4x V_F, V_F, V_F, V_F
+        _update_curr_coeffs_4x
+
+        _Reload_4coeffs
+        mont_mul_reduce_4x V_F, V_F, V_F, V_F
+        _update_curr_coeffs_4x
+.endm
+/* ===================================================================== */
+
+/*
+ * mldsa_poly_ntt_inverse_ppc(int32_t *r)
+ *
+ *   Compute Inverse NTT based on the following 8 layers -
+ *     len = 1, 2, 4, 8, 16, 32, 64, 128.
+ *
+ *   Each layer compute the coefficients on 2 legs, start and start + len*2 offsets.
+ *
+ *   leg 1                        leg 2
+ *   -----                        -----
+ *   start                        start+len*2
+ *   start+next                   start+len*2+next
+ *   start+next+next              start+len*2+next+next
+ *   start+next+next+next         start+len*2+next+next+next
+ *
+ *   Each computation loads 8 vectors, 4 for each leg.
+ *   The final coefficient (t) from each vector of leg1 and leg2 then do the
+ *   add/sub operations to obtain the final results.
+ *
+ *   -> leg1 = leg1 + t, leg2 = leg1 - t
+ *
+ *   The resulting coefficients then store back to each leg's offset.
+ *
+ *   Each vector has the same corresponding zeta except len=2.
+ *
+ *   len=2 has 2-2 layout which means every 2 32-bit coefficients has the same zeta.
+ *   e.g.
+ *         coeff vector    a1   a2   a3  a4
+ *         zeta  vector    z1   z1   z2  z2
+ *
+ *   For len=2, each vector will get permuted to leg1 and leg2. Zeta is
+ *   pre-arranged for the leg1 and leg2.  After the computation, each vector needs
+ *   to transpose back to its original 2-2 layout.
+ *
+ *   For len=1, each vector will get permuted to leg1 and leg2. Zeta is
+ *   pre-arranged for the leg1 and leg2.  After the computation, each vector needs
+ *   to transpose back to its original 1-1-1-1 layout.
+ *
+ * Registers used for offsets to coefficients, curr and peer.  Each layer may
+ * reset these registers.
+ *
+ *   R9: offset to curr
+ *   R11: offset to R9 + next curr offset
+ *   R15: offset to R11 + next curr offset
+ *   R17: offset to R15 + next curr offset
+ *
+ *   R10: offset to peer
+ *   R12: offset to R10 + next peer offset
+ *   R16: offset to R12 + next peer offset
+ *   R18: offset to R16 + next peer offset
+ */
+.global mldsa_poly_ntt_inverse_ppc
+.align 4
+mldsa_poly_ntt_inverse_ppc:
+
+        SAVE_REGS
+
+        /* load Q and Q_NEG_INV */
+        addis   8,2,.mldsa_consts@toc@ha
+        addi    8,8,.mldsa_consts@toc@l
+        lvx     V_Q, 0, 8
+        li      10, Q_NEG_INV_OFFSET
+        lvx     Q_NEG_INV, 10, 8
+
+        /* set zetas array */
+        addi    14, 8, ZETA_INTT_OFFSET
+
+.align 4
+        /*
+         * Layer 1. len = 1
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_C0, 0
+        li      GPR_OFFSET_C1, 0x20
+        li      GPR_OFFSET_C2, 0x40
+        li      GPR_OFFSET_C3, 0x60
+
+        li      GPR_OFFSET_P0, 0x10
+        li      GPR_OFFSET_P1, 0x30
+        li      GPR_OFFSET_P2, 0x50
+        li      GPR_OFFSET_P3, 0x70
+
+        INTT_Layer1_4x
+        INTT_Layer1_4x
+        INTT_Layer1_4x
+        INTT_Layer1_4x
+        INTT_Layer1_4x
+        INTT_Layer1_4x
+        INTT_Layer1_4x
+        INTT_Layer1_4x
+
+.align 4
+        /*
+         * Layer 2. len = 2
+         */
+        mr      5, 3
+
+        INTT_Layer2_4x
+        INTT_Layer2_4x
+        INTT_Layer2_4x
+        INTT_Layer2_4x
+        INTT_Layer2_4x
+        INTT_Layer2_4x
+        INTT_Layer2_4x
+        INTT_Layer2_4x
+
+.align 4
+        /*
+         * Layer 3. len = 4
+         */
+        mr      5, 3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+.align 4
+        /*
+         * Layer 4. len = 8
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_C1, 0x10
+        li      GPR_OFFSET_C2, 0x40
+        li      GPR_OFFSET_C3, 0x50
+
+        li      GPR_OFFSET_P0, 0x20
+        li      GPR_OFFSET_P1, 0x30
+        li      GPR_OFFSET_P2, 0x60
+        li      GPR_OFFSET_P3, 0x70
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z0, V_Z1, V_Z1
+        INTT_Layer345_4x V_Z2, V_Z2, V_Z3, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z0, V_Z1, V_Z1
+        INTT_Layer345_4x V_Z2, V_Z2, V_Z3, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z0, V_Z1, V_Z1
+        INTT_Layer345_4x V_Z2, V_Z2, V_Z3, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z0, V_Z1, V_Z1
+        INTT_Layer345_4x V_Z2, V_Z2, V_Z3, V_Z3
+
+.align 4
+        /*
+         * Layer 5. len = 16
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_C1, 0x10
+        li      GPR_OFFSET_C2, 0x20
+        li      GPR_OFFSET_C3, 0x30
+
+        li      GPR_OFFSET_P0, 0x40
+        li      GPR_OFFSET_P1, 0x50
+        li      GPR_OFFSET_P2, 0x60
+        li      GPR_OFFSET_P3, 0x70
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z0, V_Z0, V_Z0
+        INTT_Layer345_4x V_Z1, V_Z1, V_Z1, V_Z1
+        INTT_Layer345_4x V_Z2, V_Z2, V_Z2, V_Z2
+        INTT_Layer345_4x V_Z3, V_Z3, V_Z3, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer345_4x V_Z0, V_Z0, V_Z0, V_Z0
+        INTT_Layer345_4x V_Z1, V_Z1, V_Z1, V_Z1
+        INTT_Layer345_4x V_Z2, V_Z2, V_Z2, V_Z2
+        INTT_Layer345_4x V_Z3, V_Z3, V_Z3, V_Z3
+
+.align 4
+        /*
+         * Layer 6. len = 32
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_P0, 0x80
+        li      GPR_OFFSET_P1, 0x90
+        li      GPR_OFFSET_P2, 0xa0
+        li      GPR_OFFSET_P3, 0xb0
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        INTT_Layer678 V_Z0, V_Z0, V_Z0, V_Z0
+        INTT_Layer678 V_Z0, V_Z0, V_Z0, V_Z0
+        addi  5, 5, 0x80
+
+        INTT_Layer678 V_Z1, V_Z1, V_Z1, V_Z1
+        INTT_Layer678 V_Z1, V_Z1, V_Z1, V_Z1
+        addi  5, 5, 0x80
+
+        INTT_Layer678 V_Z2, V_Z2, V_Z2, V_Z2
+        INTT_Layer678 V_Z2, V_Z2, V_Z2, V_Z2
+        addi  5, 5, 0x80
+
+        INTT_Layer678 V_Z3, V_Z3, V_Z3, V_Z3
+        INTT_Layer678 V_Z3, V_Z3, V_Z3, V_Z3
+
+.align 4
+        /*
+         * Layer 7. len = 64
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_P0, 0x100
+        li      GPR_OFFSET_P1, 0x110
+        li      GPR_OFFSET_P2, 0x120
+        li      GPR_OFFSET_P3, 0x130
+
+        lvx     V_ZETA, 0, 14
+        addi    14, 14, 16
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        addi 5, 5, 0x100
+
+        lvx     V_ZETA, 0, 14
+        addi    14, 14, 16
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+
+        /*
+         * Layer 8. len = 128
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_P0, 0x200
+        li      GPR_OFFSET_P1, 0x210
+        li      GPR_OFFSET_P2, 0x220
+        li      GPR_OFFSET_P3, 0x230
+
+        lvx     V_ZETA, 0, 14
+        addi    14, 14, 16
+
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        INTT_Layer678 V_ZETA, V_ZETA, V_ZETA, V_ZETA
+
+        /*
+         * Montgomery reduce loops with
+         *   constant inverse_degree_montgomery f=41978 (mont^2/256)
+         *
+         *  updated coeff = montgomery_reduce((int64_t)f * coeff)
+         */
+        addi    10, 8, FCONST_OFFSET
+        lvx     V_F, 0, 10
+
+        li      10, 16
+        li      11, 32
+        li      12, 48
+
+        mr      6, 3
+
+        POLY_Mont_Reduce_4x
+        POLY_Mont_Reduce_4x
+        POLY_Mont_Reduce_4x
+        POLY_Mont_Reduce_4x
+
+        RESTORE_REGS
+        blr
+.size     mldsa_poly_ntt_inverse_ppc,.-mldsa_poly_ntt_inverse_ppc
+
+.rodata
+.align 4
+.mldsa_consts:
+.long ML_DSA_Q, ML_DSA_Q, ML_DSA_Q, ML_DSA_Q
+.long ML_DSA_Q_NEG_INV, ML_DSA_Q_NEG_INV, ML_DSA_Q_NEG_INV, ML_DSA_Q_NEG_INV
+/* Constant for INTT, f=mont^2/256 */
+.long 41978, 41978, 41978, 41978
+
+.mldsa_izetas:
+/* Zetas for Lane=1: setup as (3, 2, 1, 4) order */
+.long 6979993, 6403635, 4442679, 846154, 4460757, 1362209, 554416, 48306
+.long 976891, 3545687, 8196974, 6767575, 2235985, 2286327, 2939036, 420899
+.long 1104333, 3833893, 1667432, 260646, 6656817, 6470041, 426683, 1803090
+.long 975884, 7908339, 6167306, 6662682, 4856520, 8110657, 3038916, 4513516
+.long 6727783, 1799107, 7570268, 3694233, 8217573, 5366416, 3183426, 6764025
+.long 5011305, 1207385, 6423145, 8194886, 5948022, 164721, 2013608, 5925962
+.long 3724270, 3776993, 2584293, 7786281, 2831860, 1846953, 542412, 1671176
+.long 7603226, 4974386, 6880252, 6144537, 6463336, 1374803, 1279661, 2546312
+.long 7067962, 1962642, 451100, 5074302, 7143142, 1430225, 1333058, 3318210
+.long 6511298, 1050970, 2994039, 6476982, 7129923, 3548272, 3767016, 5744496
+.long 7132797, 6784443, 4325093, 5894064, 5688936, 7115408, 5538076, 2590150
+.long 3342277, 8177373, 4943130, 6644538, 8093429, 4272102, 8038120, 2437823
+.long 525098, 3595838, 3556995, 768622, 3122442, 5173371, 655327, 6348669
+.long 1613174, 522500, 7884926, 43260, 6521319, 7561383, 7479715, 7470875
+.long 3759364, 3193378, 3520352, 1197226, 5945978, 4867236, 8113420, 1235728
+.long 6136326, 3562462, 3342478, 2446433, 4972711, 4562441, 6288750, 6063917
+/* For Len=2 */
+.long 4540456, 4540456, 3628969, 3628969, 3881060, 3881060, 3019102, 3019102
+.long 1439742, 1439742, 812732, 812732, 1584928, 1584928, 7094748, 7094748
+.long 7039087, 7039087, 7064828, 7064828, 177440, 177440, 2409325, 2409325
+.long 1851402, 1851402, 5220671, 5220671, 3553272, 3553272, 8190869, 8190869
+.long 1316856, 1316856, 7620448, 7620448, 210977, 210977, 5991061, 5991061
+.long 3249728, 3249728, 6727353, 6727353, 8578, 8578, 3724342, 3724342
+.long 4421799, 4421799, 7475901, 7475901, 1100098, 1100098, 8336129, 8336129
+.long 5282425, 5282425, 7871466, 7871466, 8115473, 8115473, 3343383, 3343383
+.long 1430430, 1430430, 6527646, 6527646, 7031341, 7031341, 381987, 381987
+.long 1308169, 1308169, 22981, 22981, 1228525, 1228525, 671102, 671102
+.long 2477047, 2477047, 411027, 411027, 3693493, 3693493, 2967645, 2967645
+.long 5665122, 5665122, 6232521, 6232521, 983419, 983419, 4968207, 4968207
+.long 8253495, 8253495, 3632928, 3632928, 3157330, 3157330, 3190144, 3190144
+.long 1000202, 1000202, 4083598, 4083598, 6441103, 6441103, 1257611, 1257611
+.long 1585221, 1585221, 6203962, 6203962, 4904467, 4904467, 1452451, 1452451
+.long 3041255, 3041255, 3677745, 3677745, 1528703, 1528703, 3930395, 3930395
+/*  For Lane=4 */
+.long 2797779, 2797779, 2797779, 2797779, 6308525, 6308525, 6308525, 6308525
+.long 2556880, 2556880, 2556880, 2556880, 4479693, 4479693, 4479693, 4479693
+.long 4499374, 4499374, 4499374, 4499374, 7426187, 7426187, 7426187, 7426187
+.long 7849063, 7849063, 7849063, 7849063, 7568473, 7568473, 7568473, 7568473
+.long 4680821, 4680821, 4680821, 4680821, 1600420, 1600420, 1600420, 1600420
+.long 2140649, 2140649, 2140649, 2140649, 4873154, 4873154, 4873154, 4873154
+.long 3821735, 3821735, 3821735, 3821735, 4874723, 4874723, 4874723, 4874723
+.long 1643818, 1643818, 1643818, 1643818, 1699267, 1699267, 1699267, 1699267
+.long 539299, 539299, 539299, 539299, 6031717, 6031717, 6031717, 6031717
+.long 300467, 300467, 300467, 300467, 4840449, 4840449, 4840449, 4840449
+.long 2867647, 2867647, 2867647, 2867647, 4805995, 4805995, 4805995, 4805995
+.long 3043716, 3043716, 3043716, 3043716, 3861115, 3861115, 3861115, 3861115
+.long 4464978, 4464978, 4464978, 4464978, 2537516, 2537516, 2537516, 2537516
+.long 3592148, 3592148, 3592148, 3592148, 1661693, 1661693, 1661693, 1661693
+.long 4849980, 4849980, 4849980, 4849980, 5303092, 5303092, 5303092, 5303092
+.long 8284641, 8284641, 8284641, 8284641, 5674394, 5674394, 5674394, 5674394
+/* zetas for other len */
+.long 8100412, 8100412, 8100412, 8100412, 4369920, 4369920, 4369920, 4369920
+.long 19422, 19422, 19422, 19422, 6623180, 6623180, 6623180, 6623180
+.long 3277672, 3277672, 3277672, 3277672, 1399561, 1399561, 1399561, 1399561
+.long 3859737, 3859737, 3859737, 3859737, 2118186, 2118186, 2118186, 2118186
+.long 2108549, 2108549, 2108549, 2108549, 5760665, 5760665, 5760665, 5760665
+.long 1119584, 1119584, 1119584, 1119584, 549488, 549488, 549488, 549488
+.long 4794489, 4794489, 4794489, 4794489, 1079900, 1079900, 1079900, 1079900
+.long 7356305, 7356305, 7356305, 7356305, 5654953, 5654953, 5654953, 5654953
+.long 5700314, 5700314, 5700314, 5700314, 5268920, 5268920, 5268920, 5268920
+.long 2884855, 2884855, 2884855, 2884855, 5260684, 5260684, 5260684, 5260684
+.long 2091905, 2091905, 2091905, 2091905, 359251, 359251, 359251, 359251
+.long 6026966, 6026966, 6026966, 6026966, 6554070, 6554070, 6554070, 6554070
+.long 7913949, 7913949, 7913949, 7913949, 876248, 876248, 876248, 876248
+.long 777960, 777960, 777960, 777960, 8143293, 8143293, 8143293, 8143293
+.long 518909, 518909, 518909, 518909, 2608894, 2608894, 2608894, 2608894
+.long 8354570, 8354570, 8354570, 8354570
diff --git a/crypto/ml_dsa/asm/mldsa_ntt_ppc64le.S b/crypto/ml_dsa/asm/mldsa_ntt_ppc64le.S
new file mode 100644
index 0000000000..c57a852df4
--- /dev/null
+++ b/crypto/ml_dsa/asm/mldsa_ntt_ppc64le.S
@@ -0,0 +1,700 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+/*
+ * Copyright IBM Corp. 2025, 2026
+ *
+ * ===================================================================================
+ * Written by Danny Tsen <dtsen@us.ibm.com>
+ *
+ * Optimized NTT implementation for ppc64le.
+ */
+
+#define Q_NEG_INV_OFFSET 16
+#define ZETA_NTT_OFFSET 32
+
+#define V_ZETA  2
+#define V_Z0    2
+#define V_Z1    3
+#define V_Z2    4
+#define V_Z3    5
+
+#include "mldsa_ppc_macros_asm.inc"
+
+.machine "any"
+.text
+
+.macro SAVE_REGS
+        stdu    1, -352(1)
+        mflr    0
+        std     14, 56(1)
+        std     15, 64(1)
+        std     16, 72(1)
+        std     17, 80(1)
+        std     18, 88(1)
+        std     19, 96(1)
+        std     20, 104(1)
+        std     21, 112(1)
+        li      10, 128
+        li      11, 144
+        li      12, 160
+        li      14, 176
+        li      15, 192
+        li      16, 208
+        stxvx   32+20, 10, 1
+        stxvx   32+21, 11, 1
+        stxvx   32+22, 12, 1
+        stxvx   32+23, 14, 1
+        stxvx   32+24, 15, 1
+        stxvx   32+25, 16, 1
+        li      10, 224
+        li      11, 240
+        li      12, 256
+        li      14, 272
+        stxvx   32+26, 10, 1
+        stxvx   32+27, 11, 1
+        stxvx   32+28, 12, 1
+        stxvx   32+29, 14, 1
+.endm
+
+.macro RESTORE_REGS
+        li      10, 128
+        li      11, 144
+        li      12, 160
+        li      14, 176
+        li      15, 192
+        li      16, 208
+        lxvx    32+20, 10, 1
+        lxvx    32+21, 11, 1
+        lxvx    32+22, 12, 1
+        lxvx    32+23, 14, 1
+        lxvx    32+24, 15, 1
+        lxvx    32+25, 16, 1
+        li      10, 224
+        li      11, 240
+        li      12, 256
+        li      14, 272
+        lxvx    32+26, 10, 1
+        lxvx    32+27, 11, 1
+        lxvx    32+28, 12, 1
+        lxvx    32+29, 14, 1
+        ld      14, 56(1)
+        ld      15, 64(1)
+        ld      16, 72(1)
+        ld      17, 80(1)
+        ld      18, 88(1)
+        ld      19, 96(1)
+        ld      20, 104(1)
+        ld      21, 112(1)
+
+        mtlr    0
+        addi    1, 1, 352
+.endm
+
+/* ===================================================================== */
+/*
+ * Compute updated curr and peer coefficients.
+ *   updated curr = reduce_once(curr + peer);
+ *   updated peer = mod_sub(curr, peer);
+ *
+ *   Input curr : V26, V27, V28, V29
+ *   Input peer : V10, V11, V12, V13
+ *
+ *   updated peer : V18, V19, V20, V21
+ *   updated curr : V22, V23, V24, V25
+ */
+.macro _update_curr_peer_coeffs
+        vadduwm 22, 26, 10
+        mod_sub 18, 26, 10
+
+        vadduwm 23, 27, 11
+        mod_sub 19, 27, 11
+
+        vadduwm 24, 28, 12
+        mod_sub 20, 28, 12
+
+        vadduwm 25, 29, 13
+        mod_sub 21, 29, 13
+
+        reduce_once_1x 22
+        reduce_once_1x 23
+        reduce_once_1x 24
+        reduce_once_1x 25
+.endm
+
+/*
+ * Transpose the final coefficients of 2-2 layout to the original
+ * coefficient array order.
+ *
+ *   updated peer : V14, V15, V16, V17
+ *   updated curr : V10, V11, V12, V13
+ */
+.macro _delayed_writes_xxperm
+        xxpermdi 32+10, 32+22, 32+18, 0
+        xxpermdi 32+14, 32+22, 32+18, 3
+        xxpermdi 32+11, 32+23, 32+19, 0
+        xxpermdi 32+15, 32+23, 32+19, 3
+        xxpermdi 32+12, 32+24, 32+20, 0
+        xxpermdi 32+16, 32+24, 32+20, 3
+        xxpermdi 32+13, 32+25, 32+21, 0
+        xxpermdi 32+17, 32+25, 32+21, 3
+        delayed_writes_curr_peer 10, 14, 11, 15, 12, 16, 13, 17
+.endm
+
+/*
+ * Transpose the final coefficients of 1-1-1-1 layout to the original
+ * coefficient array order.
+ *
+ *   updated peer : V14, V15, V16, V17
+ *   updated curr : V10, V11, V12, V13
+ */
+.macro _delayed_writes_vmrg
+        vmrgew 10, 18, 22
+        vmrgow 11, 18, 22
+        vmrgew 12, 19, 23
+        vmrgow 13, 19, 23
+        vmrgew 14, 20, 24
+        vmrgow 15, 20, 24
+        vmrgew 16, 21, 25
+        vmrgow 17, 21, 25
+        delayed_writes_curr_peer 10, 11, 12, 13, 14, 15, 16, 17
+.endm
+
+/*
+ * NTT computation for layers, 1, 2, 3, 4, 5, 6.
+ */
+.macro _ntt_body _vz0, _vz1, _vz2, _vz3
+        lxvd2x  32+6, 5, GPR_OFFSET_P0
+        lxvd2x  32+7, 5, GPR_OFFSET_P1
+        lxvd2x  32+8, 5, GPR_OFFSET_P2
+        lxvd2x  32+9, 5, GPR_OFFSET_P3
+        mont_mul_reduce_4x \_vz0, \_vz1, \_vz2, \_vz3
+        lxvd2x  32+26, 5, GPR_OFFSET_C0
+        lxvd2x  32+27, 5, GPR_OFFSET_C1
+        lxvd2x  32+28, 5, GPR_OFFSET_C2
+        lxvd2x  32+29, 5, GPR_OFFSET_C3
+
+        /* update curr and peer coefficients */
+        _update_curr_peer_coeffs
+.endm
+
+/*
+ * For Layer 1, 2, 3, Len=32, 64, 128
+ */
+.macro NTT_Layer123_4x _vz0, _vz1, _vz2, _vz3
+        _ntt_body \_vz0, \_vz1, \_vz2, \_vz3
+        delayed_writes_curr_peer 22, 18, 23, 19, 24, 20, 25, 21
+        addi    5, 5, 64
+.endm
+
+/*
+ * For Layer 4, 5, 6, Len=4, 8, 16
+ */
+.macro NTT_Layer456_4x _vz0, _vz1, _vz2, _vz3
+        _ntt_body \_vz0, \_vz1, \_vz2, \_vz3
+        delayed_writes_curr_peer 22, 18, 23, 19, 24, 20, 25, 21
+        addi    5, 5, 128
+.endm
+
+/*
+ * For Layer 7, Len= 2, layer with 2-2 layout.
+ */
+.macro NTT_Layer7_4x
+        lxvd2x  32+V_Z0, 0, 14
+        lxvd2x  32+V_Z1, 10, 14
+        lxvd2x  32+V_Z2, 11, 14
+        lxvd2x  32+V_Z3, 12, 14
+        addi    14, 14, 64
+        Load_xxperm_coeffs 26, 27, 28, 29, 6, 7, 8, 9
+        mont_mul_reduce_4x V_Z0, V_Z1, V_Z2, V_Z3
+        _update_curr_peer_coeffs
+        _delayed_writes_xxperm
+        addi    5, 5, 128
+.endm
+
+/*
+ * For Layer 8, Len= 1, layer with 1-1-1-1 layout.
+ */
+.macro NTT_Layer8_4x
+        lxvd2x  32+V_Z0, 0, 14
+        lxvd2x  32+V_Z1, 10, 14
+        lxvd2x  32+V_Z2, 11, 14
+        lxvd2x  32+V_Z3, 12, 14
+        addi    14, 14, 64
+        Load_vmrg_coeffs 26, 27, 28, 29, 6, 7, 8, 9
+        mont_mul_reduce_4x V_Z0, V_Z1, V_Z2, V_Z3
+        _update_curr_peer_coeffs
+        _delayed_writes_vmrg
+        addi    5, 5, 128
+.endm
+
+/* ===================================================================== */
+/*
+ * mldsa_poly_ntt_ppc(int32_t *r)
+ *   Compute forward NTT based on the following 8 layers -
+ *     len = 128, 64, 32, 16, 8, 4, 2, 1.
+ *
+ *   Each layer compute the coefficients on 2 legs, start and start + len*2 offsets.
+ *
+ *   leg 1                        leg 2
+ *   -----                        -----
+ *   start                        start+len*2
+ *   start+next                   start+len*2+next
+ *   start+next+next              start+len*2+next+next
+ *   start+next+next+next         start+len*2+next+next+next
+ *
+ *   Each computation loads 8 vectors, 4 for each leg.
+ *   The final coefficient (t) from each vector of leg1 and leg2 then do the
+ *   add/sub operations to obtain the final results.
+ *
+ *   -> leg1 = leg1 + t, leg2 = leg1 - t
+ *
+ *   The resulting coefficients then store back to each leg's offset.
+ *
+ *   Each vector has the same corresponding zeta except len=2.
+ *
+ *   len=2 has 2-2 layout which means every 2 32-bit coefficients has the same zeta.
+ *   e.g.
+ *         coeff vector    a1   a2   a3  a4
+ *         zeta  vector    z1   z1   z2  z2
+ *
+ *   For len=2, each vector will get permuted to leg1 and leg2. Zeta is
+ *   pre-arranged for the leg1 and leg2.  After the computation, each vector needs
+ *   to transpose back to its original 2-2 layout.
+ *
+ *   For len=1, each vector will get permuted to leg1 and leg2. Zeta is
+ *   pre-arranged for the leg1 and leg2.  After the computation, each vector needs
+ *   to transpose back to its original 1-1-1-1 layout.
+ *
+ * Registers used for offsets to coefficients, curr and peer.  Each layer may
+ * reset these registers.
+ *
+ *   R9: offset to curr
+ *   R11: offset to R9 + next curr offset
+ *   R15: offset to R11 + next curr offset
+ *   R17: offset to R15 + next curr offset
+ *
+ *   R10: offset to peer
+ *   R12: offset to R10 + next peer offset
+ *   R16: offset to R12 + next peer offset
+ *   R18: offset to R16 + next peer offset
+ */
+.global mldsa_poly_ntt_ppc
+.align 4
+mldsa_poly_ntt_ppc:
+
+        SAVE_REGS
+
+        /* load Q and Q_NEG_INV */
+        addis   8,2,.mldsa_consts@toc@ha
+        addi    8,8,.mldsa_consts@toc@l
+        lvx     V_Q, 0, 8
+        li      10, Q_NEG_INV_OFFSET
+        lvx     Q_NEG_INV, 10, 8
+
+        /* set zetas array */
+        addi      14, 8, ZETA_NTT_OFFSET
+
+        /*
+         * 1. Layer 1, len = 128
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_C0, 0
+        li      GPR_OFFSET_C1, 0x10
+        li      GPR_OFFSET_C2, 0x20
+        li      GPR_OFFSET_C3, 0x30
+
+        li      GPR_OFFSET_P0, 0x200
+        li      GPR_OFFSET_P1, 0x210
+        li      GPR_OFFSET_P2, 0x220
+        li      GPR_OFFSET_P3, 0x230
+
+        lvx     V_ZETA, 0, 14
+        addi    14, 14, 16
+
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+
+.align 4
+        /*
+         * Layer 2. len = 64
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_P0, 0x100
+        li      GPR_OFFSET_P1, 0x110
+        li      GPR_OFFSET_P2, 0x120
+        li      GPR_OFFSET_P3, 0x130
+
+        lvx     V_ZETA, 0, 14
+        addi    14, 14, 16
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        addi  5, 5, 0x100
+
+        lvx     V_ZETA, 0, 14
+        addi    14, 14, 16
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+        NTT_Layer123_4x V_ZETA, V_ZETA, V_ZETA, V_ZETA
+
+.align 4
+        /*
+         * Layer 3. len = 32
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_P0, 0x80
+        li      GPR_OFFSET_P1, 0x90
+        li      GPR_OFFSET_P2, 0xa0
+        li      GPR_OFFSET_P3, 0xb0
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer123_4x V_Z0, V_Z0, V_Z0, V_Z0
+        NTT_Layer123_4x V_Z0, V_Z0, V_Z0, V_Z0
+        addi  5, 5, 0x80
+
+        NTT_Layer123_4x V_Z1, V_Z1, V_Z1, V_Z1
+        NTT_Layer123_4x V_Z1, V_Z1, V_Z1, V_Z1
+        addi  5, 5, 0x80
+
+        NTT_Layer123_4x V_Z2, V_Z2, V_Z2, V_Z2
+        NTT_Layer123_4x V_Z2, V_Z2, V_Z2, V_Z2
+        addi  5, 5, 0x80
+
+        NTT_Layer123_4x V_Z3, V_Z3, V_Z3, V_Z3
+        NTT_Layer123_4x V_Z3, V_Z3, V_Z3, V_Z3
+
+.align 4
+        /*
+         * Layer 4. len = 16
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_P0, 0x40
+        li      GPR_OFFSET_P1, 0x50
+        li      GPR_OFFSET_P2, 0x60
+        li      GPR_OFFSET_P3, 0x70
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z0, V_Z0, V_Z0
+        NTT_Layer456_4x V_Z1, V_Z1, V_Z1, V_Z1
+        NTT_Layer456_4x V_Z2, V_Z2, V_Z2, V_Z2
+        NTT_Layer456_4x V_Z3, V_Z3, V_Z3, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z0, V_Z0, V_Z0
+        NTT_Layer456_4x V_Z1, V_Z1, V_Z1, V_Z1
+        NTT_Layer456_4x V_Z2, V_Z2, V_Z2, V_Z2
+        NTT_Layer456_4x V_Z3, V_Z3, V_Z3, V_Z3
+
+.align 4
+        /*
+         * Layer 5. len = 8
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_C1, 0x10
+        li      GPR_OFFSET_C2, 0x40
+        li      GPR_OFFSET_C3, 0x50
+
+        li      GPR_OFFSET_P0, 0x20
+        li      GPR_OFFSET_P1, 0x30
+        li      GPR_OFFSET_P2, 0x60
+        li      GPR_OFFSET_P3, 0x70
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z0, V_Z1, V_Z1
+        NTT_Layer456_4x V_Z2, V_Z2, V_Z3, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z0, V_Z1, V_Z1
+        NTT_Layer456_4x V_Z2, V_Z2, V_Z3, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z0, V_Z1, V_Z1
+        NTT_Layer456_4x V_Z2, V_Z2, V_Z3, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z0, V_Z1, V_Z1
+        NTT_Layer456_4x V_Z2, V_Z2, V_Z3, V_Z3
+
+.align 4
+        /*
+         * Layer 6. len = 4
+         */
+        mr      5, 3
+
+        li      GPR_OFFSET_C1, 0x20
+        li      GPR_OFFSET_C2, 0x40
+        li      GPR_OFFSET_C3, 0x60
+
+        li      GPR_OFFSET_P0, 0x10
+        li      GPR_OFFSET_P1, 0x30
+        li      GPR_OFFSET_P2, 0x50
+        li      GPR_OFFSET_P3, 0x70
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+        Load_4zetas V_Z0, V_Z1, V_Z2, V_Z3
+        NTT_Layer456_4x V_Z0, V_Z1, V_Z2, V_Z3
+
+.align 4
+        /*
+         * Layer 7. len = 2
+         *   Layer 7 uses the same offset as Layer 6.
+         */
+        mr      5, 3
+
+        NTT_Layer7_4x
+        NTT_Layer7_4x
+        NTT_Layer7_4x
+        NTT_Layer7_4x
+        NTT_Layer7_4x
+        NTT_Layer7_4x
+        NTT_Layer7_4x
+        NTT_Layer7_4x
+
+.align 4
+        /*
+         * Layer 8. len = 1
+         *   Layer 8 uses the same offset as Layer 6.
+         */
+        mr      5, 3
+
+        NTT_Layer8_4x
+        NTT_Layer8_4x
+        NTT_Layer8_4x
+        NTT_Layer8_4x
+        NTT_Layer8_4x
+        NTT_Layer8_4x
+        NTT_Layer8_4x
+        NTT_Layer8_4x
+
+        RESTORE_REGS
+        blr
+.size     mldsa_poly_ntt_ppc,.-mldsa_poly_ntt_ppc
+
+/* ===================================================================== */
+.macro POLY_SAVE_REGS
+        stdu    1, -256(1)
+        mflr    0
+        li      9, 128
+        li      10, 144
+        li      11, 160
+        li      12, 176
+        stxvx   32+20, 9, 1
+        stxvx   32+21, 10, 1
+        stxvx   32+22, 11, 1
+        stxvx   32+23, 12, 1
+        li      9, 192
+        li      10, 208
+        stxvx   32+24, 9, 1
+        stxvx   32+25, 10, 1
+.endm
+
+.macro POLY_RESTORE_REGS
+        li      9, 128
+        li      10, 144
+        li      11, 160
+        li      12, 176
+        lxvx    32+20, 9, 1
+        lxvx    32+21, 10, 1
+        lxvx    32+22, 11, 1
+        lxvx    32+23, 12, 1
+        li      9, 192
+        li      10, 208
+        lxvx    32+24, 9, 1
+        lxvx    32+25, 10, 1
+
+        mtlr    0
+        addi    1, 1, 256
+.endm
+
+.macro _Load_poly_points_vectors
+        lxvd2x     32+2, 0, 4
+        lxvd2x     32+3, 10, 4
+        lxvd2x     32+4, 11, 4
+        lxvd2x     32+5, 12, 4
+
+        lxvd2x     32+6, 0, 5
+        lxvd2x     32+7, 10, 5
+        lxvd2x     32+8, 11, 5
+        lxvd2x     32+9, 12, 5
+        addi    4, 4, 64
+        addi    5, 5, 64
+.endm
+
+.macro _update_poly_coeffs
+        stxvd2x     32+10, 0, 3
+        stxvd2x     32+11, 10, 3
+        stxvd2x     32+12, 11, 3
+        stxvd2x     32+13, 12, 3
+        addi    3, 3, 64
+.endm
+
+.macro _poly_ntt_mul_4x
+        _Load_poly_points_vectors
+        //_Mont_point_mul
+        Mont_scalar_mul_4x 2, 3, 4, 5
+        Mont_reduce_4x
+        _update_poly_coeffs
+.endm
+
+/*
+ * mldsa_poly_ntt_mult(poly *c, const poly *a, const poly *b)
+ */
+.global mldsa_poly_ntt_mult_ppc
+.align 4
+mldsa_poly_ntt_mult_ppc:
+
+        POLY_SAVE_REGS
+
+        /* load Q and Q_NEG_INV */
+        addis   8,2,.mldsa_consts@toc@ha
+        addi    8,8,.mldsa_consts@toc@l
+        lvx     V_Q, 0, 8
+        li      10, 16
+        lvx     Q_NEG_INV, 10, 8
+
+        /*
+         * Montgomery reduce loops for a * b
+         *
+         *  c[j] = montgomery_reduce(a[j] * b[j])
+         */
+        li       10, 16
+        li       11, 32
+        li       12, 48
+
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+        _poly_ntt_mul_4x
+
+        POLY_RESTORE_REGS
+        blr
+.size     mldsa_poly_ntt_mult_ppc,.-mldsa_poly_ntt_mult_ppc
+/* ===================================================================== */
+
+.rodata
+.align 4
+.mldsa_consts:
+.long ML_DSA_Q, ML_DSA_Q, ML_DSA_Q, ML_DSA_Q
+.long ML_DSA_Q_NEG_INV, ML_DSA_Q_NEG_INV, ML_DSA_Q_NEG_INV, ML_DSA_Q_NEG_INV
+
+/* zetas */
+.mldsa_zetas:
+/* For Len=128, 64, 32, 16, 8 */
+.long 25847, 25847, 25847, 25847, 5771523, 5771523, 5771523, 5771523
+.long 7861508, 7861508, 7861508, 7861508, 237124, 237124, 237124, 237124
+.long 7602457, 7602457, 7602457, 7602457, 7504169, 7504169, 7504169, 7504169
+.long 466468, 466468, 466468, 466468, 1826347, 1826347, 1826347, 1826347
+.long 2353451, 2353451, 2353451, 2353451, 8021166, 8021166, 8021166, 8021166
+.long 6288512, 6288512, 6288512, 6288512, 3119733, 3119733, 3119733, 3119733
+.long 5495562, 5495562, 5495562, 5495562, 3111497, 3111497, 3111497, 3111497
+.long 2680103, 2680103, 2680103, 2680103, 2725464, 2725464, 2725464, 2725464
+.long 1024112, 1024112, 1024112, 1024112, 7300517, 7300517, 7300517, 7300517
+.long 3585928, 3585928, 3585928, 3585928, 7830929, 7830929, 7830929, 7830929
+.long 7260833, 7260833, 7260833, 7260833, 2619752, 2619752, 2619752, 2619752
+.long 6271868, 6271868, 6271868, 6271868, 6262231, 6262231, 6262231, 6262231
+.long 4520680, 4520680, 4520680, 4520680, 6980856, 6980856, 6980856, 6980856
+.long 5102745, 5102745, 5102745, 5102745, 1757237, 1757237, 1757237, 1757237
+.long 8360995, 8360995, 8360995, 8360995, 4010497, 4010497, 4010497, 4010497
+.long 280005, 280005, 280005, 280005
+/*For Len=4 */
+.long 2706023, 2706023, 2706023, 2706023, 95776, 95776, 95776, 95776
+.long 3077325, 3077325, 3077325, 3077325, 3530437, 3530437, 3530437, 3530437
+.long 6718724, 6718724, 6718724, 6718724, 4788269, 4788269, 4788269, 4788269
+.long 5842901, 5842901, 5842901, 5842901, 3915439, 3915439, 3915439, 3915439
+.long 4519302, 4519302, 4519302, 4519302, 5336701, 5336701, 5336701, 5336701
+.long 3574422, 3574422, 3574422, 3574422, 5512770, 5512770, 5512770, 5512770
+.long 3539968, 3539968, 3539968, 3539968, 8079950, 8079950, 8079950, 8079950
+.long 2348700, 2348700, 2348700, 2348700, 7841118, 7841118, 7841118, 7841118
+.long 6681150, 6681150, 6681150, 6681150, 6736599, 6736599, 6736599, 6736599
+.long 3505694, 3505694, 3505694, 3505694, 4558682, 4558682, 4558682, 4558682
+.long 3507263, 3507263, 3507263, 3507263, 6239768, 6239768, 6239768, 6239768
+.long 6779997, 6779997, 6779997, 6779997, 3699596, 3699596, 3699596, 3699596
+.long 811944, 811944, 811944, 811944, 531354, 531354, 531354, 531354
+.long 954230, 954230, 954230, 954230, 3881043, 3881043, 3881043, 3881043
+.long 3900724, 3900724, 3900724, 3900724, 5823537, 5823537, 5823537, 5823537
+.long 2071892, 2071892, 2071892, 2071892, 5582638, 5582638, 5582638, 5582638
+/* For Len=2 */
+.long 4450022, 4450022, 6851714, 6851714, 4702672, 4702672, 5339162, 5339162
+.long 6927966, 6927966, 3475950, 3475950, 2176455, 2176455, 6795196, 6795196
+.long 7122806, 7122806, 1939314, 1939314, 4296819, 4296819, 7380215, 7380215
+.long 5190273, 5190273, 5223087, 5223087, 4747489, 4747489, 126922, 126922
+.long 3412210, 3412210, 7396998, 7396998, 2147896, 2147896, 2715295, 2715295
+.long 5412772, 5412772, 4686924, 4686924, 7969390, 7969390, 5903370, 5903370
+.long 7709315, 7709315, 7151892, 7151892, 8357436, 8357436, 7072248, 7072248
+.long 7998430, 7998430, 1349076, 1349076, 1852771, 1852771, 6949987, 6949987
+.long 5037034, 5037034, 264944, 264944, 508951, 508951, 3097992, 3097992
+.long 44288, 44288, 7280319, 7280319, 904516, 904516, 3958618, 3958618
+.long 4656075, 4656075, 8371839, 8371839, 1653064, 1653064, 5130689, 5130689
+.long 2389356, 2389356, 8169440, 8169440, 759969, 759969, 7063561, 7063561
+.long 189548, 189548, 4827145, 4827145, 3159746, 3159746, 6529015, 6529015
+.long 5971092, 5971092, 8202977, 8202977, 1315589, 1315589, 1341330, 1341330
+.long 1285669, 1285669, 6795489, 6795489, 7567685, 7567685, 6940675, 6940675
+.long 5361315, 5361315, 4499357, 4499357, 4751448, 4751448, 3839961, 3839961
+/* Setup zetas for Len=1 as (3, 2, 1, 4) order */
+.long 2316500, 2091667, 3817976, 3407706, 5933984, 5037939, 4817955, 2244091
+.long 7144689, 266997, 3513181, 2434439, 7183191, 4860065, 5187039, 4621053
+.long 909542, 900702, 819034, 1859098, 8337157, 495491, 7857917, 6767243
+.long 2031748, 7725090, 3207046, 5257975, 7611795, 4823422, 4784579, 7855319
+.long 5942594, 342297, 4108315, 286988, 1735879, 3437287, 203044, 5038140
+.long 5790267, 2842341, 1265009, 2691481, 2486353, 4055324, 1595974, 1247620
+.long 2635921, 4613401, 4832145, 1250494, 1903435, 5386378, 7329447, 1869119
+.long 5062207, 7047359, 6950192, 1237275, 3306115, 7929317, 6417775, 1312455
+.long 5834105, 7100756, 7005614, 1917081, 2235880, 1500165, 3406031, 777191
+.long 6709241, 7838005, 6533464, 5548557, 594136, 5796124, 4603424, 4656147
+.long 2454455, 6366809, 8215696, 2432395, 185531, 1957272, 7173032, 3369112
+.long 1616392, 5196991, 3014001, 162844, 4686184, 810149, 6581310, 1652634
+.long 3866901, 5341501, 269760, 3523897, 1717735, 2213111, 472078, 7404533
+.long 6577327, 7953734, 1910376, 1723600, 8119771, 6712985, 4546524, 7276084
+.long 7959518, 5441381, 6094090, 6144432, 1612842, 183443, 4834730, 7403526
+.long 8332111, 7826001, 7018208, 3919660, 7534263, 3937738, 1976782, 1400424
diff --git a/crypto/ml_dsa/asm/mldsa_ppc_macros_asm.inc b/crypto/ml_dsa/asm/mldsa_ppc_macros_asm.inc
new file mode 100644
index 0000000000..e0eadff690
--- /dev/null
+++ b/crypto/ml_dsa/asm/mldsa_ppc_macros_asm.inc
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+/*
+ * Copyright IBM Corp. 2025, 2026
+ *
+ * ===================================================================================
+ * Written by Danny Tsen <dtsen@us.ibm.com>
+ */
+
+#define ML_DSA_Q 8380417   /* The modulus is 23 bits (2^23 - 2^13 + 1) */
+#define ML_DSA_Q_NEG_INV 4236238847 /* Inverse of -q modulo 2^32 */
+
+#define Q_NEG_INV    0
+#define V_Q     1
+
+#define GPR_OFFSET_C0 9
+#define GPR_OFFSET_C1 11
+#define GPR_OFFSET_C2 15
+#define GPR_OFFSET_C3 17
+
+#define GPR_OFFSET_P0 10
+#define GPR_OFFSET_P1 12
+#define GPR_OFFSET_P2 16
+#define GPR_OFFSET_P3 18
+
+/* ===================================================================== */
+/*
+ * For NTT implementation, initial peer and curr vectors setup as
+ *  peer = V6, V7, V8, V9
+ *  curr = V26, V27, V28, V29
+ *
+ * For Inverse NTT implementation, initial peer and curr vectors setup as
+ *  peer = V18, V19, V20, V21
+ *  curr = V14, V15, V16, V17
+ */
+/*
+ * Load coefficients with 1-1-1-1 layout,
+ *  each load contains 2 curr and 2 peer elements.
+ *    -> curr1 peer1 curr2 peer2
+ *       curr3 peer3 curr4 peer4
+ *  vmrgew and vmrgow ->
+ *       curr vector contains 4 curr elements and
+ *       peer vector contains 4 peer elements
+ *
+ * In order to do the coefficients computation, zeta vector will arrange
+ * in the proper order to match the multiplication.
+ */
+.macro Load_vmrg_coeffs _c1, _c2, _c3, _c4, _p1, _p2, _p3, _p4
+        lxvd2x     32+10, GPR_OFFSET_C0, 5
+        lxvd2x     32+11, GPR_OFFSET_P0, 5
+        vmrgew \_p1, 10, 11
+        vmrgow \_c1, 10, 11
+        lxvd2x     32+12, GPR_OFFSET_C1, 5
+        lxvd2x     32+13, GPR_OFFSET_P1, 5
+        vmrgew \_p2, 12, 13
+        vmrgow \_c2, 12, 13
+        lxvd2x     32+10, GPR_OFFSET_C2, 5
+        lxvd2x     32+11, GPR_OFFSET_P2, 5
+        vmrgew \_p3, 10, 11
+        vmrgow \_c3, 10, 11
+        lxvd2x     32+12, GPR_OFFSET_C3, 5
+        lxvd2x     32+13, GPR_OFFSET_P3, 5
+        vmrgew \_p4, 12, 13
+        vmrgow \_c4, 12, 13
+.endm
+
+/*
+ * Load coefficients with 2-2 layout,
+ *  each load contains 4 curr and 4 peer elements
+ *    -> curr1 curr2 peer1 peer2
+ *       curr3 curr3 pee43 peer4
+ *  xxpermidi -> 4 curr elements and 4 peer elements
+ *       curr vector contains 4 curr elements and
+ *       peer vector contains 4 peer elements
+ *
+ * In order to do the coefficients computation, zeta vector will arrange
+ * in the proper order to match the multiplication.
+ */
+.macro Load_xxperm_coeffs _c1, _c2, _c3, _c4, _p1, _p2, _p3, _p4
+        lxvd2x     1, GPR_OFFSET_C0, 5
+        lxvd2x     2, GPR_OFFSET_P0, 5
+        xxpermdi 32+\_p1, 1, 2, 3
+        xxpermdi 32+\_c1, 1, 2, 0
+        lxvd2x     3, GPR_OFFSET_C1, 5
+        lxvd2x     4, GPR_OFFSET_P1, 5
+        xxpermdi 32+\_p2, 3, 4, 3
+        xxpermdi 32+\_c2, 3, 4, 0
+        lxvd2x     1, GPR_OFFSET_C2, 5
+        lxvd2x     2, GPR_OFFSET_P2, 5
+        xxpermdi 32+\_p3, 1, 2, 3
+        xxpermdi 32+\_c3, 1, 2, 0
+        lxvd2x     3, GPR_OFFSET_C3, 5
+        lxvd2x     4, GPR_OFFSET_P3, 5
+        xxpermdi 32+\_p4, 3, 4, 3
+        xxpermdi 32+\_c4, 3, 4, 0
+.endm
+
+/*
+ * Reduces x mod q in constant time
+ * i.e. return x < q ? x : x - q;
+ * @param x Where x is assumed to be in the range 0 <= x < 2*q
+ * @returns the difference in the range 0..q-1
+ */
+.macro reduce_once_1x _vt
+        vsubuwm  16, \_vt, V_Q
+        vcmpgtsw 17, V_Q, \_vt
+        xxsel   32+\_vt, 32+16, 32+\_vt, 32+17
+.endm
+
+/*
+ * Calculate The positive value of (a-b) mod q in constant time.
+ *
+ * a - b mod q gives a value in the range -(q-1)..(q-1)
+ * By adding q we get a range of 1..(2q-1).
+ * Reducing this once then gives the range 0..q-1
+ *
+ * returns The value (q + a - b) mod q
+ */
+.macro mod_sub _x _a _b
+        vadduwm 14, \_a, V_Q
+        vsubuwm \_x, 14, \_b
+        reduce_once_1x \_x
+.endm
+
+/* Delayed writes resulting curr and peer coefficients */
+.macro delayed_writes_curr_peer _c1, _p1, _c2, _p2, _c3, _p3, _c4, _p4
+        stxvd2x    32+\_c1, GPR_OFFSET_C0, 5
+        stxvd2x    32+\_p1, GPR_OFFSET_P0, 5
+        stxvd2x    32+\_c2, GPR_OFFSET_C1, 5
+        stxvd2x    32+\_p2, GPR_OFFSET_P1, 5
+        stxvd2x    32+\_c3, GPR_OFFSET_C2, 5
+        stxvd2x    32+\_p3, GPR_OFFSET_P2, 5
+        stxvd2x    32+\_c4, GPR_OFFSET_C3, 5
+        stxvd2x    32+\_p4, GPR_OFFSET_P3, 5
+.endm
+
+.macro Load_4zetas _vz0, _vz1, _vz2, _vz3
+        lvx     \_vz0, 0, 14
+        addi    14, 14, 16
+        lvx     \_vz1, 0, 14
+        addi    14, 14, 16
+        lvx     \_vz2, 0, 14
+        addi    14, 14, 16
+        lvx     \_vz3, 0, 14
+        addi    14, 14, 16
+.endm
+
+/*
+ * Montgomery multiply:
+ *   a = zeta * peer or a = b * c
+ *   Multiply of 2 numbers in montgomery form, in the range 0...(2^32)*q
+ */
+.macro Mont_scalar_mul_4x  _vz0 _vz1 _vz2 _vz3
+        vmuleuw 10, 6, \_vz0
+        vmulouw 11, 6, \_vz0
+        vmuleuw 12, 7, \_vz1
+        vmulouw 13, 7, \_vz1
+        vmuleuw 14, 8, \_vz2
+        vmulouw 15, 8, \_vz2
+        vmuleuw 16, 9, \_vz3
+        vmulouw 17, 9, \_vz3
+.endm
+
+/*
+ * @brief When multiplying 2 numbers mod q that are in montgomery form, the
+ * product mod q needs to be multiplied by 2^-32 to be in montgomery form.
+ * See FIPS 204, Algorithm 49, MontgomeryReduce()
+ * Note it is slightly different due to the input range being positive
+ *
+ * @param a is the result of a multiply of 2 numbers in montgomery form,
+ *          in the range 0...(2^32)*q
+ * @returns The Montgomery form of 'a' with multiplier 2^32 in the range 0..q-1
+ *          The result is congruent to x * 2^-32 mod q
+ */
+.macro Mont_reduce_4x
+        vmulouw 18, 10, Q_NEG_INV
+        vmulouw 19, 11, Q_NEG_INV
+        vmulouw 20, 12, Q_NEG_INV
+        vmulouw 21, 13, Q_NEG_INV
+        vmulouw 22, 14, Q_NEG_INV
+        vmulouw 23, 15, Q_NEG_INV
+        vmulouw 24, 16, Q_NEG_INV
+        vmulouw 25, 17, Q_NEG_INV
+
+        vmulouw 18, 18, V_Q
+        vmulouw 19, 19, V_Q
+        vmulouw 20, 20, V_Q
+        vmulouw 21, 21, V_Q
+        vmulouw 22, 22, V_Q
+        vmulouw 23, 23, V_Q
+        vmulouw 24, 24, V_Q
+        vmulouw 25, 25, V_Q
+
+        vaddudm 18, 18, 10
+        vaddudm 19, 19, 11
+        vaddudm 20, 20, 12
+        vaddudm 21, 21, 13
+        vaddudm 22, 22, 14
+        vaddudm 23, 23, 15
+        vaddudm 24, 24, 16
+        vaddudm 25, 25, 17
+
+        vmrgew  10, 18, 19
+        vmrgew  11, 20, 21
+        vmrgew  12, 22, 23
+        vmrgew  13, 24, 25
+
+        reduce_once_1x 10
+        reduce_once_1x 11
+        reduce_once_1x 12
+        reduce_once_1x 13
+.endm
+
+/*
+ * -----------------------------------
+ * mont_mul_reduce_4x(_vz0, _vz1, _vz2, _vz3)
+ */
+.macro mont_mul_reduce_4x  _vz0 _vz1 _vz2 _vz3
+        Mont_scalar_mul_4x \_vz0, \_vz1, \_vz2, \_vz3
+        Mont_reduce_4x
+.endm
+/* ===================================================================== */
diff --git a/crypto/ml_dsa/build.info b/crypto/ml_dsa/build.info
index e41867f573..3c74aef2f7 100644
--- a/crypto/ml_dsa/build.info
+++ b/crypto/ml_dsa/build.info
@@ -8,6 +8,12 @@ $ML_DSA_ASM=
 IF[{- !$disabled{asm} -}]
   $ML_DSA_ASM_x86_64=ml_dsa_ntt-x86_64.s

+  IF[{- $target{sys_id} ne "AIX" && $target{sys_id} ne "MACOSX" -}]
+    $ML_DSA_ASM_ppc64=asm/mldsa_ntt_ppc64le.S asm/mldsa_intt_ppc64le.S
+  ENDIF
+
+  # Now that we have defined all the arch specific variables, use the
+  # appropriate one, and define the appropriate macros
   IF[$ML_DSA_ASM_{- $target{asm_arch} -}]
     $ML_DSA_ASM=$ML_DSA_ASM_{- $target{asm_arch} -}
   ENDIF
@@ -27,9 +33,6 @@ IF[{- !$disabled{'ml-dsa'} -}]
   ENDIF
 ENDIF

-DEFINE[../../libcrypto]=$ML_DSA_DEF
-DEFINE[../../providers/libfips.a]=$ML_DSA_DEF
-
 IF[{- !$disabled{'ml-dsa'} -}]
   SOURCE[../../libcrypto]=$COMMON $ML_DSA_ASM $ML_DSA_VX
   SOURCE[../../providers/libfips.a]=$COMMON $ML_DSA_ASM $ML_DSA_VX
diff --git a/crypto/ml_dsa/ml_dsa_ntt.c b/crypto/ml_dsa/ml_dsa_ntt.c
index 3f5ebd5206..17a63802f3 100644
--- a/crypto/ml_dsa/ml_dsa_ntt.c
+++ b/crypto/ml_dsa/ml_dsa_ntt.c
@@ -227,6 +227,34 @@ static void poly_ntt_inverse_avx2_wrapper(POLY *p)
 }
 #endif

+/*
+ * PPC64le wrapper functions.
+ */
+#if !defined(OPENSSL_NO_ASM) && defined(_ARCH_PPC64) && (defined(__LITTLE_ENDIAN__) || (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+#define MLDSA_NTT_PPC_ASM
+#include "arch/ppc_arch.h"
+#endif
+
+#if defined(MLDSA_NTT_PPC_ASM)
+extern void mldsa_poly_ntt_mult_ppc(POLY *out, const POLY *lhs, const POLY *rhs);
+static void poly_ntt_mult_ppc64le_wrapper(const POLY *lhs, const POLY *rhs, POLY *out)
+{
+    mldsa_poly_ntt_mult_ppc(out, lhs, rhs);
+}
+
+extern void mldsa_poly_ntt_ppc(uint32_t *p);
+static void poly_ntt_ppc64le_wrapper(POLY *p)
+{
+    mldsa_poly_ntt_ppc(p->coeff);
+}
+
+extern void mldsa_poly_ntt_inverse_ppc(uint32_t *p);
+static void poly_ntt_inverse_ppc64le_wrapper(POLY *p)
+{
+    mldsa_poly_ntt_inverse_ppc(p->coeff);
+}
+#endif
+
 /*
  * Initialize NTT function pointers to AVX2 implementations if available.
  * Scalar implementations are used by default.
@@ -240,6 +268,15 @@ static void ml_dsa_ntt_init(void)
         poly_ntt_mult_impl = poly_ntt_mult_avx2_wrapper;
     }
 #endif
+
+#if defined(MLDSA_NTT_PPC_ASM)
+    if (OPENSSL_ppccap_P & PPC_CRYPTO207) {
+        poly_ntt_impl = poly_ntt_ppc64le_wrapper;
+        poly_ntt_inverse_impl = poly_ntt_inverse_ppc64le_wrapper;
+        poly_ntt_mult_impl = poly_ntt_mult_ppc64le_wrapper;
+    }
+#endif
+
 #ifdef VX_COMPILER_SUPPORT_VEC128
     if (S390X_VX_CAPABLE) {
         poly_ntt_impl = ossl_ml_dsa_poly_ntt_vec128;
diff --git a/crypto/ml_dsa/ml_dsa_poly.h b/crypto/ml_dsa/ml_dsa_poly.h
index 7998fdd9c1..ca9a0c00a8 100644
--- a/crypto/ml_dsa/ml_dsa_poly.h
+++ b/crypto/ml_dsa/ml_dsa_poly.h
@@ -20,6 +20,8 @@
 struct poly_st {
 #if defined(VX_COMPILER_SUPPORT_VEC128)
     ALIGN16 uint32_t coeff[ML_DSA_NUM_POLY_COEFFICIENTS];
+#elif defined(_ARCH_PPC64)
+    ALIGN16 uint32_t coeff[ML_DSA_NUM_POLY_COEFFICIENTS];
 #else
     uint32_t coeff[ML_DSA_NUM_POLY_COEFFICIENTS];
 #endif