Class: Secp256k1::Point
- Inherits:
-
Object
- Object
- Secp256k1::Point
- Defined in:
- lib/secp256k1.rb
Overview
An elliptic curve point on secp256k1.
Stores affine coordinates (x, y) or represents the point at infinity. Scalar multiplication uses Jacobian coordinates internally with windowed-NAF for performance.
Instance Attribute Summary collapse
-
#x ⇒ Integer?
readonly
X-coordinate (nil for infinity).
-
#y ⇒ Integer?
readonly
Y-coordinate (nil for infinity).
Class Method Summary collapse
-
.from_bytes(bytes) ⇒ Point
Deserialise a point from compressed (33 bytes) or uncompressed (65 bytes) SEC1 encoding.
-
.generator ⇒ Point
The generator point G.
-
.infinity ⇒ Point
The point at infinity (additive identity).
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
(also: #eql?)
Equality comparison.
-
#add(other) ⇒ Point
Point addition: self + other.
- #hash ⇒ Object
-
#infinity? ⇒ Boolean
Whether this is the point at infinity.
-
#initialize(x, y) ⇒ Point
constructor
A new instance of Point.
-
#mul(scalar) ⇒ Point
Scalar multiplication: self * scalar (variable-time, wNAF).
-
#mul_ct(scalar) ⇒ Point
Constant-time scalar multiplication: self * scalar (Montgomery ladder).
-
#negate ⇒ Point
Point negation: -self.
-
#on_curve? ⇒ Boolean
Whether this point lies on the secp256k1 curve (y² = x³ + 7).
-
#to_octet_string(format = :compressed) ⇒ String
Serialise the point in SEC1 format.
Constructor Details
#initialize(x, y) ⇒ Point
Returns a new instance of Point.
414 415 416 417 |
# File 'lib/secp256k1.rb', line 414 def initialize(x, y) @x = x @y = y end |
Instance Attribute Details
#x ⇒ Integer? (readonly)
Returns x-coordinate (nil for infinity).
407 408 409 |
# File 'lib/secp256k1.rb', line 407 def x @x end |
#y ⇒ Integer? (readonly)
Returns y-coordinate (nil for infinity).
410 411 412 |
# File 'lib/secp256k1.rb', line 410 def y @y end |
Class Method Details
.from_bytes(bytes) ⇒ Point
Deserialise a point from compressed (33 bytes) or uncompressed (65 bytes) SEC1 encoding.
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/secp256k1.rb', line 440 def self.from_bytes(bytes) bytes = bytes.b if bytes.encoding != Encoding::BINARY prefix = bytes.getbyte(0) case prefix when 0x04 # Uncompressed raise ArgumentError, 'invalid uncompressed point length' unless bytes.length == 65 x = Secp256k1.bytes_to_int(bytes[1, 32]) y = Secp256k1.bytes_to_int(bytes[33, 32]) raise ArgumentError, 'x coordinate out of field range' if x >= P raise ArgumentError, 'y coordinate out of field range' if y >= P pt = new(x, y) raise ArgumentError, 'point is not on the curve' unless pt.on_curve? pt when 0x02, 0x03 # Compressed raise ArgumentError, 'invalid compressed point length' unless bytes.length == 33 x = Secp256k1.bytes_to_int(bytes[1, 32]) raise ArgumentError, 'x coordinate out of field range' if x >= P y_squared = Secp256k1.fadd(Secp256k1.fmul(Secp256k1.fsqr(x), x), 7) y = Secp256k1.fsqrt(y_squared) raise ArgumentError, 'invalid point: x not on curve' if y.nil? # Ensure y-parity matches prefix y = Secp256k1.fneg(y) if (y.odd? ? 0x03 : 0x02) != prefix new(x, y) else raise ArgumentError, "unknown point prefix: 0x#{prefix.to_s(16).rjust(2, '0')}" end end |
.generator ⇒ Point
The generator point G.
429 430 431 |
# File 'lib/secp256k1.rb', line 429 def self.generator @generator ||= new(GX, GY) end |
.infinity ⇒ Point
The point at infinity (additive identity).
422 423 424 |
# File 'lib/secp256k1.rb', line 422 def self.infinity new(nil, nil) end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
Equality comparison.
584 585 586 587 588 589 590 591 592 593 594 |
# File 'lib/secp256k1.rb', line 584 def ==(other) return false unless other.is_a?(Point) if infinity? && other.infinity? true elsif infinity? || other.infinity? false else @x == other.x && @y == other.y end end |
#add(other) ⇒ Point
Point addition: self + other.
558 559 560 561 562 563 564 565 566 567 568 569 |
# File 'lib/secp256k1.rb', line 558 def add(other) return other if infinity? return self if other.infinity? jp1 = [@x, @y, 1] jp2 = [other.x, other.y, 1] jp_result = Secp256k1.jp_add(jp1, jp2) affine = Secp256k1.jp_to_affine(jp_result) return self.class.infinity if affine.nil? self.class.new(affine[0], affine[1]) end |
#hash ⇒ Object
597 598 599 |
# File 'lib/secp256k1.rb', line 597 def hash infinity? ? 0 : [@x, @y].hash end |
#infinity? ⇒ Boolean
Whether this is the point at infinity.
479 480 481 |
# File 'lib/secp256k1.rb', line 479 def infinity? @x.nil? end |
#mul(scalar) ⇒ Point
Scalar multiplication: self * scalar (variable-time, wNAF).
Suitable for public scalars only (e.g. signature verification). For secret-scalar paths use #mul_ct.
520 521 522 523 524 525 526 527 528 529 530 531 |
# File 'lib/secp256k1.rb', line 520 def mul(scalar) return self.class.infinity if scalar.zero? || infinity? scalar %= N return self.class.infinity if scalar.zero? jp = Secp256k1.scalar_multiply_wnaf(scalar, @x, @y) affine = Secp256k1.jp_to_affine(jp) return self.class.infinity if affine.nil? self.class.new(affine[0], affine[1]) end |
#mul_ct(scalar) ⇒ Point
Constant-time scalar multiplication: self * scalar (Montgomery ladder).
Processes all 256 bits unconditionally so execution time does not depend on the scalar value. Use this for secret-scalar paths: key generation, signing, and ECDH shared-secret derivation.
541 542 543 544 545 546 547 548 549 550 551 552 |
# File 'lib/secp256k1.rb', line 541 def mul_ct(scalar) return self.class.infinity if scalar.zero? || infinity? scalar %= N return self.class.infinity if scalar.zero? jp = Secp256k1.scalar_multiply_ct(scalar, @x, @y) affine = Secp256k1.jp_to_affine(jp) return self.class.infinity if affine.nil? self.class.new(affine[0], affine[1]) end |
#negate ⇒ Point
Point negation: -self.
574 575 576 577 578 |
# File 'lib/secp256k1.rb', line 574 def negate return self if infinity? self.class.new(@x, Secp256k1.fneg(@y)) end |
#on_curve? ⇒ Boolean
Whether this point lies on the secp256k1 curve (y² = x³ + 7).
486 487 488 489 490 491 492 |
# File 'lib/secp256k1.rb', line 486 def on_curve? return true if infinity? lhs = Secp256k1.fsqr(@y) rhs = Secp256k1.fadd(Secp256k1.fmul(Secp256k1.fsqr(@x), @x), 7) lhs == rhs end |
#to_octet_string(format = :compressed) ⇒ String
Serialise the point in SEC1 format.
499 500 501 502 503 504 505 506 507 508 509 510 511 |
# File 'lib/secp256k1.rb', line 499 def to_octet_string(format = :compressed) raise 'cannot serialise point at infinity' if infinity? case format when :compressed prefix = @y.odd? ? "\x03".b : "\x02".b prefix + Secp256k1.int_to_bytes(@x, 32) when :uncompressed "\x04".b + Secp256k1.int_to_bytes(@x, 32) + Secp256k1.int_to_bytes(@y, 32) else raise ArgumentError, "unknown format: #{format}" end end |