Class: BSV::Primitives::Signature
- Inherits:
-
Object
- Object
- BSV::Primitives::Signature
- Defined in:
- lib/bsv/primitives/signature.rb
Overview
An ECDSA signature consisting of (r, s) components.
Supports DER encoding/decoding with strict BIP-66 validation, low-S normalisation (BIP-62 rule 5), and hex convenience methods.
Instance Attribute Summary collapse
-
#r ⇒ OpenSSL::BN
readonly
The r component.
-
#s ⇒ OpenSSL::BN
readonly
The s component.
Class Method Summary collapse
-
.from_der(der_bytes) ⇒ Signature
Parse a signature from DER-encoded bytes with strict BIP-66 validation.
-
.from_hex(hex) ⇒ Signature
Parse a signature from a hex-encoded DER string.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
trueif both signatures have equal r and s values. -
#initialize(r, s) ⇒ Signature
constructor
A new instance of Signature.
-
#low_s? ⇒ Boolean
Check whether the S value is in the lower half of the curve order.
-
#to_der ⇒ String
Serialise the signature in DER format.
-
#to_hex ⇒ String
Serialise the signature as a hex-encoded DER string.
-
#to_low_s ⇒ Signature
Return a new signature with S normalised to the lower half of the curve order.
Constructor Details
Instance Attribute Details
#r ⇒ OpenSSL::BN (readonly)
Returns the r component.
17 18 19 |
# File 'lib/bsv/primitives/signature.rb', line 17 def r @r end |
#s ⇒ OpenSSL::BN (readonly)
Returns the s component.
20 21 22 |
# File 'lib/bsv/primitives/signature.rb', line 20 def s @s end |
Class Method Details
.from_der(der_bytes) ⇒ Signature
Parse a signature from DER-encoded bytes with strict BIP-66 validation.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/bsv/primitives/signature.rb', line 34 def self.from_der(der_bytes) der_bytes = der_bytes.b if der_bytes.encoding != Encoding::ASCII_8BIT bytes = der_bytes.bytes raise ArgumentError, 'signature too short' if bytes.length < 8 raise ArgumentError, 'invalid sequence tag' unless bytes[0] == 0x30 # BIP-66 strict DER: length must be a single byte (0x00–0x7F). # Multi-byte length encoding (where the high bit of bytes[1] is set, # e.g. 0x81, 0x82 …) is not permitted in ECDSA signatures. raise ArgumentError, 'non-canonical DER length (multi-byte encoding not allowed)' if bytes[1] & 0x80 != 0 total_len = bytes[1] raise ArgumentError, 'length mismatch' unless total_len == bytes.length - 2 # Parse R raise ArgumentError, 'invalid integer tag for R' unless bytes[2] == 0x02 r_len = bytes[3] raise ArgumentError, 'R length overflows' if 4 + r_len > bytes.length raise ArgumentError, 'R is zero length' if r_len.zero? r_bytes = bytes[4, r_len] raise ArgumentError, 'R has negative flag' if r_bytes[0] & 0x80 != 0 raise ArgumentError, 'R has excessive padding' if r_len > 1 && r_bytes[0].zero? && (r_bytes[1] & 0x80).zero? # rubocop:disable Style/BitwisePredicate # Parse S s_offset = 4 + r_len raise ArgumentError, 'invalid integer tag for S' unless bytes[s_offset] == 0x02 s_len = bytes[s_offset + 1] raise ArgumentError, 'S length overflows' if s_offset + 2 + s_len > bytes.length raise ArgumentError, 'S is zero length' if s_len.zero? s_bytes = bytes[s_offset + 2, s_len] raise ArgumentError, 'S has negative flag' if s_bytes[0] & 0x80 != 0 raise ArgumentError, 'S has excessive padding' if s_len > 1 && s_bytes[0].zero? && (s_bytes[1] & 0x80).zero? # rubocop:disable Style/BitwisePredicate raise ArgumentError, 'trailing bytes' unless s_offset + 2 + s_len == bytes.length r = OpenSSL::BN.new(r_bytes.pack('C*'), 2) s = OpenSSL::BN.new(s_bytes.pack('C*'), 2) new(r, s) end |
Instance Method Details
#==(other) ⇒ Boolean
Returns true if both signatures have equal r and s values.
127 128 129 |
# File 'lib/bsv/primitives/signature.rb', line 127 def ==(other) other.is_a?(Signature) && @r == other.r && @s == other.s end |
#low_s? ⇒ Boolean
Check whether the S value is in the lower half of the curve order.
BIP-62 rule 5 requires S <= N/2 for transaction malleability protection.
112 113 114 |
# File 'lib/bsv/primitives/signature.rb', line 112 def low_s? @s <= Curve::HALF_N end |
#to_der ⇒ String
Serialise the signature in DER format.
82 83 84 85 86 87 88 89 90 |
# File 'lib/bsv/primitives/signature.rb', line 82 def to_der rb = canonicalise_int(@r) sb = canonicalise_int(@s) der = [0x30, rb.length + sb.length + 4, 0x02, rb.length, *rb, 0x02, sb.length, *sb] der.pack('C*') end |
#to_hex ⇒ String
Serialise the signature as a hex-encoded DER string.
103 104 105 |
# File 'lib/bsv/primitives/signature.rb', line 103 def to_hex to_der.unpack1('H*') end |