Module: BSV::Primitives::ECDSA
- Defined in:
- lib/bsv/primitives/ecdsa.rb
Overview
Deterministic ECDSA signing and verification on secp256k1.
Implements RFC 6979 deterministic nonce generation to produce signatures that are fully reproducible from the same (key, hash) pair. All signatures are normalised to low-S form (BIP-62 rule 5).
Typically used indirectly via PrivateKey#sign and PublicKey#verify rather than calling this module directly.
Constant Summary collapse
- BYTE_LEN =
Byte length of a secp256k1 scalar (256 bits).
32
Class Method Summary collapse
-
.recover_public_key(hash, signature, recovery_id) ⇒ PublicKey
Recover a public key from a signature and recovery ID.
-
.sign(hash, private_key_bn, force_low_s: false) ⇒ Signature
Sign a 32-byte message hash with a private key.
-
.sign_recoverable(hash, private_key_bn) ⇒ Array(Signature, Integer)
Sign a hash and return both the signature and recovery ID.
-
.verify(hash, signature, public_key_point) ⇒ Boolean
Verify an ECDSA signature against a message hash and public key.
Class Method Details
.recover_public_key(hash, signature, recovery_id) ⇒ PublicKey
Recover a public key from a signature and recovery ID.
Given a message hash, signature, and the recovery ID produced during signing, reconstructs the public key that created the signature.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/bsv/primitives/ecdsa.rb', line 65 def recover_public_key(hash, signature, recovery_id) r = signature.r s = signature.s n = Curve::N # Reconstruct R.x (may include overflow when recovery_id >= 2) x = recovery_id >= 2 ? r + n : r # Decompress R from x-coordinate and y-parity prefix = (recovery_id & 1).odd? ? "\x03".b : "\x02".b x_bytes = x.to_s(2) x_bytes = ("\x00".b * (32 - x_bytes.length)) + x_bytes if x_bytes.length < 32 r_point = Curve.point_from_bytes(prefix + x_bytes) # Q = r^(-1) * (s*R - e*G) r_inv = r.mod_inverse(n) e = OpenSSL::BN.new(hash, 2) u1 = ((n - e) * r_inv) % n u2 = (s * r_inv) % n p1 = Curve.multiply_generator(u1) p2 = Curve.multiply_point(r_point, u2) q = Curve.add_points(p1, p2) raise ArgumentError, 'recovered point is at infinity' if q.infinity? PublicKey.new(q) end |
.sign(hash, private_key_bn, force_low_s: false) ⇒ Signature
Sign a 32-byte message hash with a private key.
The signature is always low-S normalised per BIP-62 rule 5, as required by BSV consensus (sign_raw normalises internally). The force_low_s: keyword makes this explicit at the call site for readability — it is currently a no-op because sign_raw already guarantees low-S. It exists so callers can document intent without relying on implementation details.
36 37 38 39 |
# File 'lib/bsv/primitives/ecdsa.rb', line 36 def sign(hash, private_key_bn, force_low_s: false) # rubocop:disable Lint/UnusedMethodArgument sig, _recovery_id = sign_raw(hash, private_key_bn) sig end |
.sign_recoverable(hash, private_key_bn) ⇒ Array(Signature, Integer)
Sign a hash and return both the signature and recovery ID.
The recovery ID (0-3) allows the public key to be recovered from the signature without knowing it in advance, as used by Bitcoin Signed Messages (BSM) and compact signature formats.
50 51 52 |
# File 'lib/bsv/primitives/ecdsa.rb', line 50 def sign_recoverable(hash, private_key_bn) sign_raw(hash, private_key_bn) end |
.verify(hash, signature, public_key_point) ⇒ Boolean
Verify an ECDSA signature against a message hash and public key.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/bsv/primitives/ecdsa.rb', line 100 def verify(hash, signature, public_key_point) r = signature.r s = signature.s n = Curve::N return false if r <= OpenSSL::BN.new('0') || r >= n return false if s <= OpenSSL::BN.new('0') || s >= n e = OpenSSL::BN.new(hash, 2) s_inv = s.mod_inverse(n) u1 = (e * s_inv) % n u2 = (r * s_inv) % n # R' = u1*G + u2*Q point1 = Curve.multiply_generator(u1) point2 = Curve.multiply_point(public_key_point, u2) result_point = Curve.add_points(point1, point2) return false if result_point.infinity? x = Curve.point_x(result_point) % n x == r end |