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
(also: #mul_ct)
Scalar multiplication: self * scalar (constant-time, Montgomery ladder).
-
#mul_vt(scalar) ⇒ Point
Variable-time scalar multiplication: self * scalar (wNAF).
-
#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.
440 441 442 443 |
# File 'lib/secp256k1.rb', line 440 def initialize(x, y) @x = x @y = y end |
Instance Attribute Details
#x ⇒ Integer? (readonly)
Returns x-coordinate (nil for infinity).
433 434 435 |
# File 'lib/secp256k1.rb', line 433 def x @x end |
#y ⇒ Integer? (readonly)
Returns y-coordinate (nil for infinity).
436 437 438 |
# File 'lib/secp256k1.rb', line 436 def y @y end |
Class Method Details
.from_bytes(bytes) ⇒ Point
Deserialise a point from compressed (33 bytes) or uncompressed (65 bytes) SEC1 encoding.
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 |
# File 'lib/secp256k1.rb', line 466 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.
455 456 457 |
# File 'lib/secp256k1.rb', line 455 def self.generator @generator ||= new(GX, GY) end |
.infinity ⇒ Point
The point at infinity (additive identity).
448 449 450 |
# File 'lib/secp256k1.rb', line 448 def self.infinity new(nil, nil) end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
Equality comparison.
627 628 629 630 631 632 633 634 635 636 637 |
# File 'lib/secp256k1.rb', line 627 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.
601 602 603 604 605 606 607 608 609 610 611 612 |
# File 'lib/secp256k1.rb', line 601 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
640 641 642 |
# File 'lib/secp256k1.rb', line 640 def hash infinity? ? 0 : [@x, @y].hash end |
#infinity? ⇒ Boolean
Whether this is the point at infinity.
505 506 507 |
# File 'lib/secp256k1.rb', line 505 def infinity? @x.nil? end |
#mul(scalar) ⇒ Point Also known as: mul_ct
Scalar multiplication: self * scalar (constant-time, Montgomery ladder).
Processes all 256 bits unconditionally so execution time does not depend on the scalar value. Safe for both secret and public scalars. This is the default because the safe path should be the easy path.
For performance-critical public-scalar paths (e.g. batch verification) where constant-time is unnecessary, use #mul_vt.
Raises InsecureOperationError if the native C extension is not loaded, unless explicitly allowed via Secp256k1.allow_pure_ruby_ct! or the SECP256K1_ALLOW_PURE_RUBY_CT environment variable.
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 |
# File 'lib/secp256k1.rb', line 554 def mul(scalar) unless Secp256k1.native? || Secp256k1.pure_ruby_ct_allowed? raise Secp256k1::InsecureOperationError, 'mul requires the native C extension for constant-time guarantees. ' \ 'Set SECP256K1_ALLOW_PURE_RUBY_CT=1 or call Secp256k1.allow_pure_ruby_ct! to override.' end 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 |
#mul_vt(scalar) ⇒ Point
Variable-time scalar multiplication: self * scalar (wNAF).
Faster than #mul but leaks timing information about the scalar. Use only when the scalar is public (e.g. signature verification, computing known generator multiples). Never use with secret scalars.
584 585 586 587 588 589 590 591 592 593 594 595 |
# File 'lib/secp256k1.rb', line 584 def mul_vt(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 |
#negate ⇒ Point
Point negation: -self.
617 618 619 620 621 |
# File 'lib/secp256k1.rb', line 617 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).
512 513 514 515 516 517 518 |
# File 'lib/secp256k1.rb', line 512 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.
525 526 527 528 529 530 531 532 533 534 535 536 537 |
# File 'lib/secp256k1.rb', line 525 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 |