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
-
.allow_pure_ruby_ct! ⇒ Object
Explicitly allow constant-time operations in pure-Ruby mode.
-
.bytes_to_int(bytes) ⇒ Integer
Convert a big-endian binary string to an Integer.
-
.fadd(a, b) ⇒ Object
Modular addition in the field.
-
.finv(a) ⇒ Integer
Modular multiplicative inverse in the field (Fermat’s little theorem).
-
.fmul(a, b) ⇒ Object
Modular multiplication in the field.
-
.fneg(a) ⇒ Object
Modular negation in the field.
-
.fred(x) ⇒ Integer
Fast reduction modulo the secp256k1 prime.
-
.fsqr(a) ⇒ Object
Modular squaring in the field.
-
.fsqrt(a) ⇒ Integer?
Modular square root in the field.
-
.fsub(a, b) ⇒ Object
Modular subtraction in the field.
-
.int_to_bytes(n, length = 32) ⇒ String
Convert an Integer to a fixed-length big-endian binary string.
-
.jp_add(p, q) ⇒ Array
Add two Jacobian points.
-
.jp_double(p) ⇒ Array(Integer, Integer, Integer)
Double a Jacobian point.
-
.jp_neg(p) ⇒ Object
Negate a Jacobian point.
-
.jp_to_affine(jp) ⇒ Array(Integer, Integer)
Convert a Jacobian point to affine coordinates.
-
.native? ⇒ Boolean
Whether the native C extension is loaded and active.
- .pure_ruby_ct_allowed? ⇒ Boolean private
-
.scalar_add(a, b) ⇒ Object
Scalar addition mod N.
-
.scalar_inv(a) ⇒ Object
Scalar multiplicative inverse (Fermat).
-
.scalar_mod(a) ⇒ Object
Reduce modulo the curve order.
-
.scalar_mul(a, b) ⇒ Object
Scalar multiplication mod N.
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.
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).
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.
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.
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.
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.
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).
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.
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.
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.
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).
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 |