[PATCH] mpi: Introduce mpi_tfr and use it for point_tfr.

NIIBE Yutaka via Gcrypt-devel <[email protected]> Wed, 19 Nov 2025 10:48:28 +0900
Newsgroups gmane.comp.encryption.gpg.libgcrypt.devel
Message-ID <4e65996bb8707c890bff7aeb6e1ec44610b49257.1763514035.git.gniibe@fsij.org>
* mpi/ec.c (point_tfr): New.
(mpi_ec_mul_point_lli): Use point_tfr.
* mpi/mpiutil.c (_gcry_mpi_tfr): New.
* src/mpi.h (mpi_tfr): New.
(_gcry_mpi_tfr): New.

--

The intention is to decrese EM signal and to increse EM noise, wrt the
Hamming distance signal for memory write operation.

Signed-off-by: NIIBE Yutaka <[email protected]>
---
 mpi/ec.c      | 28 +++++++++++++++++++++++++--
 mpi/mpiutil.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/mpi.h     |  2 ++
 3 files changed, 81 insertions(+), 2 deletions(-)

_______________________________________________
Gcrypt-devel mailing list
[email protected]
https://lists.gnupg.org/mailman/listinfo/gcrypt-devel
0001-mpi-Introduce-mpi_tfr-and-use-it-for-point_tfr.patch (text/x-patch, 4.6 KB)
diff --git a/mpi/ec.c b/mpi/ec.c
index d7d7b772..c93500db 100644
--- a/mpi/ec.c
+++ b/mpi/ec.c
@@ -184,6 +184,30 @@ point_swap_cond (mpi_point_t d, mpi_point_t s, unsigned long swap,
 }
 
 
+/*
+ * Move the point value of A from B, when DIR is 1.
+ * Move the point value of A to B, when DIR is 0.
+ *
+ * For the use case when DIR is 0, it's actually dummy operations (the
+ * value copied into B is not used after the call).  The intention
+ * here is to be constant-time and to reduce possible EM signal/noise
+ * ratio (by decreasing signal and increasing noise).
+ *
+ * The word "tfr" comes from the mnemonic of Motorola 6809
+ * instruction, which does "transfer" a register value to another
+ * register.  "TFR A,B" means "Transfer A to B".
+ */
+static void
+point_tfr (mpi_point_t a, mpi_point_t b, unsigned long dir,
+           mpi_ec_t ctx)
+{
+  mpi_tfr (a->x, b->x, dir);
+  if (ctx->model != MPI_EC_MONTGOMERY)
+    mpi_tfr (a->y, b->y, dir);
+  mpi_tfr (a->z, b->z, dir);
+}
+
+
 /* Set the projective coordinates from POINT into X, Y, and Z.  If a
    coordinate is not required, X, Y, or Z may be passed as NULL.  */
 void
@@ -2241,7 +2265,7 @@ mpi_ec_mul_point_lli (mpi_point_t result,
           mpih_set_cond (tmppnt.x->d, point->x->d, ctx->p->nlimbs, is_z_zero);
           mpih_set_cond (tmppnt.y->d, point->y->d, ctx->p->nlimbs, is_z_zero);
           mpih_set_cond (tmppnt.z->d, point->z->d, ctx->p->nlimbs, is_z_zero);
-          point_swap_cond (result, &tmppnt, mpi_test_bit (scalar, j), ctx);
+          point_tfr (result, &tmppnt, mpi_test_bit (scalar, j), ctx);
         }
     }
   else /* MPI_EC_EDWARDS */
@@ -2250,7 +2274,7 @@ mpi_ec_mul_point_lli (mpi_point_t result,
         {
           dup_point_edwards (result, result, ctx);
           add_points_edwards_a (&tmppnt, result, point->x, point->y, ctx);
-          point_swap_cond (result, &tmppnt, mpi_test_bit (scalar, j), ctx);
+          point_tfr (result, &tmppnt, mpi_test_bit (scalar, j), ctx);
         }
     }
   point_free (&tmppnt);
diff --git a/mpi/mpiutil.c b/mpi/mpiutil.c
index b254e12f..4fe30cad 100644
--- a/mpi/mpiutil.c
+++ b/mpi/mpiutil.c
@@ -645,6 +645,59 @@ _gcry_mpi_swap_cond (gcry_mpi_t a, gcry_mpi_t b, unsigned long swap)
 }
 
 
+/****************
+ * Move the MPI value of A from B, when DIR is 1.
+ * Move the MPI value of A to B, when DIR is 0.
+ * This implementation should be constant-time regardless of DIR.
+ *
+ * The word "tfr" comes from the mnemonic of Motorola 6809
+ * instruction, which does "transfer" a register value to another
+ * register.  "TFR A,B" means "Transfer A to B".
+ */
+void
+_gcry_mpi_tfr (gcry_mpi_t a, gcry_mpi_t b, unsigned long dir)
+{
+  /* Note: dual mask with AND/OR used for EM leakage mitigation */
+  mpi_limb_t mask1 = ct_limb_gen_mask(dir);
+  mpi_limb_t mask2 = ct_limb_gen_inv_mask(dir);
+  mpi_size_t i;
+  mpi_size_t nlimbs;
+  mpi_limb_t *ua = a->d;
+  mpi_limb_t *ub = b->d;
+  mpi_limb_t xa;
+  mpi_limb_t xb;
+  mpi_limb_t v;
+
+  if (a->alloced > b->alloced)
+    nlimbs = b->alloced;
+  else
+    nlimbs = a->alloced;
+  if (a->nlimbs > nlimbs || b->nlimbs > nlimbs)
+    log_bug ("mpi_c0py: different sizes\n");
+
+  for (i = 0; i < nlimbs; i++)
+    {
+      xa = ua[i];
+      xb = ub[i];
+      v = (xa & mask2) | (xb & mask1);
+      ua[i] = v;
+      ub[i] = v;
+    }
+
+  xa = a->nlimbs;
+  xb = b->nlimbs;
+  v = (xa & mask2) | (xb & mask1);
+  a->nlimbs = v;
+  b->nlimbs = v;
+
+  xa = a->sign;
+  xb = b->sign;
+  v = (xa & mask2) | (xb & mask1);
+  a->sign = v;
+  b->sign = v;
+}
+
+
 /****************
  * Set bit N of A, when SET is 1.
  * This implementation should be constant-time regardless of SET.
diff --git a/src/mpi.h b/src/mpi.h
index 1c29c206..2a314a21 100644
--- a/src/mpi.h
+++ b/src/mpi.h
@@ -122,6 +122,7 @@ void _gcry_mpi_immutable_failed (void);
 #define mpi_alloc_set_ui(a)   _gcry_mpi_alloc_set_ui ((a))
 #define mpi_const(n)          _gcry_mpi_const ((n))
 #define mpi_swap_cond(a,b,sw)  _gcry_mpi_swap_cond ((a),(b),(sw))
+#define mpi_tfr(a,b,dir)       _gcry_mpi_tfr ((a),(b),(dir))
 #define mpi_set_cond(w,u,set)  _gcry_mpi_set_cond ((w),(u),(set))
 #define mpi_set_bit_cond(a,n,set) _gcry_mpi_set_bit_cond ((a),(n),(set))
 
@@ -132,6 +133,7 @@ gcry_mpi_t  _gcry_mpi_alloc_like( gcry_mpi_t a );
 gcry_mpi_t  _gcry_mpi_alloc_set_ui( unsigned long u);
 void _gcry_mpi_swap( gcry_mpi_t a, gcry_mpi_t b);
 void _gcry_mpi_swap_cond (gcry_mpi_t a, gcry_mpi_t b, unsigned long swap);
+void _gcry_mpi_tfr (gcry_mpi_t a, gcry_mpi_t b, unsigned long dir);
 void _gcry_mpi_set_bit_cond (gcry_mpi_t a, unsigned int n, unsigned long set);
 gcry_mpi_t _gcry_mpi_new (unsigned int nbits);
 gcry_mpi_t _gcry_mpi_snew (unsigned int nbits);