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: 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.15.0'
Class Method Summary collapse
-
.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.
-
.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
.bytes_to_int(bytes) ⇒ Integer
Convert a big-endian binary string to an Integer.
53 54 55 56 |
# File 'lib/secp256k1.rb', line 53 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.
110 111 112 |
# File 'lib/secp256k1.rb', line 110 def fadd(a, b) fred(a + b) end |
.finv(a) ⇒ Integer
Modular multiplicative inverse in the field (Fermat’s little theorem).
129 130 131 132 133 |
# File 'lib/secp256k1.rb', line 129 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.
100 101 102 |
# File 'lib/secp256k1.rb', line 100 def fmul(a, b) fred(a * b) end |
.fneg(a) ⇒ Object
Modular negation in the field.
120 121 122 |
# File 'lib/secp256k1.rb', line 120 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.
86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/secp256k1.rb', line 86 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.
105 106 107 |
# File 'lib/secp256k1.rb', line 105 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.
142 143 144 145 |
# File 'lib/secp256k1.rb', line 142 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.
115 116 117 |
# File 'lib/secp256k1.rb', line 115 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.
63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/secp256k1.rb', line 63 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.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/secp256k1.rb', line 212 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).
194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/secp256k1.rb', line 194 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.
390 391 392 393 394 |
# File 'lib/secp256k1.rb', line 390 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.
246 247 248 249 250 251 252 253 254 255 |
# File 'lib/secp256k1.rb', line 246 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 |
.scalar_add(a, b) ⇒ Object
Scalar addition mod N.
173 174 175 |
# File 'lib/secp256k1.rb', line 173 def scalar_add(a, b) (a + b) % N end |
.scalar_inv(a) ⇒ Object
Scalar multiplicative inverse (Fermat).
161 162 163 164 165 |
# File 'lib/secp256k1.rb', line 161 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.
152 153 154 155 156 |
# File 'lib/secp256k1.rb', line 152 def scalar_mod(a) r = a % N r += N if r.negative? r end |
.scalar_mul(a, b) ⇒ Object
Scalar multiplication mod N.
168 169 170 |
# File 'lib/secp256k1.rb', line 168 def scalar_mul(a, b) (a * b) % N end |