Module: Secp256k1

Defined in:
lib/secp256k1.rb,
lib/secp256k1/version.rb

Overview

Pure Ruby secp256k1 elliptic curve implementation.

Provides field arithmetic, point operations with Jacobian coordinates, and windowed-NAF scalar multiplication. Ported from the BSV TypeScript SDK reference implementation.

All field operations work on plain Ruby Integer values (arbitrary precision, C-backed in MRI). No external gems required.

Defined Under Namespace

Classes: InsecureOperationError, Point

Constant Summary collapse

P =

The secp256k1 field prime: p = 2^256 - 2^32 - 977

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
N =

The curve order (number of points on the curve).

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
HALF_N =

Half the curve order, used for low-S normalisation (BIP-62).

N >> 1
GX =

Generator point x-coordinate.

0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
GY =

Generator point y-coordinate.

0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
P_PLUS1_DIV4 =

(P + 1) / 4 — used for modular square root since P ≡ 3 (mod 4).

(P + 1) >> 2
MASK_256 =

256-bit mask for fast reduction.

(1 << 256) - 1
VERSION =
'0.16.0'

Class Method Summary collapse

Class Method Details

.allow_pure_ruby_ct!Object

Explicitly allow constant-time operations in pure-Ruby mode. Call this only after evaluating the risks documented in docs/risks.md.



36
37
38
# File 'lib/secp256k1.rb', line 36

def self.allow_pure_ruby_ct!
  @allow_pure_ruby_ct = true
end

.bytes_to_int(bytes) ⇒ Integer

Convert a big-endian binary string to an Integer.

Parameters:

  • bytes (String)

    binary string (ASCII-8BIT)

Returns:

  • (Integer)


79
80
81
82
# File 'lib/secp256k1.rb', line 79

def bytes_to_int(bytes)
  # C-backed hex route is the fastest pure-Ruby byte→integer path (10x faster than inject).
  bytes.unpack1('H*').to_i(16)
end

.fadd(a, b) ⇒ Object

Modular addition in the field.



136
137
138
# File 'lib/secp256k1.rb', line 136

def fadd(a, b)
  fred(a + b)
end

.finv(a) ⇒ Integer

Modular multiplicative inverse in the field (Fermat’s little theorem).

Parameters:

  • a (Integer)

    value to invert (must be non-zero mod P)

Returns:

  • (Integer)

    a^(P-2) mod P

Raises:

  • (ArgumentError)

    if a is zero mod P



155
156
157
158
159
# File 'lib/secp256k1.rb', line 155

def finv(a)
  raise ArgumentError, 'field inverse is undefined for zero' if (a % P).zero?

  a.pow(P - 2, P)
end

.fmul(a, b) ⇒ Object

Modular multiplication in the field.



126
127
128
# File 'lib/secp256k1.rb', line 126

def fmul(a, b)
  fred(a * b)
end

.fneg(a) ⇒ Object

Modular negation in the field.



146
147
148
# File 'lib/secp256k1.rb', line 146

def fneg(a)
  a.zero? ? 0 : P - a
end

.fred(x) ⇒ Integer

Fast reduction modulo the secp256k1 prime.

Exploits the structure P = 2^256 - 2^32 - 977 to avoid generic modular division. Two folding passes plus a conditional subtraction.

Parameters:

  • x (Integer)

    non-negative integer

Returns:

  • (Integer)

    x mod P, in range [0, P)



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/secp256k1.rb', line 112

def fred(x)
  # First fold
  hi = x >> 256
  x = (x & MASK_256) + (hi << 32) + (hi * 977)

  # Second fold (hi <= 2^32 + 977, so one more pass suffices)
  hi = x >> 256
  x = (x & MASK_256) + (hi << 32) + (hi * 977)

  # Final conditional subtraction
  x >= P ? x - P : x
end

.fsqr(a) ⇒ Object

Modular squaring in the field.



131
132
133
# File 'lib/secp256k1.rb', line 131

def fsqr(a)
  fred(a * a)
end

.fsqrt(a) ⇒ Integer?

Modular square root in the field.

Uses the identity sqrt(a) = a^((P+1)/4) mod P, valid because P ≡ 3 (mod 4). Returns nil if a is not a quadratic residue.

Parameters:

  • a (Integer)

Returns:

  • (Integer, nil)

    the square root, or nil if none exists



168
169
170
171
# File 'lib/secp256k1.rb', line 168

def fsqrt(a)
  r = a.pow(P_PLUS1_DIV4, P)
  fsqr(r) == (a % P) ? r : nil
end

.fsub(a, b) ⇒ Object

Modular subtraction in the field.



141
142
143
# File 'lib/secp256k1.rb', line 141

def fsub(a, b)
  a >= b ? a - b : P - (b - a)
end

.int_to_bytes(n, length = 32) ⇒ String

Convert an Integer to a fixed-length big-endian binary string.

Parameters:

  • n (Integer)

    the integer to convert

  • length (Integer) (defaults to: 32)

    desired byte length (default 32)

Returns:

  • (String)

    binary string (ASCII-8BIT)

Raises:

  • (ArgumentError)


89
90
91
92
93
94
95
96
97
98
99
# File 'lib/secp256k1.rb', line 89

def int_to_bytes(n, length = 32)
  raise ArgumentError, 'negative integer' if n.negative?

  # C-backed hex route is the fastest pure-Ruby integer→byte path. Module deliberately avoids OpenSSL.
  hex = n.to_s(16)
  hex = "0#{hex}" if hex.length.odd?
  raise ArgumentError, "integer too large for #{length} bytes" if hex.length > length * 2

  hex = hex.rjust(length * 2, '0')
  [hex].pack('H*')
end

.jp_add(p, q) ⇒ Array

Add two Jacobian points.

Parameters:

  • p (Array)

    first Jacobian point

  • q (Array)

    second Jacobian point

Returns:

  • (Array)

    resulting Jacobian point



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/secp256k1.rb', line 238

def jp_add(p, q)
  _px, _py, pz = p
  _qx, _qy, qz = q
  return q if pz.zero?
  return p if qz.zero?

  z1z1 = fsqr(pz)
  z2z2 = fsqr(qz)
  u1 = fmul(p[0], z2z2)
  u2 = fmul(q[0], z1z1)
  s1 = fmul(p[1], fmul(z2z2, qz))
  s2 = fmul(q[1], fmul(z1z1, pz))

  h = fsub(u2, u1)
  r = fsub(s2, s1)

  if h.zero?
    return r.zero? ? jp_double(p) : JP_INFINITY
  end

  hh = fsqr(h)
  hhh = fmul(h, hh)
  v = fmul(u1, hh)

  x3 = fsub(fsub(fsqr(r), hhh), fmul(2, v))
  y3 = fsub(fmul(r, fsub(v, x3)), fmul(s1, hhh))
  z3 = fmul(h, fmul(pz, qz))
  [x3, y3, z3]
end

.jp_double(p) ⇒ Array(Integer, Integer, Integer)

Double a Jacobian point.

Formula from hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html (a=0 for secp256k1).

Parameters:

  • p (Array(Integer, Integer, Integer))

    Jacobian point [X, Y, Z]

Returns:

  • (Array(Integer, Integer, Integer))


220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/secp256k1.rb', line 220

def jp_double(p)
  x1, y1, z1 = p
  return JP_INFINITY if y1.zero?

  y1sq = fsqr(y1)
  s = fmul(4, fmul(x1, y1sq))
  m = fmul(3, fsqr(x1)) # a=0 for secp256k1
  x3 = fsub(fsqr(m), fmul(2, s))
  y3 = fsub(fmul(m, fsub(s, x3)), fmul(8, fsqr(y1sq)))
  z3 = fmul(2, fmul(y1, z1))
  [x3, y3, z3]
end

.jp_neg(p) ⇒ Object

Negate a Jacobian point.



416
417
418
419
420
# File 'lib/secp256k1.rb', line 416

def jp_neg(p)
  return p if p[2].zero?

  [p[0], fneg(p[1]), p[2]]
end

.jp_to_affine(jp) ⇒ Array(Integer, Integer)

Convert a Jacobian point to affine coordinates.

Parameters:

  • jp (Array(Integer, Integer, Integer))

Returns:

  • (Array(Integer, Integer))

    affine [x, y], or nil for infinity



272
273
274
275
276
277
278
279
280
281
# File 'lib/secp256k1.rb', line 272

def jp_to_affine(jp)
  _x, _y, z = jp
  return nil if z.zero?

  zinv = finv(z)
  zinv2 = fsqr(zinv)
  x = fmul(jp[0], zinv2)
  y = fmul(jp[1], fmul(zinv2, zinv))
  [x, y]
end

.native?Boolean

Whether the native C extension is loaded and active.

Returns:

  • (Boolean)


30
31
32
# File 'lib/secp256k1.rb', line 30

def self.native?
  @native == true
end

.pure_ruby_ct_allowed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


41
42
43
# File 'lib/secp256k1.rb', line 41

def self.pure_ruby_ct_allowed?
  @allow_pure_ruby_ct || ENV.key?('SECP256K1_ALLOW_PURE_RUBY_CT')
end

.scalar_add(a, b) ⇒ Object

Scalar addition mod N.



199
200
201
# File 'lib/secp256k1.rb', line 199

def scalar_add(a, b)
  (a + b) % N
end

.scalar_inv(a) ⇒ Object

Scalar multiplicative inverse (Fermat).

Raises:

  • (ArgumentError)

    if a is zero mod N



187
188
189
190
191
# File 'lib/secp256k1.rb', line 187

def scalar_inv(a)
  raise ArgumentError, 'scalar inverse is undefined for zero' if (a % N).zero?

  a.pow(N - 2, N)
end

.scalar_mod(a) ⇒ Object

Reduce modulo the curve order.



178
179
180
181
182
# File 'lib/secp256k1.rb', line 178

def scalar_mod(a)
  r = a % N
  r += N if r.negative?
  r
end

.scalar_mul(a, b) ⇒ Object

Scalar multiplication mod N.



194
195
196
# File 'lib/secp256k1.rb', line 194

def scalar_mul(a, b)
  (a * b) % N
end