Module: Nuckle::Internals::Curve25519

Defined in:
lib/nuckle/internals/curve25519.rb

Overview

Curve25519 elliptic-curve Diffie-Hellman.

Montgomery curve: y^2 = x^3 + 486662*x^2 + x over GF(2^255 - 19). Uses the Montgomery ladder for scalar multiplication.

Reference: RFC 7748

Constant Summary collapse

P =
(1 << 255) - 19
A24 =

(486662 + 2) / 4

121666
BASE_U =
9
MASK64 =
0xFFFFFFFFFFFFFFFF

Class Method Summary collapse

Class Method Details

.decode_scalar(s) ⇒ Object

Decode a 32-byte scalar with clamping (per RFC 7748).



83
84
85
86
87
88
89
# File 'lib/nuckle/internals/curve25519.rb', line 83

def decode_scalar(s)
  buf = s.b.dup
  buf.setbyte(0,  buf.getbyte(0)  & 248)
  buf.setbyte(31, (buf.getbyte(31) & 127) | 64)
  w = buf.unpack("Q<4")
  w[0] | (w[1] << 64) | (w[2] << 128) | (w[3] << 192)
end

.decode_u(u_bytes) ⇒ Object

Decode a u-coordinate from 32 bytes (little-endian, mask high bit).



92
93
94
95
96
97
# File 'lib/nuckle/internals/curve25519.rb', line 92

def decode_u(u_bytes)
  buf = u_bytes.b.dup
  buf.setbyte(31, buf.getbyte(31) & 0x7F)
  w = buf.unpack("Q<4")
  w[0] | (w[1] << 64) | (w[2] << 128) | (w[3] << 192)
end

.encode_u(u) ⇒ Object

Encode a field element to 32 bytes (little-endian).



100
101
102
# File 'lib/nuckle/internals/curve25519.rb', line 100

def encode_u(u)
  [u & MASK64, (u >> 64) & MASK64, (u >> 128) & MASK64, (u >> 192) & MASK64].pack("Q<4")
end

.scalarmult(scalar, u_bytes) ⇒ String

Scalar multiplication: compute scalar * point on Curve25519.

Parameters:

  • scalar (String)

    32-byte scalar (private key)

  • u_bytes (String)

    32-byte u-coordinate (public key / base point)

Returns:

  • (String)

    32-byte result u-coordinate



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/nuckle/internals/curve25519.rb', line 27

def scalarmult(scalar, u_bytes)
  k = decode_scalar(scalar)
  u = decode_u(u_bytes)

  x_1 = u
  x_2 = 1
  z_2 = 0
  x_3 = u
  z_3 = 1

  swap = 0

  254.downto(0) do |t|
    k_t  = (k >> t) & 1
    swap ^= k_t
    # Constant-time conditional swap (XOR mask)
    mask   = -swap
    dummy  = (x_2 ^ x_3) & mask; x_2 ^= dummy; x_3 ^= dummy
    dummy  = (z_2 ^ z_3) & mask; z_2 ^= dummy; z_3 ^= dummy
    swap = k_t

    a  = (x_2 + z_2) % P
    aa = (a * a) % P
    b  = (x_2 - z_2) % P
    bb = (b * b) % P
    e  = (aa - bb) % P
    c  = (x_3 + z_3) % P
    d  = (x_3 - z_3) % P
    da = (d * a) % P
    cb = (c * b) % P

    sum = (da + cb) % P; x_3 = (sum * sum) % P
    dif = (da - cb) % P; z_3 = (x_1 * ((dif * dif) % P)) % P
    x_2 = (aa * bb) % P
    z_2 = (e * ((bb + A24 * e) % P)) % P
  end

  # Final cswap
  mask  = -swap
  dummy = (x_2 ^ x_3) & mask; x_2 ^= dummy
  dummy = (z_2 ^ z_3) & mask; z_2 ^= dummy

  result = (x_2 * z_2.pow(P - 2, P)) % P
  encode_u(result)
end

.scalarmult_base(scalar) ⇒ String

Scalar multiplication with the standard base point (u=9).

Parameters:

  • scalar (String)

    32-byte scalar (private key)

Returns:

  • (String)

    32-byte public key



78
79
80
# File 'lib/nuckle/internals/curve25519.rb', line 78

def scalarmult_base(scalar)
  scalarmult(scalar, "\x09".b + "\x00".b * 31)
end