Module: BSV::Primitives::Hex
- Defined in:
- lib/bsv/primitives/hex.rb
Overview
Strict hex encoding/decoding utilities.
Ruby’s Array#pack(‘H*’) silently drops non-hex characters and truncates odd-length strings. This module rejects both, raising ArgumentError on invalid input so consumer-facing parse paths fail loudly rather than producing garbage.
Internal paths that serialise/deserialise trusted hex (e.g. round-tripping our own unpack1(‘H*’) output) can continue using pack(‘H*’) directly — the validation overhead isn’t warranted when the hex is known-good.
Class Method Summary collapse
-
.decode(str, name: 'hex value') ⇒ String
Decode a hex string to binary bytes.
-
.encode(bytes) ⇒ String
Encode binary bytes as lowercase hex.
-
.valid?(str) ⇒ Boolean
Test whether
stris valid hex (even-length, hex-only). -
.validate!(str, name: 'hex value') ⇒ String
Validate
stras hex, raising on failure. -
.validate_dtxid_hex!(value, name: 'dtxid_hex') ⇒ String
Validate that
valueis a 64-character display-order hex transaction ID. -
.validate_hash32!(value, name: 'hash') ⇒ String
Validate that
valueis a 32-byte binary hash. -
.validate_wtxid!(value, name: 'wtxid') ⇒ String
Validate that
valueis a 32-byte wire-order transaction ID.
Class Method Details
.decode(str, name: 'hex value') ⇒ String
Decode a hex string to binary bytes.
63 64 65 66 |
# File 'lib/bsv/primitives/hex.rb', line 63 def self.decode(str, name: 'hex value') validate!(str, name: name) [str].pack('H*') end |
.encode(bytes) ⇒ String
Encode binary bytes as lowercase hex.
72 73 74 |
# File 'lib/bsv/primitives/hex.rb', line 72 def self.encode(bytes) bytes.unpack1('H*') end |
.valid?(str) ⇒ Boolean
Test whether str is valid hex (even-length, hex-only).
32 33 34 35 36 |
# File 'lib/bsv/primitives/hex.rb', line 32 def self.valid?(str) str.is_a?(String) && str.match?(HEX_RE) rescue Encoding::CompatibilityError false end |
.validate!(str, name: 'hex value') ⇒ String
Validate str as hex, raising on failure.
44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/bsv/primitives/hex.rb', line 44 def self.validate!(str, name: 'hex value') return str if valid?(str) reason = if !str.is_a?(String) "expected String, got #{str.class}" elsif str.length.odd? 'odd length' else 'contains non-hex characters' end raise ArgumentError, "invalid #{name}: #{reason} (#{str.inspect})" end |
.validate_dtxid_hex!(value, name: 'dtxid_hex') ⇒ String
Validate that value is a 64-character display-order hex transaction ID.
126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/bsv/primitives/hex.rb', line 126 def self.validate_dtxid_hex!(value, name: 'dtxid_hex') unless value.is_a?(String) && value.length == 64 && value.match?(HEX_RE) hint = if value.is_a?(String) && value.bytesize == 32 && !value.match?(HEX_RE) ' (looks like binary bytes — use dtxid_hex or unpack to convert)' else '' end size = value.is_a?(String) ? "#{value.length}-char string" : value.class.to_s raise ArgumentError, "expected 64-char display-order hex for #{name}, got #{size}#{hint}" end value end |
.validate_hash32!(value, name: 'hash') ⇒ String
Validate that value is a 32-byte binary hash.
General-purpose validator for any 32-byte hash (merkle nodes, roots, etc.) — not specific to transaction IDs. For txid-specific validation use validate_wtxid! or validate_dtxid_hex! instead.
106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/bsv/primitives/hex.rb', line 106 def self.validate_hash32!(value, name: 'hash') unless value.is_a?(String) && value.bytesize == 32 hint = if value.is_a?(String) && value.bytesize == 64 && value.match?(HEX_RE) ' (looks like hex — decode it first)' else '' end size = value.is_a?(String) ? "#{value.bytesize}-byte string" : value.class.to_s raise ArgumentError, "expected 32-byte hash for #{name}, got #{size}#{hint}" end value end |
.validate_wtxid!(value, name: 'wtxid') ⇒ String
Validate that value is a 32-byte wire-order transaction ID.
82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/bsv/primitives/hex.rb', line 82 def self.validate_wtxid!(value, name: 'wtxid') unless value.is_a?(String) && value.bytesize == 32 hint = if value.is_a?(String) && value.bytesize == 64 && value.match?(HEX_RE) ' (looks like a hex txid — use wtxid_from_hex to convert)' else '' end size = value.is_a?(String) ? "#{value.bytesize}-byte string" : value.class.to_s raise ArgumentError, "expected 32-byte wire-order wtxid for #{name}, got #{size}#{hint}" end value end |