Elliptic arithmetic notes / 06

Scalar multiplication

Scalar multiplication is where algebra meets leakage: the loop invariant is mathematical, but the branch pattern is cryptographic.

Given \(k\) and \(P\in E(\mathbb F_p)\), compute \([k]P\). The book treats this as the dominant operation in ECC schemes and presents binary, NAF, window, fixed-base, and multiple-point methods.

Binary double-and-add

Left-to-right binary multiplication maintains the invariant:

after processing a bit prefix \(u\) of \(k\), the accumulator is \(Q=[u]P\).

For the next bit \(b\),

\[Q\leftarrow [2u]P,\qquad Q\leftarrow [2u+b]P.\]

This proves correctness, but a branch on \(b\) leaks scalar bits.

Ladder-style constant-time shape

For a short-Weierstrass implementation with incomplete addition formulas, do not merely write “Montgomery ladder” and assume safety. A useful invariant is still

\[R_0=[u]P,\qquad R_1=[u+1]P,\]

but the update must be implemented with formulas that are valid for all states reached by the loop, or by computing both possible updates and selecting by masks:

for (uint32_t i = scalar_bits; i-- > 0;) {
    ec_jac_t d0, d1, s;
    uint32_t bit = scalar_get_bit(k, i);
    ec_double(&d0, &r0);       /* [2u]P */
    ec_add(&s, &r0, &r1);      /* [2u+1]P */
    ec_double(&d1, &r1);       /* [2u+2]P */
    ec_jac_select(&r0, &d0, &s, bit);
    ec_jac_select(&r1, &s, &d1, bit);
}

This is a constant-operation-count pattern, not automatically a complete implementation. The proof must still show that ec_add is valid for the pair (r0,r1) in every iteration, or replace it with complete formulas for the chosen curve model. Montgomery curves such as Curve25519 have their own x-coordinate ladder formulas; those are not the same as generic short-Weierstrass Jacobian addition.

Fixed-window multiplication

For public fixed base \(G\), precompute odd multiples or a comb table. For secret scalars, the table lookup must scan every entry:

static uint32_t ct_eq_u32(uint32_t x, uint32_t y) {
    uint32_t z = x ^ y;
    z |= 0u - z;
    return (z >> 31) ^ 1u;
}

static void ec_select_table(ec_jac_t *r, const ec_jac_t table[], uint32_t len, uint32_t idx) {
    ec_jac_set_infinity(r);
    for (uint32_t i = 0; i < len; i++) {
        uint32_t take = ct_eq_u32(i, idx);
        ec_jac_cmov(r, &table[i], take);
    }
}

Precondition: idx < len, len is public, and the window value has already been reduced to the table range. The helper is a selector, not an input validator.

Signed digits and NAF

A non-adjacent form writes a scalar as

\[k=\sum_i u_i2^i,\qquad u_i\in\{0,\pm 1\}\]

with no two adjacent nonzero digits in the width-2 case. Wider signed-window variants use odd digits such as +/-1, +/-3, …, +/-(2^{w-1}-1). The benefit is fewer additions; the cost is a signed table and one more proof obligation. Selecting P versus -P cannot branch on a secret digit, and negation must be represented as a constant-time conditional replacement of Y by -Y.

For example, 13 has ordinary NAF digits 1,0,-1,0,1 from low to high, namely 13 = 1 - 4 + 16. For a width-4 signed window, a table of odd positive multiples can be reused for negative digits only if sign handling and table selection are constant-time.

Two examples

For \(k=13=(1101)_2\), binary left-to-right performs:

\[\mathcal O \to P \to 2P+P=3P \to 6P \to 12P+P=13P.\]

For width-4 fixed-window multiplication, a 256-bit scalar uses 64 windows. The loop count is public. The selected table entry per window is secret, so the selection must be a full scan or use another constant-time mechanism.

SageMath scalar checks

p = 17
E = EllipticCurve(GF(p), [2, 2])
P = E(5, 1)
for k in [0, 1, 2, 13, 37]:
    print(k, k*P)
print(13*P == sum([P for _ in range(13)], E(0)))