Class: BSV::Primitives::Secp256k1::Point
- Inherits:
-
Object
- Object
- BSV::Primitives::Secp256k1::Point
- Defined in:
- lib/bsv/primitives/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.
412 413 414 415 |
# File 'lib/bsv/primitives/secp256k1.rb', line 412 def initialize(x, y) @x = x @y = y end |
Instance Attribute Details
#x ⇒ Integer? (readonly)
Returns x-coordinate (nil for infinity).
405 406 407 |
# File 'lib/bsv/primitives/secp256k1.rb', line 405 def x @x end |
#y ⇒ Integer? (readonly)
Returns y-coordinate (nil for infinity).
408 409 410 |
# File 'lib/bsv/primitives/secp256k1.rb', line 408 def y @y end |
Class Method Details
.from_bytes(bytes) ⇒ Point
Deserialise a point from compressed (33 bytes) or uncompressed (65 bytes) SEC1 encoding.
438 439 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 |
# File 'lib/bsv/primitives/secp256k1.rb', line 438 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.
427 428 429 |
# File 'lib/bsv/primitives/secp256k1.rb', line 427 def self.generator @generator ||= new(GX, GY) end |
.infinity ⇒ Point
The point at infinity (additive identity).
420 421 422 |
# File 'lib/bsv/primitives/secp256k1.rb', line 420 def self.infinity new(nil, nil) end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
Equality comparison.
582 583 584 585 586 587 588 589 590 591 592 |
# File 'lib/bsv/primitives/secp256k1.rb', line 582 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.
556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'lib/bsv/primitives/secp256k1.rb', line 556 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
595 596 597 |
# File 'lib/bsv/primitives/secp256k1.rb', line 595 def hash infinity? ? 0 : [@x, @y].hash end |
#infinity? ⇒ Boolean
Whether this is the point at infinity.
477 478 479 |
# File 'lib/bsv/primitives/secp256k1.rb', line 477 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.
518 519 520 521 522 523 524 525 526 527 528 529 |
# File 'lib/bsv/primitives/secp256k1.rb', line 518 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.
539 540 541 542 543 544 545 546 547 548 549 550 |
# File 'lib/bsv/primitives/secp256k1.rb', line 539 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.
572 573 574 575 576 |
# File 'lib/bsv/primitives/secp256k1.rb', line 572 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).
484 485 486 487 488 489 490 |
# File 'lib/bsv/primitives/secp256k1.rb', line 484 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.
497 498 499 500 501 502 503 504 505 506 507 508 509 |
# File 'lib/bsv/primitives/secp256k1.rb', line 497 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 |